diff --git a/.flake8 b/.flake8
index 5735456ae7d..4b852abd7c6 100644
--- a/.flake8
+++ b/.flake8
@@ -29,6 +29,9 @@ ignore =
B950,
W191,
E124, # closing bracket, irritating while writing QB code
+ E131, # continuation line unaligned for hanging indent
+ E123, # closing bracket does not match indentation of opening bracket's line
+ E101, # ensured by use of black
max-line-length = 200
exclude=.github/helper/semgrep_rules
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index 88049be32d4..3bc22af96ab 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -23,3 +23,9 @@ b147b85e6ac19a9220cd1e2958a6ebd99373283a
# removing six compatibility layer
8fe5feb6a4372bf5f2dfaf65fca41bbcc25c8ce7
+
+# bulk format python code with black
+494bd9ef78313436f0424b918f200dab8fc7c20b
+
+# bulk format python code with black
+baec607ff5905b1c67531096a9cf50ec7ff00a5d
\ No newline at end of file
diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml
index d05bbbec50a..afabe43fec0 100644
--- a/.github/workflows/patch.yml
+++ b/.github/workflows/patch.yml
@@ -4,7 +4,10 @@ on:
pull_request:
paths-ignore:
- '**.js'
+ - '**.css'
- '**.md'
+ - '**.html'
+ - '**.csv'
workflow_dispatch:
concurrency:
diff --git a/.github/workflows/server-tests-mariadb.yml b/.github/workflows/server-tests-mariadb.yml
index 40f93651f4a..69be7656a6c 100644
--- a/.github/workflows/server-tests-mariadb.yml
+++ b/.github/workflows/server-tests-mariadb.yml
@@ -4,8 +4,10 @@ on:
pull_request:
paths-ignore:
- '**.js'
+ - '**.css'
- '**.md'
- '**.html'
+ - '**.csv'
push:
branches: [ develop ]
paths-ignore:
diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml
deleted file mode 100644
index ab6a53b5d92..00000000000
--- a/.github/workflows/ui-tests.yml
+++ /dev/null
@@ -1,117 +0,0 @@
-name: UI
-
-on:
- pull_request:
- paths-ignore:
- - '**.md'
- workflow_dispatch:
-
-concurrency:
- group: ui-develop-${{ github.event.number }}
- cancel-in-progress: true
-
-jobs:
- test:
- runs-on: ubuntu-latest
- timeout-minutes: 60
-
- strategy:
- fail-fast: false
-
- name: UI Tests (Cypress)
-
- services:
- mysql:
- image: mariadb:10.3
- env:
- MYSQL_ALLOW_EMPTY_PASSWORD: YES
- ports:
- - 3306:3306
- options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
-
- steps:
- - name: Clone
- uses: actions/checkout@v2
-
- - name: Setup Python
- uses: actions/setup-python@v2
- with:
- python-version: 3.8
-
- - uses: actions/setup-node@v2
- with:
- node-version: 14
- check-latest: true
-
- - name: Add to Hosts
- run: |
- echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
-
- - name: Cache pip
- uses: actions/cache@v2
- with:
- path: ~/.cache/pip
- key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
- restore-keys: |
- ${{ runner.os }}-pip-
- ${{ runner.os }}-
-
- - name: Cache node modules
- uses: actions/cache@v2
- env:
- cache-name: cache-node-modules
- with:
- path: ~/.npm
- key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
- restore-keys: |
- ${{ runner.os }}-build-${{ env.cache-name }}-
- ${{ runner.os }}-build-
- ${{ runner.os }}-
-
- - name: Get yarn cache directory path
- id: yarn-cache-dir-path
- run: echo "::set-output name=dir::$(yarn cache dir)"
-
- - uses: actions/cache@v2
- id: yarn-cache
- with:
- path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
- key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
- restore-keys: |
- ${{ runner.os }}-yarn-
-
- - name: Cache cypress binary
- uses: actions/cache@v2
- with:
- path: ~/.cache
- key: ${{ runner.os }}-cypress-
- restore-keys: |
- ${{ runner.os }}-cypress-
- ${{ runner.os }}-
-
- - name: Install
- run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
- env:
- DB: mariadb
- TYPE: ui
-
- - name: Site Setup
- run: cd ~/frappe-bench/ && bench --site test_site execute erpnext.setup.utils.before_tests
-
- - name: cypress pre-requisites
- run: cd ~/frappe-bench/apps/frappe && yarn add cypress-file-upload@^5 @testing-library/cypress@^8 --no-lockfile
-
-
- - name: Build Assets
- run: cd ~/frappe-bench/ && bench build
- env:
- CI: Yes
-
- - name: UI Tests
- run: cd ~/frappe-bench/ && bench --site test_site run-ui-tests erpnext --headless
- env:
- CYPRESS_RECORD_KEY: 60a8e3bf-08f5-45b1-9269-2b207d7d30cd
-
- - name: Show bench console if tests failed
- if: ${{ failure() }}
- run: cat ~/frappe-bench/bench_run_logs.txt
diff --git a/.mergify.yml b/.mergify.yml
index f3d04096cfc..cc8c0802f12 100644
--- a/.mergify.yml
+++ b/.mergify.yml
@@ -7,6 +7,8 @@ pull_request_rules:
- author!=gavindsouza
- author!=rohitwaghchaure
- author!=nabinhait
+ - author!=ankush
+ - author!=deepeshgarg007
- or:
- base=version-13
- base=version-12
@@ -14,9 +16,39 @@ pull_request_rules:
close:
comment:
message: |
- @{{author}}, thanks for the contribution, but we do not accept pull requests on a stable branch. Please raise PR on an appropriate hotfix branch.
+ @{{author}}, thanks for the contribution, but we do not accept pull requests on a stable branch. Please raise PR on an appropriate hotfix branch.
https://github.com/frappe/erpnext/wiki/Pull-Request-Checklist#which-branch
+ - name: backport to develop
+ conditions:
+ - label="backport develop"
+ actions:
+ backport:
+ branches:
+ - develop
+ assignees:
+ - "{{ author }}"
+
+ - name: backport to version-14-hotfix
+ conditions:
+ - label="backport version-14-hotfix"
+ actions:
+ backport:
+ branches:
+ - version-14-hotfix
+ assignees:
+ - "{{ author }}"
+
+ - name: backport to version-14-pre-release
+ conditions:
+ - label="backport version-14-pre-release"
+ actions:
+ backport:
+ branches:
+ - version-14-pre-release
+ assignees:
+ - "{{ author }}"
+
- name: backport to version-13-hotfix
conditions:
- label="backport version-13-hotfix"
@@ -55,4 +87,38 @@ pull_request_rules:
branches:
- version-12-pre-release
assignees:
- - "{{ author }}"
\ No newline at end of file
+ - "{{ author }}"
+
+ - name: Automatic merge on CI success and review
+ conditions:
+ - status-success=linters
+ - status-success=Sider
+ - status-success=Semantic Pull Request
+ - status-success=Patch Test
+ - status-success=Python Unit Tests (1)
+ - status-success=Python Unit Tests (2)
+ - status-success=Python Unit Tests (3)
+ - label!=dont-merge
+ - label!=squash
+ - "#approved-reviews-by>=1"
+ actions:
+ merge:
+ method: merge
+ - name: Automatic squash on CI success and review
+ conditions:
+ - status-success=linters
+ - status-success=Sider
+ - status-success=Patch Test
+ - status-success=Python Unit Tests (1)
+ - status-success=Python Unit Tests (2)
+ - status-success=Python Unit Tests (3)
+ - label!=dont-merge
+ - label=squash
+ - "#approved-reviews-by>=1"
+ actions:
+ merge:
+ method: squash
+ commit_message_template: |
+ {{ title }} (#{{ number }})
+
+ {{ body }}
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index b74d9a640da..dc3011f050f 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -26,12 +26,19 @@ repos:
args: ['--config', '.github/helper/.flake8_strict']
exclude: ".*setup.py$"
+ - repo: https://github.com/adityahase/black
+ rev: 9cb0a69f4d0030cdf687eddf314468b39ed54119
+ hooks:
+ - id: black
+ additional_dependencies: ['click==8.0.4']
+
- repo: https://github.com/timothycrosley/isort
rev: 5.9.1
hooks:
- id: isort
exclude: ".*setup.py$"
+
ci:
autoupdate_schedule: weekly
skip: []
diff --git a/README.md b/README.md
index 96093531d33..c26660c5a25 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,7 @@
[](https://github.com/frappe/erpnext/actions/workflows/server-tests.yml)
+[](https://github.com/erpnext/erpnext_ui_tests/actions/workflows/ui-tests.yml)
[](https://www.codetriage.com/frappe/erpnext)
[](https://codecov.io/gh/frappe/erpnext)
[](https://hub.docker.com/r/frappe/erpnext-worker)
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index fc0b6df13a9..e0f0c98e9c9 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -2,49 +2,57 @@ import inspect
import frappe
-__version__ = '14.0.0-beta.2'
+__version__ = "14.0.0-dev"
+
def get_default_company(user=None):
- '''Get default company for user'''
+ """Get default company for user"""
from frappe.defaults import get_user_default_as_list
if not user:
user = frappe.session.user
- companies = get_user_default_as_list(user, 'company')
+ companies = get_user_default_as_list(user, "company")
if companies:
default_company = companies[0]
else:
- default_company = frappe.db.get_single_value('Global Defaults', 'default_company')
+ default_company = frappe.db.get_single_value("Global Defaults", "default_company")
return default_company
def get_default_currency():
- '''Returns the currency of the default company'''
+ """Returns the currency of the default company"""
company = get_default_company()
if company:
- return frappe.get_cached_value('Company', company, 'default_currency')
+ return frappe.get_cached_value("Company", company, "default_currency")
+
def get_default_cost_center(company):
- '''Returns the default cost center of the company'''
+ """Returns the default cost center of the company"""
if not company:
return None
if not frappe.flags.company_cost_center:
frappe.flags.company_cost_center = {}
if not company in frappe.flags.company_cost_center:
- frappe.flags.company_cost_center[company] = frappe.get_cached_value('Company', company, 'cost_center')
+ frappe.flags.company_cost_center[company] = frappe.get_cached_value(
+ "Company", company, "cost_center"
+ )
return frappe.flags.company_cost_center[company]
+
def get_company_currency(company):
- '''Returns the default company currency'''
+ """Returns the default company currency"""
if not frappe.flags.company_currency:
frappe.flags.company_currency = {}
if not company in frappe.flags.company_currency:
- frappe.flags.company_currency[company] = frappe.db.get_value('Company', company, 'default_currency', cache=True)
+ frappe.flags.company_currency[company] = frappe.db.get_value(
+ "Company", company, "default_currency", cache=True
+ )
return frappe.flags.company_currency[company]
+
def set_perpetual_inventory(enable=1, company=None):
if not company:
company = "_Test Company" if frappe.flags.in_test else get_default_company()
@@ -53,9 +61,10 @@ def set_perpetual_inventory(enable=1, company=None):
company.enable_perpetual_inventory = enable
company.save()
+
def encode_company_abbr(name, company=None, abbr=None):
- '''Returns name encoded with company abbreviation'''
- company_abbr = abbr or frappe.get_cached_value('Company', company, "abbr")
+ """Returns name encoded with company abbreviation"""
+ company_abbr = abbr or frappe.get_cached_value("Company", company, "abbr")
parts = name.rsplit(" - ", 1)
if parts[-1].lower() != company_abbr.lower():
@@ -63,62 +72,69 @@ def encode_company_abbr(name, company=None, abbr=None):
return " - ".join(parts)
+
def is_perpetual_inventory_enabled(company):
if not company:
company = "_Test Company" if frappe.flags.in_test else get_default_company()
- if not hasattr(frappe.local, 'enable_perpetual_inventory'):
+ if not hasattr(frappe.local, "enable_perpetual_inventory"):
frappe.local.enable_perpetual_inventory = {}
if not company in frappe.local.enable_perpetual_inventory:
- frappe.local.enable_perpetual_inventory[company] = frappe.get_cached_value('Company',
- company, "enable_perpetual_inventory") or 0
+ frappe.local.enable_perpetual_inventory[company] = (
+ frappe.get_cached_value("Company", company, "enable_perpetual_inventory") or 0
+ )
return frappe.local.enable_perpetual_inventory[company]
+
def get_default_finance_book(company=None):
if not company:
company = get_default_company()
- if not hasattr(frappe.local, 'default_finance_book'):
+ if not hasattr(frappe.local, "default_finance_book"):
frappe.local.default_finance_book = {}
if not company in frappe.local.default_finance_book:
- frappe.local.default_finance_book[company] = frappe.get_cached_value('Company',
- company, "default_finance_book")
+ frappe.local.default_finance_book[company] = frappe.get_cached_value(
+ "Company", company, "default_finance_book"
+ )
return frappe.local.default_finance_book[company]
+
def get_party_account_type(party_type):
- if not hasattr(frappe.local, 'party_account_types'):
+ if not hasattr(frappe.local, "party_account_types"):
frappe.local.party_account_types = {}
if not party_type in frappe.local.party_account_types:
- frappe.local.party_account_types[party_type] = frappe.db.get_value("Party Type",
- party_type, "account_type") or ''
+ frappe.local.party_account_types[party_type] = (
+ frappe.db.get_value("Party Type", party_type, "account_type") or ""
+ )
return frappe.local.party_account_types[party_type]
+
def get_region(company=None):
- '''Return the default country based on flag, company or global settings
+ """Return the default country based on flag, company or global settings
You can also set global company flag in `frappe.flags.company`
- '''
+ """
if company or frappe.flags.company:
- return frappe.get_cached_value('Company',
- company or frappe.flags.company, 'country')
+ return frappe.get_cached_value("Company", company or frappe.flags.company, "country")
elif frappe.flags.country:
return frappe.flags.country
else:
- return frappe.get_system_settings('country')
+ return frappe.get_system_settings("country")
+
def allow_regional(fn):
- '''Decorator to make a function regionally overridable
+ """Decorator to make a function regionally overridable
Example:
@erpnext.allow_regional
def myfunction():
- pass'''
+ pass"""
def caller(*args, **kwargs):
overrides = frappe.get_hooks("regional_overrides", {}).get(get_region())
@@ -131,11 +147,3 @@ def allow_regional(fn):
return frappe.get_attr(overrides[function_path][-1])(*args, **kwargs)
return caller
-
-def get_last_membership(member):
- '''Returns last membership if exists'''
- last_membership = frappe.get_all('Membership', 'name,to_date,membership_type',
- dict(member=member, paid=1), order_by='to_date desc', limit=1)
-
- if last_membership:
- return last_membership[0]
diff --git a/erpnext/accounts/custom/address.py b/erpnext/accounts/custom/address.py
index 551048e50b4..775a81fd25f 100644
--- a/erpnext/accounts/custom/address.py
+++ b/erpnext/accounts/custom/address.py
@@ -21,37 +21,39 @@ class ERPNextAddress(Address):
return super(ERPNextAddress, self).link_address()
def update_compnay_address(self):
- for link in self.get('links'):
- if link.link_doctype == 'Company':
+ for link in self.get("links"):
+ if link.link_doctype == "Company":
self.is_your_company_address = 1
def validate_reference(self):
if self.is_your_company_address and not [
row for row in self.links if row.link_doctype == "Company"
]:
- frappe.throw(_("Address needs to be linked to a Company. Please add a row for Company in the Links table."),
- title=_("Company Not Linked"))
+ frappe.throw(
+ _("Address needs to be linked to a Company. Please add a row for Company in the Links table."),
+ title=_("Company Not Linked"),
+ )
def on_update(self):
"""
After Address is updated, update the related 'Primary Address' on Customer.
"""
address_display = get_address_display(self.as_dict())
- filters = { "customer_primary_address": self.name }
+ filters = {"customer_primary_address": self.name}
customers = frappe.db.get_all("Customer", filters=filters, as_list=True)
for customer_name in customers:
frappe.db.set_value("Customer", customer_name[0], "primary_address", address_display)
+
@frappe.whitelist()
-def get_shipping_address(company, address = None):
+def get_shipping_address(company, address=None):
filters = [
["Dynamic Link", "link_doctype", "=", "Company"],
["Dynamic Link", "link_name", "=", company],
- ["Address", "is_your_company_address", "=", 1]
+ ["Address", "is_your_company_address", "=", 1],
]
fields = ["*"]
- if address and frappe.db.get_value('Dynamic Link',
- {'parent': address, 'link_name': company}):
+ if address and frappe.db.get_value("Dynamic Link", {"parent": address, "link_name": company}):
filters.append(["Address", "name", "=", address])
if not address:
filters.append(["Address", "is_shipping_address", "=", 1])
diff --git a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py
index 1c1364ed111..fefec0ee7b4 100644
--- a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py
+++ b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py
@@ -12,15 +12,24 @@ from frappe.utils.nestedset import get_descendants_of
@frappe.whitelist()
@cache_source
-def get(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(
+ 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))
timespan = chart.timespan
- if chart.timespan == 'Select Date Range':
+ if chart.timespan == "Select Date Range":
from_date = chart.from_date
to_date = chart.to_date
@@ -31,17 +40,23 @@ def get(chart_name = None, chart = None, no_cache = None, filters = None, from_d
company = filters.get("company")
if not account and chart_name:
- frappe.throw(_("Account is not set for the dashboard chart {0}")
- .format(get_link_to_form("Dashboard Chart", chart_name)))
+ frappe.throw(
+ _("Account is not set for the dashboard chart {0}").format(
+ get_link_to_form("Dashboard Chart", chart_name)
+ )
+ )
if not frappe.db.exists("Account", account) and chart_name:
- frappe.throw(_("Account {0} does not exists in the dashboard chart {1}")
- .format(account, get_link_to_form("Dashboard Chart", chart_name)))
+ frappe.throw(
+ _("Account {0} does not exists in the dashboard chart {1}").format(
+ account, get_link_to_form("Dashboard Chart", chart_name)
+ )
+ )
if not to_date:
to_date = nowdate()
if not from_date:
- if timegrain in ('Monthly', 'Quarterly'):
+ if timegrain in ("Monthly", "Quarterly"):
from_date = get_from_date_from_timespan(to_date, timespan)
# fetch dates to plot
@@ -54,16 +69,14 @@ def get(chart_name = None, chart = None, no_cache = None, filters = None, from_d
result = build_result(account, dates, gl_entries)
return {
- "labels": [formatdate(r[0].strftime('%Y-%m-%d')) for r in result],
- "datasets": [{
- "name": account,
- "values": [r[1] for r in result]
- }]
+ "labels": [formatdate(r[0].strftime("%Y-%m-%d")) for r in result],
+ "datasets": [{"name": account, "values": [r[1] for r in result]}],
}
+
def build_result(account, dates, gl_entries):
result = [[getdate(date), 0.0] for date in dates]
- root_type = frappe.db.get_value('Account', account, 'root_type')
+ root_type = frappe.db.get_value("Account", account, "root_type")
# start with the first date
date_index = 0
@@ -78,30 +91,34 @@ def build_result(account, dates, gl_entries):
result[date_index][1] += entry.debit - entry.credit
# if account type is credit, switch balances
- if root_type not in ('Asset', 'Expense'):
+ if root_type not in ("Asset", "Expense"):
for r in result:
r[1] = -1 * r[1]
# for balance sheet accounts, the totals are cumulative
- if root_type in ('Asset', 'Liability', 'Equity'):
+ if root_type in ("Asset", "Liability", "Equity"):
for i, r in enumerate(result):
if i > 0:
- r[1] = r[1] + result[i-1][1]
+ r[1] = r[1] + result[i - 1][1]
return result
+
def get_gl_entries(account, to_date):
- child_accounts = get_descendants_of('Account', account, ignore_permissions=True)
+ child_accounts = get_descendants_of("Account", account, ignore_permissions=True)
child_accounts.append(account)
- return frappe.db.get_all('GL Entry',
- fields = ['posting_date', 'debit', 'credit'],
- filters = [
- dict(posting_date = ('<', to_date)),
- dict(account = ('in', child_accounts)),
- dict(voucher_type = ('!=', 'Period Closing Voucher'))
+ return frappe.db.get_all(
+ "GL Entry",
+ fields=["posting_date", "debit", "credit"],
+ filters=[
+ dict(posting_date=("<", to_date)),
+ dict(account=("in", child_accounts)),
+ dict(voucher_type=("!=", "Period Closing Voucher")),
],
- order_by = 'posting_date asc')
+ order_by="posting_date asc",
+ )
+
def get_dates_from_timegrain(from_date, to_date, timegrain):
days = months = years = 0
@@ -116,6 +133,8 @@ def get_dates_from_timegrain(from_date, to_date, timegrain):
dates = [get_period_ending(from_date, timegrain)]
while getdate(dates[-1]) < getdate(to_date):
- date = get_period_ending(add_to_date(dates[-1], years=years, months=months, days=days), timegrain)
+ date = get_period_ending(
+ add_to_date(dates[-1], years=years, months=months, days=days), timegrain
+ )
dates.append(date)
return dates
diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py
index 9e2cdfffd9a..0611f880c5e 100644
--- a/erpnext/accounts/deferred_revenue.py
+++ b/erpnext/accounts/deferred_revenue.py
@@ -22,20 +22,23 @@ from erpnext.accounts.utils import get_account_currency
def validate_service_stop_date(doc):
- ''' Validates service_stop_date for Purchase Invoice and Sales Invoice '''
+ """Validates service_stop_date for Purchase Invoice and Sales Invoice"""
- enable_check = "enable_deferred_revenue" \
- if doc.doctype=="Sales Invoice" else "enable_deferred_expense"
+ enable_check = (
+ "enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense"
+ )
old_stop_dates = {}
- old_doc = frappe.db.get_all("{0} Item".format(doc.doctype),
- {"parent": doc.name}, ["name", "service_stop_date"])
+ old_doc = frappe.db.get_all(
+ "{0} Item".format(doc.doctype), {"parent": doc.name}, ["name", "service_stop_date"]
+ )
for d in old_doc:
old_stop_dates[d.name] = d.service_stop_date or ""
for item in doc.items:
- if not item.get(enable_check): continue
+ if not item.get(enable_check):
+ continue
if item.service_stop_date:
if date_diff(item.service_stop_date, item.service_start_date) < 0:
@@ -44,21 +47,31 @@ def validate_service_stop_date(doc):
if date_diff(item.service_stop_date, item.service_end_date) > 0:
frappe.throw(_("Service Stop Date cannot be after Service End Date"))
- if old_stop_dates and old_stop_dates.get(item.name) and item.service_stop_date!=old_stop_dates.get(item.name):
+ if (
+ old_stop_dates
+ and old_stop_dates.get(item.name)
+ and item.service_stop_date != old_stop_dates.get(item.name)
+ ):
frappe.throw(_("Cannot change Service Stop Date for item in row {0}").format(item.idx))
+
def build_conditions(process_type, account, company):
- conditions=''
- deferred_account = "item.deferred_revenue_account" if process_type=="Income" else "item.deferred_expense_account"
+ conditions = ""
+ deferred_account = (
+ "item.deferred_revenue_account" if process_type == "Income" else "item.deferred_expense_account"
+ )
if account:
- conditions += "AND %s='%s'"%(deferred_account, account)
+ conditions += "AND %s='%s'" % (deferred_account, account)
elif company:
conditions += f"AND p.company = {frappe.db.escape(company)}"
return conditions
-def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_date=None, conditions=''):
+
+def convert_deferred_expense_to_expense(
+ deferred_process, start_date=None, end_date=None, conditions=""
+):
# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
if not start_date:
@@ -67,14 +80,19 @@ def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_d
end_date = add_days(today(), -1)
# check for the purchase invoice for which GL entries has to be done
- invoices = frappe.db.sql_list('''
+ invoices = frappe.db.sql_list(
+ """
select distinct item.parent
from `tabPurchase Invoice Item` item, `tabPurchase Invoice` p
where item.service_start_date<=%s and item.service_end_date>=%s
and item.enable_deferred_expense = 1 and item.parent=p.name
and item.docstatus = 1 and ifnull(item.amount, 0) > 0
{0}
- '''.format(conditions), (end_date, start_date)) #nosec
+ """.format(
+ conditions
+ ),
+ (end_date, start_date),
+ ) # nosec
# For each invoice, book deferred expense
for invoice in invoices:
@@ -84,7 +102,10 @@ def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_d
if frappe.flags.deferred_accounting_error:
send_mail(deferred_process)
-def convert_deferred_revenue_to_income(deferred_process, start_date=None, end_date=None, conditions=''):
+
+def convert_deferred_revenue_to_income(
+ deferred_process, start_date=None, end_date=None, conditions=""
+):
# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
if not start_date:
@@ -93,14 +114,19 @@ def convert_deferred_revenue_to_income(deferred_process, start_date=None, end_da
end_date = add_days(today(), -1)
# check for the sales invoice for which GL entries has to be done
- invoices = frappe.db.sql_list('''
+ invoices = frappe.db.sql_list(
+ """
select distinct item.parent
from `tabSales Invoice Item` item, `tabSales Invoice` p
where item.service_start_date<=%s and item.service_end_date>=%s
and item.enable_deferred_revenue = 1 and item.parent=p.name
and item.docstatus = 1 and ifnull(item.amount, 0) > 0
{0}
- '''.format(conditions), (end_date, start_date)) #nosec
+ """.format(
+ conditions
+ ),
+ (end_date, start_date),
+ ) # nosec
for invoice in invoices:
doc = frappe.get_doc("Sales Invoice", invoice)
@@ -109,30 +135,43 @@ def convert_deferred_revenue_to_income(deferred_process, start_date=None, end_da
if frappe.flags.deferred_accounting_error:
send_mail(deferred_process)
+
def get_booking_dates(doc, item, posting_date=None):
if not posting_date:
posting_date = add_days(today(), -1)
last_gl_entry = False
- deferred_account = "deferred_revenue_account" if doc.doctype=="Sales Invoice" else "deferred_expense_account"
+ deferred_account = (
+ "deferred_revenue_account" if doc.doctype == "Sales Invoice" else "deferred_expense_account"
+ )
- prev_gl_entry = frappe.db.sql('''
+ prev_gl_entry = frappe.db.sql(
+ """
select name, posting_date from `tabGL Entry` where company=%s and account=%s and
voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
+ and is_cancelled = 0
order by posting_date desc limit 1
- ''', (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
+ """,
+ (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
+ as_dict=True,
+ )
- prev_gl_via_je = frappe.db.sql('''
+ prev_gl_via_je = frappe.db.sql(
+ """
SELECT p.name, p.posting_date FROM `tabJournal Entry` p, `tabJournal Entry Account` c
WHERE p.name = c.parent and p.company=%s and c.account=%s
and c.reference_type=%s and c.reference_name=%s
and c.reference_detail_no=%s and c.docstatus < 2 order by posting_date desc limit 1
- ''', (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
+ """,
+ (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
+ as_dict=True,
+ )
if prev_gl_via_je:
- if (not prev_gl_entry) or (prev_gl_entry and
- prev_gl_entry[0].posting_date < prev_gl_via_je[0].posting_date):
+ if (not prev_gl_entry) or (
+ prev_gl_entry and prev_gl_entry[0].posting_date < prev_gl_via_je[0].posting_date
+ ):
prev_gl_entry = prev_gl_via_je
if prev_gl_entry:
@@ -156,66 +195,94 @@ def get_booking_dates(doc, item, posting_date=None):
else:
return None, None, None
-def calculate_monthly_amount(doc, item, last_gl_entry, start_date, end_date, total_days, total_booking_days, account_currency):
+
+def calculate_monthly_amount(
+ doc, item, last_gl_entry, start_date, end_date, total_days, total_booking_days, account_currency
+):
amount, base_amount = 0, 0
if not last_gl_entry:
- total_months = (item.service_end_date.year - item.service_start_date.year) * 12 + \
- (item.service_end_date.month - item.service_start_date.month) + 1
+ total_months = (
+ (item.service_end_date.year - item.service_start_date.year) * 12
+ + (item.service_end_date.month - item.service_start_date.month)
+ + 1
+ )
- prorate_factor = flt(date_diff(item.service_end_date, item.service_start_date)) \
- / flt(date_diff(get_last_day(item.service_end_date), get_first_day(item.service_start_date)))
+ prorate_factor = flt(date_diff(item.service_end_date, item.service_start_date)) / flt(
+ date_diff(get_last_day(item.service_end_date), get_first_day(item.service_start_date))
+ )
actual_months = rounded(total_months * prorate_factor, 1)
- already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(doc, item)
+ already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(
+ doc, item
+ )
base_amount = flt(item.base_net_amount / actual_months, item.precision("base_net_amount"))
if base_amount + already_booked_amount > item.base_net_amount:
base_amount = item.base_net_amount - already_booked_amount
- if account_currency==doc.company_currency:
+ if account_currency == doc.company_currency:
amount = base_amount
else:
- amount = flt(item.net_amount/actual_months, item.precision("net_amount"))
+ amount = flt(item.net_amount / actual_months, item.precision("net_amount"))
if amount + already_booked_amount_in_account_currency > item.net_amount:
amount = item.net_amount - already_booked_amount_in_account_currency
if not (get_first_day(start_date) == start_date and get_last_day(end_date) == end_date):
- partial_month = flt(date_diff(end_date, start_date)) \
- / flt(date_diff(get_last_day(end_date), get_first_day(start_date)))
+ partial_month = flt(date_diff(end_date, start_date)) / flt(
+ date_diff(get_last_day(end_date), get_first_day(start_date))
+ )
base_amount = rounded(partial_month, 1) * base_amount
amount = rounded(partial_month, 1) * amount
else:
- already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(doc, item)
- base_amount = flt(item.base_net_amount - already_booked_amount, item.precision("base_net_amount"))
- if account_currency==doc.company_currency:
+ already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(
+ doc, item
+ )
+ base_amount = flt(
+ item.base_net_amount - already_booked_amount, item.precision("base_net_amount")
+ )
+ if account_currency == doc.company_currency:
amount = base_amount
else:
- amount = flt(item.net_amount - already_booked_amount_in_account_currency, item.precision("net_amount"))
+ amount = flt(
+ item.net_amount - already_booked_amount_in_account_currency, item.precision("net_amount")
+ )
return amount, base_amount
+
def calculate_amount(doc, item, last_gl_entry, total_days, total_booking_days, account_currency):
amount, base_amount = 0, 0
if not last_gl_entry:
- base_amount = flt(item.base_net_amount*total_booking_days/flt(total_days), item.precision("base_net_amount"))
- if account_currency==doc.company_currency:
+ base_amount = flt(
+ item.base_net_amount * total_booking_days / flt(total_days), item.precision("base_net_amount")
+ )
+ if account_currency == doc.company_currency:
amount = base_amount
else:
- amount = flt(item.net_amount*total_booking_days/flt(total_days), item.precision("net_amount"))
+ amount = flt(
+ item.net_amount * total_booking_days / flt(total_days), item.precision("net_amount")
+ )
else:
- already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(doc, item)
+ already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(
+ doc, item
+ )
- base_amount = flt(item.base_net_amount - already_booked_amount, item.precision("base_net_amount"))
- if account_currency==doc.company_currency:
+ base_amount = flt(
+ item.base_net_amount - already_booked_amount, item.precision("base_net_amount")
+ )
+ if account_currency == doc.company_currency:
amount = base_amount
else:
- amount = flt(item.net_amount - already_booked_amount_in_account_currency, item.precision("net_amount"))
+ amount = flt(
+ item.net_amount - already_booked_amount_in_account_currency, item.precision("net_amount")
+ )
return amount, base_amount
+
def get_already_booked_amount(doc, item):
if doc.doctype == "Sales Invoice":
total_credit_debit, total_credit_debit_currency = "debit", "debit_in_account_currency"
@@ -224,20 +291,31 @@ def get_already_booked_amount(doc, item):
total_credit_debit, total_credit_debit_currency = "credit", "credit_in_account_currency"
deferred_account = "deferred_expense_account"
- gl_entries_details = frappe.db.sql('''
+ gl_entries_details = frappe.db.sql(
+ """
select sum({0}) as total_credit, sum({1}) as total_credit_in_account_currency, voucher_detail_no
from `tabGL Entry` where company=%s and account=%s and voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
+ and is_cancelled = 0
group by voucher_detail_no
- '''.format(total_credit_debit, total_credit_debit_currency),
- (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
+ """.format(
+ total_credit_debit, total_credit_debit_currency
+ ),
+ (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
+ as_dict=True,
+ )
- journal_entry_details = frappe.db.sql('''
+ journal_entry_details = frappe.db.sql(
+ """
SELECT sum(c.{0}) as total_credit, sum(c.{1}) as total_credit_in_account_currency, reference_detail_no
FROM `tabJournal Entry` p , `tabJournal Entry Account` c WHERE p.name = c.parent and
p.company = %s and c.account=%s and c.reference_type=%s and c.reference_name=%s and c.reference_detail_no=%s
and p.docstatus < 2 group by reference_detail_no
- '''.format(total_credit_debit, total_credit_debit_currency),
- (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
+ """.format(
+ total_credit_debit, total_credit_debit_currency
+ ),
+ (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
+ as_dict=True,
+ )
already_booked_amount = gl_entries_details[0].total_credit if gl_entries_details else 0
already_booked_amount += journal_entry_details[0].total_credit if journal_entry_details else 0
@@ -245,20 +323,29 @@ def get_already_booked_amount(doc, item):
if doc.currency == doc.company_currency:
already_booked_amount_in_account_currency = already_booked_amount
else:
- already_booked_amount_in_account_currency = gl_entries_details[0].total_credit_in_account_currency if gl_entries_details else 0
- already_booked_amount_in_account_currency += journal_entry_details[0].total_credit_in_account_currency if journal_entry_details else 0
+ already_booked_amount_in_account_currency = (
+ gl_entries_details[0].total_credit_in_account_currency if gl_entries_details else 0
+ )
+ already_booked_amount_in_account_currency += (
+ journal_entry_details[0].total_credit_in_account_currency if journal_entry_details else 0
+ )
return already_booked_amount, already_booked_amount_in_account_currency
+
def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
- enable_check = "enable_deferred_revenue" \
- if doc.doctype=="Sales Invoice" else "enable_deferred_expense"
+ enable_check = (
+ "enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense"
+ )
- accounts_frozen_upto = frappe.get_cached_value('Accounts Settings', 'None', 'acc_frozen_upto')
+ accounts_frozen_upto = frappe.get_cached_value("Accounts Settings", "None", "acc_frozen_upto")
- def _book_deferred_revenue_or_expense(item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on):
+ def _book_deferred_revenue_or_expense(
+ item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on
+ ):
start_date, end_date, last_gl_entry = get_booking_dates(doc, item, posting_date=posting_date)
- if not (start_date and end_date): return
+ if not (start_date and end_date):
+ return
account_currency = get_account_currency(item.expense_account or item.income_account)
if doc.doctype == "Sales Invoice":
@@ -271,107 +358,180 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
total_days = date_diff(item.service_end_date, item.service_start_date) + 1
total_booking_days = date_diff(end_date, start_date) + 1
- if book_deferred_entries_based_on == 'Months':
- amount, base_amount = calculate_monthly_amount(doc, item, last_gl_entry,
- start_date, end_date, total_days, total_booking_days, account_currency)
+ if book_deferred_entries_based_on == "Months":
+ amount, base_amount = calculate_monthly_amount(
+ doc,
+ item,
+ last_gl_entry,
+ start_date,
+ end_date,
+ total_days,
+ total_booking_days,
+ account_currency,
+ )
else:
- amount, base_amount = calculate_amount(doc, item, last_gl_entry,
- total_days, total_booking_days, account_currency)
+ amount, base_amount = calculate_amount(
+ doc, item, last_gl_entry, total_days, total_booking_days, account_currency
+ )
if not amount:
return
# check if books nor frozen till endate:
- if getdate(end_date) >= getdate(accounts_frozen_upto):
+ if accounts_frozen_upto and (end_date) <= getdate(accounts_frozen_upto):
end_date = get_last_day(add_days(accounts_frozen_upto, 1))
if via_journal_entry:
- book_revenue_via_journal_entry(doc, credit_account, debit_account, against, amount,
- base_amount, end_date, project, account_currency, item.cost_center, item, deferred_process, submit_journal_entry)
+ book_revenue_via_journal_entry(
+ doc,
+ credit_account,
+ debit_account,
+ against,
+ amount,
+ base_amount,
+ end_date,
+ project,
+ account_currency,
+ item.cost_center,
+ item,
+ deferred_process,
+ submit_journal_entry,
+ )
else:
- make_gl_entries(doc, credit_account, debit_account, against,
- amount, base_amount, end_date, project, account_currency, item.cost_center, item, deferred_process)
+ make_gl_entries(
+ doc,
+ credit_account,
+ debit_account,
+ against,
+ amount,
+ base_amount,
+ end_date,
+ project,
+ account_currency,
+ item.cost_center,
+ item,
+ deferred_process,
+ )
# Returned in case of any errors because it tries to submit the same record again and again in case of errors
if frappe.flags.deferred_accounting_error:
return
if getdate(end_date) < getdate(posting_date) and not last_gl_entry:
- _book_deferred_revenue_or_expense(item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on)
+ _book_deferred_revenue_or_expense(
+ item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on
+ )
- via_journal_entry = cint(frappe.db.get_singles_value('Accounts Settings', 'book_deferred_entries_via_journal_entry'))
- submit_journal_entry = cint(frappe.db.get_singles_value('Accounts Settings', 'submit_journal_entries'))
- book_deferred_entries_based_on = frappe.db.get_singles_value('Accounts Settings', 'book_deferred_entries_based_on')
+ via_journal_entry = cint(
+ frappe.db.get_singles_value("Accounts Settings", "book_deferred_entries_via_journal_entry")
+ )
+ submit_journal_entry = cint(
+ frappe.db.get_singles_value("Accounts Settings", "submit_journal_entries")
+ )
+ book_deferred_entries_based_on = frappe.db.get_singles_value(
+ "Accounts Settings", "book_deferred_entries_based_on"
+ )
- for item in doc.get('items'):
+ for item in doc.get("items"):
if item.get(enable_check):
- _book_deferred_revenue_or_expense(item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on)
+ _book_deferred_revenue_or_expense(
+ item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on
+ )
+
def process_deferred_accounting(posting_date=None):
- ''' Converts deferred income/expense into income/expense
- Executed via background jobs on every month end '''
+ """Converts deferred income/expense into income/expense
+ Executed via background jobs on every month end"""
if not posting_date:
posting_date = today()
- if not cint(frappe.db.get_singles_value('Accounts Settings', 'automatically_process_deferred_accounting_entry')):
+ if not cint(
+ frappe.db.get_singles_value(
+ "Accounts Settings", "automatically_process_deferred_accounting_entry"
+ )
+ ):
return
start_date = add_months(today(), -1)
end_date = add_days(today(), -1)
- companies = frappe.get_all('Company')
+ companies = frappe.get_all("Company")
for company in companies:
- for record_type in ('Income', 'Expense'):
- doc = frappe.get_doc(dict(
- doctype='Process Deferred Accounting',
- company=company.name,
- posting_date=posting_date,
- start_date=start_date,
- end_date=end_date,
- type=record_type
- ))
+ for record_type in ("Income", "Expense"):
+ doc = frappe.get_doc(
+ dict(
+ doctype="Process Deferred Accounting",
+ company=company.name,
+ posting_date=posting_date,
+ start_date=start_date,
+ end_date=end_date,
+ type=record_type,
+ )
+ )
doc.insert()
doc.submit()
-def make_gl_entries(doc, credit_account, debit_account, against,
- amount, base_amount, posting_date, project, account_currency, cost_center, item, deferred_process=None):
+
+def make_gl_entries(
+ doc,
+ credit_account,
+ debit_account,
+ against,
+ amount,
+ base_amount,
+ posting_date,
+ project,
+ account_currency,
+ cost_center,
+ item,
+ deferred_process=None,
+):
# GL Entry for crediting the amount in the deferred expense
from erpnext.accounts.general_ledger import make_gl_entries
- if amount == 0: return
+ if amount == 0:
+ return
gl_entries = []
gl_entries.append(
- doc.get_gl_dict({
- "account": credit_account,
- "against": against,
- "credit": base_amount,
- "credit_in_account_currency": amount,
- "cost_center": cost_center,
- "voucher_detail_no": item.name,
- 'posting_date': posting_date,
- 'project': project,
- 'against_voucher_type': 'Process Deferred Accounting',
- 'against_voucher': deferred_process
- }, account_currency, item=item)
+ doc.get_gl_dict(
+ {
+ "account": credit_account,
+ "against": against,
+ "credit": base_amount,
+ "credit_in_account_currency": amount,
+ "cost_center": cost_center,
+ "voucher_detail_no": item.name,
+ "posting_date": posting_date,
+ "project": project,
+ "against_voucher_type": "Process Deferred Accounting",
+ "against_voucher": deferred_process,
+ },
+ account_currency,
+ item=item,
+ )
)
# GL Entry to debit the amount from the expense
gl_entries.append(
- doc.get_gl_dict({
- "account": debit_account,
- "against": against,
- "debit": base_amount,
- "debit_in_account_currency": amount,
- "cost_center": cost_center,
- "voucher_detail_no": item.name,
- 'posting_date': posting_date,
- 'project': project,
- 'against_voucher_type': 'Process Deferred Accounting',
- 'against_voucher': deferred_process
- }, account_currency, item=item)
+ doc.get_gl_dict(
+ {
+ "account": debit_account,
+ "against": against,
+ "debit": base_amount,
+ "debit_in_account_currency": amount,
+ "cost_center": cost_center,
+ "voucher_detail_no": item.name,
+ "posting_date": posting_date,
+ "project": project,
+ "against_voucher_type": "Process Deferred Accounting",
+ "against_voucher": deferred_process,
+ },
+ account_currency,
+ item=item,
+ )
)
if gl_entries:
@@ -381,68 +541,88 @@ def make_gl_entries(doc, credit_account, debit_account, against,
except Exception as e:
if frappe.flags.in_test:
traceback = frappe.get_traceback()
- frappe.log_error(title=_('Error while processing deferred accounting for Invoice {0}').format(doc.name), message=traceback)
+ frappe.log_error(
+ title=_("Error while processing deferred accounting for Invoice {0}").format(doc.name),
+ message=traceback,
+ )
raise e
else:
frappe.db.rollback()
traceback = frappe.get_traceback()
- frappe.log_error(title=_('Error while processing deferred accounting for Invoice {0}').format(doc.name), message=traceback)
+ frappe.log_error(
+ title=_("Error while processing deferred accounting for Invoice {0}").format(doc.name),
+ message=traceback,
+ )
frappe.flags.deferred_accounting_error = True
+
def send_mail(deferred_process):
title = _("Error while processing deferred accounting for {0}").format(deferred_process)
- link = get_link_to_form('Process Deferred Accounting', deferred_process)
+ link = get_link_to_form("Process Deferred Accounting", deferred_process)
content = _("Deferred accounting failed for some invoices:") + "\n"
- content += _("Please check Process Deferred Accounting {0} and submit manually after resolving errors.").format(link)
+ content += _(
+ "Please check Process Deferred Accounting {0} and submit manually after resolving errors."
+ ).format(link)
sendmail_to_system_managers(title, content)
-def book_revenue_via_journal_entry(doc, credit_account, debit_account, against,
- amount, base_amount, posting_date, project, account_currency, cost_center, item,
- deferred_process=None, submit='No'):
- if amount == 0: return
+def book_revenue_via_journal_entry(
+ doc,
+ credit_account,
+ debit_account,
+ against,
+ amount,
+ base_amount,
+ posting_date,
+ project,
+ account_currency,
+ cost_center,
+ item,
+ deferred_process=None,
+ submit="No",
+):
- journal_entry = frappe.new_doc('Journal Entry')
+ if amount == 0:
+ return
+
+ journal_entry = frappe.new_doc("Journal Entry")
journal_entry.posting_date = posting_date
journal_entry.company = doc.company
- journal_entry.voucher_type = 'Deferred Revenue' if doc.doctype == 'Sales Invoice' \
- else 'Deferred Expense'
+ journal_entry.voucher_type = (
+ "Deferred Revenue" if doc.doctype == "Sales Invoice" else "Deferred Expense"
+ )
debit_entry = {
- 'account': credit_account,
- 'credit': base_amount,
- 'credit_in_account_currency': amount,
- 'account_currency': account_currency,
- 'reference_name': doc.name,
- 'reference_type': doc.doctype,
- 'reference_detail_no': item.name,
- 'cost_center': cost_center,
- 'project': project,
+ "account": credit_account,
+ "credit": base_amount,
+ "credit_in_account_currency": amount,
+ "account_currency": account_currency,
+ "reference_name": doc.name,
+ "reference_type": doc.doctype,
+ "reference_detail_no": item.name,
+ "cost_center": cost_center,
+ "project": project,
}
credit_entry = {
- 'account': debit_account,
- 'debit': base_amount,
- 'debit_in_account_currency': amount,
- 'account_currency': account_currency,
- 'reference_name': doc.name,
- 'reference_type': doc.doctype,
- 'reference_detail_no': item.name,
- 'cost_center': cost_center,
- 'project': project,
+ "account": debit_account,
+ "debit": base_amount,
+ "debit_in_account_currency": amount,
+ "account_currency": account_currency,
+ "reference_name": doc.name,
+ "reference_type": doc.doctype,
+ "reference_detail_no": item.name,
+ "cost_center": cost_center,
+ "project": project,
}
for dimension in get_accounting_dimensions():
- debit_entry.update({
- dimension: item.get(dimension)
- })
+ debit_entry.update({dimension: item.get(dimension)})
- credit_entry.update({
- dimension: item.get(dimension)
- })
+ credit_entry.update({dimension: item.get(dimension)})
- journal_entry.append('accounts', debit_entry)
- journal_entry.append('accounts', credit_entry)
+ journal_entry.append("accounts", debit_entry)
+ journal_entry.append("accounts", credit_entry)
try:
journal_entry.save()
@@ -454,20 +634,30 @@ def book_revenue_via_journal_entry(doc, credit_account, debit_account, against,
except Exception:
frappe.db.rollback()
traceback = frappe.get_traceback()
- frappe.log_error(title=_('Error while processing deferred accounting for Invoice {0}').format(doc.name), message=traceback)
+ frappe.log_error(
+ title=_("Error while processing deferred accounting for Invoice {0}").format(doc.name),
+ message=traceback,
+ )
frappe.flags.deferred_accounting_error = True
+
def get_deferred_booking_accounts(doctype, voucher_detail_no, dr_or_cr):
- if doctype == 'Sales Invoice':
- credit_account, debit_account = frappe.db.get_value('Sales Invoice Item', {'name': voucher_detail_no},
- ['income_account', 'deferred_revenue_account'])
+ if doctype == "Sales Invoice":
+ credit_account, debit_account = frappe.db.get_value(
+ "Sales Invoice Item",
+ {"name": voucher_detail_no},
+ ["income_account", "deferred_revenue_account"],
+ )
else:
- credit_account, debit_account = frappe.db.get_value('Purchase Invoice Item', {'name': voucher_detail_no},
- ['deferred_expense_account', 'expense_account'])
+ credit_account, debit_account = frappe.db.get_value(
+ "Purchase Invoice Item",
+ {"name": voucher_detail_no},
+ ["deferred_expense_account", "expense_account"],
+ )
- if dr_or_cr == 'Debit':
+ if dr_or_cr == "Debit":
return debit_account
else:
return credit_account
diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py
index f8a06c7243f..c71ea3648b9 100644
--- a/erpnext/accounts/doctype/account/account.py
+++ b/erpnext/accounts/doctype/account/account.py
@@ -10,11 +10,17 @@ from frappe.utils.nestedset import NestedSet, get_ancestors_of, get_descendants_
import erpnext
-class RootNotEditable(frappe.ValidationError): pass
-class BalanceMismatchError(frappe.ValidationError): pass
+class RootNotEditable(frappe.ValidationError):
+ pass
+
+
+class BalanceMismatchError(frappe.ValidationError):
+ pass
+
class Account(NestedSet):
- nsm_parent_field = 'parent_account'
+ nsm_parent_field = "parent_account"
+
def on_update(self):
if frappe.local.flags.ignore_update_nsm:
return
@@ -22,17 +28,20 @@ class Account(NestedSet):
super(Account, self).on_update()
def onload(self):
- frozen_accounts_modifier = frappe.db.get_value("Accounts Settings", "Accounts Settings",
- "frozen_accounts_modifier")
+ frozen_accounts_modifier = frappe.db.get_value(
+ "Accounts Settings", "Accounts Settings", "frozen_accounts_modifier"
+ )
if not frozen_accounts_modifier or frozen_accounts_modifier in frappe.get_roles():
self.set_onload("can_freeze_account", True)
def autoname(self):
from erpnext.accounts.utils import get_autoname_with_number
+
self.name = get_autoname_with_number(self.account_number, self.account_name, None, self.company)
def validate(self):
from erpnext.accounts.utils import validate_field_number
+
if frappe.local.flags.allow_unverified_charts:
return
self.validate_parent()
@@ -49,22 +58,33 @@ class Account(NestedSet):
def validate_parent(self):
"""Fetch Parent Details and validate parent account"""
if self.parent_account:
- par = frappe.db.get_value("Account", self.parent_account,
- ["name", "is_group", "company"], as_dict=1)
+ par = frappe.db.get_value(
+ "Account", self.parent_account, ["name", "is_group", "company"], as_dict=1
+ )
if not par:
- throw(_("Account {0}: Parent account {1} does not exist").format(self.name, self.parent_account))
+ throw(
+ _("Account {0}: Parent account {1} does not exist").format(self.name, self.parent_account)
+ )
elif par.name == self.name:
throw(_("Account {0}: You can not assign itself as parent account").format(self.name))
elif not par.is_group:
- throw(_("Account {0}: Parent account {1} can not be a ledger").format(self.name, self.parent_account))
+ throw(
+ _("Account {0}: Parent account {1} can not be a ledger").format(
+ self.name, self.parent_account
+ )
+ )
elif par.company != self.company:
- throw(_("Account {0}: Parent account {1} does not belong to company: {2}")
- .format(self.name, self.parent_account, self.company))
+ throw(
+ _("Account {0}: Parent account {1} does not belong to company: {2}").format(
+ self.name, self.parent_account, self.company
+ )
+ )
def set_root_and_report_type(self):
if self.parent_account:
- par = frappe.db.get_value("Account", self.parent_account,
- ["report_type", "root_type"], as_dict=1)
+ par = frappe.db.get_value(
+ "Account", self.parent_account, ["report_type", "root_type"], as_dict=1
+ )
if par.report_type:
self.report_type = par.report_type
@@ -75,15 +95,20 @@ class Account(NestedSet):
db_value = frappe.db.get_value("Account", self.name, ["report_type", "root_type"], as_dict=1)
if db_value:
if self.report_type != db_value.report_type:
- frappe.db.sql("update `tabAccount` set report_type=%s where lft > %s and rgt < %s",
- (self.report_type, self.lft, self.rgt))
+ frappe.db.sql(
+ "update `tabAccount` set report_type=%s where lft > %s and rgt < %s",
+ (self.report_type, self.lft, self.rgt),
+ )
if self.root_type != db_value.root_type:
- frappe.db.sql("update `tabAccount` set root_type=%s where lft > %s and rgt < %s",
- (self.root_type, self.lft, self.rgt))
+ frappe.db.sql(
+ "update `tabAccount` set root_type=%s where lft > %s and rgt < %s",
+ (self.root_type, self.lft, self.rgt),
+ )
if self.root_type and not self.report_type:
- self.report_type = "Balance Sheet" \
- if self.root_type in ("Asset", "Liability", "Equity") else "Profit and Loss"
+ self.report_type = (
+ "Balance Sheet" if self.root_type in ("Asset", "Liability", "Equity") else "Profit and Loss"
+ )
def validate_root_details(self):
# does not exists parent
@@ -96,21 +121,26 @@ class Account(NestedSet):
def validate_root_company_and_sync_account_to_children(self):
# ignore validation while creating new compnay or while syncing to child companies
- if frappe.local.flags.ignore_root_company_validation or self.flags.ignore_root_company_validation:
+ if (
+ frappe.local.flags.ignore_root_company_validation or self.flags.ignore_root_company_validation
+ ):
return
ancestors = get_root_company(self.company)
if ancestors:
if frappe.get_value("Company", self.company, "allow_account_creation_against_child_company"):
return
- if not frappe.db.get_value("Account",
- {'account_name': self.account_name, 'company': ancestors[0]}, 'name'):
+ if not frappe.db.get_value(
+ "Account", {"account_name": self.account_name, "company": ancestors[0]}, "name"
+ ):
frappe.throw(_("Please add the account to root level Company - {}").format(ancestors[0]))
elif self.parent_account:
- descendants = get_descendants_of('Company', self.company)
- if not descendants: return
+ descendants = get_descendants_of("Company", self.company)
+ if not descendants:
+ return
parent_acc_name_map = {}
- parent_acc_name, parent_acc_number = frappe.db.get_value('Account', self.parent_account, \
- ["account_name", "account_number"])
+ parent_acc_name, parent_acc_number = frappe.db.get_value(
+ "Account", self.parent_account, ["account_name", "account_number"]
+ )
filters = {
"company": ["in", descendants],
"account_name": parent_acc_name,
@@ -118,10 +148,13 @@ class Account(NestedSet):
if parent_acc_number:
filters["account_number"] = parent_acc_number
- for d in frappe.db.get_values('Account', filters=filters, fieldname=["company", "name"], as_dict=True):
+ for d in frappe.db.get_values(
+ "Account", filters=filters, fieldname=["company", "name"], as_dict=True
+ ):
parent_acc_name_map[d["company"]] = d["name"]
- if not parent_acc_name_map: return
+ if not parent_acc_name_map:
+ return
self.create_account_for_child_company(parent_acc_name_map, descendants, parent_acc_name)
@@ -142,26 +175,38 @@ class Account(NestedSet):
def validate_frozen_accounts_modifier(self):
old_value = frappe.db.get_value("Account", self.name, "freeze_account")
if old_value and old_value != self.freeze_account:
- frozen_accounts_modifier = frappe.db.get_value('Accounts Settings', None, 'frozen_accounts_modifier')
- if not frozen_accounts_modifier or \
- frozen_accounts_modifier not in frappe.get_roles():
- throw(_("You are not authorized to set Frozen value"))
+ frozen_accounts_modifier = frappe.db.get_value(
+ "Accounts Settings", None, "frozen_accounts_modifier"
+ )
+ if not frozen_accounts_modifier or frozen_accounts_modifier not in frappe.get_roles():
+ throw(_("You are not authorized to set Frozen value"))
def validate_balance_must_be_debit_or_credit(self):
from erpnext.accounts.utils import get_balance_on
+
if not self.get("__islocal") and self.balance_must_be:
account_balance = get_balance_on(self.name)
if account_balance > 0 and self.balance_must_be == "Credit":
- frappe.throw(_("Account balance already in Debit, you are not allowed to set 'Balance Must Be' as 'Credit'"))
+ frappe.throw(
+ _(
+ "Account balance already in Debit, you are not allowed to set 'Balance Must Be' as 'Credit'"
+ )
+ )
elif account_balance < 0 and self.balance_must_be == "Debit":
- frappe.throw(_("Account balance already in Credit, you are not allowed to set 'Balance Must Be' as 'Debit'"))
+ frappe.throw(
+ _(
+ "Account balance already in Credit, you are not allowed to set 'Balance Must Be' as 'Debit'"
+ )
+ )
def validate_account_currency(self):
if not self.account_currency:
- self.account_currency = frappe.get_cached_value('Company', self.company, "default_currency")
+ self.account_currency = frappe.get_cached_value("Company", self.company, "default_currency")
- elif self.account_currency != frappe.db.get_value("Account", self.name, "account_currency"):
+ gl_currency = frappe.db.get_value("GL Entry", {"account": self.name}, "account_currency")
+
+ if gl_currency and self.account_currency != gl_currency:
if frappe.db.get_value("GL Entry", {"account": self.name}):
frappe.throw(_("Currency can not be changed after making entries using some other currency"))
@@ -170,45 +215,52 @@ class Account(NestedSet):
company_bold = frappe.bold(company)
parent_acc_name_bold = frappe.bold(parent_acc_name)
if not parent_acc_name_map.get(company):
- frappe.throw(_("While creating account for Child Company {0}, parent account {1} not found. Please create the parent account in corresponding COA")
- .format(company_bold, parent_acc_name_bold), title=_("Account Not Found"))
+ frappe.throw(
+ _(
+ "While creating account for Child Company {0}, parent account {1} not found. Please create the parent account in corresponding COA"
+ ).format(company_bold, parent_acc_name_bold),
+ title=_("Account Not Found"),
+ )
# validate if parent of child company account to be added is a group
- if (frappe.db.get_value("Account", self.parent_account, "is_group")
- and not frappe.db.get_value("Account", parent_acc_name_map[company], "is_group")):
- msg = _("While creating account for Child Company {0}, parent account {1} found as a ledger account.").format(company_bold, parent_acc_name_bold)
+ if frappe.db.get_value("Account", self.parent_account, "is_group") and not frappe.db.get_value(
+ "Account", parent_acc_name_map[company], "is_group"
+ ):
+ msg = _(
+ "While creating account for Child Company {0}, parent account {1} found as a ledger account."
+ ).format(company_bold, parent_acc_name_bold)
msg += "0:
- coupon.used=coupon.used-1
+ frappe.throw(
+ _("{0} Coupon used are {1}. Allowed quantity is exhausted").format(
+ coupon.coupon_code, coupon.used
+ )
+ )
+ elif transaction_type == "cancelled":
+ if coupon.used > 0:
+ coupon.used = coupon.used - 1
coupon.save(ignore_permissions=True)
diff --git a/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.py b/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.py
index d544f976d2a..08a7f4110f6 100644
--- a/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.py
+++ b/erpnext/accounts/doctype/process_deferred_accounting/process_deferred_accounting.py
@@ -21,17 +21,17 @@ class ProcessDeferredAccounting(Document):
def on_submit(self):
conditions = build_conditions(self.type, self.account, self.company)
- if self.type == 'Income':
+ if self.type == "Income":
convert_deferred_revenue_to_income(self.name, self.start_date, self.end_date, conditions)
else:
convert_deferred_expense_to_expense(self.name, self.start_date, self.end_date, conditions)
def on_cancel(self):
- self.ignore_linked_doctypes = ['GL Entry']
- gl_entries = frappe.get_all('GL Entry', fields = ['*'],
- filters={
- 'against_voucher_type': self.doctype,
- 'against_voucher': self.name
- })
+ self.ignore_linked_doctypes = ["GL Entry"]
+ gl_entries = frappe.get_all(
+ "GL Entry",
+ fields=["*"],
+ filters={"against_voucher_type": self.doctype, "against_voucher": self.name},
+ )
make_reverse_gl_entries(gl_entries=gl_entries)
diff --git a/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py b/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py
index 757d0fa3d01..164ba6aa348 100644
--- a/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py
+++ b/erpnext/accounts/doctype/process_deferred_accounting/test_process_deferred_accounting.py
@@ -15,9 +15,12 @@ from erpnext.stock.doctype.item.test_item import create_item
class TestProcessDeferredAccounting(unittest.TestCase):
def test_creation_of_ledger_entry_on_submit(self):
- ''' test creation of gl entries on submission of document '''
- deferred_account = create_account(account_name="Deferred Revenue",
- parent_account="Current Liabilities - _TC", company="_Test Company")
+ """test creation of gl entries on submission of document"""
+ 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
@@ -25,7 +28,9 @@ class TestProcessDeferredAccounting(unittest.TestCase):
item.no_of_months = 12
item.save()
- si = create_sales_invoice(item=item.name, update_stock=0, posting_date="2019-01-10", do_not_submit=True)
+ si = create_sales_invoice(
+ item=item.name, update_stock=0, posting_date="2019-01-10", do_not_submit=True
+ )
si.items[0].enable_deferred_revenue = 1
si.items[0].service_start_date = "2019-01-10"
si.items[0].service_end_date = "2019-03-15"
@@ -33,20 +38,22 @@ class TestProcessDeferredAccounting(unittest.TestCase):
si.save()
si.submit()
- process_deferred_accounting = doc = frappe.get_doc(dict(
- doctype='Process Deferred Accounting',
- posting_date="2019-01-01",
- start_date="2019-01-01",
- end_date="2019-01-31",
- type="Income"
- ))
+ process_deferred_accounting = doc = frappe.get_doc(
+ dict(
+ doctype="Process Deferred Accounting",
+ posting_date="2019-01-01",
+ start_date="2019-01-01",
+ end_date="2019-01-31",
+ type="Income",
+ )
+ )
process_deferred_accounting.insert()
process_deferred_accounting.submit()
expected_gle = [
[deferred_account, 33.85, 0.0, "2019-01-31"],
- ["Sales - _TC", 0.0, 33.85, "2019-01-31"]
+ ["Sales - _TC", 0.0, 33.85, "2019-01-31"],
]
check_gl_entries(self, si.name, expected_gle, "2019-01-10")
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
index f8d191cc3f8..82705a9cea4 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
@@ -64,10 +64,10 @@
|
{{ frappe.format(row.account, {fieldtype: "Link"}) or " " }} |
- {{ row.account and frappe.utils.fmt_money(row.debit, currency=filters.presentation_currency) }}
+ {{ row.get('account', '') and frappe.utils.fmt_money(row.debit, currency=filters.presentation_currency) }}
|
- {{ row.account and frappe.utils.fmt_money(row.credit, currency=filters.presentation_currency) }}
+ {{ row.get('account', '') and frappe.utils.fmt_money(row.credit, currency=filters.presentation_currency) }}
|
{% endif %}
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js
index 088c190f451..7dd77fbb3c7 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.js
@@ -8,7 +8,7 @@ frappe.ui.form.on('Process Statement Of Accounts', {
},
refresh: function(frm){
if(!frm.doc.__islocal) {
- frm.add_custom_button('Send Emails',function(){
+ frm.add_custom_button(__('Send Emails'), function(){
frappe.call({
method: "erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_emails",
args: {
@@ -24,7 +24,7 @@ frappe.ui.form.on('Process Statement Of Accounts', {
}
});
});
- frm.add_custom_button('Download',function(){
+ frm.add_custom_button(__('Download'), function(){
var url = frappe.urllib.get_full_url(
'/api/method/erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.download_statements?'
+ 'document_name='+encodeURIComponent(frm.doc.name))
@@ -51,6 +51,13 @@ frappe.ui.form.on('Process Statement Of Accounts', {
}
}
});
+ frm.set_query("account", function() {
+ return {
+ filters: {
+ 'company': frm.doc.company
+ }
+ };
+ });
if(frm.doc.__islocal){
frm.set_value('from_date', frappe.datetime.add_months(frappe.datetime.get_today(), -1));
frm.set_value('to_date', frappe.datetime.get_today());
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py
index 09aa72352e4..fea55a3f289 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py
@@ -23,15 +23,15 @@ from erpnext.accounts.report.general_ledger.general_ledger import execute as get
class ProcessStatementOfAccounts(Document):
def validate(self):
if not self.subject:
- self.subject = 'Statement Of Accounts for {{ customer.name }}'
+ self.subject = "Statement Of Accounts for {{ customer.name }}"
if not self.body:
- self.body = 'Hello {{ customer.name }}, 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 5fbe93ee68d..fac9be7bdb1 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 6d079242682..70e87404a35 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 49192a45f87..b3b9d7b2086 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.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index 1a398aba2ec..ee29d2a7448 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -141,7 +141,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
})
}, __("Get Items From"));
}
- this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted==="Yes");
+ this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted);
if (doc.docstatus == 1 && !doc.inter_company_invoice_reference) {
frappe.model.with_doc("Supplier", me.frm.doc.supplier, function() {
@@ -276,6 +276,8 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
if(this.frm.updating_party_details || this.frm.doc.inter_company_invoice_reference)
return;
+ if (this.frm.doc.__onload && this.frm.doc.__onload.load_after_mapping) return;
+
erpnext.utils.get_party_details(this.frm, "erpnext.accounts.party.get_party_details",
{
posting_date: this.frm.doc.posting_date,
@@ -569,10 +571,10 @@ frappe.ui.form.on("Purchase Invoice", {
},
is_subcontracted: function(frm) {
- if (frm.doc.is_subcontracted === "Yes") {
+ if (frm.doc.is_subcontracted) {
erpnext.buying.get_default_bom(frm);
}
- frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted==="Yes");
+ frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted);
},
update_stock: function(frm) {
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index bd0116443ff..9f87c5ab54e 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -543,11 +543,10 @@
"fieldtype": "Column Break"
},
{
- "default": "No",
+ "default": "0",
"fieldname": "is_subcontracted",
- "fieldtype": "Select",
- "label": "Raw Materials Supplied",
- "options": "No\nYes",
+ "fieldtype": "Check",
+ "label": "Is Subcontracted",
"print_hide": 1
},
{
@@ -1366,7 +1365,7 @@
"width": "50px"
},
{
- "depends_on": "eval:doc.update_stock && doc.is_subcontracted==\"Yes\"",
+ "depends_on": "eval:doc.update_stock && doc.is_subcontracted",
"fieldname": "supplier_warehouse",
"fieldtype": "Link",
"label": "Supplier Warehouse",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 2c3156175fb..e6a46d0676b 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -32,6 +32,7 @@ from erpnext.accounts.utils import get_account_currency, get_fiscal_year
from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
from erpnext.buying.utils import check_on_hold_or_closed_status
+from erpnext.controllers.accounts_controller import validate_account_head
from erpnext.controllers.buying_controller import BuyingController
from erpnext.stock import get_warehouse_account_map
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
@@ -40,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()
@@ -69,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()
@@ -89,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()
@@ -107,45 +110,53 @@ class PurchaseInvoice(BuyingController):
self.validate_uom_is_integer("uom", "qty")
self.validate_uom_is_integer("stock_uom", "stock_qty")
self.set_expense_account(for_validate=True)
+ self.validate_expense_account()
self.set_against_expense_account()
self.validate_write_off_account()
self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount", "items")
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:
@@ -157,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):
@@ -167,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
@@ -187,51 +207,62 @@ 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'):
- 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)
+ stock_items = self.get_stock_items()
+ for d in self.get("items"):
+ if not d.warehouse and d.item_code in stock_items:
+ 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)
@@ -259,51 +290,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:
@@ -311,6 +373,10 @@ class PurchaseInvoice(BuyingController):
elif not item.expense_account and for_validate:
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")
+
def set_against_expense_account(self):
against_accounts = []
for item in self.get("items"):
@@ -320,32 +386,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):
@@ -353,56 +431,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()
@@ -411,8 +498,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()
@@ -427,6 +515,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
@@ -449,13 +538,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)
@@ -504,28 +603,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
@@ -537,22 +649,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 = {}
@@ -564,86 +687,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)
@@ -660,103 +819,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")
@@ -764,125 +974,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
@@ -892,7 +1156,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)
@@ -902,24 +1168,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"
@@ -933,17 +1210,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
@@ -952,18 +1234,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
@@ -971,13 +1259,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
@@ -985,30 +1278,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):
@@ -1018,48 +1323,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)
@@ -1090,10 +1411,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):
@@ -1114,19 +1435,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]
@@ -1136,16 +1460,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)
@@ -1153,25 +1487,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)
@@ -1189,8 +1527,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)
@@ -1199,27 +1540,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:
@@ -1233,8 +1580,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"))
@@ -1245,19 +1592,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"
@@ -1265,106 +1618,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):
@@ -1372,33 +1745,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 76c9fcdd7cb..10dd0ef6e25 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 d51a008d943..73390dd6f45 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=1
+ )
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"
@@ -1334,31 +1611,34 @@ def make_purchase_invoice(**args):
pi.conversion_rate = args.conversion_rate or 1
pi.is_return = args.is_return
pi.return_against = args.return_against
- pi.is_subcontracted = args.is_subcontracted or "No"
+ pi.is_subcontracted = args.is_subcontracted or 0
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"
@@ -1393,29 +1674,33 @@ def make_purchase_invoice_against_cost_center(**args):
pi.is_return = args.is_return
pi.is_return = args.is_return
pi.credit_to = args.return_against or "Creditors - _TC"
- pi.is_subcontracted = args.is_subcontracted or "No"
+ pi.is_subcontracted = args.is_subcontracted or 0
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_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
index f9b2efd053b..6651195e5f2 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -623,7 +623,7 @@
},
{
"default": "0",
- "depends_on": "eval:parent.is_subcontracted == 'Yes'",
+ "depends_on": "eval:parent.is_subcontracted",
"fieldname": "include_exploded_items",
"fieldtype": "Check",
"label": "Include Exploded Items",
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 f5eb404d0a4..70d29bfda25 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 3176556ec53..1f0ea211f20 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 b5b4a67d759..1d02f055048 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.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index af6a52a6429..6818955c2f1 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -280,6 +280,9 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
}
var me = this;
if(this.frm.updating_party_details) return;
+
+ if (this.frm.doc.__onload && this.frm.doc.__onload.load_after_mapping) return;
+
erpnext.utils.get_party_details(this.frm,
"erpnext.accounts.party.get_party_details", {
posting_date: this.frm.doc.posting_date,
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index 5062c1c807a..80b95db8868 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -2,7 +2,7 @@
"actions": [],
"allow_import": 1,
"autoname": "naming_series:",
- "creation": "2013-05-24 19:29:05",
+ "creation": "2022-01-25 10:29:57.771398",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
@@ -651,7 +651,6 @@
"hide_seconds": 1,
"label": "Ignore Pricing Rule",
"no_copy": 1,
- "permlevel": 0,
"print_hide": 1
},
{
@@ -1974,9 +1973,10 @@
},
{
"default": "0",
+ "description": "Issue a debit note with 0 qty against an existing Sales Invoice",
"fieldname": "is_debit_note",
"fieldtype": "Check",
- "label": "Is Debit Note"
+ "label": "Is Rate Adjustment Entry (Debit Note)"
},
{
"default": "0",
@@ -2038,7 +2038,7 @@
"link_fieldname": "consolidated_invoice"
}
],
- "modified": "2021-12-23 20:19:38.667508",
+ "modified": "2022-03-08 16:08:53.517903",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
@@ -2089,8 +2089,9 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"timeline_field": "customer",
"title_field": "title",
"track_changes": 1,
"track_seen": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index b894f90c7e1..1efd3dca0d3 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -37,35 +37,40 @@ from erpnext.assets.doctype.asset.depreciation import (
get_gl_entries_on_asset_regain,
make_depreciation_entry,
)
+from erpnext.controllers.accounts_controller import validate_account_head
from erpnext.controllers.selling_controller import SellingController
from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data
from erpnext.setup.doctype.company.company import update_company_current_month_sales
from erpnext.stock.doctype.batch.batch import set_batch_nos
from erpnext.stock.doctype.delivery_note.delivery_note import update_billed_amount_based_on_so
-from erpnext.stock.doctype.serial_no.serial_no import get_delivery_note_serial_no, get_serial_nos
-from erpnext.stock.utils import calculate_mapped_packed_items_return
+from erpnext.stock.doctype.serial_no.serial_no import (
+ get_delivery_note_serial_no,
+ get_serial_nos,
+ 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"""
@@ -108,7 +113,11 @@ class SalesInvoice(SellingController):
self.validate_fixed_asset()
self.set_income_account_for_fixed_assets()
self.validate_item_cost_centers()
- validate_inter_company_party(self.doctype, self.customer, self.company, self.inter_company_invoice_reference)
+ self.validate_income_account()
+
+ validate_inter_company_party(
+ self.doctype, self.customer, self.company, self.inter_company_invoice_reference
+ )
if cint(self.is_pos):
self.validate_pos()
@@ -124,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()
@@ -149,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")
@@ -166,14 +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")
def set_tax_withholding(self):
tax_withholding_details = get_party_tax_withholding_details(self)
@@ -192,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)
@@ -208,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()
@@ -226,6 +264,8 @@ class SalesInvoice(SellingController):
# because updating reserved qty in bin depends upon updated delivered qty in SO
if self.update_stock == 1:
self.update_stock_ledger()
+ 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()
@@ -245,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)
@@ -253,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()
@@ -263,6 +307,9 @@ class SalesInvoice(SellingController):
self.process_common_party_accounting()
def validate_pos_return(self):
+ if self.is_consolidated:
+ # pos return is already validated in pos invoice
+ return
if self.is_pos and self.is_return:
total_amount_in_payments = 0
@@ -279,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"))
@@ -330,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()
@@ -345,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
@@ -402,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
@@ -418,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 {
@@ -434,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):
@@ -450,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):
@@ -462,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
@@ -473,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):
@@ -482,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:
@@ -508,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)
@@ -560,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"))
@@ -584,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")
@@ -645,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)
@@ -682,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)
@@ -702,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:
@@ -722,30 +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:
- if cint(self.is_return) and self.return_against:
- calculate_mapped_packed_items_return(self)
- else:
- from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
- make_packing_list(self)
+ 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
@@ -760,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()
@@ -782,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]
@@ -802,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))
@@ -825,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
@@ -870,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)
@@ -902,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")):
@@ -936,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:
@@ -945,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:
@@ -961,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
@@ -1021,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()
@@ -1038,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
@@ -1047,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()
@@ -1063,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
@@ -1091,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
@@ -1099,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:
@@ -1137,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:
@@ -1172,94 +1378,127 @@ 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")
+ frappe.throw(_("Select change amount account"), title=_("Mandatory Field"))
def make_write_off_gl_entry(self, gl_entries):
# 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.so_detail:
- updated_delivery_notes += update_billed_amount_based_on_so(d.so_detail, update_modified)
- elif 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)
+ 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 = 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)
for dn in set(updated_delivery_notes):
frappe.get_doc("Delivery Note", dn).update_billing_percentage(update_modified=update_modified)
@@ -1271,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
@@ -1281,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:
@@ -1319,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:
@@ -1334,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:
@@ -1350,63 +1591,90 @@ 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):
- 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)
- return abs(flt(returned_amount[0][0])) if returned_amount else 0
+ 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)
+ )
+ ).run()
+
+ return abs(returned_amount[0][0]) if returned_amount[0][0] else 0
# redeem the loyalty points.
def apply_loyalty_points(self):
@@ -1414,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
@@ -1428,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"))
@@ -1462,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:
@@ -1470,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"
@@ -1490,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(
@@ -1532,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
@@ -1540,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]
@@ -1549,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
@@ -1577,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):
@@ -1590,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):
@@ -1606,48 +1893,57 @@ 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):
- target.ignore_pricing_rule = 1
target.run_method("set_missing_values")
target.run_method("set_po_nos")
target.run_method("calculate_taxes_and_totals")
@@ -1659,82 +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)
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
@@ -1743,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."))
@@ -1757,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)
@@ -1790,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
@@ -1798,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:
@@ -1931,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)
@@ -1946,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)
@@ -1966,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))
@@ -2028,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(
@@ -2049,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):
@@ -2068,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 = []
@@ -2111,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
@@ -2121,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:
@@ -2130,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 5cdc8dae25f..b83d6a575e0 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 941061f2a22..6c38a7e597a 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,46 +907,123 @@ 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.write_off_outstanding_amount_automatically = 1
pos.insert()
pos.submit()
self.assertEqual(pos.grand_total, 100.0)
- self.assertEqual(pos.write_off_amount, -5)
+ self.assertEqual(pos.write_off_amount, 0)
+
+ def test_auto_write_off_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_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.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": 40})
+
+ pos.write_off_outstanding_amount_automatically = 1
+ pos.insert()
+ pos.submit()
+
+ self.assertEqual(pos.grand_total, 100.0)
+ self.assertEqual(pos.write_off_amount, 10)
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 +1033,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 +1046,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 +1097,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 +1121,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 +1152,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 +1187,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 +1205,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 +1257,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 +1298,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 +1319,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 +1409,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 +1468,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 +1501,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 +1521,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 +1550,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 +1586,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 +1639,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 +1652,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 +1766,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 +1792,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 +1810,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 +1822,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 +1896,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 +1922,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,26 +1953,98 @@ 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)
self.assertEqual(expected_values[gle.account][1], gle.debit)
self.assertEqual(expected_values[gle.account][2], gle.credit)
+ def test_rounding_adjustment_3(self):
+ 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",
+ },
+ )
+ 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.save()
+ si.submit()
+ self.assertEqual(si.net_total, 4007.16)
+ self.assertEqual(si.grand_total, 4488.02)
+ 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],
+ ]
+ )
+
+ 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,
+ )
+
+ 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
+
+ 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])
@@ -1611,29 +2057,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)
@@ -1648,30 +2097,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)
@@ -1681,16 +2131,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
@@ -1699,18 +2153,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)
@@ -1719,21 +2173,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)
@@ -1741,8 +2195,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
@@ -1758,14 +2215,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()
@@ -1776,17 +2235,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")
@@ -1795,7 +2257,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"
@@ -1803,14 +2267,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()
@@ -1821,13 +2287,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):
@@ -1836,45 +2302,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")
@@ -1886,57 +2354,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()
@@ -1957,19 +2434,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])
@@ -1981,8 +2458,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)
@@ -1996,82 +2474,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
@@ -2087,43 +2575,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
@@ -2135,15 +2626,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)
@@ -2151,29 +2642,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 (
@@ -2182,14 +2683,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))
@@ -2201,27 +2705,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))
@@ -2229,20 +2739,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):
@@ -2253,23 +2765,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)
@@ -2284,7 +2801,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()
@@ -2294,8 +2813,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):
@@ -2320,30 +2839,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
@@ -2353,16 +2876,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")
@@ -2401,21 +2922,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()
@@ -2427,16 +2950,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[0])
- item = copy.deepcopy(si.get('items')[0])
- item.update({
- "qty": 1,
- "rate": 500,
- "grant_commission": 1
- })
- si.append("items", item)
+ 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)
+
+ 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,
+ }
+ )
# Test valid values
for commission_rate, total_commission in ((0, 0), (10, 50), (100, 500)):
@@ -2452,7 +2986,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()
@@ -2461,17 +2995,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()
@@ -2479,7 +3015,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:
@@ -2487,13 +3023,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()
@@ -2503,12 +3042,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"
@@ -2516,16 +3062,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()
@@ -2536,13 +3084,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)
@@ -2550,146 +3102,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.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()
@@ -2700,20 +3270,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"
@@ -2722,32 +3295,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)
@@ -2755,6 +3339,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)
@@ -2769,32 +3354,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()
@@ -2807,6 +3395,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)
@@ -2822,20 +3411,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()
@@ -2850,59 +3442,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
@@ -2911,11 +3513,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_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
index ae9ac35729a..b3ba1199b61 100644
--- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
+++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
@@ -813,6 +813,7 @@
"fieldtype": "Currency",
"label": "Incoming Rate (Costing)",
"no_copy": 1,
+ "options": "Company:company:default_currency",
"print_hide": 1
},
{
@@ -832,6 +833,7 @@
},
{
"default": "0",
+ "fetch_from": "item_code.grant_commission",
"fieldname": "grant_commission",
"fieldtype": "Check",
"label": "Grant Commission",
@@ -841,7 +843,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2021-10-05 12:24:54.968907",
+ "modified": "2022-03-23 08:18:04.928287",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",
@@ -849,5 +851,6 @@
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
- "sort_order": "DESC"
+ "sort_order": "DESC",
+ "states": []
}
\ No newline at end of file
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 b5909447dc8..d9009bae4c0 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)
@@ -46,14 +51,31 @@ def valdiate_taxes_and_charges_template(doc):
for tax in doc.get("taxes"):
validate_taxes_and_charges(tax)
- validate_account_head(tax, doc)
+ validate_account_head(tax.idx, tax.account_head, doc.company)
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 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 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))
+ )
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 bc1fd8ec32f..6432acaae93 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 7b13c6c6925..972b773501a 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 b543ad8204d..4f49843c1eb 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 bc3a52167db..97310743605 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 d5551d12471..19604b332a3 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 8a0fa85a692..b0e2493f7a6 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 c01ac23f1ee..fa9d431c19e 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 792e7d21a78..1d79503a05e 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
@@ -71,12 +83,13 @@ class ShippingRule(Document):
if doc.currency != doc.company_currency:
shipping_amount = flt(shipping_amount / doc.conversion_rate, 2)
- if shipping_amount:
- self.add_shipping_rule_to_tax_table(doc, shipping_amount)
+ self.add_shipping_rule_to_tax_table(doc, shipping_amount)
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
@@ -84,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"
@@ -128,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
@@ -154,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 fc706211592..60ce120c54f 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 c06dae09701..a24e834c572 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 467d4a13345..f6dd86afa21 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 6f67bc5128b..eb17daa282f 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 1285343d196..a95e0a9c2da 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 d076e39964b..7df76cde80c 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 4bdb70a480a..17a275ebc34 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 2d94bc376a0..27b78e9fab6 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 d5ac9b20c55..848e05424bc 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 5bb9b931854..a519d8be736 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,32 @@ 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'):
- 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))
+ for d in self.get("rates"):
+ if (
+ d.cumulative_threshold and d.single_threshold and d.cumulative_threshold < d.single_threshold
+ ):
+ 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 +59,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 +131,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 +158,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 +175,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 +196,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 +236,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 +263,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 +293,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 +354,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 +429,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 256d4ac2178..8a510ea023f 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 a3fcf7da7a5..3059f8d64b8 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 d24d56b4bbb..f52e517f730 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,37 +218,42 @@ 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)
- round_off_debit_credit(gl_map)
+ process_debit_credit_difference(gl_map)
if gl_map:
check_freezing_date(gl_map[0]["posting_date"], adv_adj)
@@ -210,37 +261,70 @@ 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"))
+def process_debit_credit_difference(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"),
+ )
+
+ voucher_type = gl_map[0].voucher_type
+ voucher_no = gl_map[0].voucher_no
+ allowance = get_debit_credit_allowance(voucher_type, precision)
+
+ debit_credit_diff = get_debit_credit_difference(gl_map, precision)
+ if abs(debit_credit_diff) > allowance:
+ raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no)
+
+ elif abs(debit_credit_diff) >= (1.0 / (10**precision)):
+ make_round_off_gle(gl_map, debit_credit_diff, precision)
+
+ debit_credit_diff = get_debit_credit_difference(gl_map, precision)
+ if abs(debit_credit_diff) > allowance:
+ raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no)
+
+
+def get_debit_credit_difference(gl_map, precision):
debit_credit_diff = 0.0
for entry in gl_map:
entry.debit = flt(entry.debit, precision)
@@ -249,20 +333,30 @@ def round_off_debit_credit(gl_map):
debit_credit_diff = flt(debit_credit_diff, precision)
- if gl_map[0]["voucher_type"] in ("Journal Entry", "Payment Entry"):
+ return debit_credit_diff
+
+
+def get_debit_credit_allowance(voucher_type, precision):
+ if 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))
+ return allowance
+
+
+def raise_debit_credit_not_equal_error(debit_credit_diff, voucher_type, voucher_no):
+ frappe.throw(
+ _("Debit and Credit not equal for {0} #{1}. Difference is {2}.").format(
+ voucher_type, 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:
@@ -274,35 +368,38 @@ def make_round_off_gle(gl_map, debit_credit_diff, precision):
debit_credit_diff += flt(d.credit)
round_off_account_exists = True
- if round_off_account_exists and abs(debit_credit_diff) <= (1.0 / (10**precision)):
+ if round_off_account_exists and abs(debit_credit_diff) < (1.0 / (10**precision)):
gl_map.remove(round_off_gle)
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 +408,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 c13bc23c159..b0b3049d487 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -33,125 +33,230 @@ 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":
+ if party_type == "Customer":
to_copy = ["customer_name", "customer_group", "territory", "language"]
else:
to_copy = ["supplier_name", "supplier_group", "language"]
@@ -159,112 +264,121 @@ 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"):
return party.default_price_list
if party.doctype == "Customer":
- price_list = frappe.get_cached_value("Customer Group",
- party.customer_group, "default_price_list")
- if price_list:
- return price_list
+ return frappe.db.get_value("Customer Group", party.customer_group, "default_price_list")
- return None
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():
@@ -273,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"""
@@ -303,32 +428,58 @@ 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)
@frappe.whitelist()
@@ -340,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
@@ -361,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
@@ -404,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"})
@@ -448,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:
@@ -478,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)
@@ -486,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))
@@ -601,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.
@@ -613,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
@@ -665,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/print_format/gst_e_invoice/gst_e_invoice.html b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html
index e6580493095..605ce8383e4 100644
--- a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html
+++ b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html
@@ -1,7 +1,8 @@
{%- from "templates/print_formats/standard_macros.html" import add_header, render_field, print_value -%}
-{%- set einvoice = json.loads(doc.signed_einvoice) -%}
+ {% if doc.signed_einvoice %}
+ {%- set einvoice = json.loads(doc.signed_einvoice) -%}
+ {% else %}
+
+ You must generate IRN before you can preview GST E-Invoice.
+
+ {% endif %}
+
diff --git a/erpnext/accounts/report/account_balance/account_balance.py b/erpnext/accounts/report/account_balance/account_balance.py
index a2c70a45f99..824a965cdcf 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 73370e4ed6f..13fa05d4743 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_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js
index 81c60bb337d..f6961eb95fa 100644
--- a/erpnext/accounts/report/accounts_payable/accounts_payable.js
+++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js
@@ -53,6 +53,22 @@ frappe.query_reports["Accounts Payable"] = {
}
}
},
+ {
+ "fieldname": "party_account",
+ "label": __("Payable Account"),
+ "fieldtype": "Link",
+ "options": "Account",
+ get_query: () => {
+ var company = frappe.query_report.get_filter_value('company');
+ return {
+ filters: {
+ 'company': company,
+ 'account_type': 'Payable',
+ 'is_group': 0
+ }
+ };
+ }
+ },
{
"fieldname": "ageing_based_on",
"label": __("Ageing Based On"),
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
index 570029851e8..748bcde4354 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
@@ -66,6 +66,22 @@ frappe.query_reports["Accounts Receivable"] = {
}
}
},
+ {
+ "fieldname": "party_account",
+ "label": __("Receivable Account"),
+ "fieldtype": "Link",
+ "options": "Account",
+ get_query: () => {
+ var company = frappe.query_report.get_filter_value('company');
+ return {
+ filters: {
+ 'company': company,
+ 'account_type': 'Receivable',
+ 'is_group': 0
+ }
+ };
+ }
+ },
{
"fieldname": "ageing_based_on",
"label": __("Ageing Based On"),
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index a990f23cd6b..de9d63d849a 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,73 @@ 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,
+ party_account=gle.account,
+ 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 +194,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 +208,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 +227,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 +248,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 +280,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 +291,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 +305,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 +355,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 +424,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 +444,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 +463,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 +500,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 +508,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 +524,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 +541,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 +576,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 +603,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 +648,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 +692,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 +704,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 +737,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 +751,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"
@@ -680,21 +778,29 @@ class ReceivablePayableReport(object):
conditions.append("party=%s")
values.append(self.filters.get(party_type_field))
- # 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})]
+ if self.filters.party_account:
+ conditions.append("account =%s")
+ values.append(self.filters.party_account)
+ else:
+ # 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}
+ )
+ ]
- if accounts:
- conditions.append("account in (%s)" % ','.join(['%s'] *len(accounts)))
- values += accounts
+ if 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 +812,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 +828,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 +837,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 +850,175 @@ 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,
+ )
+ self.add_column(
+ label="Receivable Account" if self.party_type == "Customer" else "Payable Account",
+ fieldname="party_account",
+ fieldtype="Link",
+ options="Account",
+ 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 +1027,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 ab95c93e368..f38890e980c 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,41 +40,65 @@ 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)
report = execute(filters)
- expected_data_after_credit_note = [100, 0, 0, 40, -40]
+ expected_data_after_credit_note = [100, 0, 0, 40, -40, "Debtors - _TC2"]
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,
+ row.party_account,
+ ],
+ )
+
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 +107,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 8e3bd8be1b9..889f5a22a8a 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 98f5b74eaac..57d80492ae0 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 0f9435f4a57..ad9b1ba58eb 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 f10a5eab102..07552e311c0 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",
+ "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)",
+ "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 b456e89f344..20f7643a1c9 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 6c401fb8f3b..2ac1fea5afc 100644
--- a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py
+++ b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py
@@ -4,121 +4,134 @@
import frappe
from frappe import _
-from frappe.utils import flt, getdate, nowdate
+from frappe.query_builder.custom import ConstantColumn
+from frappe.query_builder.functions import Sum
+from frappe.utils import flt, getdate
+from pypika import CustomFunction
+
+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")
data = get_entries(filters)
- from erpnext.accounts.utils import get_balance_on
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 = frappe.db.sql("""
+ journal_entries = get_journal_entries(filters)
+
+ payment_entries = get_payment_entries(filters)
+
+ loan_entries = get_loan_entries(filters)
+
+ pos_entries = []
+ 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"]),
+ )
+
+
+def get_journal_entries(filters):
+ 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,
@@ -128,9 +141,15 @@ def get_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,
+ )
- payment_entries = frappe.db.sql("""
+
+def get_payment_entries(filters):
+ return frappe.db.sql(
+ """
select
"Payment Entry" as payment_document, name as payment_entry,
reference_no, reference_date as ref_date,
@@ -143,11 +162,15 @@ def get_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,
+ )
- pos_entries = []
- if filters.include_pos_transactions:
- pos_entries = frappe.db.sql("""
+
+def get_pos_entries(filters):
+ 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,
@@ -159,30 +182,107 @@ def get_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"])
+
+ if doctype == "Loan Disbursement":
+ amount_field = (loan_doc.disbursed_amount).as_("credit")
+ posting_date = (loan_doc.disbursement_date).as_("posting_date")
+ account = loan_doc.disbursement_account
+ else:
+ amount_field = (loan_doc.amount_paid).as_("debit")
+ 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)
+ )
+
+ loan_docs.extend(entries)
+
+ return loan_docs
- return sorted(list(payment_entries)+list(journal_entries+list(pos_entries)),
- key=lambda k: k['posting_date'] or getdate(nowdate()))
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
- return je_amount + pe_amount
+ loan_amount = get_loan_amount(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"])
+
+ if doctype == "Loan Disbursement":
+ amount_field = Sum(loan_doc.disbursed_amount)
+ posting_date = (loan_doc.disbursement_date).as_("posting_date")
+ account = loan_doc.disbursement_account
+ else:
+ amount_field = Sum(loan_doc.amount_paid)
+ 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]
+ )
+
+ total_amount += flt(amount)
+
+ return total_amount
+
def get_balance_row(label, amount, account_currency):
if amount > 0:
@@ -190,12 +290,12 @@ def get_balance_row(label, amount, account_currency):
"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 1d7463c8920..62bee82590b 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 56ee5008cf6..7b774ba740b 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
@@ -96,46 +97,35 @@ def get_columns(filters):
if filters["period"] == "Yearly":
labels = [
_("Budget") + " " + str(year[0]),
- _("Actual ") + " " + str(year[0]),
- _("Variance ") + " " + str(year[0])
+ _("Actual") + " " + 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 15041f2516e..74926b90ffa 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 45d147e7a21..b165c88c068 100644
--- a/erpnext/accounts/report/cash_flow/custom_cash_flow.py
+++ b/erpnext/accounts/report/cash_flow/custom_cash_flow.py
@@ -4,7 +4,8 @@
import frappe
from frappe import _
-from frappe.utils import add_to_date
+from frappe.query_builder.functions import Sum
+from frappe.utils import add_to_date, flt, get_date_str
from erpnext.accounts.report.financial_statements import get_columns, get_data, get_period_list
from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import (
@@ -13,41 +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):
- return frappe.db.sql('''
- 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
- from `tabCash Flow Mapping Accounts` cfma
- join `tabCash Flow Mapping` cfm on cfma.parent=cfm.name
- where cfma.parent in (%s)
- order by cfm.is_working_capital
- ''', (', '.join('"%s"' % d for d in mapping_names)))
+ 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()
+
+ 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:
@@ -57,96 +76,123 @@ def setup_mappers(mappers):
account_types = [
dict(
- name=account[0], 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], 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], 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], 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], 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['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['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['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['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['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)
@@ -154,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
@@ -276,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]
@@ -284,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):
@@ -318,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):
@@ -336,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()
@@ -347,43 +440,72 @@ 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 {}
+
from erpnext.accounts.report.cash_flow.cash_flow import get_start_date
company = filters.company
data = {}
total = 0
+ 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)
- accounts = ', '.join('"%s"' % d for d in account_names)
+
+ account_subquery = (
+ frappe.qb.from_(Account)
+ .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])
@@ -391,36 +513,35 @@ 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)
- 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 name IN (%s)
- OR parent_account IN (%s))
- """, (company, start, end, accounts, accounts))
- else:
- 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 name IN (%s)
- OR parent_account IN (%s))
- """, (company, start_date if accumulated_values else period['from_date'],
- period['to_date'], accounts, accounts))
+ start, end = get_date_str(start), get_date_str(end)
- if gl_sum and gl_sum[0]:
- amount = gl_sum[0]
else:
- amount = 0
+ start, end = start_date if accumulated_values else period["from_date"], period["to_date"]
+ start, end = get_date_str(start), get_date_str(end)
- total += amount
- data.setdefault(period["key"], amount)
+ 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))
+ )
+ ).run()
+
+ if result and result[0]:
+ gl_sum = result[0][0]
+ else:
+ gl_sum = 0
+
+ total += flt(gl_sum)
+ data.setdefault(period["key"], flt(gl_sum))
data["total"] = total
return data
@@ -431,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 758e3e93379..98dbbf6c449 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,48 +404,56 @@ 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):
if d.parent_account:
account = d.parent_account_name
- # if not accounts_by_name.get(account):
- # continue
-
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, filters)
+ accounts = get_accounts(root_type, companies)
if not accounts:
return None, None, None
@@ -378,32 +464,34 @@ 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:
if account.parent_account:
- account["parent_account_name"] = name_to_account_map[account.parent_account]
+ account["parent_account_name"] = name_to_account_map.get(account.parent_account)
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:
@@ -412,40 +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"])
+ 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))
+ 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, filters):
- return frappe.db.sql(""" select name, is_group, company,
- parent_account, lft, rgt, root_type, report_type, account_name, account_number
- from
- `tabAccount` where company = %s and root_type = %s
- """ , (filters.get('company'), root_type), as_dict=1)
-def prepare_data(accounts, start_date, end_date, balance_must_be, companies, company_currency, filters):
+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},
+ ):
+ 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
+):
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":
@@ -466,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,
@@ -499,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
@@ -531,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)
@@ -550,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 = []
@@ -562,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:
@@ -591,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)
@@ -609,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 56db841e94d..cafe95b3603 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 3a51db8a97f..1eb257ac853 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 86eb2134fe8..023ff225eea 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
@@ -88,10 +88,12 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
posting_date="2021-05-01",
parent_cost_center="Main - _CD",
cost_center="Main - _CD",
- do_not_submit=True,
+ do_not_save=True,
rate=300,
price_list_rate=300,
)
+
+ si.items[0].income_account = "Sales - _CD"
si.items[0].enable_deferred_revenue = 1
si.items[0].service_start_date = "2021-05-01"
si.items[0].service_end_date = "2021-08-01"
@@ -269,11 +271,13 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
posting_date="2021-05-01",
parent_cost_center="Main - _CD",
cost_center="Main - _CD",
- do_not_submit=True,
+ do_not_save=True,
rate=300,
price_list_rate=300,
)
+
si.items[0].enable_deferred_revenue = 1
+ si.items[0].income_account = "Sales - _CD"
si.items[0].deferred_revenue_account = deferred_revenue_account
si.items[0].income_account = "Sales - _CD"
si.save()
@@ -318,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 004d09250ab..59914dc29ac 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.js b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js
index 6a0394861b8..ea05a35b259 100644
--- a/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js
+++ b/erpnext/accounts/report/dimension_wise_accounts_balance_report/dimension_wise_accounts_balance_report.js
@@ -39,12 +39,14 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
"label": __("From Date"),
"fieldtype": "Date",
"default": frappe.defaults.get_user_default("year_start_date"),
+ "reqd": 1
},
{
"fieldname": "to_date",
"label": __("To Date"),
"fieldtype": "Date",
"default": frappe.defaults.get_user_default("year_end_date"),
+ "reqd": 1
},
{
"fieldname": "finance_book",
@@ -56,6 +58,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
"fieldname": "dimension",
"label": __("Select Dimension"),
"fieldtype": "Select",
+ "default": "Cost Center",
"options": get_accounting_dimension_options(),
"reqd": 1,
},
@@ -70,7 +73,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
});
function get_accounting_dimension_options() {
- let options =["", "Cost Center", "Project"];
+ let options =["Cost Center", "Project"];
frappe.db.get_list('Accounting Dimension',
{fields:['document_type']}).then((res) => {
res.forEach((dimension) => {
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 d547470bf3c..ecad9f104fa 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
@@ -15,21 +15,24 @@ from erpnext.accounts.report.trial_balance.trial_balance import validate_filters
def execute(filters=None):
- validate_filters(filters)
- dimension_items_list = get_dimension_items_list(filters.dimension, filters.company)
- if not dimension_items_list:
+ validate_filters(filters)
+ dimension_list = get_dimensions(filters)
+
+ if not dimension_list:
return [], []
- dimension_items_list = [''.join(d) for d in dimension_items_list]
- columns = get_columns(dimension_items_list)
- data = get_data(filters, dimension_items_list)
+ columns = get_columns(dimension_list)
+ data = get_data(filters, dimension_list)
return columns, data
-def get_data(filters, dimension_items_list):
+
+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
@@ -37,88 +40,104 @@ def get_data(filters, dimension_items_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_items_list, filters, account, gl_entries_by_account)
- format_gl_entries(gl_entries_by_account, accounts_by_name, dimension_items_list)
- accumulate_values_into_parents(accounts, accounts_by_name, dimension_items_list)
- out = prepare_data(accounts, filters, parent_children_map, company_currency, dimension_items_list)
+ 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"))
+ )
+ 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_items_list, filters, account, gl_entries_by_account):
- for item in dimension_items_list:
- condition = get_condition(filters.from_date, item, filters.dimension)
- if 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"))
- }
+def set_gl_entries_by_account(dimension_list, filters, account, gl_entries_by_account):
+ condition = get_condition(filters.get("dimension"))
- gl_filters['item'] = ''.join(item)
+ if account:
+ condition += " and account in ({})".format(", ".join([frappe.db.escape(d) for d in account]))
- if filters.get("include_default_book_entries"):
- gl_filters["company_fb"] = frappe.db.get_value("Company",
- filters.company, 'default_finance_book')
+ 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")),
+ }
- for key, value in filters.items():
- if value:
- gl_filters.update({
- key: value
- })
+ gl_filters["dimensions"] = set(dimension_list)
- gl_entries = frappe.db.sql("""
+ if filters.get("include_default_book_entries"):
+ gl_filters["company_fb"] = frappe.db.get_value(
+ "Company", filters.company, "default_finance_book"
+ )
+
+ gl_entries = frappe.db.sql(
+ """
select
- posting_date, account, debit, credit, is_opening, fiscal_year,
+ posting_date, account, {dimension}, debit, credit, is_opening, fiscal_year,
debit_in_account_currency, credit_in_account_currency, account_currency
from
`tabGL Entry`
where
company=%(company)s
{condition}
+ and posting_date >= %(from_date)s
and posting_date <= %(to_date)s
and is_cancelled = 0
order by account, posting_date""".format(
- 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:
- entry['dimension_item'] = ''.join(item)
- gl_entries_by_account.setdefault(entry.account, []).append(entry)
+ 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_items_list):
+
+def format_gl_entries(gl_entries_by_account, accounts_by_name, dimension_list, dimension_type):
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 item in dimension_items_list:
- if item == entry.dimension_item:
- d[frappe.scrub(item)] = d.get(frappe.scrub(item), 0.0) + flt(entry.debit) - flt(entry.credit)
-def prepare_data(accounts, filters, parent_children_map, company_currency, dimension_items_list):
+ 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)
+ )
+
+
+def prepare_data(accounts, filters, company_currency, dimension_list):
data = []
for d in accounts:
@@ -131,17 +150,18 @@ def prepare_data(accounts, filters, parent_children_map, company_currency, dimen
"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 item in dimension_items_list:
- row[frappe.scrub(item)] = flt(d.get(frappe.scrub(item), 0.0), 3)
+ for dimension in dimension_list:
+ row[frappe.scrub(dimension)] = flt(d.get(frappe.scrub(dimension), 0.0), 3)
- if abs(row[frappe.scrub(item)]) >= 0.005:
+ if abs(row[frappe.scrub(dimension)]) >= 0.005:
# ignore zero values
has_value = True
- total += flt(d.get(frappe.scrub(item), 0.0), 3)
+ total += flt(d.get(frappe.scrub(dimension), 0.0), 3)
row["has_value"] = has_value
row["total"] = total
@@ -149,68 +169,72 @@ def prepare_data(accounts, filters, parent_children_map, company_currency, dimen
return data
-def accumulate_values_into_parents(accounts, accounts_by_name, dimension_items_list):
+
+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 item in dimension_items_list:
- accounts_by_name[d.parent_account][frappe.scrub(item)] = \
- accounts_by_name[d.parent_account].get(frappe.scrub(item), 0.0) + d.get(frappe.scrub(item), 0.0)
+ 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)
-def get_condition(from_date, item, dimension):
+
+def get_condition(dimension):
conditions = []
- if from_date:
- conditions.append("posting_date >= %(from_date)s")
- if dimension:
- if dimension not in ['Cost Center', 'Project']:
- if dimension in ['Customer', 'Supplier']:
- dimension = 'Party'
- else:
- dimension = 'Voucher No'
- txt = "{0} = %(item)s".format(frappe.scrub(dimension))
- conditions.append(txt)
+ conditions.append("{0} in %(dimensions)s".format(frappe.scrub(dimension)))
return " and {}".format(" and ".join(conditions)) if conditions else ""
-def get_dimension_items_list(dimension, company):
- meta = frappe.get_meta(dimension, cached=False)
- fieldnames = [d.fieldname for d in meta.get("fields")]
- filters = {}
- if 'company' in fieldnames:
- filters['company'] = company
- return frappe.get_all(dimension, filters, as_list=True)
-def get_columns(dimension_items_list, accumulated_values=1, company=None):
- columns = [{
- "fieldname": "account",
- "label": _("Account"),
- "fieldtype": "Link",
- "options": "Account",
- "width": 300
- }]
- if company:
- columns.append({
+def get_dimensions(filters):
+ meta = frappe.get_meta(filters.get("dimension"), cached=False)
+ query_filters = {}
+
+ if meta.has_field("company"):
+ query_filters = {"company": filters.get("company")}
+
+ 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
- })
- for item in dimension_items_list:
- columns.append({
- "fieldname": frappe.scrub(item),
- "label": item,
- "fieldtype": "Currency",
- "options": "currency",
- "width": 150
- })
- columns.append({
+ "hidden": 1,
+ },
+ ]
+
+ for dimension in dimension_list:
+ columns.append(
+ {
+ "fieldname": frappe.scrub(dimension),
+ "label": dimension,
+ "fieldtype": "Currency",
+ "options": "currency",
+ "width": 150,
+ }
+ )
+
+ columns.append(
+ {
"fieldname": "total",
- "label": "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 db28cdfdd3c..56d68e1fa99 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 4ff0297dba7..636d624506d 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
new file mode 100644
index 00000000000..b10e7696187
--- /dev/null
+++ b/erpnext/accounts/report/general_ledger/test_general_ledger.py
@@ -0,0 +1,152 @@
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
+# MIT License. See license.txt
+
+import frappe
+from frappe.tests.utils import FrappeTestCase
+from frappe.utils import today
+
+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
+ """
+ # 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.insert(ignore_if_duplicate=True)
+ # create a JV to debit 1000 USD at 75 exchange rate
+ jv = frappe.new_doc("Journal Entry")
+ jv.posting_date = today()
+ 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.save()
+ jv.submit()
+ # create a JV to credit 900 USD at 100 exchange rate
+ jv = frappe.new_doc("Journal Entry")
+ jv.posting_date = today()
+ 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.save()
+ jv.submit()
+
+ # create an exchange rate revaluation entry at 77 exchange rate
+ 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.save()
+ revaluation.submit()
+
+ # post journal entry to revaluate
+ 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"
+ for acc in revaluation_jv.get("accounts"):
+ acc.cost_center = "_Test Cost Center - _TC"
+ revaluation_jv.save()
+ revaluation_jv.submit()
+
+ # check the balance of the account
+ balance = frappe.db.sql(
+ """
+ select sum(debit_in_account_currency) - sum(credit_in_account_currency)
+ from `tabGL Entry`
+ where account = %s
+ group by account
+ """,
+ 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)",
+ }
+ )
+ )
+
+ self.assertEqual(data[1]["account"], account.name)
+ self.assertEqual(data[1]["debit"], 1000)
+ self.assertEqual(data[1]["credit"], 0)
+ self.assertEqual(data[2]["debit"], 0)
+ self.assertEqual(data[2]["credit"], 900)
+ self.assertEqual(data[3]["debit"], 100)
+ 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 b18b940fd2b..9d566785416 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.js b/erpnext/accounts/report/gross_profit/gross_profit.js
index 2ba649da07f..158ff4d3437 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.js
+++ b/erpnext/accounts/report/gross_profit/gross_profit.js
@@ -8,20 +8,22 @@ frappe.query_reports["Gross Profit"] = {
"label": __("Company"),
"fieldtype": "Link",
"options": "Company",
- "reqd": 1,
- "default": frappe.defaults.get_user_default("Company")
+ "default": frappe.defaults.get_user_default("Company"),
+ "reqd": 1
},
{
"fieldname":"from_date",
"label": __("From Date"),
"fieldtype": "Date",
- "default": frappe.defaults.get_user_default("year_start_date")
+ "default": frappe.defaults.get_user_default("year_start_date"),
+ "reqd": 1
},
{
"fieldname":"to_date",
"label": __("To Date"),
"fieldtype": "Date",
- "default": frappe.defaults.get_user_default("year_end_date")
+ "default": frappe.defaults.get_user_default("year_end_date"),
+ "reqd": 1
},
{
"fieldname":"sales_invoice",
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.json b/erpnext/accounts/report/gross_profit/gross_profit.json
index 76c560ad247..0730ffd77e5 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.json
+++ b/erpnext/accounts/report/gross_profit/gross_profit.json
@@ -1,5 +1,5 @@
{
- "add_total_row": 0,
+ "add_total_row": 1,
"columns": [],
"creation": "2013-02-25 17:03:34",
"disable_prepared_report": 0,
@@ -9,7 +9,7 @@
"filters": [],
"idx": 3,
"is_standard": "Yes",
- "modified": "2021-11-13 19:14:23.730198",
+ "modified": "2022-02-11 10:18:36.956558",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Gross Profit",
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py
index 84effc0f467..9668992e022 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,80 +159,207 @@ 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 idx, src in enumerate(gross_profit_data.grouped_data):
+ for src in gross_profit_data.grouped_data:
row = []
for col in group_wise_columns.get(scrub(filters.group_by)):
row.append(src.get(col))
row.append(filters.currency)
- if idx == len(gross_profit_data.grouped_data)-1:
- row[0] = "Total"
data.append(row)
+
def get_columns(group_wise_columns, filters):
columns = []
- column_map = frappe._dict({
- "parent": _("Sales Invoice") + ":Link/Sales Invoice:120",
- "invoice_or_item": _("Sales Invoice") + ":Link/Sales Invoice:120",
- "posting_date": _("Posting Date") + ":Date:100",
- "posting_time": _("Posting Time") + ":Data:100",
- "item_code": _("Item Code") + ":Link/Item:100",
- "item_name": _("Item Name") + ":Data:100",
- "item_group": _("Item Group") + ":Link/Item Group:100",
- "brand": _("Brand") + ":Link/Brand:100",
- "description": _("Description") +":Data:100",
- "warehouse": _("Warehouse") + ":Link/Warehouse:100",
- "qty": _("Qty") + ":Float:80",
- "base_rate": _("Avg. Selling Rate") + ":Currency/currency:100",
- "buying_rate": _("Valuation Rate") + ":Currency/currency:100",
- "base_amount": _("Selling Amount") + ":Currency/currency:100",
- "buying_amount": _("Buying Amount") + ":Currency/currency:100",
- "gross_profit": _("Gross Profit") + ":Currency/currency:100",
- "gross_profit_percent": _("Gross Profit %") + ":Percent:100",
- "project": _("Project") + ":Link/Project:100",
- "sales_person": _("Sales person"),
- "allocated_amount": _("Allocated Amount") + ":Currency/currency:100",
- "customer": _("Customer") + ":Link/Customer:100",
- "customer_group": _("Customer Group") + ":Link/Customer Group:100",
- "territory": _("Territory") + ":Link/Territory: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):
@@ -151,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()
@@ -173,7 +390,7 @@ class GrossProfitGenerator(object):
buying_amount = 0
for row in reversed(self.si_list):
- if self.skip_row(row, self.product_bundles):
+ if self.skip_row(row):
continue
row.base_amount = flt(row.base_net_amount, self.currency_precision)
@@ -182,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:
@@ -212,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
@@ -223,20 +444,10 @@ class GrossProfitGenerator(object):
self.get_average_rate_based_on_group_by()
def get_average_rate_based_on_group_by(self):
- # sum buying / selling totals for group
- self.totals = frappe._dict(
- qty=0,
- base_amount=0,
- buying_amount=0,
- gross_profit=0,
- gross_profit_percent=0,
- base_rate=0,
- buying_rate=0
- )
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)
@@ -244,53 +455,47 @@ class GrossProfitGenerator(object):
new_row.base_amount += flt(row.base_amount, self.currency_precision)
new_row = self.set_average_rate(new_row)
self.grouped_data.append(new_row)
- self.add_to_totals(new_row)
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)
- self.add_to_totals(row)
-
- self.set_average_gross_profit(self.totals)
-
- if self.filters.get("group_by") == "Invoice":
- self.totals.indent = 0.0
- self.totals.parent_invoice = ""
- self.totals.invoice_or_item = "Total"
- self.si_list.append(self.totals)
- else:
- self.grouped_data.append(self.totals)
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
-
- def add_to_totals(self, new_row):
- for key in self.totals:
- if new_row.get(key):
- self.totals[key] += new_row[key]
+ 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
@@ -299,24 +504,27 @@ 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, product_bundles):
+ def skip_row(self, row):
if self.filters.get("group_by") != "Invoice":
if not row.get(scrub(self.filters.get("group_by", ""))):
return True
- elif row.get("is_return") == 1:
- return True
+
+ return False
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)
@@ -326,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
@@ -339,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)
@@ -356,33 +566,43 @@ 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]
def get_last_purchase_rate(self, item_code, row):
- condition = ''
- if row.project:
- condition += " AND a.project=%s" % (frappe.db.escape(row.project))
- elif row.cost_center:
- condition += " AND a.cost_center=%s" % (frappe.db.escape(row.cost_center))
- if self.filters.to_date:
- condition += " AND modified='%s'" % (self.filters.to_date)
+ purchase_invoice = frappe.qb.DocType("Purchase Invoice")
+ purchase_invoice_item = frappe.qb.DocType("Purchase Invoice Item")
- last_purchase_rate = frappe.db.sql("""
- select (a.base_rate / a.conversion_factor)
- from `tabPurchase Invoice Item` a
- where a.item_code = %s and a.docstatus=1
- {0}
- order by a.modified desc limit 1""".format(condition), 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)
+
+ if 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)
+ last_purchase_rate = query.run()
return flt(last_purchase_rate[0][0]) if last_purchase_rate else 0
@@ -395,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:
@@ -408,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,
@@ -430,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 = []
@@ -459,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:
@@ -557,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 2f23c8ed1d6..1a003993aac 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 aaed58d070d..c04b9c71252 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 9b35538bb68..2e7213f42b1 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 a421bc5c206..39c5311cd99 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 6c12093763d..3f178f4715c 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 77e7568533e..1bda0d8ae38 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 882e411246b..66353358a06 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 f4b8731ba87..3e7aa1e3680 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 406f7a50e8b..8af9bb3ac89 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 a9696bd104a..a73c72c6d82 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 = []
+ unrealized_profit_loss_accounts = []
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
+ ]
+ tax_columns = [
+ (account + ":Currency/currency:120")
+ for account in tax_accounts
+ if account not in expense_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 e88675bb8d6..1dcacb97420 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 966b1d4fd01..483debaf8bf 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 3b736282cf9..4eef3072867 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,68 +202,101 @@ 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("""select a.owner, a.posting_date,
- ifnull(b.mode_of_payment, '') as mode_of_payment, sum(b.base_amount) as paid_amount
- from `tabSales Invoice` a, `tabSales Invoice Payment` b
- where a.name = b.parent
- and a.docstatus = 1
- and a.name in ({invoice_list_names})
- group by a.owner, a.posting_date, mode_of_payment
- union
- select a.owner,a.posting_date,
- ifnull(b.mode_of_payment, '') as mode_of_payment, sum(b.base_paid_amount) as paid_amount
- from `tabSales Invoice` a, `tabPayment Entry` b,`tabPayment Entry Reference` c
- where a.name = c.reference_name
- and b.name = c.parent
- and b.docstatus = 1
- and a.name in ({invoice_list_names})
- group by a.owner, a.posting_date, mode_of_payment
- union
- select a.owner, a.posting_date,
- ifnull(a.voucher_type,'') as mode_of_payment, sum(b.credit)
- from `tabJournal Entry` a, `tabJournal Entry Account` b
- where a.name = b.parent
- and a.docstatus = 1
- and b.reference_type = "Sales Invoice"
- and b.reference_name in ({invoice_list_names})
- group by a.owner, a.posting_date, mode_of_payment
- """.format(invoice_list_names=invoice_list_names), as_dict=1)
+ inv_mop_detail = frappe.db.sql(
+ """
+ select t.owner,
+ t.posting_date,
+ t.mode_of_payment,
+ sum(t.paid_amount) as paid_amount
+ from (
+ select a.owner, a.posting_date,
+ ifnull(b.mode_of_payment, '') as mode_of_payment, sum(b.base_amount) as paid_amount
+ from `tabSales Invoice` a, `tabSales Invoice Payment` b
+ where a.name = b.parent
+ and a.docstatus = 1
+ and a.name in ({invoice_list_names})
+ group by a.owner, a.posting_date, mode_of_payment
+ union
+ select a.owner,a.posting_date,
+ ifnull(b.mode_of_payment, '') as mode_of_payment, sum(c.allocated_amount) as paid_amount
+ from `tabSales Invoice` a, `tabPayment Entry` b,`tabPayment Entry Reference` c
+ where a.name = c.reference_name
+ and b.name = c.parent
+ and b.docstatus = 1
+ and a.name in ({invoice_list_names})
+ group by a.owner, a.posting_date, mode_of_payment
+ union
+ select a.owner, a.posting_date,
+ ifnull(a.voucher_type,'') as mode_of_payment, sum(b.credit)
+ from `tabJournal Entry` a, `tabJournal Entry Account` b
+ where a.name = b.parent
+ and a.docstatus = 1
+ and b.reference_type = "Sales Invoice"
+ and b.reference_name in ({invoice_list_names})
+ 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,
+ )
- 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.mode_of_payment = 'Cash'
+ 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 b3f6c72a3ef..3ad0ff2ce26 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 a9d0081bacc..fc48dd28169 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 943c4e150f0..d02f53b0d2b 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 b3ff6e48a6f..d6c3bd059f4 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 eeb8483586f..ba8d3072283 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.json b/erpnext/accounts/report/tax_detail/test_tax_detail.json
index 3a4b1754554..e4903167cba 100644
--- a/erpnext/accounts/report/tax_detail/test_tax_detail.json
+++ b/erpnext/accounts/report/tax_detail/test_tax_detail.json
@@ -302,7 +302,7 @@
"is_opening": "No",
"is_paid": 0,
"is_return": 0,
- "is_subcontracted": "No",
+ "is_subcontracted": 0,
"items": [
{
"allow_zero_valuation_rate": 0,
diff --git a/erpnext/accounts/report/tax_detail/test_tax_detail.py b/erpnext/accounts/report/tax_detail/test_tax_detail.py
index bf668ab779d..869f2450218 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,155 +31,183 @@ 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:
db_doc.submit()
else:
- db_doc.insert()
+ db_doc.insert(ignore_if_duplicate=True)
except frappe.exceptions.DuplicateEntryError:
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, '3.4'))
- self.assertFalse(filter_match(None, ' <'))
- self.assertFalse(filter_match(None, 'ew'))
- self.assertFalse(filter_match(None, ' '))
- self.assertFalse(filter_match(None, ' f :'))
+ self.assertTrue(filter_match(None, "!="))
+ self.assertTrue(filter_match(None, "<"))
+ self.assertTrue(filter_match(None, "3.4"))
+ self.assertFalse(filter_match(None, " <"))
+ self.assertFalse(filter_match(None, "ew"))
+ self.assertFalse(filter_match(None, " "))
+ self.assertFalse(filter_match(None, " f :"))
# Numbers
- self.assertTrue(filter_match(3.4, '3.4'))
- self.assertTrue(filter_match(3.4, '.4'))
- self.assertTrue(filter_match(3.4, '3'))
- self.assertTrue(filter_match(-3.4, '< -3'))
- self.assertTrue(filter_match(-3.4, '> -4'))
- self.assertTrue(filter_match(3.4, '= 3.4 '))
- self.assertTrue(filter_match(3.4, '!=4.5'))
- self.assertTrue(filter_match(3.4, ' 3 : 4 '))
- self.assertTrue(filter_match(0.0, ' : '))
- self.assertFalse(filter_match(3.4, '=4.5'))
- self.assertFalse(filter_match(3.4, ' = 3.4 '))
- self.assertFalse(filter_match(3.4, '!=3.4'))
- self.assertFalse(filter_match(3.4, '>6'))
- self.assertFalse(filter_match(3.4, '<-4.5'))
- self.assertFalse(filter_match(3.4, '4.5'))
- self.assertFalse(filter_match(3.4, '5:9'))
+ self.assertTrue(filter_match(3.4, "3.4"))
+ self.assertTrue(filter_match(3.4, ".4"))
+ self.assertTrue(filter_match(3.4, "3"))
+ self.assertTrue(filter_match(-3.4, "< -3"))
+ self.assertTrue(filter_match(-3.4, "> -4"))
+ self.assertTrue(filter_match(3.4, "= 3.4 "))
+ self.assertTrue(filter_match(3.4, "!=4.5"))
+ self.assertTrue(filter_match(3.4, " 3 : 4 "))
+ self.assertTrue(filter_match(0.0, " : "))
+ self.assertFalse(filter_match(3.4, "=4.5"))
+ self.assertFalse(filter_match(3.4, " = 3.4 "))
+ self.assertFalse(filter_match(3.4, "!=3.4"))
+ self.assertFalse(filter_match(3.4, ">6"))
+ self.assertFalse(filter_match(3.4, "<-4.5"))
+ self.assertFalse(filter_match(3.4, "4.5"))
+ self.assertFalse(filter_match(3.4, "5:9"))
# Strings
- self.assertTrue(filter_match('ACC-SINV-2021-00001', 'SINV'))
- self.assertTrue(filter_match('ACC-SINV-2021-00001', 'sinv'))
- self.assertTrue(filter_match('ACC-SINV-2021-00001', '-2021'))
- self.assertTrue(filter_match(' ACC-SINV-2021-00001', ' acc'))
- self.assertTrue(filter_match('ACC-SINV-2021-00001', '=2021'))
- self.assertTrue(filter_match('ACC-SINV-2021-00001', '!=zz'))
- self.assertTrue(filter_match('ACC-SINV-2021-00001', '< zzz '))
- self.assertTrue(filter_match('ACC-SINV-2021-00001', ' : sinv '))
- self.assertFalse(filter_match('ACC-SINV-2021-00001', ' sinv :'))
- self.assertFalse(filter_match('ACC-SINV-2021-00001', ' acc'))
- self.assertFalse(filter_match('ACC-SINV-2021-00001', '= 2021 '))
- self.assertFalse(filter_match('ACC-SINV-2021-00001', '!=sinv'))
- self.assertFalse(filter_match('ACC-SINV-2021-00001', ' >'))
- self.assertFalse(filter_match('ACC-SINV-2021-00001', '>aa'))
- self.assertFalse(filter_match('ACC-SINV-2021-00001', ' <'))
- self.assertFalse(filter_match('ACC-SINV-2021-00001', '< '))
- self.assertFalse(filter_match('ACC-SINV-2021-00001', ' ='))
- self.assertFalse(filter_match('ACC-SINV-2021-00001', '='))
+ self.assertTrue(filter_match("ACC-SINV-2021-00001", "SINV"))
+ self.assertTrue(filter_match("ACC-SINV-2021-00001", "sinv"))
+ self.assertTrue(filter_match("ACC-SINV-2021-00001", "-2021"))
+ self.assertTrue(filter_match(" ACC-SINV-2021-00001", " acc"))
+ self.assertTrue(filter_match("ACC-SINV-2021-00001", "=2021"))
+ self.assertTrue(filter_match("ACC-SINV-2021-00001", "!=zz"))
+ self.assertTrue(filter_match("ACC-SINV-2021-00001", "< zzz "))
+ self.assertTrue(filter_match("ACC-SINV-2021-00001", " : sinv "))
+ self.assertFalse(filter_match("ACC-SINV-2021-00001", " sinv :"))
+ self.assertFalse(filter_match("ACC-SINV-2021-00001", " acc"))
+ self.assertFalse(filter_match("ACC-SINV-2021-00001", "= 2021 "))
+ self.assertFalse(filter_match("ACC-SINV-2021-00001", "!=sinv"))
+ self.assertFalse(filter_match("ACC-SINV-2021-00001", " >"))
+ self.assertFalse(filter_match("ACC-SINV-2021-00001", ">aa"))
+ self.assertFalse(filter_match("ACC-SINV-2021-00001", " <"))
+ self.assertFalse(filter_match("ACC-SINV-2021-00001", "< "))
+ self.assertFalse(filter_match("ACC-SINV-2021-00001", " ="))
+ self.assertFalse(filter_match("ACC-SINV-2021-00001", "="))
# Date - always match
- self.assertTrue(filter_match(datetime.date(2021, 3, 19), ' kdsjkldfs '))
+ self.assertTrue(filter_match(datetime.date(2021, 3, 19), " kdsjkldfs "))
diff --git a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py
index 07f2e435e41..08d20086823 100644
--- a/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py
+++ b/erpnext/accounts/report/tds_computation_summary/tds_computation_summary.py
@@ -11,7 +11,7 @@ from erpnext.accounts.utils import get_fiscal_year
def execute(filters=None):
validate_filters(filters)
- filters.naming_series = frappe.db.get_single_value('Buying Settings', 'supp_master_name')
+ filters.naming_series = frappe.db.get_single_value("Buying Settings", "supp_master_name")
columns = get_columns(filters)
tds_docs, tds_accounts, tax_category_map = get_tds_docs(filters)
@@ -21,8 +21,9 @@ def execute(filters=None):
return columns, final_result
+
def validate_filters(filters):
- ''' Validate if dates are properly set and lie in the same fiscal year'''
+ """Validate if dates are properly set and lie in the same fiscal year"""
if filters.from_date > filters.to_date:
frappe.throw(_("From Date must be before To Date"))
@@ -33,26 +34,32 @@ def validate_filters(filters):
filters["fiscal_year"] = from_year
+
def group_by_supplier_and_category(data):
supplier_category_wise_map = {}
for row in data:
- supplier_category_wise_map.setdefault((row.get('supplier'), row.get('section_code')), {
- 'pan': row.get('pan'),
- 'supplier': row.get('supplier'),
- 'supplier_name': row.get('supplier_name'),
- 'section_code': row.get('section_code'),
- 'entity_type': row.get('entity_type'),
- 'tds_rate': row.get('tds_rate'),
- 'total_amount_credited': 0.0,
- 'tds_deducted': 0.0
- })
+ supplier_category_wise_map.setdefault(
+ (row.get("supplier"), row.get("section_code")),
+ {
+ "pan": row.get("pan"),
+ "supplier": row.get("supplier"),
+ "supplier_name": row.get("supplier_name"),
+ "section_code": row.get("section_code"),
+ "entity_type": row.get("entity_type"),
+ "tds_rate": row.get("tds_rate"),
+ "total_amount_credited": 0.0,
+ "tds_deducted": 0.0,
+ },
+ )
- supplier_category_wise_map.get((row.get('supplier'), row.get('section_code')))['total_amount_credited'] += \
- row.get('total_amount_credited', 0.0)
+ supplier_category_wise_map.get((row.get("supplier"), row.get("section_code")))[
+ "total_amount_credited"
+ ] += row.get("total_amount_credited", 0.0)
- supplier_category_wise_map.get((row.get('supplier'), row.get('section_code')))['tds_deducted'] += \
- row.get('tds_deducted', 0.0)
+ supplier_category_wise_map.get((row.get("supplier"), row.get("section_code")))[
+ "tds_deducted"
+ ] += row.get("tds_deducted", 0.0)
final_result = get_final_result(supplier_category_wise_map)
@@ -66,62 +73,48 @@ def get_final_result(supplier_category_wise_map):
return out
+
def get_columns(filters):
columns = [
- {
- "label": _("PAN"),
- "fieldname": "pan",
- "fieldtype": "Data",
- "width": 90
- },
+ {"label": _("PAN"), "fieldname": "pan", "fieldtype": "Data", "width": 90},
{
"label": _("Supplier"),
"options": "Supplier",
"fieldname": "supplier",
"fieldtype": "Link",
- "width": 180
- }]
+ "width": 180,
+ },
+ ]
- if filters.naming_series == 'Naming Series':
- columns.append({
- "label": _("Supplier Name"),
- "fieldname": "supplier_name",
- "fieldtype": "Data",
- "width": 180
- })
+ if filters.naming_series == "Naming Series":
+ columns.append(
+ {"label": _("Supplier Name"), "fieldname": "supplier_name", "fieldtype": "Data", "width": 180}
+ )
- columns.extend([
- {
- "label": _("Section Code"),
- "options": "Tax Withholding Category",
- "fieldname": "section_code",
- "fieldtype": "Link",
- "width": 180
- },
- {
- "label": _("Entity Type"),
- "fieldname": "entity_type",
- "fieldtype": "Data",
- "width": 180
- },
- {
- "label": _("TDS Rate %"),
- "fieldname": "tds_rate",
- "fieldtype": "Percent",
- "width": 90
- },
- {
- "label": _("Total Amount Credited"),
- "fieldname": "total_amount_credited",
- "fieldtype": "Float",
- "width": 90
- },
- {
- "label": _("Amount of TDS Deducted"),
- "fieldname": "tds_deducted",
- "fieldtype": "Float",
- "width": 90
- }
- ])
+ columns.extend(
+ [
+ {
+ "label": _("Section Code"),
+ "options": "Tax Withholding Category",
+ "fieldname": "section_code",
+ "fieldtype": "Link",
+ "width": 180,
+ },
+ {"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 180},
+ {"label": _("TDS Rate %"), "fieldname": "tds_rate", "fieldtype": "Percent", "width": 90},
+ {
+ "label": _("Total Amount Credited"),
+ "fieldname": "total_amount_credited",
+ "fieldtype": "Float",
+ "width": 90,
+ },
+ {
+ "label": _("Amount of TDS Deducted"),
+ "fieldname": "tds_deducted",
+ "fieldtype": "Float",
+ "width": 90,
+ },
+ ]
+ )
return columns
diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
index 57f79748f0a..30ea5a9e4e1 100644
--- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
+++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
@@ -15,11 +15,13 @@ def execute(filters=None):
res = get_result(filters, tds_docs, tds_accounts, tax_category_map)
return columns, res
+
def validate_filters(filters):
- ''' Validate if dates are properly set '''
+ """Validate if dates are properly set"""
if filters.from_date > filters.to_date:
frappe.throw(_("From Date must be before To Date"))
+
def get_result(filters, tds_docs, tds_accounts, tax_category_map):
supplier_map = get_supplier_pan_map()
tax_rate_map = get_tax_rate_map(filters)
@@ -37,57 +39,63 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map):
voucher_type = entry.voucher_type
if not tax_withholding_category:
- tax_withholding_category = supplier_map.get(supplier, {}).get('tax_withholding_category')
+ tax_withholding_category = supplier_map.get(supplier, {}).get("tax_withholding_category")
rate = tax_rate_map.get(tax_withholding_category)
if entry.account in tds_accounts:
- tds_deducted += (entry.credit - entry.debit)
+ tds_deducted += entry.credit - entry.debit
- total_amount_credited += (entry.credit - entry.debit)
+ total_amount_credited += entry.credit
if tds_deducted:
row = {
- 'pan' if frappe.db.has_column('Supplier', 'pan') else 'tax_id': supplier_map.get(supplier, {}).get('pan'),
- 'supplier': supplier_map.get(supplier, {}).get('name')
+ "pan"
+ if frappe.db.has_column("Supplier", "pan")
+ else "tax_id": supplier_map.get(supplier, {}).get("pan"),
+ "supplier": supplier_map.get(supplier, {}).get("name"),
}
- if filters.naming_series == 'Naming Series':
- row.update({'supplier_name': supplier_map.get(supplier, {}).get('supplier_name')})
+ if filters.naming_series == "Naming Series":
+ row.update({"supplier_name": supplier_map.get(supplier, {}).get("supplier_name")})
- row.update({
- 'section_code': tax_withholding_category,
- 'entity_type': supplier_map.get(supplier, {}).get('supplier_type'),
- 'tds_rate': rate,
- 'total_amount_credited': total_amount_credited,
- 'tds_deducted': tds_deducted,
- 'transaction_date': posting_date,
- 'transaction_type': voucher_type,
- 'ref_no': name
- })
+ row.update(
+ {
+ "section_code": tax_withholding_category,
+ "entity_type": supplier_map.get(supplier, {}).get("supplier_type"),
+ "tds_rate": rate,
+ "total_amount_credited": total_amount_credited,
+ "tds_deducted": tds_deducted,
+ "transaction_date": posting_date,
+ "transaction_type": voucher_type,
+ "ref_no": name,
+ }
+ )
out.append(row)
return out
+
def get_supplier_pan_map():
supplier_map = frappe._dict()
- suppliers = frappe.db.get_all('Supplier', fields=['name', 'pan', 'supplier_type', 'supplier_name', 'tax_withholding_category'])
+ suppliers = frappe.db.get_all(
+ "Supplier", fields=["name", "pan", "supplier_type", "supplier_name", "tax_withholding_category"]
+ )
for d in suppliers:
supplier_map[d.name] = d
return supplier_map
+
def get_gle_map(documents):
# create gle_map of the form
# {"purchase_invoice": list of dict of all gle created for this invoice}
gle_map = {}
- gle = frappe.db.get_all('GL Entry',
- {
- "voucher_no": ["in", documents],
- "is_cancelled": 0
- },
+ gle = frappe.db.get_all(
+ "GL Entry",
+ {"voucher_no": ["in", documents], "is_cancelled": 0},
["credit", "debit", "account", "voucher_no", "posting_date", "voucher_type", "against", "party"],
)
@@ -99,85 +107,68 @@ def get_gle_map(documents):
return gle_map
+
def get_columns(filters):
pan = "pan" if frappe.db.has_column("Supplier", "pan") else "tax_id"
columns = [
- {
- "label": _(frappe.unscrub(pan)),
- "fieldname": pan,
- "fieldtype": "Data",
- "width": 90
- },
+ {"label": _(frappe.unscrub(pan)), "fieldname": pan, "fieldtype": "Data", "width": 90},
{
"label": _("Supplier"),
"options": "Supplier",
"fieldname": "supplier",
"fieldtype": "Link",
- "width": 180
- }]
+ "width": 180,
+ },
+ ]
- if filters.naming_series == 'Naming Series':
- columns.append({
- "label": _("Supplier Name"),
- "fieldname": "supplier_name",
- "fieldtype": "Data",
- "width": 180
- })
+ if filters.naming_series == "Naming Series":
+ columns.append(
+ {"label": _("Supplier Name"), "fieldname": "supplier_name", "fieldtype": "Data", "width": 180}
+ )
- columns.extend([
- {
- "label": _("Section Code"),
- "options": "Tax Withholding Category",
- "fieldname": "section_code",
- "fieldtype": "Link",
- "width": 180
- },
- {
- "label": _("Entity Type"),
- "fieldname": "entity_type",
- "fieldtype": "Data",
- "width": 180
- },
- {
- "label": _("TDS Rate %"),
- "fieldname": "tds_rate",
- "fieldtype": "Percent",
- "width": 90
- },
- {
- "label": _("Total Amount Credited"),
- "fieldname": "total_amount_credited",
- "fieldtype": "Float",
- "width": 90
- },
- {
- "label": _("Amount of TDS Deducted"),
- "fieldname": "tds_deducted",
- "fieldtype": "Float",
- "width": 90
- },
- {
- "label": _("Date of Transaction"),
- "fieldname": "transaction_date",
- "fieldtype": "Date",
- "width": 90
- },
- {
- "label": _("Transaction Type"),
- "fieldname": "transaction_type",
- "width": 90
- },
- {
- "label": _("Reference No."),
- "fieldname": "ref_no",
- "fieldtype": "Dynamic Link",
- "options": "transaction_type",
- "width": 90
- }
- ])
+ columns.extend(
+ [
+ {
+ "label": _("Section Code"),
+ "options": "Tax Withholding Category",
+ "fieldname": "section_code",
+ "fieldtype": "Link",
+ "width": 180,
+ },
+ {"label": _("Entity Type"), "fieldname": "entity_type", "fieldtype": "Data", "width": 180},
+ {"label": _("TDS Rate %"), "fieldname": "tds_rate", "fieldtype": "Percent", "width": 90},
+ {
+ "label": _("Total Amount Credited"),
+ "fieldname": "total_amount_credited",
+ "fieldtype": "Float",
+ "width": 90,
+ },
+ {
+ "label": _("Amount of TDS Deducted"),
+ "fieldname": "tds_deducted",
+ "fieldtype": "Float",
+ "width": 90,
+ },
+ {
+ "label": _("Date of Transaction"),
+ "fieldname": "transaction_date",
+ "fieldtype": "Date",
+ "width": 90,
+ },
+ {"label": _("Transaction Type"), "fieldname": "transaction_type", "width": 90},
+ {
+ "label": _("Reference No."),
+ "fieldname": "ref_no",
+ "fieldtype": "Dynamic Link",
+ "options": "transaction_type",
+ "width": 90,
+ },
+ ]
+ )
return columns
+
def get_tds_docs(filters):
tds_documents = []
purchase_invoices = []
@@ -185,27 +176,30 @@ def get_tds_docs(filters):
journal_entries = []
tax_category_map = {}
or_filters = {}
- bank_accounts = frappe.get_all('Account', {'is_group': 0, 'account_type': 'Bank'}, pluck="name")
+ bank_accounts = frappe.get_all("Account", {"is_group": 0, "account_type": "Bank"}, pluck="name")
- tds_accounts = frappe.get_all("Tax Withholding Account", {'company': filters.get('company')},
- pluck="account")
+ tds_accounts = frappe.get_all(
+ "Tax Withholding Account", {"company": filters.get("company")}, pluck="account"
+ )
query_filters = {
"account": ("in", tds_accounts),
"posting_date": ("between", [filters.get("from_date"), filters.get("to_date")]),
"is_cancelled": 0,
- "against": ("not in", bank_accounts)
+ "against": ("not in", bank_accounts),
}
if filters.get("supplier"):
del query_filters["account"]
del query_filters["against"]
- or_filters = {
- "against": filters.get('supplier'),
- "party": filters.get('supplier')
- }
+ or_filters = {"against": filters.get("supplier"), "party": filters.get("supplier")}
- tds_docs = frappe.get_all("GL Entry", filters=query_filters, or_filters=or_filters, fields=["voucher_no", "voucher_type", "against", "party"])
+ tds_docs = frappe.get_all(
+ "GL Entry",
+ filters=query_filters,
+ or_filters=or_filters,
+ fields=["voucher_no", "voucher_type", "against", "party"],
+ )
for d in tds_docs:
if d.voucher_type == "Purchase Invoice":
@@ -218,24 +212,39 @@ def get_tds_docs(filters):
tds_documents.append(d.voucher_no)
if purchase_invoices:
- get_tax_category_map(purchase_invoices, 'Purchase Invoice', tax_category_map)
+ get_tax_category_map(purchase_invoices, "Purchase Invoice", tax_category_map)
if payment_entries:
- get_tax_category_map(payment_entries, 'Payment Entry', tax_category_map)
+ get_tax_category_map(payment_entries, "Payment Entry", tax_category_map)
if journal_entries:
- get_tax_category_map(journal_entries, 'Journal Entry', tax_category_map)
+ get_tax_category_map(journal_entries, "Journal Entry", tax_category_map)
return tds_documents, tds_accounts, tax_category_map
+
def get_tax_category_map(vouchers, doctype, tax_category_map):
- tax_category_map.update(frappe._dict(frappe.get_all(doctype,
- filters = {'name': ('in', vouchers)}, fields=['name', 'tax_withholding_category'], as_list=1)))
+ tax_category_map.update(
+ frappe._dict(
+ frappe.get_all(
+ doctype,
+ filters={"name": ("in", vouchers)},
+ fields=["name", "tax_withholding_category"],
+ as_list=1,
+ )
+ )
+ )
+
def get_tax_rate_map(filters):
- rate_map = frappe.get_all('Tax Withholding Rate', filters={
- 'from_date': ('<=', filters.get('from_date')),
- 'to_date': ('>=', filters.get('to_date'))
- }, fields=['parent', 'tax_withholding_rate'], as_list=1)
+ rate_map = frappe.get_all(
+ "Tax Withholding Rate",
+ filters={
+ "from_date": ("<=", filters.get("from_date")),
+ "to_date": (">=", filters.get("to_date")),
+ },
+ fields=["parent", "tax_withholding_rate"],
+ as_list=1,
+ )
- return frappe._dict(rate_map)
\ No newline at end of file
+ return frappe._dict(rate_map)
diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py
index bda44f66aa1..dd0ac756544 100644
--- a/erpnext/accounts/report/trial_balance/trial_balance.py
+++ b/erpnext/accounts/report/trial_balance/trial_balance.py
@@ -17,7 +17,15 @@ from erpnext.accounts.report.financial_statements import (
set_gl_entries_by_account,
)
-value_fields = ("opening_debit", "opening_credit", "debit", "credit", "closing_debit", "closing_credit")
+value_fields = (
+ "opening_debit",
+ "opening_credit",
+ "debit",
+ "credit",
+ "closing_debit",
+ "closing_credit",
+)
+
def execute(filters=None):
validate_filters(filters)
@@ -25,11 +33,14 @@ def execute(filters=None):
columns = get_columns()
return columns, data
+
def validate_filters(filters):
if not filters.fiscal_year:
frappe.throw(_("Fiscal Year {0} is required").format(filters.fiscal_year))
- fiscal_year = frappe.db.get_value("Fiscal Year", filters.fiscal_year, ["year_start_date", "year_end_date"], as_dict=True)
+ fiscal_year = frappe.db.get_value(
+ "Fiscal Year", filters.fiscal_year, ["year_start_date", "year_end_date"], as_dict=True
+ )
if not fiscal_year:
frappe.throw(_("Fiscal Year {0} does not exist").format(filters.fiscal_year))
else:
@@ -49,21 +60,32 @@ def validate_filters(filters):
frappe.throw(_("From Date cannot be greater than To Date"))
if (filters.from_date < filters.year_start_date) or (filters.from_date > filters.year_end_date):
- frappe.msgprint(_("From Date should be within the Fiscal Year. Assuming From Date = {0}")\
- .format(formatdate(filters.year_start_date)))
+ frappe.msgprint(
+ _("From Date should be within the Fiscal Year. Assuming From Date = {0}").format(
+ formatdate(filters.year_start_date)
+ )
+ )
filters.from_date = filters.year_start_date
if (filters.to_date < filters.year_start_date) or (filters.to_date > filters.year_end_date):
- frappe.msgprint(_("To Date should be within the Fiscal Year. Assuming To Date = {0}")\
- .format(formatdate(filters.year_end_date)))
+ frappe.msgprint(
+ _("To Date should be within the Fiscal Year. Assuming To Date = {0}").format(
+ formatdate(filters.year_end_date)
+ )
+ )
filters.to_date = filters.year_end_date
+
def get_data(filters):
- accounts = frappe.db.sql("""select name, account_number, parent_account, account_name, root_type, report_type, lft, rgt
+ accounts = frappe.db.sql(
+ """select name, account_number, parent_account, account_name, root_type, report_type, lft, rgt
- from `tabAccount` where company=%s order by lft""", filters.company, as_dict=True)
+ from `tabAccount` where company=%s order by lft""",
+ filters.company,
+ as_dict=True,
+ )
company_currency = filters.presentation_currency or erpnext.get_company_currency(filters.company)
if not accounts:
@@ -71,28 +93,44 @@ def get_data(filters):
accounts, accounts_by_name, parent_children_map = filter_accounts(accounts)
- 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]
gl_entries_by_account = {}
opening_balances = get_opening_balances(filters)
- #add filter inside list so that the query in financial_statements.py doesn't break
+ # add filter inside list so that the query in financial_statements.py doesn't break
if filters.project:
filters.project = [filters.project]
- set_gl_entries_by_account(filters.company, filters.from_date,
- filters.to_date, min_lft, max_rgt, filters, gl_entries_by_account, ignore_closing_entries=not flt(filters.with_period_closing_entry))
+ set_gl_entries_by_account(
+ filters.company,
+ filters.from_date,
+ filters.to_date,
+ min_lft,
+ max_rgt,
+ filters,
+ gl_entries_by_account,
+ ignore_closing_entries=not flt(filters.with_period_closing_entry),
+ )
- total_row = calculate_values(accounts, gl_entries_by_account, opening_balances, filters, company_currency)
+ total_row = calculate_values(
+ accounts, gl_entries_by_account, opening_balances, filters, company_currency
+ )
accumulate_values_into_parents(accounts, accounts_by_name)
data = prepare_data(accounts, filters, total_row, parent_children_map, company_currency)
- 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 get_opening_balances(filters):
balance_sheet_opening = get_rootwise_opening_balances(filters, "Balance Sheet")
pl_opening = get_rootwise_opening_balances(filters, "Profit and Loss")
@@ -104,16 +142,20 @@ def get_opening_balances(filters):
def get_rootwise_opening_balances(filters, report_type):
additional_conditions = ""
if not filters.show_unclosed_fy_pl_balances:
- additional_conditions = " and posting_date >= %(year_start_date)s" \
- if report_type == "Profit and Loss" else ""
+ additional_conditions = (
+ " and posting_date >= %(year_start_date)s" if report_type == "Profit and Loss" else ""
+ )
if not flt(filters.with_period_closing_entry):
additional_conditions += " and ifnull(voucher_type, '')!='Period Closing Voucher'"
if filters.cost_center:
- lft, rgt = frappe.db.get_value('Cost Center', filters.cost_center, ['lft', 'rgt'])
+ lft, rgt = frappe.db.get_value("Cost Center", filters.cost_center, ["lft", "rgt"])
additional_conditions += """ and cost_center in (select name from `tabCost Center`
- where lft >= %s and rgt <= %s)""" % (lft, rgt)
+ where lft >= %s and rgt <= %s)""" % (
+ lft,
+ rgt,
+ )
if filters.project:
additional_conditions += " and project = %(project)s"
@@ -121,7 +163,9 @@ def get_rootwise_opening_balances(filters, report_type):
if filters.finance_book:
fb_conditions = " AND finance_book = %(finance_book)s"
if filters.include_default_book_entries:
- fb_conditions = " AND (finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)"
+ fb_conditions = (
+ " AND (finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)"
+ )
additional_conditions += fb_conditions
@@ -134,24 +178,24 @@ def get_rootwise_opening_balances(filters, report_type):
"year_start_date": filters.year_start_date,
"project": filters.project,
"finance_book": filters.finance_book,
- "company_fb": frappe.db.get_value("Company", filters.company, 'default_finance_book')
+ "company_fb": frappe.db.get_value("Company", filters.company, "default_finance_book"),
}
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 += "and {0} in %({0})s".format(dimension.fieldname)
else:
additional_conditions += "and {0} in (%({0})s)".format(dimension.fieldname)
- query_filters.update({
- dimension.fieldname: filters.get(dimension.fieldname)
- })
+ query_filters.update({dimension.fieldname: filters.get(dimension.fieldname)})
- gle = frappe.db.sql("""
+ gle = frappe.db.sql(
+ """
select
account, sum(debit) as opening_debit, sum(credit) as opening_credit
from `tabGL Entry`
@@ -161,7 +205,12 @@ def get_rootwise_opening_balances(filters, report_type):
and (posting_date < %(from_date)s or ifnull(is_opening, 'No') = 'Yes')
and account in (select name from `tabAccount` where report_type=%(report_type)s)
and is_cancelled = 0
- group by account""".format(additional_conditions=additional_conditions), query_filters , as_dict=True)
+ group by account""".format(
+ additional_conditions=additional_conditions
+ ),
+ query_filters,
+ as_dict=True,
+ )
opening = frappe._dict()
for d in gle:
@@ -169,6 +218,7 @@ def get_rootwise_opening_balances(filters, report_type):
return opening
+
def calculate_values(accounts, gl_entries_by_account, opening_balances, filters, company_currency):
init = {
"opening_debit": 0.0,
@@ -176,7 +226,7 @@ def calculate_values(accounts, gl_entries_by_account, opening_balances, filters,
"debit": 0.0,
"credit": 0.0,
"closing_debit": 0.0,
- "closing_credit": 0.0
+ "closing_credit": 0.0,
}
total_row = {
@@ -192,7 +242,7 @@ def calculate_values(accounts, gl_entries_by_account, opening_balances, filters,
"parent_account": None,
"indent": 0,
"has_value": True,
- "currency": company_currency
+ "currency": company_currency,
}
for d in accounts:
@@ -217,12 +267,14 @@ def calculate_values(accounts, gl_entries_by_account, opening_balances, 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, company_currency):
data = []
@@ -239,8 +291,9 @@ def prepare_data(accounts, filters, total_row, parent_children_map, company_curr
"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 key in value_fields:
@@ -253,10 +306,11 @@ def prepare_data(accounts, filters, total_row, parent_children_map, company_curr
row["has_value"] = has_value
data.append(row)
- data.extend([{},total_row])
+ data.extend([{}, total_row])
return data
+
def get_columns():
return [
{
@@ -264,59 +318,60 @@ def get_columns():
"label": _("Account"),
"fieldtype": "Link",
"options": "Account",
- "width": 300
+ "width": 300,
},
{
"fieldname": "currency",
"label": _("Currency"),
"fieldtype": "Link",
"options": "Currency",
- "hidden": 1
+ "hidden": 1,
},
{
"fieldname": "opening_debit",
"label": _("Opening (Dr)"),
"fieldtype": "Currency",
"options": "currency",
- "width": 120
+ "width": 120,
},
{
"fieldname": "opening_credit",
"label": _("Opening (Cr)"),
"fieldtype": "Currency",
"options": "currency",
- "width": 120
+ "width": 120,
},
{
"fieldname": "debit",
"label": _("Debit"),
"fieldtype": "Currency",
"options": "currency",
- "width": 120
+ "width": 120,
},
{
"fieldname": "credit",
"label": _("Credit"),
"fieldtype": "Currency",
"options": "currency",
- "width": 120
+ "width": 120,
},
{
"fieldname": "closing_debit",
"label": _("Closing (Dr)"),
"fieldtype": "Currency",
"options": "currency",
- "width": 120
+ "width": 120,
},
{
"fieldname": "closing_credit",
"label": _("Closing (Cr)"),
"fieldtype": "Currency",
"options": "currency",
- "width": 120
- }
+ "width": 120,
+ },
]
+
def prepare_opening_closing(row):
dr_or_cr = "debit" if row["root_type"] in ["Asset", "Equity", "Expense"] else "credit"
reverse_dr_or_cr = "credit" if dr_or_cr == "debit" else "debit"
diff --git a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py
index d843dfd3ce3..869f6aaf942 100644
--- a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py
+++ b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py
@@ -19,102 +19,101 @@ def execute(filters=None):
return columns, data
+
def get_data(filters, show_party_name):
- if filters.get('party_type') in ('Customer', 'Supplier', 'Employee', 'Member'):
- party_name_field = "{0}_name".format(frappe.scrub(filters.get('party_type')))
- elif filters.get('party_type') == 'Student':
- party_name_field = 'first_name'
- elif filters.get('party_type') == 'Shareholder':
- party_name_field = 'title'
+ if filters.get("party_type") in ("Customer", "Supplier", "Employee", "Member"):
+ party_name_field = "{0}_name".format(frappe.scrub(filters.get("party_type")))
+ elif filters.get("party_type") == "Student":
+ party_name_field = "first_name"
+ elif filters.get("party_type") == "Shareholder":
+ party_name_field = "title"
else:
- party_name_field = 'name'
+ party_name_field = "name"
party_filters = {"name": filters.get("party")} if filters.get("party") else {}
- parties = frappe.get_all(filters.get("party_type"), fields = ["name", party_name_field],
- filters = party_filters, order_by="name")
- company_currency = frappe.get_cached_value('Company', filters.company, "default_currency")
+ parties = frappe.get_all(
+ filters.get("party_type"),
+ fields=["name", party_name_field],
+ filters=party_filters,
+ order_by="name",
+ )
+ company_currency = frappe.get_cached_value("Company", filters.company, "default_currency")
opening_balances = get_opening_balances(filters)
balances_within_period = get_balances_within_period(filters)
data = []
# total_debit, total_credit = 0, 0
- total_row = frappe._dict({
- "opening_debit": 0,
- "opening_credit": 0,
- "debit": 0,
- "credit": 0,
- "closing_debit": 0,
- "closing_credit": 0
- })
+ total_row = frappe._dict(
+ {
+ "opening_debit": 0,
+ "opening_credit": 0,
+ "debit": 0,
+ "credit": 0,
+ "closing_debit": 0,
+ "closing_credit": 0,
+ }
+ )
for party in parties:
- row = { "party": party.name }
+ row = {"party": party.name}
if show_party_name:
row["party_name"] = party.get(party_name_field)
# opening
opening_debit, opening_credit = opening_balances.get(party.name, [0, 0])
- row.update({
- "opening_debit": opening_debit,
- "opening_credit": opening_credit
- })
+ row.update({"opening_debit": opening_debit, "opening_credit": opening_credit})
# within period
debit, credit = balances_within_period.get(party.name, [0, 0])
- row.update({
- "debit": debit,
- "credit": credit
- })
+ row.update({"debit": debit, "credit": credit})
# closing
- closing_debit, closing_credit = toggle_debit_credit(opening_debit + debit, opening_credit + credit)
- row.update({
- "closing_debit": closing_debit,
- "closing_credit": closing_credit
- })
+ closing_debit, closing_credit = toggle_debit_credit(
+ opening_debit + debit, opening_credit + credit
+ )
+ row.update({"closing_debit": closing_debit, "closing_credit": closing_credit})
# totals
for col in total_row:
total_row[col] += row.get(col)
- row.update({
- "currency": company_currency
- })
+ row.update({"currency": company_currency})
has_value = False
- if (opening_debit or opening_credit or debit or credit or closing_debit or closing_credit):
- has_value =True
+ if opening_debit or opening_credit or debit or credit or closing_debit or closing_credit:
+ has_value = True
if cint(filters.show_zero_values) or has_value:
data.append(row)
# Add total row
- total_row.update({
- "party": "'" + _("Totals") + "'",
- "currency": company_currency
- })
+ total_row.update({"party": "'" + _("Totals") + "'", "currency": company_currency})
data.append(total_row)
return data
+
def get_opening_balances(filters):
- account_filter = ''
- if filters.get('account'):
- account_filter = "and account = %s" % (frappe.db.escape(filters.get('account')))
+ account_filter = ""
+ if filters.get("account"):
+ account_filter = "and account = %s" % (frappe.db.escape(filters.get("account")))
- gle = frappe.db.sql("""
+ gle = frappe.db.sql(
+ """
select party, sum(debit) as opening_debit, sum(credit) as opening_credit
from `tabGL Entry`
where company=%(company)s
+ and is_cancelled=0
and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != ''
and (posting_date < %(from_date)s or ifnull(is_opening, 'No') = 'Yes')
{account_filter}
- group by party""".format(account_filter=account_filter), {
- "company": filters.company,
- "from_date": filters.from_date,
- "party_type": filters.party_type
- }, as_dict=True)
+ group by party""".format(
+ account_filter=account_filter
+ ),
+ {"company": filters.company, "from_date": filters.from_date, "party_type": filters.party_type},
+ as_dict=True,
+ )
opening = frappe._dict()
for d in gle:
@@ -123,26 +122,34 @@ def get_opening_balances(filters):
return opening
+
def get_balances_within_period(filters):
- account_filter = ''
- if filters.get('account'):
- account_filter = "and account = %s" % (frappe.db.escape(filters.get('account')))
+ account_filter = ""
+ if filters.get("account"):
+ account_filter = "and account = %s" % (frappe.db.escape(filters.get("account")))
- gle = frappe.db.sql("""
+ gle = frappe.db.sql(
+ """
select party, sum(debit) as debit, sum(credit) as credit
from `tabGL Entry`
where company=%(company)s
+ and is_cancelled = 0
and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != ''
and posting_date >= %(from_date)s and posting_date <= %(to_date)s
and ifnull(is_opening, 'No') = 'No'
{account_filter}
- group by party""".format(account_filter=account_filter), {
+ group by party""".format(
+ account_filter=account_filter
+ ),
+ {
"company": filters.company,
"from_date": filters.from_date,
"to_date": filters.to_date,
- "party_type": filters.party_type
- }, as_dict=True)
+ "party_type": filters.party_type,
+ },
+ as_dict=True,
+ )
balances_within_period = frappe._dict()
for d in gle:
@@ -150,6 +157,7 @@ def get_balances_within_period(filters):
return balances_within_period
+
def toggle_debit_credit(debit, credit):
if flt(debit) > flt(credit):
debit = flt(debit) - flt(credit)
@@ -160,6 +168,7 @@ def toggle_debit_credit(debit, credit):
return debit, credit
+
def get_columns(filters, show_party_name):
columns = [
{
@@ -167,73 +176,77 @@ def get_columns(filters, show_party_name):
"label": _(filters.party_type),
"fieldtype": "Link",
"options": filters.party_type,
- "width": 200
+ "width": 200,
},
{
"fieldname": "opening_debit",
"label": _("Opening (Dr)"),
"fieldtype": "Currency",
"options": "currency",
- "width": 120
+ "width": 120,
},
{
"fieldname": "opening_credit",
"label": _("Opening (Cr)"),
"fieldtype": "Currency",
"options": "currency",
- "width": 120
+ "width": 120,
},
{
"fieldname": "debit",
"label": _("Debit"),
"fieldtype": "Currency",
"options": "currency",
- "width": 120
+ "width": 120,
},
{
"fieldname": "credit",
"label": _("Credit"),
"fieldtype": "Currency",
"options": "currency",
- "width": 120
+ "width": 120,
},
{
"fieldname": "closing_debit",
"label": _("Closing (Dr)"),
"fieldtype": "Currency",
"options": "currency",
- "width": 120
+ "width": 120,
},
{
"fieldname": "closing_credit",
"label": _("Closing (Cr)"),
"fieldtype": "Currency",
"options": "currency",
- "width": 120
+ "width": 120,
},
{
"fieldname": "currency",
"label": _("Currency"),
"fieldtype": "Link",
"options": "Currency",
- "hidden": 1
- }
+ "hidden": 1,
+ },
]
if show_party_name:
- columns.insert(1, {
- "fieldname": "party_name",
- "label": _(filters.party_type) + " Name",
- "fieldtype": "Data",
- "width": 200
- })
+ columns.insert(
+ 1,
+ {
+ "fieldname": "party_name",
+ "label": _(filters.party_type) + " Name",
+ "fieldtype": "Data",
+ "width": 200,
+ },
+ )
return columns
+
def is_party_name_visible(filters):
show_party_name = False
- if filters.get('party_type') in ['Customer', 'Supplier']:
+ if filters.get("party_type") in ["Customer", "Supplier"]:
if filters.get("party_type") == "Customer":
party_naming_by = frappe.db.get_single_value("Selling Settings", "cust_master_name")
else:
diff --git a/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.py b/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.py
index 26b938966b3..62b4f63b3a3 100644
--- a/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.py
+++ b/erpnext/accounts/report/unpaid_expense_claim/unpaid_expense_claim.py
@@ -12,16 +12,25 @@ def execute(filters=None):
data = get_unclaimed_expese_claims(filters)
return columns, data
+
def get_columns():
- return [_("Employee") + ":Link/Employee:120", _("Employee Name") + "::120",_("Expense Claim") + ":Link/Expense Claim:120",
- _("Sanctioned Amount") + ":Currency:120", _("Paid Amount") + ":Currency:120", _("Outstanding Amount") + ":Currency:150"]
+ return [
+ _("Employee") + ":Link/Employee:120",
+ _("Employee Name") + "::120",
+ _("Expense Claim") + ":Link/Expense Claim:120",
+ _("Sanctioned Amount") + ":Currency:120",
+ _("Paid Amount") + ":Currency:120",
+ _("Outstanding Amount") + ":Currency:150",
+ ]
+
def get_unclaimed_expese_claims(filters):
cond = "1=1"
if filters.get("employee"):
cond = "ec.employee = %(employee)s"
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
select
ec.employee, ec.employee_name, ec.name, ec.total_sanctioned_amount, ec.total_amount_reimbursed,
sum(gle.credit_in_account_currency - gle.debit_in_account_currency) as outstanding_amt
@@ -32,4 +41,9 @@ def get_unclaimed_expese_claims(filters):
and gle.party is not null and ec.docstatus = 1 and ec.is_paid = 0 and {cond} group by ec.name
having
outstanding_amt > 0
- """.format(cond=cond), filters, as_list=1)
+ """.format(
+ cond=cond
+ ),
+ filters,
+ as_list=1,
+ )
diff --git a/erpnext/accounts/report/utils.py b/erpnext/accounts/report/utils.py
index c38e4b8c7f3..eed58367739 100644
--- a/erpnext/accounts/report/utils.py
+++ b/erpnext/accounts/report/utils.py
@@ -7,6 +7,7 @@ from erpnext.setup.utils import get_exchange_rate
__exchange_rates = {}
+
def get_currency(filters):
"""
Returns a dictionary containing currency information. The keys of the dict are
@@ -23,15 +24,22 @@ def get_currency(filters):
"""
company = get_appropriate_company(filters)
company_currency = get_company_currency(company)
- presentation_currency = filters['presentation_currency'] if filters.get('presentation_currency') else company_currency
+ presentation_currency = (
+ filters["presentation_currency"] if filters.get("presentation_currency") else company_currency
+ )
- report_date = filters.get('to_date')
+ report_date = filters.get("to_date")
if not report_date:
- fiscal_year_to_date = get_from_and_to_date(filters.get('to_fiscal_year'))["to_date"]
+ fiscal_year_to_date = get_from_and_to_date(filters.get("to_fiscal_year"))["to_date"]
report_date = formatdate(get_datetime_str(fiscal_year_to_date), "dd-MM-yyyy")
- currency_map = dict(company=company, company_currency=company_currency, presentation_currency=presentation_currency, report_date=report_date)
+ currency_map = dict(
+ company=company,
+ company_currency=company_currency,
+ presentation_currency=presentation_currency,
+ report_date=report_date,
+ )
return currency_map
@@ -62,13 +70,14 @@ def get_rate_as_at(date, from_currency, to_currency):
:return: Retrieved exchange rate
"""
- rate = __exchange_rates.get('{0}-{1}@{2}'.format(from_currency, to_currency, date))
+ rate = __exchange_rates.get("{0}-{1}@{2}".format(from_currency, to_currency, date))
if not rate:
rate = get_exchange_rate(from_currency, to_currency, date) or 1
- __exchange_rates['{0}-{1}@{2}'.format(from_currency, to_currency, date)] = rate
+ __exchange_rates["{0}-{1}@{2}".format(from_currency, to_currency, date)] = rate
return rate
+
def convert_to_presentation_currency(gl_entries, currency_info, company):
"""
Take a list of GL Entries and change the 'debit' and 'credit' values to currencies
@@ -78,35 +87,35 @@ def convert_to_presentation_currency(gl_entries, currency_info, company):
:return:
"""
converted_gl_list = []
- presentation_currency = currency_info['presentation_currency']
- company_currency = currency_info['company_currency']
+ presentation_currency = currency_info["presentation_currency"]
+ company_currency = currency_info["company_currency"]
- account_currencies = list(set(entry['account_currency'] for entry in gl_entries))
+ account_currencies = list(set(entry["account_currency"] for entry in gl_entries))
for entry in gl_entries:
- account = entry['account']
- debit = flt(entry['debit'])
- credit = flt(entry['credit'])
- debit_in_account_currency = flt(entry['debit_in_account_currency'])
- credit_in_account_currency = flt(entry['credit_in_account_currency'])
- account_currency = entry['account_currency']
+ account = entry["account"]
+ debit = flt(entry["debit"])
+ credit = flt(entry["credit"])
+ debit_in_account_currency = flt(entry["debit_in_account_currency"])
+ credit_in_account_currency = flt(entry["credit_in_account_currency"])
+ account_currency = entry["account_currency"]
if len(account_currencies) == 1 and account_currency == presentation_currency:
- if entry.get('debit'):
- entry['debit'] = debit_in_account_currency
+ if debit_in_account_currency:
+ entry["debit"] = debit_in_account_currency
- if entry.get('credit'):
- entry['credit'] = credit_in_account_currency
+ if credit_in_account_currency:
+ entry["credit"] = credit_in_account_currency
else:
- date = currency_info['report_date']
+ date = currency_info["report_date"]
converted_debit_value = convert(debit, presentation_currency, company_currency, date)
converted_credit_value = convert(credit, presentation_currency, company_currency, date)
- if entry.get('debit'):
- entry['debit'] = converted_debit_value
+ if entry.get("debit"):
+ entry["debit"] = converted_debit_value
- if entry.get('credit'):
- entry['credit'] = converted_credit_value
+ if entry.get("credit"):
+ entry["credit"] = converted_credit_value
converted_gl_list.append(entry)
@@ -114,26 +123,29 @@ def convert_to_presentation_currency(gl_entries, currency_info, company):
def get_appropriate_company(filters):
- if filters.get('company'):
- company = filters['company']
+ if filters.get("company"):
+ company = filters["company"]
else:
company = get_default_company()
return company
+
@frappe.whitelist()
-def get_invoiced_item_gross_margin(sales_invoice=None, item_code=None, company=None, with_item_data=False):
+def get_invoiced_item_gross_margin(
+ sales_invoice=None, item_code=None, company=None, with_item_data=False
+):
from erpnext.accounts.report.gross_profit.gross_profit import GrossProfitGenerator
- sales_invoice = sales_invoice or frappe.form_dict.get('sales_invoice')
- item_code = item_code or frappe.form_dict.get('item_code')
- company = company or frappe.get_cached_value("Sales Invoice", sales_invoice, 'company')
+ sales_invoice = sales_invoice or frappe.form_dict.get("sales_invoice")
+ item_code = item_code or frappe.form_dict.get("item_code")
+ company = company or frappe.get_cached_value("Sales Invoice", sales_invoice, "company")
filters = {
- 'sales_invoice': sales_invoice,
- 'item_code': item_code,
- 'company': company,
- 'group_by': 'Invoice'
+ "sales_invoice": sales_invoice,
+ "item_code": item_code,
+ "company": company,
+ "group_by": "Invoice",
}
gross_profit_data = GrossProfitGenerator(filters)
diff --git a/erpnext/accounts/test/test_reports.py b/erpnext/accounts/test/test_reports.py
index 78c109ab947..19fe74fffc1 100644
--- a/erpnext/accounts/test/test_reports.py
+++ b/erpnext/accounts/test/test_reports.py
@@ -8,18 +8,18 @@ DEFAULT_FILTERS = {
"from_date": "2010-01-01",
"to_date": "2030-01-01",
"period_start_date": "2010-01-01",
- "period_end_date": "2030-01-01"
+ "period_end_date": "2030-01-01",
}
REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [
- ("General Ledger", {"group_by": "Group by Voucher (Consolidated)"} ),
- ("General Ledger", {"group_by": "Group by Voucher (Consolidated)", "include_dimensions": 1} ),
+ ("General Ledger", {"group_by": "Group by Voucher (Consolidated)"}),
+ ("General Ledger", {"group_by": "Group by Voucher (Consolidated)", "include_dimensions": 1}),
("Accounts Payable", {"range1": 30, "range2": 60, "range3": 90, "range4": 120}),
("Accounts Receivable", {"range1": 30, "range2": 60, "range3": 90, "range4": 120}),
- ("Consolidated Financial Statement", {"report": "Balance Sheet"} ),
- ("Consolidated Financial Statement", {"report": "Profit and Loss Statement"} ),
- ("Consolidated Financial Statement", {"report": "Cash Flow"} ),
+ ("Consolidated Financial Statement", {"report": "Balance Sheet"}),
+ ("Consolidated Financial Statement", {"report": "Profit and Loss Statement"}),
+ ("Consolidated Financial Statement", {"report": "Cash Flow"}),
("Gross Profit", {"group_by": "Invoice"}),
("Gross Profit", {"group_by": "Item Code"}),
("Gross Profit", {"group_by": "Item Group"}),
@@ -29,7 +29,10 @@ REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [
("Item-wise Purchase Register", {}),
("Sales Register", {}),
("Purchase Register", {}),
- ("Tax Detail", {"mode": "run", "report_name": "Tax Detail"},),
+ (
+ "Tax Detail",
+ {"mode": "run", "report_name": "Tax Detail"},
+ ),
]
OPTIONAL_FILTERS = {}
@@ -39,10 +42,11 @@ class TestReports(unittest.TestCase):
def test_execute_all_accounts_reports(self):
"""Test that all script report in stock modules are executable with supported filters"""
for report, filter in REPORT_FILTER_TEST_CASES:
- execute_script_report(
- report_name=report,
- module="Accounts",
- filters=filter,
- default_filters=DEFAULT_FILTERS,
- optional_filters=OPTIONAL_FILTERS if filter.get("_optional") else None,
- )
+ with self.subTest(report=report):
+ execute_script_report(
+ report_name=report,
+ module="Accounts",
+ filters=filter,
+ default_filters=DEFAULT_FILTERS,
+ optional_filters=OPTIONAL_FILTERS if filter.get("_optional") else None,
+ )
diff --git a/erpnext/accounts/test/test_utils.py b/erpnext/accounts/test/test_utils.py
index effc913da05..0fe50083045 100644
--- a/erpnext/accounts/test/test_utils.py
+++ b/erpnext/accounts/test/test_utils.py
@@ -43,7 +43,8 @@ class TestUtils(unittest.TestCase):
posting_date = "2021-01-01"
gl_entries = get_voucherwise_gl_entries(future_vouchers, posting_date)
self.assertTrue(
- voucher_type_and_no in gl_entries, msg="get_voucherwise_gl_entries not returning expected GLes",
+ voucher_type_and_no in gl_entries,
+ msg="get_voucherwise_gl_entries not returning expected GLes",
)
diff --git a/erpnext/accounts/test_party.py b/erpnext/accounts/test_party.py
new file mode 100644
index 00000000000..9d3de5e8282
--- /dev/null
+++ b/erpnext/accounts/test_party.py
@@ -0,0 +1,18 @@
+import frappe
+from frappe.tests.utils import FrappeTestCase
+
+from erpnext.accounts.party import get_default_price_list
+
+
+class PartyTestCase(FrappeTestCase):
+ def test_get_default_price_list_should_return_none_for_invalid_group(self):
+ customer = frappe.get_doc(
+ {
+ "doctype": "Customer",
+ "customer_name": "test customer",
+ }
+ ).insert(ignore_permissions=True, ignore_mandatory=True)
+ customer.customer_group = None
+ customer.save()
+ price_list = get_default_price_list(customer)
+ assert price_list is None
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 39e84e3ceff..d17207a70ae 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -18,15 +18,28 @@ from erpnext.stock import get_warehouse_account_map
from erpnext.stock.utils import get_stock_value_on
-class StockValueAndAccountBalanceOutOfSync(frappe.ValidationError): pass
-class FiscalYearError(frappe.ValidationError): pass
-class PaymentEntryUnlinkError(frappe.ValidationError): pass
+class StockValueAndAccountBalanceOutOfSync(frappe.ValidationError):
+ pass
+
+
+class FiscalYearError(frappe.ValidationError):
+ pass
+
+
+class PaymentEntryUnlinkError(frappe.ValidationError):
+ pass
+
@frappe.whitelist()
-def get_fiscal_year(date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False):
+def get_fiscal_year(
+ date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False
+):
return get_fiscal_years(date, fiscal_year, label, verbose, company, as_dict=as_dict)[0]
-def get_fiscal_years(transaction_date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False):
+
+def get_fiscal_years(
+ transaction_date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False
+):
fiscal_years = frappe.cache().hget("fiscal_years", company) or []
if not fiscal_years:
@@ -46,7 +59,8 @@ def get_fiscal_years(transaction_date=None, fiscal_year=None, label="Date", verb
)
"""
- fiscal_years = frappe.db.sql("""
+ fiscal_years = frappe.db.sql(
+ """
select
fy.name, fy.year_start_date, fy.year_end_date
from
@@ -54,9 +68,12 @@ def get_fiscal_years(transaction_date=None, fiscal_year=None, label="Date", verb
where
disabled = 0 {0}
order by
- fy.year_start_date desc""".format(cond), {
- "company": company
- }, as_dict=True)
+ fy.year_start_date desc""".format(
+ cond
+ ),
+ {"company": company},
+ as_dict=True,
+ )
frappe.cache().hset("fiscal_years", company, fiscal_years)
@@ -71,8 +88,11 @@ def get_fiscal_years(transaction_date=None, fiscal_year=None, label="Date", verb
if fiscal_year and fy.name == fiscal_year:
matched = True
- if (transaction_date and getdate(fy.year_start_date) <= transaction_date
- and getdate(fy.year_end_date) >= transaction_date):
+ if (
+ transaction_date
+ and getdate(fy.year_start_date) <= transaction_date
+ and getdate(fy.year_end_date) >= transaction_date
+ ):
matched = True
if matched:
@@ -81,30 +101,35 @@ def get_fiscal_years(transaction_date=None, fiscal_year=None, label="Date", verb
else:
return ((fy.name, fy.year_start_date, fy.year_end_date),)
- error_msg = _("""{0} {1} is not in any active Fiscal Year""").format(label, formatdate(transaction_date))
+ error_msg = _("""{0} {1} is not in any active Fiscal Year""").format(
+ label, formatdate(transaction_date)
+ )
if company:
error_msg = _("""{0} for {1}""").format(error_msg, frappe.bold(company))
- if verbose==1: frappe.msgprint(error_msg)
+ if verbose == 1:
+ frappe.msgprint(error_msg)
raise FiscalYearError(error_msg)
+
@frappe.whitelist()
def get_fiscal_year_filter_field(company=None):
- field = {
- "fieldtype": "Select",
- "options": [],
- "operator": "Between",
- "query_value": True
- }
+ field = {"fieldtype": "Select", "options": [], "operator": "Between", "query_value": True}
fiscal_years = get_fiscal_years(company=company)
for fiscal_year in fiscal_years:
- field["options"].append({
- "label": fiscal_year.name,
- "value": fiscal_year.name,
- "query_value": [fiscal_year.year_start_date.strftime("%Y-%m-%d"), fiscal_year.year_end_date.strftime("%Y-%m-%d")]
- })
+ field["options"].append(
+ {
+ "label": fiscal_year.name,
+ "value": fiscal_year.name,
+ "query_value": [
+ fiscal_year.year_start_date.strftime("%Y-%m-%d"),
+ fiscal_year.year_end_date.strftime("%Y-%m-%d"),
+ ],
+ }
+ )
return field
+
def validate_fiscal_year(date, fiscal_year, company, label="Date", doc=None):
years = [f[0] for f in get_fiscal_years(date, label=_(label), company=company)]
if fiscal_year not in years:
@@ -113,9 +138,18 @@ def validate_fiscal_year(date, fiscal_year, company, label="Date", doc=None):
else:
throw(_("{0} '{1}' not in Fiscal Year {2}").format(label, formatdate(date), fiscal_year))
+
@frappe.whitelist()
-def get_balance_on(account=None, date=None, party_type=None, party=None, company=None,
- in_account_currency=True, cost_center=None, ignore_account_permission=False):
+def get_balance_on(
+ account=None,
+ date=None,
+ party_type=None,
+ party=None,
+ company=None,
+ in_account_currency=True,
+ cost_center=None,
+ ignore_account_permission=False,
+):
if not account and frappe.form_dict.get("account"):
account = frappe.form_dict.get("account")
if not date and frappe.form_dict.get("date"):
@@ -127,7 +161,6 @@ def get_balance_on(account=None, date=None, party_type=None, party=None, company
if not cost_center and frappe.form_dict.get("cost_center"):
cost_center = frappe.form_dict.get("cost_center")
-
cond = ["is_cancelled=0"]
if date:
cond.append("posting_date <= %s" % frappe.db.escape(cstr(date)))
@@ -155,45 +188,52 @@ def get_balance_on(account=None, date=None, party_type=None, party=None, company
else:
report_type = ""
- if cost_center and report_type == 'Profit and Loss':
+ if cost_center and report_type == "Profit and Loss":
cc = frappe.get_doc("Cost Center", cost_center)
if cc.is_group:
- cond.append(""" exists (
+ cond.append(
+ """ exists (
select 1 from `tabCost Center` cc where cc.name = gle.cost_center
and cc.lft >= %s and cc.rgt <= %s
- )""" % (cc.lft, cc.rgt))
+ )"""
+ % (cc.lft, cc.rgt)
+ )
else:
- cond.append("""gle.cost_center = %s """ % (frappe.db.escape(cost_center, percent=False), ))
-
+ cond.append("""gle.cost_center = %s """ % (frappe.db.escape(cost_center, percent=False),))
if account:
- if not (frappe.flags.ignore_account_permission
- or ignore_account_permission):
+ if not (frappe.flags.ignore_account_permission or ignore_account_permission):
acc.check_permission("read")
- if report_type == 'Profit and Loss':
+ if report_type == "Profit and Loss":
# for pl accounts, get balance within a fiscal year
- cond.append("posting_date >= '%s' and voucher_type != 'Period Closing Voucher'" \
- % year_start_date)
+ cond.append(
+ "posting_date >= '%s' and voucher_type != 'Period Closing Voucher'" % year_start_date
+ )
# different filter for group and ledger - improved performance
if acc.is_group:
- cond.append("""exists (
+ cond.append(
+ """exists (
select name from `tabAccount` ac where ac.name = gle.account
and ac.lft >= %s and ac.rgt <= %s
- )""" % (acc.lft, acc.rgt))
+ )"""
+ % (acc.lft, acc.rgt)
+ )
# If group and currency same as company,
# always return balance based on debit and credit in company currency
- if acc.account_currency == frappe.get_cached_value('Company', acc.company, "default_currency"):
+ if acc.account_currency == frappe.get_cached_value("Company", acc.company, "default_currency"):
in_account_currency = False
else:
- cond.append("""gle.account = %s """ % (frappe.db.escape(account, percent=False), ))
+ cond.append("""gle.account = %s """ % (frappe.db.escape(account, percent=False),))
if party_type and party:
- cond.append("""gle.party_type = %s and gle.party = %s """ %
- (frappe.db.escape(party_type), frappe.db.escape(party, percent=False)))
+ cond.append(
+ """gle.party_type = %s and gle.party = %s """
+ % (frappe.db.escape(party_type), frappe.db.escape(party, percent=False))
+ )
if company:
cond.append("""gle.company = %s """ % (frappe.db.escape(company, percent=False)))
@@ -203,14 +243,19 @@ def get_balance_on(account=None, date=None, party_type=None, party=None, company
select_field = "sum(debit_in_account_currency) - sum(credit_in_account_currency)"
else:
select_field = "sum(debit) - sum(credit)"
- bal = frappe.db.sql("""
+ bal = frappe.db.sql(
+ """
SELECT {0}
FROM `tabGL Entry` gle
- WHERE {1}""".format(select_field, " and ".join(cond)))[0][0]
+ WHERE {1}""".format(
+ select_field, " and ".join(cond)
+ )
+ )[0][0]
# if bal is None, return 0
return flt(bal)
+
def get_count_on(account, fieldname, date):
cond = ["is_cancelled=0"]
if date:
@@ -238,53 +283,71 @@ def get_count_on(account, fieldname, date):
acc.check_permission("read")
# for pl accounts, get balance within a fiscal year
- if acc.report_type == 'Profit and Loss':
- cond.append("posting_date >= '%s' and voucher_type != 'Period Closing Voucher'" \
- % year_start_date)
+ if acc.report_type == "Profit and Loss":
+ cond.append(
+ "posting_date >= '%s' and voucher_type != 'Period Closing Voucher'" % year_start_date
+ )
# different filter for group and ledger - improved performance
if acc.is_group:
- cond.append("""exists (
+ cond.append(
+ """exists (
select name from `tabAccount` ac where ac.name = gle.account
and ac.lft >= %s and ac.rgt <= %s
- )""" % (acc.lft, acc.rgt))
+ )"""
+ % (acc.lft, acc.rgt)
+ )
else:
- cond.append("""gle.account = %s """ % (frappe.db.escape(account, percent=False), ))
+ cond.append("""gle.account = %s """ % (frappe.db.escape(account, percent=False),))
- entries = frappe.db.sql("""
+ entries = frappe.db.sql(
+ """
SELECT name, posting_date, account, party_type, party,debit,credit,
voucher_type, voucher_no, against_voucher_type, against_voucher
FROM `tabGL Entry` gle
- WHERE {0}""".format(" and ".join(cond)), as_dict=True)
+ WHERE {0}""".format(
+ " and ".join(cond)
+ ),
+ as_dict=True,
+ )
count = 0
for gle in entries:
- if fieldname not in ('invoiced_amount','payables'):
+ if fieldname not in ("invoiced_amount", "payables"):
count += 1
else:
dr_or_cr = "debit" if fieldname == "invoiced_amount" else "credit"
cr_or_dr = "credit" if fieldname == "invoiced_amount" else "debit"
- select_fields = "ifnull(sum(credit-debit),0)" \
- if fieldname == "invoiced_amount" else "ifnull(sum(debit-credit),0)"
+ select_fields = (
+ "ifnull(sum(credit-debit),0)"
+ if fieldname == "invoiced_amount"
+ else "ifnull(sum(debit-credit),0)"
+ )
- if ((not gle.against_voucher) or (gle.against_voucher_type in ["Sales Order", "Purchase Order"]) or
- (gle.against_voucher==gle.voucher_no and gle.get(dr_or_cr) > 0)):
- payment_amount = frappe.db.sql("""
+ if (
+ (not gle.against_voucher)
+ or (gle.against_voucher_type in ["Sales Order", "Purchase Order"])
+ or (gle.against_voucher == gle.voucher_no and gle.get(dr_or_cr) > 0)
+ ):
+ payment_amount = frappe.db.sql(
+ """
SELECT {0}
FROM `tabGL Entry` gle
WHERE docstatus < 2 and posting_date <= %(date)s and against_voucher = %(voucher_no)s
- and party = %(party)s and name != %(name)s"""
- .format(select_fields),
- {"date": date, "voucher_no": gle.voucher_no,
- "party": gle.party, "name": gle.name})[0][0]
+ and party = %(party)s and name != %(name)s""".format(
+ select_fields
+ ),
+ {"date": date, "voucher_no": gle.voucher_no, "party": gle.party, "name": gle.name},
+ )[0][0]
outstanding_amount = flt(gle.get(dr_or_cr)) - flt(gle.get(cr_or_dr)) - payment_amount
currency_precision = get_currency_precision() or 2
- if abs(flt(outstanding_amount)) > 0.1/10**currency_precision:
+ if abs(flt(outstanding_amount)) > 0.1 / 10**currency_precision:
count += 1
return count
+
@frappe.whitelist()
def add_ac(args=None):
from frappe.desk.treeview import make_tree_args
@@ -316,6 +379,7 @@ def add_ac(args=None):
return ac.name
+
@frappe.whitelist()
def add_cc(args=None):
from frappe.desk.treeview import make_tree_args
@@ -327,8 +391,9 @@ def add_cc(args=None):
args = make_tree_args(**args)
if args.parent_cost_center == args.company:
- args.parent_cost_center = "{0} - {1}".format(args.parent_cost_center,
- frappe.get_cached_value('Company', args.company, 'abbr'))
+ args.parent_cost_center = "{0} - {1}".format(
+ args.parent_cost_center, frappe.get_cached_value("Company", args.company, "abbr")
+ )
cc = frappe.new_doc("Cost Center")
cc.update(args)
@@ -340,9 +405,10 @@ def add_cc(args=None):
cc.insert()
return cc.name
+
def reconcile_against_document(args):
"""
- Cancel PE or JV, Update against document, split if required and resubmit
+ Cancel PE or JV, Update against document, split if required and resubmit
"""
# To optimize making GL Entry for PE or JV with multiple references
reconciled_entries = {}
@@ -374,36 +440,44 @@ def reconcile_against_document(args):
doc.save(ignore_permissions=True)
# re-submit advance entry
doc = frappe.get_doc(entry.voucher_type, entry.voucher_no)
- doc.make_gl_entries(cancel = 0, adv_adj =1)
+ doc.make_gl_entries(cancel=0, adv_adj=1)
frappe.flags.ignore_party_validation = False
- if entry.voucher_type in ('Payment Entry', 'Journal Entry'):
+ if entry.voucher_type in ("Payment Entry", "Journal Entry"):
doc.update_expense_claim()
+
def check_if_advance_entry_modified(args):
"""
- check if there is already a voucher reference
- check if amount is same
- check if jv is submitted
+ check if there is already a voucher reference
+ check if amount is same
+ check if jv is submitted
"""
- if not args.get('unreconciled_amount'):
- args.update({'unreconciled_amount': args.get('unadjusted_amount')})
+ if not args.get("unreconciled_amount"):
+ args.update({"unreconciled_amount": args.get("unadjusted_amount")})
ret = None
if args.voucher_type == "Journal Entry":
- ret = frappe.db.sql("""
+ ret = frappe.db.sql(
+ """
select t2.{dr_or_cr} from `tabJournal Entry` t1, `tabJournal Entry Account` t2
where t1.name = t2.parent and t2.account = %(account)s
and t2.party_type = %(party_type)s and t2.party = %(party)s
and (t2.reference_type is null or t2.reference_type in ("", "Sales Order", "Purchase Order"))
and t1.name = %(voucher_no)s and t2.name = %(voucher_detail_no)s
- and t1.docstatus=1 """.format(dr_or_cr = args.get("dr_or_cr")), args)
+ and t1.docstatus=1 """.format(
+ dr_or_cr=args.get("dr_or_cr")
+ ),
+ args,
+ )
else:
- party_account_field = ("paid_from"
- if erpnext.get_party_account_type(args.party_type) == 'Receivable' else "paid_to")
+ party_account_field = (
+ "paid_from" if erpnext.get_party_account_type(args.party_type) == "Receivable" else "paid_to"
+ )
if args.voucher_detail_no:
- ret = frappe.db.sql("""select t1.name
+ ret = frappe.db.sql(
+ """select t1.name
from `tabPayment Entry` t1, `tabPayment Entry Reference` t2
where
t1.name = t2.parent and t1.docstatus = 1
@@ -411,37 +485,53 @@ def check_if_advance_entry_modified(args):
and t1.party_type = %(party_type)s and t1.party = %(party)s and t1.{0} = %(account)s
and t2.reference_doctype in ("", "Sales Order", "Purchase Order")
and t2.allocated_amount = %(unreconciled_amount)s
- """.format(party_account_field), args)
+ """.format(
+ party_account_field
+ ),
+ args,
+ )
else:
- ret = frappe.db.sql("""select name from `tabPayment Entry`
+ ret = frappe.db.sql(
+ """select name from `tabPayment Entry`
where
name = %(voucher_no)s and docstatus = 1
and party_type = %(party_type)s and party = %(party)s and {0} = %(account)s
and unallocated_amount = %(unreconciled_amount)s
- """.format(party_account_field), args)
+ """.format(
+ party_account_field
+ ),
+ args,
+ )
if not ret:
throw(_("""Payment Entry has been modified after you pulled it. Please pull it again."""))
+
def validate_allocated_amount(args):
- precision = args.get('precision') or frappe.db.get_single_value("System Settings", "currency_precision")
+ precision = args.get("precision") or frappe.db.get_single_value(
+ "System Settings", "currency_precision"
+ )
if args.get("allocated_amount") < 0:
throw(_("Allocated amount cannot be negative"))
elif flt(args.get("allocated_amount"), precision) > flt(args.get("unadjusted_amount"), precision):
throw(_("Allocated amount cannot be greater than unadjusted amount"))
+
def update_reference_in_journal_entry(d, journal_entry, do_not_save=False):
"""
- Updates against document, if partial amount splits into rows
+ Updates against document, if partial amount splits into rows
"""
jv_detail = journal_entry.get("accounts", {"name": d["voucher_detail_no"]})[0]
- if flt(d['unadjusted_amount']) - flt(d['allocated_amount']) != 0:
+ if flt(d["unadjusted_amount"]) - flt(d["allocated_amount"]) != 0:
# adjust the unreconciled balance
- amount_in_account_currency = flt(d['unadjusted_amount']) - flt(d['allocated_amount'])
+ amount_in_account_currency = flt(d["unadjusted_amount"]) - flt(d["allocated_amount"])
amount_in_company_currency = amount_in_account_currency * flt(jv_detail.exchange_rate)
- jv_detail.set(d['dr_or_cr'], amount_in_account_currency)
- jv_detail.set('debit' if d['dr_or_cr'] == 'debit_in_account_currency' else 'credit', amount_in_company_currency)
+ jv_detail.set(d["dr_or_cr"], amount_in_account_currency)
+ jv_detail.set(
+ "debit" if d["dr_or_cr"] == "debit_in_account_currency" else "credit",
+ amount_in_company_currency,
+ )
else:
journal_entry.remove(jv_detail)
@@ -451,12 +541,18 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False):
new_row.update((frappe.copy_doc(jv_detail)).as_dict())
new_row.set(d["dr_or_cr"], d["allocated_amount"])
- new_row.set('debit' if d['dr_or_cr'] == 'debit_in_account_currency' else 'credit',
- d["allocated_amount"] * flt(jv_detail.exchange_rate))
+ new_row.set(
+ "debit" if d["dr_or_cr"] == "debit_in_account_currency" else "credit",
+ d["allocated_amount"] * flt(jv_detail.exchange_rate),
+ )
- new_row.set('credit_in_account_currency' if d['dr_or_cr'] == 'debit_in_account_currency'
- else 'debit_in_account_currency', 0)
- new_row.set('credit' if d['dr_or_cr'] == 'debit_in_account_currency' else 'debit', 0)
+ new_row.set(
+ "credit_in_account_currency"
+ if d["dr_or_cr"] == "debit_in_account_currency"
+ else "debit_in_account_currency",
+ 0,
+ )
+ new_row.set("credit" if d["dr_or_cr"] == "debit_in_account_currency" else "debit", 0)
new_row.set("reference_type", d["against_voucher_type"])
new_row.set("reference_name", d["against_voucher"])
@@ -470,6 +566,7 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False):
if not do_not_save:
journal_entry.save(ignore_permissions=True)
+
def update_reference_in_payment_entry(d, payment_entry, do_not_save=False):
reference_details = {
"reference_doctype": d.against_voucher_type,
@@ -477,8 +574,10 @@ def update_reference_in_payment_entry(d, payment_entry, do_not_save=False):
"total_amount": d.grand_total,
"outstanding_amount": d.outstanding_amount,
"allocated_amount": d.allocated_amount,
- "exchange_rate": d.exchange_rate if not d.exchange_gain_loss else payment_entry.get_exchange_rate(),
- "exchange_gain_loss": d.exchange_gain_loss # only populated from invoice in case of advance allocation
+ "exchange_rate": d.exchange_rate
+ if not d.exchange_gain_loss
+ else payment_entry.get_exchange_rate(),
+ "exchange_gain_loss": d.exchange_gain_loss, # only populated from invoice in case of advance allocation
}
if d.voucher_detail_no:
@@ -505,57 +604,75 @@ def update_reference_in_payment_entry(d, payment_entry, do_not_save=False):
if d.difference_amount and d.difference_account:
account_details = {
- 'account': d.difference_account,
- 'cost_center': payment_entry.cost_center or frappe.get_cached_value('Company',
- payment_entry.company, "cost_center")
+ "account": d.difference_account,
+ "cost_center": payment_entry.cost_center
+ or frappe.get_cached_value("Company", payment_entry.company, "cost_center"),
}
if d.difference_amount:
- account_details['amount'] = d.difference_amount
+ account_details["amount"] = d.difference_amount
payment_entry.set_gain_or_loss(account_details=account_details)
if not do_not_save:
payment_entry.save(ignore_permissions=True)
+
def unlink_ref_doc_from_payment_entries(ref_doc):
remove_ref_doc_link_from_jv(ref_doc.doctype, ref_doc.name)
remove_ref_doc_link_from_pe(ref_doc.doctype, ref_doc.name)
- frappe.db.sql("""update `tabGL Entry`
+ frappe.db.sql(
+ """update `tabGL Entry`
set against_voucher_type=null, against_voucher=null,
modified=%s, modified_by=%s
where against_voucher_type=%s and against_voucher=%s
and voucher_no != ifnull(against_voucher, '')""",
- (now(), frappe.session.user, ref_doc.doctype, ref_doc.name))
+ (now(), frappe.session.user, ref_doc.doctype, ref_doc.name),
+ )
if ref_doc.doctype in ("Sales Invoice", "Purchase Invoice"):
ref_doc.set("advances", [])
- frappe.db.sql("""delete from `tab{0} Advance` where parent = %s"""
- .format(ref_doc.doctype), ref_doc.name)
+ frappe.db.sql(
+ """delete from `tab{0} Advance` where parent = %s""".format(ref_doc.doctype), ref_doc.name
+ )
+
def remove_ref_doc_link_from_jv(ref_type, ref_no):
- linked_jv = frappe.db.sql_list("""select parent from `tabJournal Entry Account`
- where reference_type=%s and reference_name=%s and docstatus < 2""", (ref_type, ref_no))
+ linked_jv = frappe.db.sql_list(
+ """select parent from `tabJournal Entry Account`
+ where reference_type=%s and reference_name=%s and docstatus < 2""",
+ (ref_type, ref_no),
+ )
if linked_jv:
- frappe.db.sql("""update `tabJournal Entry Account`
+ frappe.db.sql(
+ """update `tabJournal Entry Account`
set reference_type=null, reference_name = null,
modified=%s, modified_by=%s
where reference_type=%s and reference_name=%s
- and docstatus < 2""", (now(), frappe.session.user, ref_type, ref_no))
+ and docstatus < 2""",
+ (now(), frappe.session.user, ref_type, ref_no),
+ )
frappe.msgprint(_("Journal Entries {0} are un-linked").format("\n".join(linked_jv)))
+
def remove_ref_doc_link_from_pe(ref_type, ref_no):
- linked_pe = frappe.db.sql_list("""select parent from `tabPayment Entry Reference`
- where reference_doctype=%s and reference_name=%s and docstatus < 2""", (ref_type, ref_no))
+ linked_pe = frappe.db.sql_list(
+ """select parent from `tabPayment Entry Reference`
+ where reference_doctype=%s and reference_name=%s and docstatus < 2""",
+ (ref_type, ref_no),
+ )
if linked_pe:
- frappe.db.sql("""update `tabPayment Entry Reference`
+ frappe.db.sql(
+ """update `tabPayment Entry Reference`
set allocated_amount=0, modified=%s, modified_by=%s
where reference_doctype=%s and reference_name=%s
- and docstatus < 2""", (now(), frappe.session.user, ref_type, ref_no))
+ and docstatus < 2""",
+ (now(), frappe.session.user, ref_type, ref_no),
+ )
for pe in linked_pe:
try:
@@ -565,42 +682,62 @@ def remove_ref_doc_link_from_pe(ref_type, ref_no):
pe_doc.validate_payment_type_with_outstanding()
except Exception as e:
msg = _("There were issues unlinking payment entry {0}.").format(pe_doc.name)
- msg += ' '
+ 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)
+ 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 39f0f1a88be..fc9ba386a38 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 6e87426ccbe..257488dfc38 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,16 +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))
+ 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))
+ 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:
@@ -430,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:
@@ -439,24 +520,46 @@ class Asset(AccountsController):
else:
self.number_of_depreciations_booked = 0
- if cint(self.number_of_depreciations_booked) > cint(row.total_number_of_depreciations):
- frappe.throw(_("Number of Depreciations Booked cannot be greater than Total Number of Depreciations"))
+ 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"),
+ )
- 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
@@ -472,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")):
@@ -489,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))
@@ -531,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):
@@ -543,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:
@@ -570,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
@@ -589,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)
@@ -619,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(
@@ -640,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:
@@ -654,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):
@@ -689,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))
@@ -711,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)
@@ -806,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")
@@ -860,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
@@ -883,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)
@@ -940,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
@@ -975,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
@@ -988,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
@@ -1024,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()
@@ -1033,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 00d08473d66..9a45bcb0f9a 100644
--- a/erpnext/assets/doctype/asset/asset_dashboard.py
+++ b/erpnext/assets/doctype/asset/asset_dashboard.py
@@ -1,12 +1,8 @@
+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 874fb630f87..3f7e9459943 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 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 c08dc21a8fe..e759ad0719b 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)
@@ -67,7 +68,7 @@ class TestAsset(AssetSetup):
def test_item_exists(self):
asset = create_asset(item_code="MacBook", do_not_save=1)
- self.assertRaises(frappe.DoesNotExistError, asset.save)
+ self.assertRaises(frappe.ValidationError, asset.save)
def test_validate_item(self):
asset = create_asset(item_code="MacBook Pro", do_not_save=1)
@@ -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,57 +943,73 @@ 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)
def test_number_of_depreciations(self):
- """Tests if an error is raised when number_of_depreciations_booked > total_number_of_depreciations."""
+ """Tests if an error is raised when number_of_depreciations_booked >= total_number_of_depreciations."""
+ # 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,
+ )
+
+ 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)
@@ -925,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")
@@ -946,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":
@@ -979,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":
@@ -1012,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")
@@ -1030,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")
@@ -1074,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)
@@ -1107,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")
@@ -1133,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")
@@ -1174,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)
@@ -1206,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)
@@ -1214,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()
@@ -1222,49 +1342,52 @@ 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:
- asset.save()
+ asset.insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError:
pass
@@ -1273,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.insert()
+ 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"
@@ -1321,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 bd573bf479d..a4d2c82845a 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 = _("""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 3d19fa39d1e..2c926565768 100644
--- a/erpnext/assets/doctype/asset_category/test_asset_category.py
+++ b/erpnext/assets/doctype/asset_category/test_asset_category.py
@@ -15,20 +15,25 @@ 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()
+ asset_category.insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError:
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.js b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.js
index 52996e93475..5c03b98873b 100644
--- a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.js
+++ b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.js
@@ -48,7 +48,7 @@ frappe.ui.form.on('Asset Maintenance', {
`).appendTo(rows);
diff --git a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py
index 4fc4c4cb993..e603d346266 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 8acb61b9671..e40a5519eb2 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 7d3453fc982..ff791b27549 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 07bea616da6..143f215db2e 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,73 @@ 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 +113,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 +132,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 025facc4fda..a5fe52cefa2 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.js b/erpnext/assets/doctype/asset_repair/asset_repair.js
index d554d52a718..f5e4e723b44 100644
--- a/erpnext/assets/doctype/asset_repair/asset_repair.js
+++ b/erpnext/assets/doctype/asset_repair/asset_repair.js
@@ -32,7 +32,7 @@ frappe.ui.form.on('Asset Repair', {
refresh: function(frm) {
if (frm.doc.docstatus) {
- frm.add_custom_button("View General Ledger", function() {
+ frm.add_custom_button(__("View General Ledger"), function() {
frappe.route_options = {
"voucher_no": frm.doc.name
};
@@ -68,6 +68,28 @@ frappe.ui.form.on('Asset Repair', {
});
frappe.ui.form.on('Asset Repair Consumed Item', {
+ item_code: function(frm, cdt, cdn) {
+ var item = locals[cdt][cdn];
+
+ let item_args = {
+ 'item_code': item.item_code,
+ 'warehouse': frm.doc.warehouse,
+ 'qty': item.consumed_quantity,
+ 'serial_no': item.serial_no,
+ 'company': frm.doc.company
+ };
+
+ frappe.call({
+ method: 'erpnext.stock.utils.get_incoming_rate',
+ args: {
+ args: item_args
+ },
+ callback: function(r) {
+ frappe.model.set_value(cdt, cdn, 'valuation_rate', r.message);
+ }
+ });
+ },
+
consumed_quantity: function(frm, cdt, cdn) {
var row = locals[cdt][cdn];
frappe.model.set_value(cdt, cdn, 'total_value', row.consumed_quantity * row.valuation_rate);
diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py
index 36848e9f15c..5bf6011cf80 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 7c0d05748e1..4e7cf78090b 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_repair_consumed_item/asset_repair_consumed_item.json b/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json
index f63add12356..4685a09db63 100644
--- a/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json
+++ b/erpnext/assets/doctype/asset_repair_consumed_item/asset_repair_consumed_item.json
@@ -13,12 +13,10 @@
],
"fields": [
{
- "fetch_from": "item.valuation_rate",
"fieldname": "valuation_rate",
"fieldtype": "Currency",
"in_list_view": 1,
- "label": "Valuation Rate",
- "read_only": 1
+ "label": "Valuation Rate"
},
{
"fieldname": "consumed_quantity",
@@ -49,7 +47,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2021-11-11 18:23:00.492483",
+ "modified": "2022-02-08 17:37:20.028290",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Repair Consumed Item",
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 0b646ed4ede..20865e8ddc4 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 ef13c5617f5..ebeb174d135 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 abc7325cf6c..0d87bb2bf4d 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 36e1dd4ce4b..b8563cb0a29 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 db513364f49..6b14dce084e 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.js b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js
index a739cc37306..0073170a855 100644
--- a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js
+++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js
@@ -3,15 +3,11 @@
frappe.ui.form.on('Bulk Transaction Log', {
- before_load: function(frm) {
- query(frm);
- },
-
refresh: function(frm) {
frm.disable_save();
frm.add_custom_button(__('Retry Failed Transactions'), ()=>{
frappe.confirm(__("Retry Failing Transactions ?"), ()=>{
- query(frm);
+ query(frm, 1);
}
);
});
@@ -25,8 +21,8 @@ function query(frm) {
log_date: frm.doc.log_date
}
}).then((r) => {
- if (r.message) {
- frm.remove_custom_button("Retry Failed Transactions");
+ if (r.message === "No Failed Records") {
+ frappe.show_alert(__(r.message), 5);
} else {
frappe.show_alert(__("Retrying Failed Transactions"), 5);
}
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 de7cde5a6d3..0596be4462a 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
@@ -15,6 +15,8 @@ class BulkTransactionLog(Document):
@frappe.whitelist()
def retry_failing_transaction(log_date=None):
+ if not log_date:
+ log_date = str(date.today())
btp = frappe.qb.DocType("Bulk Transaction Log Detail")
data = (
frappe.qb.from_(btp)
@@ -26,8 +28,6 @@ def retry_failing_transaction(log_date=None):
).run(as_dict=True)
if data:
- if not log_date:
- log_date = str(date.today())
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 a78e697b6f9..646dba51ce9 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 2b6ff43530f..5507254bbc2 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.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index 2005dac37d7..c9e67987c6b 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -179,7 +179,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
if (doc.status != "On Hold") {
if(flt(doc.per_received) < 100 && allow_receipt) {
cur_frm.add_custom_button(__('Purchase Receipt'), this.make_purchase_receipt, __('Create'));
- if(doc.is_subcontracted==="Yes" && me.has_unsupplied_items()) {
+ if(doc.is_subcontracted && me.has_unsupplied_items()) {
cur_frm.add_custom_button(__('Material to Supplier'),
function() { me.make_stock_entry(); }, __("Transfer"));
}
@@ -636,7 +636,7 @@ function set_schedule_date(frm) {
frappe.provide("erpnext.buying");
frappe.ui.form.on("Purchase Order", "is_subcontracted", function(frm) {
- if (frm.doc.is_subcontracted === "Yes") {
+ if (frm.doc.is_subcontracted) {
erpnext.buying.get_default_bom(frm);
}
});
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json
index 896208f25e1..9a1f9d19958 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.json
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.json
@@ -457,16 +457,15 @@
"fieldtype": "Column Break"
},
{
- "default": "No",
+ "default": "0",
"fieldname": "is_subcontracted",
- "fieldtype": "Select",
+ "fieldtype": "Check",
"in_standard_filter": 1,
- "label": "Supply Raw Materials",
- "options": "No\nYes",
+ "label": "Is Subcontracted",
"print_hide": 1
},
{
- "depends_on": "eval:doc.is_subcontracted==\"Yes\"",
+ "depends_on": "eval:doc.is_subcontracted",
"fieldname": "supplier_warehouse",
"fieldtype": "Link",
"label": "Supplier Warehouse",
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index 1b5f35efbb4..5860c4c8ae2 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":
+ if self.is_subcontracted:
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,31 +268,33 @@ 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()
self.set_status(update=True, status=status)
self.update_requested_qty()
self.update_ordered_qty()
- if self.is_subcontracted == "Yes":
+ if self.is_subcontracted:
self.update_reserved_qty_for_subcontract()
self.notify_update()
@@ -265,11 +311,12 @@ class PurchaseOrder(BuyingController):
self.update_ordered_qty()
self.validate_budget()
- if self.is_subcontracted == "Yes":
+ if self.is_subcontracted:
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()
@@ -284,12 +331,12 @@ class PurchaseOrder(BuyingController):
if self.has_drop_ship_item():
self.update_delivered_qty_in_sales_order()
- if self.is_subcontracted == "Yes":
+ if self.is_subcontracted:
self.update_reserved_qty_for_subcontract()
self.check_on_hold_or_closed_status()
- frappe.db.set(self,'status','Cancelled')
+ frappe.db.set(self, "status", "Cancelled")
self.update_prevdoc_status()
@@ -306,16 +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": "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"""
@@ -354,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"):
@@ -382,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":
@@ -391,69 +456,78 @@ def close_or_unclose_purchase_orders(names, status):
frappe.local.message_log = []
+
def set_missing_values(source, target):
- target.ignore_pricing_rule = 1
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)
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()
@@ -462,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",
@@ -490,19 +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 = 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
@@ -543,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)
@@ -559,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
@@ -628,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 d288f881deb..81f20100c39 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 645e97ee7c8..1a7f2dd5d97 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -3,9 +3,9 @@
import json
-import unittest
import frappe
+from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_days, flt, getdate, nowdate
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
@@ -27,7 +27,7 @@ from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
-class TestPurchaseOrder(unittest.TestCase):
+class TestPurchaseOrder(FrappeTestCase):
def test_make_purchase_receipt(self):
po = create_purchase_order(do_not_submit=True)
self.assertRaises(frappe.ValidationError, make_purchase_receipt, po.name)
@@ -51,7 +51,7 @@ class TestPurchaseOrder(unittest.TestCase):
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(unittest.TestCase):
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(unittest.TestCase):
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(unittest.TestCase):
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(unittest.TestCase):
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(unittest.TestCase):
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(unittest.TestCase):
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(unittest.TestCase):
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,29 +379,38 @@ class TestPurchaseOrder(unittest.TestCase):
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()
frappe.get_doc("Item Tax Template", "Test Update Items Template - _TC").delete()
def test_update_child_uom_conv_factor_change(self):
- po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes")
+ po = create_purchase_order(item_code="_Test FG Item", is_subcontracted=1)
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(unittest.TestCase):
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(unittest.TestCase):
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(unittest.TestCase):
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(unittest.TestCase):
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(unittest.TestCase):
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,75 +567,89 @@ class TestPurchaseOrder(unittest.TestCase):
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):
- po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes")
+ po = create_purchase_order(item_code="_Test FG Item", is_subcontracted=1)
self.assertEqual(len(po.get("supplied_items")), 2)
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": 0,
+ "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(unittest.TestCase):
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(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()
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(unittest.TestCase):
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")
+ po = create_purchase_order(item_code="_Test FG Item", is_subcontracted=1)
- 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(unittest.TestCase):
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(unittest.TestCase):
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(unittest.TestCase):
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=1,
+ 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=1,
+ 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=1,
+ 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(unittest.TestCase):
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=1,
+ 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(unittest.TestCase):
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(unittest.TestCase):
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(unittest.TestCase):
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(unittest.TestCase):
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(unittest.TestCase):
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)
@@ -1081,8 +1283,8 @@ def create_purchase_order(**args):
po.schedule_date = add_days(nowdate(), 1)
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.is_subcontracted = args.is_subcontracted or 0
+ 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,21 +1292,24 @@ 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:
po.insert()
if not args.do_not_submit:
- if po.is_subcontracted == "Yes":
+ if po.is_subcontracted:
supp_items = po.get("supplied_items")
for d in supp_items:
if not d.reserve_warehouse:
@@ -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/test_records.json b/erpnext/buying/doctype/purchase_order/test_records.json
index 74b8f1b429b..896050ce43a 100644
--- a/erpnext/buying/doctype/purchase_order/test_records.json
+++ b/erpnext/buying/doctype/purchase_order/test_records.json
@@ -8,7 +8,7 @@
"doctype": "Purchase Order",
"base_grand_total": 5000.0,
"grand_total": 5000.0,
- "is_subcontracted": "Yes",
+ "is_subcontracted": 1,
"naming_series": "_T-Purchase Order-",
"base_net_total": 5000.0,
"items": [
@@ -42,7 +42,7 @@
"doctype": "Purchase Order",
"base_grand_total": 5000.0,
"grand_total": 5000.0,
- "is_subcontracted": "No",
+ "is_subcontracted": 0,
"naming_series": "_T-Purchase Order-",
"base_net_total": 5000.0,
"items": [
diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
index 87cd57517e2..f72c5988404 100644
--- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
+++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
@@ -63,6 +63,7 @@
"material_request_item",
"sales_order",
"sales_order_item",
+ "sales_order_packed_item",
"supplier_quotation",
"supplier_quotation_item",
"col_break5",
@@ -571,7 +572,7 @@
"read_only": 1
},
{
- "depends_on": "eval:parent.is_subcontracted == 'Yes'",
+ "depends_on": "eval:parent.is_subcontracted",
"fieldname": "bom",
"fieldtype": "Link",
"label": "BOM",
@@ -580,7 +581,7 @@
},
{
"default": "0",
- "depends_on": "eval:parent.is_subcontracted == 'Yes'",
+ "depends_on": "eval:parent.is_subcontracted",
"fieldname": "include_exploded_items",
"fieldtype": "Check",
"label": "Include Exploded Items",
@@ -837,21 +838,30 @@
"label": "Product Bundle",
"options": "Product Bundle",
"read_only": 1
+ },
+ {
+ "fieldname": "sales_order_packed_item",
+ "fieldtype": "Data",
+ "label": "Sales Order Packed Item",
+ "no_copy": 1,
+ "print_hide": 1
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2021-08-30 20:06:26.712097",
+ "modified": "2022-02-02 13:10:18.398976",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item",
+ "naming_rule": "Random",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"search_fields": "item_name",
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"track_changes": 1
}
\ No newline at end of file
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 0cef0deee55..a8bafda0029 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 2db750e75c4..d39aec19bab 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 dc1cda126aa..505e3e0f674 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 51901991b5a..dcdba095fbf 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
@@ -1,9 +1,9 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
-import unittest
import frappe
+from frappe.tests.utils import FrappeTestCase
from frappe.utils import nowdate
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import (
@@ -16,40 +16,40 @@ from erpnext.stock.doctype.item.test_item import make_item
from erpnext.templates.pages.rfq import check_supplier_has_docname_access
-class TestRequestforQuotation(unittest.TestCase):
+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(unittest.TestCase):
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(unittest.TestCase):
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 4f9ff43cd43..97d0ba0b9c1 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 78efd8eea09..11bb06e0caa 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 13fe9df13ee..55722686fe4 100644
--- a/erpnext/buying/doctype/supplier/test_supplier.py
+++ b/erpnext/buying/doctype/supplier/test_supplier.py
@@ -1,7 +1,6 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
-import unittest
import frappe
from frappe.test_runner import make_test_records
@@ -9,156 +8,165 @@ 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
-class TestSupplier(unittest.TestCase):
- def test_get_supplier_group_details(self):
- doc = frappe.new_doc("Supplier Group")
- doc.supplier_group_name = "_Testing Supplier Group"
- doc.payment_terms = "_Test Payment Term Template 3"
- doc.accounts = []
- test_account_details = {
- "company": "_Test Company",
- "account": "Creditors - _TC",
- }
- doc.append("accounts", test_account_details)
- doc.save()
- s_doc = frappe.new_doc("Supplier")
- s_doc.supplier_name = "Testing Supplier"
- s_doc.supplier_group = "_Testing Supplier Group"
- s_doc.payment_terms = ""
- s_doc.accounts = []
- s_doc.insert()
- s_doc.get_supplier_group_details()
- self.assertEqual(s_doc.payment_terms, "_Test Payment Term Template 3")
- self.assertEqual(s_doc.accounts[0].company, "_Test Company")
- self.assertEqual(s_doc.accounts[0].account, "Creditors - _TC")
- s_doc.delete()
- doc.delete()
+class TestSupplier(FrappeTestCase):
+ def test_get_supplier_group_details(self):
+ doc = frappe.new_doc("Supplier Group")
+ doc.supplier_group_name = "_Testing Supplier Group"
+ doc.payment_terms = "_Test Payment Term Template 3"
+ doc.accounts = []
+ test_account_details = {
+ "company": "_Test Company",
+ "account": "Creditors - _TC",
+ }
+ doc.append("accounts", test_account_details)
+ doc.save()
+ s_doc = frappe.new_doc("Supplier")
+ s_doc.supplier_name = "Testing Supplier"
+ s_doc.supplier_group = "_Testing Supplier Group"
+ s_doc.payment_terms = ""
+ s_doc.accounts = []
+ s_doc.insert()
+ s_doc.get_supplier_group_details()
+ self.assertEqual(s_doc.payment_terms, "_Test Payment Term Template 3")
+ self.assertEqual(s_doc.accounts[0].company, "_Test Company")
+ self.assertEqual(s_doc.accounts[0].account, "Creditors - _TC")
+ s_doc.delete()
+ doc.delete()
- 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")
+ 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"
+ )
- due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier With Template 1")
- self.assertEqual(due_date, "2016-02-21")
+ due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier With Template 1")
+ self.assertEqual(due_date, "2016-02-21")
- due_date = get_due_date("2017-01-22", "Supplier", "_Test Supplier With Template 1")
- self.assertEqual(due_date, "2017-02-21")
+ due_date = get_due_date("2017-01-22", "Supplier", "_Test Supplier With Template 1")
+ self.assertEqual(due_date, "2017-02-21")
- # 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")
+ # 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"
+ )
- due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier With Template 1")
- self.assertEqual(due_date, "2016-02-29")
+ due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier With Template 1")
+ self.assertEqual(due_date, "2016-02-29")
- due_date = get_due_date("2017-01-22", "Supplier", "_Test Supplier With Template 1")
- self.assertEqual(due_date, "2017-02-28")
+ due_date = get_due_date("2017-01-22", "Supplier", "_Test Supplier With Template 1")
+ self.assertEqual(due_date, "2017-02-28")
- frappe.db.set_value("Supplier", "_Test Supplier With Template 1", "payment_terms", "")
+ 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")
+ # 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"
+ )
- due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier With Template 1")
- self.assertEqual(due_date, "2016-02-21")
+ 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")
+ # 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"
+ )
- # Leap year
- due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier With Template 1")
- self.assertEqual(due_date, "2016-02-29")
- # # Non Leap year
- due_date = get_due_date("2017-01-22", "Supplier", "_Test Supplier With Template 1")
- self.assertEqual(due_date, "2017-02-28")
+ # Leap year
+ due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier With Template 1")
+ self.assertEqual(due_date, "2016-02-29")
+ # # Non Leap year
+ due_date = get_due_date("2017-01-22", "Supplier", "_Test Supplier With Template 1")
+ self.assertEqual(due_date, "2017-02-28")
- # Supplier with no default Payment Terms Template
- frappe.db.set_value("Supplier Group", "_Test Supplier Group", "payment_terms", "")
- frappe.db.set_value("Supplier", "_Test Supplier", "payment_terms", "")
+ # Supplier with no default Payment Terms Template
+ frappe.db.set_value("Supplier Group", "_Test Supplier Group", "payment_terms", "")
+ frappe.db.set_value("Supplier", "_Test Supplier", "payment_terms", "")
- due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier")
- self.assertEqual(due_date, "2016-01-22")
- # # Non Leap year
- due_date = get_due_date("2017-01-22", "Supplier", "_Test Supplier")
- self.assertEqual(due_date, "2017-01-22")
+ due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier")
+ self.assertEqual(due_date, "2016-01-22")
+ # # Non Leap year
+ due_date = get_due_date("2017-01-22", "Supplier", "_Test Supplier")
+ self.assertEqual(due_date, "2017-01-22")
- def test_supplier_disabled(self):
- make_test_records("Item")
+ def test_supplier_disabled(self):
+ make_test_records("Item")
- frappe.db.set_value("Supplier", "_Test Supplier", "disabled", 1)
+ frappe.db.set_value("Supplier", "_Test Supplier", "disabled", 1)
- from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
+ from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
- po = create_purchase_order(do_not_save=True)
+ po = create_purchase_order(do_not_save=True)
- self.assertRaises(PartyDisabled, po.save)
+ self.assertRaises(PartyDisabled, po.save)
- frappe.db.set_value("Supplier", "_Test Supplier", "disabled", 0)
+ frappe.db.set_value("Supplier", "_Test Supplier", "disabled", 0)
- po.save()
+ po.save()
- 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())
+ 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())
- # Test if test supplier field record is 'Greece'
- self.assertEqual(supplier.country, "Greece")
+ # 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.save()
- self.assertEqual(supplier.country, "Greece")
+ # Test update Supplier instance country value
+ supplier = frappe.get_doc("Supplier", "_Test Supplier")
+ supplier.country = "Greece"
+ supplier.save()
+ self.assertEqual(supplier.country, "Greece")
- def test_party_details_tax_category(self):
- from erpnext.accounts.party import get_party_details
+ def test_party_details_tax_category(self):
+ from erpnext.accounts.party import get_party_details
- frappe.delete_doc_if_exists("Address", "_Test Address With Tax Category-Billing")
+ frappe.delete_doc_if_exists("Address", "_Test Address With Tax Category-Billing")
- # Tax Category without Address
- details = get_party_details("_Test Supplier With Tax Category", party_type="Supplier")
- self.assertEqual(details.tax_category, "_Test Tax Category 1")
+ # Tax Category without Address
+ 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")
- self.assertEqual(details.tax_category, "_Test Tax Category 2")
+ # Tax Category with Address
+ details = get_party_details("_Test Supplier With Tax Category", party_type="Supplier")
+ self.assertEqual(details.tax_category, "_Test Tax Category 2")
+
+ # Rollback
+ address.delete()
- # Rollback
- address.delete()
def create_supplier(**args):
- args = frappe._dict(args)
+ args = frappe._dict(args)
- try:
- 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()
+ if frappe.db.exists("Supplier", args.supplier_name):
+ return frappe.get_doc("Supplier", args.supplier_name)
- return doc
+ 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()
- except frappe.DuplicateEntryError:
- return frappe.get_doc("Supplier", args.supplier_name)
+ return doc
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
index 023c95d697d..8d1939a101b 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
@@ -72,8 +72,8 @@
"section_break_46",
"base_grand_total",
"base_rounding_adjustment",
- "base_in_words",
"base_rounded_total",
+ "base_in_words",
"column_break4",
"grand_total",
"rounding_adjustment",
@@ -635,6 +635,7 @@
"fieldname": "rounded_total",
"fieldtype": "Currency",
"label": "Rounded Total",
+ "options": "currency",
"read_only": 1
},
{
@@ -772,11 +773,10 @@
"fieldtype": "Column Break"
},
{
- "default": "No",
+ "default": "0",
"fieldname": "is_subcontracted",
- "fieldtype": "Select",
+ "fieldtype": "Check",
"label": "Is Subcontracted",
- "options": "\nYes\nNo",
"print_hide": 1
},
{
@@ -810,7 +810,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2021-12-11 06:43:20.924080",
+ "modified": "2022-03-14 16:13:20.284572",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier Quotation",
@@ -875,6 +875,7 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"timeline_field": "supplier",
"title_field": "title"
}
\ No newline at end of file
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
index 171de7882dc..c19c1df180e 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,30 +97,41 @@ 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):
- target.ignore_pricing_rule = 1
target.run_method("set_missing_values")
target.run_method("get_schedule_dates")
target.run_method("calculate_taxes_and_totals")
@@ -117,73 +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)
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 236b91ad58b..369fc94deee 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_records.json b/erpnext/buying/doctype/supplier_quotation/test_records.json
index 0f835d2a40a..8acac3210d5 100644
--- a/erpnext/buying/doctype/supplier_quotation/test_records.json
+++ b/erpnext/buying/doctype/supplier_quotation/test_records.json
@@ -7,7 +7,7 @@
"doctype": "Supplier Quotation",
"base_grand_total": 5000.0,
"grand_total": 5000.0,
- "is_subcontracted": "No",
+ "is_subcontracted": 0,
"naming_series": "_T-Supplier Quotation-",
"base_net_total": 5000.0,
"items": [
diff --git a/erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py
index d48ac7eb3b4..13c851c7353 100644
--- a/erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py
+++ b/erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py
@@ -2,20 +2,17 @@
# License: GNU General Public License v3. See license.txt
-
-import unittest
-
import frappe
+from frappe.tests.utils import FrappeTestCase
-class TestPurchaseOrder(unittest.TestCase):
+class TestPurchaseOrder(FrappeTestCase):
def test_make_purchase_order(self):
from erpnext.buying.doctype.supplier_quotation.supplier_quotation import make_purchase_order
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(unittest.TestCase):
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 3bcc0debae9..486bf23e909 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,179 @@ 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 5d693263f8c..e3557bd0d81 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 7908c35cbbe..2694f96fbe3 100644
--- a/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py
+++ b/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py
@@ -1,13 +1,12 @@
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
-import unittest
import frappe
+from frappe.tests.utils import FrappeTestCase
-class TestSupplierScorecard(unittest.TestCase):
-
+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(unittest.TestCase):
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 7cd18c31e8b..ab7d4879c43 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 dacc982420e..90468d6c168 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
@@ -1,30 +1,37 @@
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
-import unittest
import frappe
+from frappe.tests.utils import FrappeTestCase
-class TestSupplierScorecardCriteria(unittest.TestCase):
+class TestSupplierScorecardCriteria(FrappeTestCase):
def test_variables_exist(self):
delete_test_scorecards()
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 c247241cf35..a8b76db0931 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 11ebe6da13c..929e8a363fd 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 217aadba6bd..fb8819eaf81 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 4d75981125f..60d84644cf5 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
@@ -1,22 +1,22 @@
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
-import unittest
import frappe
+from frappe.tests.utils import FrappeTestCase
from erpnext.buying.doctype.supplier_scorecard_variable.supplier_scorecard_variable import (
VariablePathNotFound,
)
-class TestSupplierScorecardVariable(unittest.TestCase):
+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(unittest.TestCase):
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 295a19d052e..e0b02ee4e2a 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 84de8c67438..47a66ad46f2 100644
--- a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py
+++ b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py
@@ -2,10 +2,10 @@
# For license information, please see license.txt
-import unittest
from datetime import datetime
import frappe
+from frappe.tests.utils import FrappeTestCase
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
from erpnext.buying.report.procurement_tracker.procurement_tracker import execute
@@ -14,29 +14,30 @@ from erpnext.stock.doctype.material_request.test_material_request import make_ma
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
-class TestProcurementTracker(unittest.TestCase):
+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(unittest.TestCase):
"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(unittest.TestCase):
"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 9dd912118ff..a5c464910de 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 21643a896b7..dbdc62e9ec7 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": _(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 f98e5f12c2d..21241e08603 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
@@ -6,26 +6,25 @@ import copy
import frappe
from frappe import _
+from frappe.query_builder.functions import Coalesce, Sum
from frappe.utils import date_diff, flt, getdate
def execute(filters=None):
if not filters:
- return [],[]
+ return [], []
validate_filters(filters)
columns = get_columns(filters)
- conditions = get_conditions(filters)
+ data = get_data(filters)
- #get queried data
- data = get_data(filters, conditions)
-
- #prepare data for report and chart views
+ # prepare data for report and chart views
data, chart_data = prepare_data(data, filters)
return columns, data, None, chart_data
+
def validate_filters(filters):
from_date, to_date = filters.get("from_date"), filters.get("to_date")
@@ -34,59 +33,74 @@ 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 = ''
+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)
+ .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"),
+ 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.received_qty, 0)).as_("received_qty"),
+ (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.material_request_type == "Purchase")
+ & (mr.docstatus == 1)
+ & (mr.status != "Stopped")
+ & (mr.per_received < 100)
+ )
+ )
+
+ 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)
+ 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"):
- conditions += " and mr.transaction_date between '{0}' and '{1}'".format(filters.get("from_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"):
- conditions += " and mr.company = '{0}'".format(filters.get("company"))
+ query = query.where(mr.company == filters.get("company"))
if filters.get("material_request"):
- conditions += " and mr.name = '{0}'".format(filters.get("material_request"))
+ query = query.where(mr.name == filters.get("material_request"))
if filters.get("item_code"):
- conditions += " and mr_item.item_code = '{0}'".format(filters.get("item_code"))
+ query = query.where(mr_item.item_code == filters.get("item_code"))
- return conditions
+ return query
-def get_data(filters, conditions):
- data = frappe.db.sql("""
- 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(ifnull(mr_item.stock_qty, 0)) as qty,
- ifnull(mr_item.stock_uom, '') as uom,
- sum(ifnull(mr_item.ordered_qty, 0)) as ordered_qty,
- sum(ifnull(mr_item.received_qty, 0)) as received_qty,
- (sum(ifnull(mr_item.stock_qty, 0)) - sum(ifnull(mr_item.received_qty, 0))) as qty_to_receive,
- (sum(ifnull(mr_item.stock_qty, 0)) - sum(ifnull(mr_item.ordered_qty, 0))) as qty_to_order,
- mr_item.item_name as item_name,
- mr_item.description as "description",
- mr.company as company
- from
- `tabMaterial Request` mr, `tabMaterial Request Item` mr_item
- where
- mr_item.parent = mr.name
- and mr.material_request_type = "Purchase"
- and mr.docstatus = 1
- and mr.status != "Stopped"
- {conditions}
- group by mr.name, mr_item.item_code
- having
- sum(ifnull(mr_item.ordered_qty, 0)) < sum(ifnull(mr_item.stock_qty, 0))
- order by mr.transaction_date, mr.schedule_date""".format(conditions=conditions), as_dict=1)
-
- return data
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 = {}, {}
@@ -95,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"]]
@@ -115,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 = [], [], [], [], []
@@ -143,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 = [
{
@@ -179,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
new file mode 100644
index 00000000000..5b84113a9cf
--- /dev/null
+++ b/erpnext/buying/report/requested_items_to_order_and_receive/test_requested_items_to_order_and_receive.py
@@ -0,0 +1,69 @@
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+import frappe
+from frappe.tests.utils import FrappeTestCase
+from frappe.utils import add_days, today
+
+from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
+from erpnext.buying.report.requested_items_to_order_and_receive.requested_items_to_order_and_receive import (
+ get_data,
+)
+from erpnext.stock.doctype.item.test_item import create_item
+from erpnext.stock.doctype.material_request.material_request import make_purchase_order
+
+
+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.filters = frappe._dict(
+ company="_Test Company",
+ from_date=today(),
+ to_date=add_days(today(), 30),
+ item_code="Test MR Report Item",
+ )
+
+ def tearDown(self) -> None:
+ frappe.db.rollback()
+
+ def test_date_range(self):
+ data = get_data(self.filters)
+ 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
+
+ def test_ordered_received_material_requests(self):
+ data = get_data(self.filters)
+
+ # from the 3 MRs made, only 2 (to receive) should be fetched
+ self.assertEqual(len(data), 2)
+ self.assertEqual(data[0].ordered_qty, 0.0)
+ self.assertEqual(data[1].ordered_qty, 57.0)
+
+ def setup_material_request(self, order=False, receive=False, days=0):
+ po = None
+ test_records = frappe.get_test_records("Material Request")
+
+ mr = frappe.copy_doc(test_records[0])
+ mr.transaction_date = add_days(today(), days)
+ mr.schedule_date = add_days(mr.transaction_date, 1)
+ for row in mr.items:
+ row.item_code = "Test MR Report Item"
+ row.item_name = "Test MR Report Item"
+ row.description = "Test MR Report Item"
+ row.uom = "Nos"
+ row.schedule_date = mr.schedule_date
+ mr.submit()
+
+ if order or receive:
+ po = make_purchase_order(mr.name)
+ po.supplier = "_Test Supplier"
+ po.submit()
+ if receive:
+ pr = make_purchase_receipt(po.name)
+ pr.submit()
diff --git a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js
index 5ba52f1b21e..6889322fb93 100644
--- a/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js
+++ b/erpnext/buying/report/subcontract_order_summary/subcontract_order_summary.js
@@ -35,7 +35,7 @@ frappe.query_reports["Subcontract Order Summary"] = {
return {
filters: {
docstatus: 1,
- is_subcontracted: 'Yes',
+ is_subcontracted: 1,
company: frappe.query_report.get_filter_value('company')
}
}
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 8e5c2f9a30d..3d666375764 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", "=", 1],
+ [
+ "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 67e275f9851..2e90de66efe 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", "=", 1],
+ ["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 144523ad522..57f8741b5bf 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
@@ -3,9 +3,9 @@
# Compiled at: 2019-05-06 09:51:46
# Decompiled by https://python-decompiler.com
-import unittest
import frappe
+from frappe.tests.utils import FrappeTestCase
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
@@ -15,27 +15,41 @@ from erpnext.buying.report.subcontracted_item_to_be_received.subcontracted_item_
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
-class TestSubcontractedItemToBeReceived(unittest.TestCase):
-
+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=1)
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 6b605add4c7..6b8a3b140a7 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", "is_subcontracted", "=", 1],
["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 3c203ac23fa..2791a26db78 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
@@ -4,9 +4,9 @@
# Decompiled by https://python-decompiler.com
import json
-import unittest
import frappe
+from frappe.tests.utils import FrappeTestCase
from erpnext.buying.doctype.purchase_order.purchase_order import make_rm_stock_entry
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
@@ -16,83 +16,88 @@ from erpnext.buying.report.subcontracted_raw_materials_to_be_transferred.subcont
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
-class TestSubcontractedItemToBeTransferred(unittest.TestCase):
-
+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=1, 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 65f9ce3c57e..3013b6d1607 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 66c60d56379..e904af0dce3 100644
--- a/erpnext/buying/utils.py
+++ b/erpnext/buying/utils.py
@@ -3,18 +3,19 @@
import json
+from typing import Dict
import frappe
from frappe import _
-from frappe.utils import cint, cstr, flt
+from frappe.utils import cint, cstr, flt, getdate
from erpnext.stock.doctype.item.item import get_last_purchase_details, validate_end_of_life
-def update_last_purchase_rate(doc, is_submit):
+def update_last_purchase_rate(doc, is_submit) -> None:
"""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 = 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,12 +38,10 @@ 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):
+def validate_for_items(doc) -> None:
items = []
for d in doc.get("items"):
if not d.qty:
@@ -49,45 +49,87 @@ def validate_for_items(doc):
continue
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)
-
- 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,
- is_sub_contracted_item, end_of_life, disabled from `tabItem` where name=%s""",
- d.item_code, as_dict=1)[0]
-
+ set_stock_levels(row=d) # update with latest quantities
+ item = validate_item_and_get_basic_data(row=d)
+ validate_stock_item_warehouse(row=d, item=item)
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))
-
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):
+
+def set_stock_levels(row) -> None:
+ projected_qty = frappe.db.get_value(
+ "Bin",
+ {
+ "item_code": row.item_code,
+ "warehouse": row.warehouse,
+ },
+ "projected_qty",
+ )
+
+ qty_data = {
+ "projected_qty": flt(projected_qty),
+ "ordered_qty": 0,
+ "received_qty": 0,
+ }
+ if row.doctype in ("Purchase Receipt Item", "Purchase Invoice Item"):
+ qty_data.pop("received_qty")
+
+ for field in qty_data:
+ if row.meta.get_field(field):
+ row.set(field, qty_data[field])
+
+
+def validate_item_and_get_basic_data(row) -> Dict:
+ item = frappe.db.get_values(
+ "Item",
+ filters={"name": row.item_code},
+ fieldname=["is_stock_item", "is_sub_contracted_item", "end_of_life", "disabled"],
+ as_dict=1,
+ )
+ if not item:
+ frappe.throw(_("Row #{0}: Item {1} does not exist").format(row.idx, frappe.bold(row.item_code)))
+
+ return item[0]
+
+
+def validate_stock_item_warehouse(row, item) -> None:
+ if (
+ item.is_stock_item == 1
+ and row.qty
+ and not row.warehouse
+ and not row.get("delivered_by_supplier")
+ ):
+ frappe.throw(
+ _("Row #{1}: Warehouse is mandatory for stock Item {0}").format(
+ frappe.bold(row.item_code), row.idx
+ )
+ )
+
+
+def check_on_hold_or_closed_status(doctype, docname) -> None:
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 +140,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 8e12fad3d75..ddf0acc96ae 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 d718a942527..c37cf2b196e 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 f4675e749a8..6186a4e8204 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 d05787fdfb4..8a9318e184e 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,49 @@ 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("""
- select name from `tabItem`
- where name in (%s) and is_stock_item=1
- """ % (", ".join(["%s"] * len(item_codes)),), item_codes)]
+ stock_items = frappe.db.get_values(
+ "Item", {"name": ["in", item_codes], "is_stock_item": 1}, pluck="name", cache=True
+ )
return stock_items
@@ -1081,7 +1283,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 +1292,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 +1316,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 += "
- " + _("'Account' in the Accounting section of Customer {0}").format(link_to_party) + "
"
- message += "- " + _("'Default {0} Account' in Company {1}").format(rec_or_pay, link_to_company) + "
"
+ message += (
+ "
- "
+ + _("'Account' in the Accounting section of Customer {0}").format(link_to_party)
+ + "
"
+ )
+ message += (
+ "- "
+ + _("'Default {0} Account' in Company {1}").format(rec_or_pay, link_to_company)
+ + "
"
+ )
frappe.throw(message, title=_("Account Missing"), exc=AccountMissingError)
@@ -1146,10 +1370,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 +1396,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 +1417,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 +1463,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 +1485,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 +1562,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 +1585,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 +1610,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 +1656,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 +1684,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 +1718,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 +1730,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 +1743,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 +1763,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 +1786,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 +1797,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 +1807,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,106 +1826,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(tax, doc):
- company = frappe.get_cached_value('Account',
- tax.account_head, 'company')
+def validate_account_head(idx, account, company, context=""):
+ account_company = frappe.get_cached_value("Account", account, "company")
- if company != doc.company:
- frappe.throw(_('Row {0}: Account {1} does not belong to Company {2}')
- .format(tax.idx, frappe.bold(tax.account_head), frappe.bold(doc.company)), title=_('Invalid Account'))
+ 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"),
+ )
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,
@@ -1652,31 +1988,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,
@@ -1688,12 +2043,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`
@@ -1701,11 +2060,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()
@@ -1723,10 +2087,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 = (
@@ -1741,21 +2102,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"
@@ -1773,7 +2127,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
@@ -1781,14 +2137,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)
@@ -1815,6 +2175,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
@@ -1826,6 +2187,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
@@ -1837,64 +2199,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)})
@@ -1904,8 +2275,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
@@ -1914,28 +2287,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)."""
@@ -1945,6 +2343,7 @@ def update_bin_on_delete(row, doctype):
get_reserved_qty,
update_bin_qty,
)
+
qty_dict = {}
if doctype == "Sales Order":
@@ -1958,6 +2357,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]
@@ -1980,14 +2380,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)
@@ -2007,13 +2411,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):
@@ -2024,10 +2432,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:
@@ -2039,28 +2447,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)
@@ -2070,9 +2488,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)
@@ -2080,18 +2503,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):
@@ -2101,14 +2528,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 = ""
@@ -2131,11 +2560,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():
@@ -2149,21 +2579,21 @@ 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()
parent.update_ordered_and_reserved_qty()
parent.update_receiving_percentage()
- if parent.is_subcontracted == "Yes":
+ if parent.is_subcontracted:
parent.update_reserved_qty_for_subcontract()
parent.create_raw_materials_supplied("supplied_items")
parent.save()
else:
parent.update_reserved_qty()
parent.update_project()
- parent.update_prevdoc_status('submit')
+ parent.update_prevdoc_status("submit")
parent.update_delivery_status()
parent.reload()
@@ -2173,10 +2603,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 a181af73133..eda36868b9f 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"):
+ 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,44 +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'),
- "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
- })
+ 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
@@ -290,10 +339,7 @@ class BuyingController(StockController, Subcontracting):
return supplied_items_cost
def validate_for_subcontracting(self):
- if not self.is_subcontracted and self.sub_contracted_items:
- frappe.throw(_("Please enter 'Is Subcontracted' as Yes or No"))
-
- if self.is_subcontracted == "Yes":
+ if self.is_subcontracted:
if self.doctype in ["Purchase Receipt", "Purchase Invoice"] and not self.supplier_warehouse:
frappe.throw(_("Supplier Warehouse mandatory for sub-contracted {0}").format(self.doctype))
@@ -314,27 +360,25 @@ 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:
self.set_materials_for_subcontracted_items(raw_material_table)
elif self.doctype in ["Purchase Receipt", "Purchase Invoice"]:
for item in self.get("items"):
item.rm_supp_cost = 0.0
- if self.is_subcontracted == "No" and self.get("supplied_items"):
- self.set('supplied_items', [])
+ if not self.is_subcontracted and self.get("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
@@ -348,9 +392,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"):
@@ -366,20 +412,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):
@@ -389,15 +441,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()
@@ -405,76 +462,92 @@ class BuyingController(StockController, Subcontracting):
sl_entries = []
stock_items = self.get_stock_items()
- for d in self.get('items'):
- if d.item_code in stock_items and d.warehouse:
+ for d in self.get("items"):
+ if d.item_code not in stock_items:
+ continue
+
+ if 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()
- })
- if self.is_return:
- outgoing_rate = get_rate_for_return(self.doctype, self.name, d.item_code, self.return_against, item_row=d)
+ sle = self.get_sl_entries(
+ d, {"actual_qty": flt(pr_qty), "serial_no": cstr(d.serial_no).strip()}
+ )
- sle.update({
- "outgoing_rate": outgoing_rate,
- "recalculate_rate": 1
- })
+ if self.is_return:
+ 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})
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
- }))
+ 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,
+ },
+ )
+ )
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():
@@ -482,68 +555,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()
@@ -558,12 +641,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:
@@ -573,21 +656,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")
@@ -596,31 +689,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
@@ -629,22 +725,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
@@ -657,8 +756,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
@@ -668,7 +770,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)
@@ -679,37 +781,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:
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]
@@ -720,16 +832,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 ae2c73758cb..8ff397dbfc0 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,36 +87,38 @@ 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
def get_task_dates(self, activity, holiday_list):
start_date = end_date = None
- if activity.begin_on:
+ if activity.begin_on is not None:
start_date = add_days(self.boarding_begins_on, activity.begin_on)
start_date = self.update_if_holiday(start_date, holiday_list)
- if activity.duration:
+ if activity.duration is not None:
end_date = add_days(self.boarding_begins_on, activity.begin_on + activity.duration)
end_date = self.update_if_holiday(end_date, 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 68ad702b979..e68ee909d9f 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 cf9de52d4cd..d2c80961a36 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 dd9b45cc3f9..abe9977c847 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,36 +137,32 @@ 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
{account_type_condition}
AND is_group = 0
AND company = %(company)s
- AND account_currency = %(currency)s
+ AND (account_currency = %(currency)s or ifnull(account_currency, '') = '')
AND `{searchfield}` LIKE %(txt)s
{mcond}
ORDER BY idx DESC, name
@@ -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 df3c5f10c1b..bdde3a1fd8c 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,54 +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(row_name, doctype):
+
+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"
+ else:
+ 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)]
- data = frappe.db.get_list(doctype,
- fields = fields,
- filters = [
+ # 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=[
+ [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)
@@ -265,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"):
@@ -304,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_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
@@ -321,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_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
@@ -335,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_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
@@ -348,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_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
@@ -369,39 +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)
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"
@@ -409,52 +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'),
- "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"
@@ -463,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 31b22093998..7877827ac79 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,42 +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'),
- "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()
@@ -428,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()
@@ -493,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():
@@ -536,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:
@@ -552,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)
@@ -570,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 affde4aa8ab..3c0a10e0860 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 c8e5eddfeac..feec42f43a3 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,32 +611,69 @@ 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
- })
- if future_sle_exists(args):
- item_based_reposting = cint(frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting"))
+ 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")
+ )
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",
+ filters={
+ "voucher_type": doc.doctype,
+ "voucher_no": doc.name,
+ "actual_qty": ("<", 0),
+ "is_cancelled": 0,
+ },
+ fields=["item_code", "warehouse", "stock_queue"],
+ )
+ item_warehouses = [(sle.item_code, sle.warehouse) for sle in consuming_sles]
+
+ unique_item_warehouses = set(item_warehouses)
+
+ if len(unique_item_warehouses) == len(item_warehouses):
+ return False
+
+ for sle in consuming_sles:
+ if sle.stock_queue != "[]": # using FIFO/LIFO valuation
+ return True
+ return False
+
+
@frappe.whitelist()
def make_quality_inspections(doctype, docname, items):
if isinstance(items, str):
@@ -523,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)
@@ -565,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
@@ -576,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 = {}
@@ -626,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 3addb91aaa0..4bce06ff9b0 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 not self.is_subcontracted:
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,15 +435,13 @@ 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):
- self.__validate_consumed_qty(row)
-
key = (row.rm_item_code, row.main_item_code, row.purchase_order)
if not self.__transferred_items or not self.__transferred_items.get(key):
return
@@ -372,25 +449,21 @@ class Subcontracting():
self.__validate_batch_no(row, key)
self.__validate_serial_no(row, key)
- def __validate_consumed_qty(self, row):
- if self.backflush_based_on != 'BOM' and flt(row.consumed_qty) == 0.0:
- msg = f'Row {row.idx}: the consumed qty cannot be zero for the item {frappe.bold(row.rm_item_code)}'
-
- frappe.throw(_(msg),title=_('Consumed Items Qty Check'))
-
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 27766282277..8183b6e2c99 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -37,6 +37,8 @@ class calculate_taxes_and_totals(object):
self.set_discount_amount()
self.apply_discount_amount()
+ self.calculate_shipping_charges()
+
if self.doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
self.calculate_total_advance()
@@ -50,7 +52,6 @@ class calculate_taxes_and_totals(object):
self.initialize_taxes()
self.determine_exclusive_rate()
self.calculate_net_total()
- self.calculate_shipping_charges()
self.calculate_taxes()
self.manipulate_grand_total_for_inclusive_tax()
self.calculate_totals()
@@ -58,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
@@ -89,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
@@ -100,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:
@@ -117,16 +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.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 = flt(
+ item.price_list_rate * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate")
+ )
- 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.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
+
+ 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
@@ -145,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):
@@ -165,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)
@@ -190,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"])
@@ -217,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
@@ -230,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)
@@ -253,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
@@ -269,13 +311,23 @@ class calculate_taxes_and_totals(object):
shipping_rule = frappe.get_doc("Shipping Rule", self.doc.shipping_rule)
shipping_rule.apply(self.doc)
+ self._calculate()
+
def calculate_taxes(self):
- if not self.doc.get('is_consolidated'):
+ 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)
@@ -290,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'
@@ -305,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)
@@ -323,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 self.doc.get('is_consolidated'):
- 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
@@ -347,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)
@@ -356,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
@@ -377,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:
@@ -389,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
@@ -402,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
@@ -415,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):
@@ -425,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"):
@@ -446,58 +528,70 @@ 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 not self.doc.get('is_consolidated'):
- if self.doc.meta.get_field("rounded_total"):
- if self.doc.is_rounded_total_disabled():
- self.doc.rounded_total = self.doc.base_rounded_total = 0
- return
+ if self.doc.get("is_consolidated") and self.doc.get("rounding_adjustment"):
+ return
- self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total,
- self.doc.currency, self.doc.precision("rounded_total"))
+ if self.doc.meta.get_field("rounded_total"):
+ if self.doc.is_rounded_total_disabled():
+ self.doc.rounded_total = self.doc.base_rounded_total = 0
+ return
- #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.doc.rounded_total = round_based_on_smallest_currency_fraction(
+ self.doc.grand_total, self.doc.currency, self.doc.precision("rounded_total")
+ )
- self._set_in_company_currency(self.doc, ["rounding_adjustment", "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")
+ )
+
+ 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")
@@ -506,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
@@ -544,42 +642,56 @@ 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:
+ if self.doc.get("write_off_outstanding_amount_automatically"):
+ self.doc.write_off_amount = 0
+
self.calculate_outstanding_amount()
+ self.calculate_write_off_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
@@ -591,39 +703,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'):
- self.calculate_write_off_amount()
+ if self.doc.doctype == "Sales Invoice" and not self.doc.get("is_return"):
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'):
+ 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.set_total_amount_to_default_mop(total_amount_to_pay)
self.calculate_paid_amount()
@@ -632,17 +767,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"))
@@ -653,22 +788,31 @@ 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.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.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"))
+ if self.doc.get("write_off_outstanding_amount_automatically"):
+ self.doc.write_off_amount = flt(
+ self.doc.outstanding_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.calculate_outstanding_amount()
def calculate_margin(self, item):
rate_with_margin = 0.0
@@ -677,10 +821,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
@@ -691,12 +840,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)
@@ -706,16 +860,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:
@@ -725,7 +887,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)
@@ -741,34 +903,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)
@@ -777,10 +945,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 {}
@@ -797,16 +966,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:
@@ -816,16 +985,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()
@@ -834,7 +1005,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):
@@ -843,8 +1014,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 5c6e06a7d73..68c8d2cd2c3 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 e75587607ec..919bcdab660 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 49b844bf7e8..2e9dfd2faa0 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 f4d3f97ef0d..1471543f1b2 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 1cb101f214a..1d6c5dc0be0 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 23463abe0ae..467323035ea 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 20fb987c601..5f5923dc89b 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,9 +224,7 @@ def _check_agent_availability(agent_email, scheduled_time):
def _get_employee_from_user(user):
- employee_docname = frappe.db.exists(
- {'doctype': 'Employee', 'user_id': user})
+ employee_docname = frappe.db.get_value("Employee", {"user_id": user})
if employee_docname:
- # frappe.db.exists returns a tuple of a tuple
- return frappe.get_doc('Employee', employee_docname[0][0])
+ return frappe.get_doc("Employee", employee_docname)
return None
diff --git a/erpnext/crm/doctype/appointment/test_appointment.py b/erpnext/crm/doctype/appointment/test_appointment.py
index f4086dc37c8..776e6043331 100644
--- a/erpnext/crm/doctype/appointment/test_appointment.py
+++ b/erpnext/crm/doctype/appointment/test_appointment.py
@@ -8,50 +8,44 @@ import frappe
def create_test_lead():
- test_lead = frappe.db.exists({'doctype': 'Lead', 'email_id':'test@example.com'})
- if test_lead:
- return frappe.get_doc('Lead', test_lead[0][0])
- test_lead = frappe.get_doc({
- 'doctype': 'Lead',
- 'lead_name': 'Test Lead',
- 'email_id': 'test@example.com'
- })
- test_lead.insert(ignore_permissions=True)
- return test_lead
+ test_lead = frappe.db.get_value("Lead", {"email_id": "test@example.com"})
+ if test_lead:
+ return frappe.get_doc("Lead", test_lead)
+ test_lead = frappe.get_doc(
+ {"doctype": "Lead", "lead_name": "Test Lead", "email_id": "test@example.com"}
+ )
+ test_lead.insert(ignore_permissions=True)
+ return test_lead
def create_test_appointments():
- test_appointment = frappe.db.exists(
- {'doctype': 'Appointment', 'scheduled_time':datetime.datetime.now(),'email':'test@example.com'})
- if test_appointment:
- return frappe.get_doc('Appointment', test_appointment[0][0])
- test_appointment = frappe.get_doc({
- 'doctype': 'Appointment',
- 'email': 'test@example.com',
- 'status': 'Open',
- 'customer_name': 'Test Lead',
- 'customer_phone_number': '666',
- 'customer_skype': 'test',
- 'customer_email': 'test@example.com',
- 'scheduled_time': datetime.datetime.now()
- })
- test_appointment.insert()
- return test_appointment
+ test_appointment = frappe.get_doc(
+ {
+ "doctype": "Appointment",
+ "email": "test@example.com",
+ "status": "Open",
+ "customer_name": "Test Lead",
+ "customer_phone_number": "666",
+ "customer_skype": "test",
+ "customer_email": "test@example.com",
+ "scheduled_time": datetime.datetime.now(),
+ }
+ )
+ test_appointment.insert()
+ return test_appointment
class TestAppointment(unittest.TestCase):
- test_appointment = test_lead = None
+ test_appointment = test_lead = None
- def setUp(self):
- self.test_lead = create_test_lead()
- self.test_appointment = create_test_appointments()
+ def setUp(self):
+ self.test_lead = create_test_lead()
+ self.test_appointment = create_test_appointments()
- def test_calendar_event_created(self):
- cal_event = frappe.get_doc(
- 'Event', self.test_appointment.calendar_event)
- self.assertEqual(cal_event.starts_on,
- self.test_appointment.scheduled_time)
+ def test_calendar_event_created(self):
+ cal_event = frappe.get_doc("Event", self.test_appointment.calendar_event)
+ self.assertEqual(cal_event.starts_on, self.test_appointment.scheduled_time)
- def test_lead_linked(self):
- lead = frappe.get_doc('Lead', self.test_lead.name)
- self.assertIsNotNone(lead)
+ def test_lead_linked(self):
+ lead = frappe.get_doc("Lead", self.test_lead.name)
+ self.assertIsNotNone(lead)
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 1431b03a2ef..e43f4601e9c 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 8b628004098..5d06075bdfb 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 e21f46a3837..1c2470b6e4e 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 e685362a494..13901683de8 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 7439e4c9171..a5b0ee08f04 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 d44443237e8..9ec54ffc1ef 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 c31b068a43f..c9a64ff8e6e 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,43 +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
return contact
+
@frappe.whitelist()
def make_customer(source_name, target_doc=None):
return _make_customer(source_name, target_doc)
@@ -233,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
@@ -252,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
@@ -274,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")
@@ -289,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
@@ -308,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
@@ -349,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()
@@ -364,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 017390dc83e..730e8e68a97 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 3882974022a..166ae2c3536 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.js b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js
index 7aa0b777596..d532236b7d2 100644
--- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js
+++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js
@@ -37,7 +37,7 @@ frappe.ui.form.on('LinkedIn Settings', {
let msg,color;
if (days>0){
- msg = __("Your Session will be expire in ") + days + __(" days.");
+ msg = __("Your Session will be expire in {0} days.", [days]);
color = "green";
}
else {
diff --git a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
index d2ac10adea2..b4657a2e362 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 2d538748ec2..96c730c668c 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,18 @@ 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.lost_reasons = self.competitors = []
+ 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 +148,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 +245,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 +297,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 +352,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 +391,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 start and post_time <= end:
- sm_post = frappe.get_doc('Social Media Post', post.name)
+ sm_post = frappe.get_doc("Social Media Post", post.name)
sm_post.post()
diff --git a/erpnext/crm/doctype/twitter_settings/twitter_settings.py b/erpnext/crm/doctype/twitter_settings/twitter_settings.py
index be7d9145c53..42874ddeea5 100644
--- a/erpnext/crm/doctype/twitter_settings/twitter_settings.py
+++ b/erpnext/crm/doctype/twitter_settings/twitter_settings.py
@@ -16,22 +16,26 @@ from tweepy.error import TweepError
class TwitterSettings(Document):
@frappe.whitelist()
def get_authorize_url(self):
- callback_url = "{0}/api/method/erpnext.crm.doctype.twitter_settings.twitter_settings.callback?".format(frappe.utils.get_url())
- auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret"), callback_url)
+ callback_url = (
+ "{0}/api/method/erpnext.crm.doctype.twitter_settings.twitter_settings.callback?".format(
+ frappe.utils.get_url()
+ )
+ )
+ auth = tweepy.OAuthHandler(
+ self.consumer_key, self.get_password(fieldname="consumer_secret"), callback_url
+ )
try:
redirect_url = auth.get_authorization_url()
return redirect_url
except tweepy.TweepError as e:
frappe.msgprint(_("Error! Failed to get request token."))
- frappe.throw(_('Invalid {0} or {1}').format(frappe.bold("Consumer Key"), frappe.bold("Consumer Secret Key")))
-
+ frappe.throw(
+ _("Invalid {0} or {1}").format(frappe.bold("Consumer Key"), frappe.bold("Consumer Secret Key"))
+ )
def get_access_token(self, oauth_token, oauth_verifier):
auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret"))
- auth.request_token = {
- 'oauth_token' : oauth_token,
- 'oauth_token_secret' : oauth_verifier
- }
+ auth.request_token = {"oauth_token": oauth_token, "oauth_token_secret": oauth_verifier}
try:
auth.get_access_token(oauth_verifier)
@@ -39,21 +43,25 @@ class TwitterSettings(Document):
self.access_token_secret = auth.access_token_secret
api = self.get_api()
user = api.me()
- profile_pic = (user._json["profile_image_url"]).replace("_normal","")
+ profile_pic = (user._json["profile_image_url"]).replace("_normal", "")
- frappe.db.set_value(self.doctype, self.name, {
- "access_token" : auth.access_token,
- "access_token_secret" : auth.access_token_secret,
- "account_name" : user._json["screen_name"],
- "profile_pic" : profile_pic,
- "session_status" : "Active"
- })
+ frappe.db.set_value(
+ self.doctype,
+ self.name,
+ {
+ "access_token": auth.access_token,
+ "access_token_secret": auth.access_token_secret,
+ "account_name": user._json["screen_name"],
+ "profile_pic": profile_pic,
+ "session_status": "Active",
+ },
+ )
frappe.local.response["type"] = "redirect"
- frappe.local.response["location"] = get_url_to_form("Twitter Settings","Twitter Settings")
+ frappe.local.response["location"] = get_url_to_form("Twitter Settings", "Twitter Settings")
except TweepError as e:
frappe.msgprint(_("Error! Failed to get access token."))
- frappe.throw(_('Invalid Consumer Key or Consumer Secret Key'))
+ frappe.throw(_("Invalid Consumer Key or Consumer Secret Key"))
def get_api(self):
# authentication of consumer key and secret
@@ -82,9 +90,9 @@ class TwitterSettings(Document):
api = self.get_api()
try:
if media_id:
- response = api.update_status(status = text, media_ids = [media_id])
+ response = api.update_status(status=text, media_ids=[media_id])
else:
- response = api.update_status(status = text)
+ response = api.update_status(status=text)
return response
@@ -113,15 +121,18 @@ class TwitterSettings(Document):
if e.response.status_code == 401:
self.db_set("session_status", "Expired")
frappe.db.commit()
- frappe.throw(content["message"],title=_("Twitter Error {0} : {1}").format(e.response.status_code, e.response.reason))
+ frappe.throw(
+ content["message"],
+ title=_("Twitter Error {0} : {1}").format(e.response.status_code, e.response.reason),
+ )
@frappe.whitelist(allow_guest=True)
-def callback(oauth_token = None, oauth_verifier = None):
+def callback(oauth_token=None, oauth_verifier=None):
if oauth_token and oauth_verifier:
twitter_settings = frappe.get_single("Twitter Settings")
- twitter_settings.get_access_token(oauth_token,oauth_verifier)
+ twitter_settings.get_access_token(oauth_token, oauth_verifier)
frappe.db.commit()
else:
frappe.local.response["type"] = "redirect"
- frappe.local.response["location"] = get_url_to_form("Twitter Settings","Twitter Settings")
+ frappe.local.response["location"] = get_url_to_form("Twitter Settings", "Twitter Settings")
diff --git a/erpnext/crm/doctype/utils.py b/erpnext/crm/doctype/utils.py
index 9b56170da31..6bcfcb7e626 100644
--- a/erpnext/crm/doctype/utils.py
+++ b/erpnext/crm/doctype/utils.py
@@ -4,16 +4,17 @@ import frappe
@frappe.whitelist()
def get_last_interaction(contact=None, lead=None):
- if not contact and not lead: return
+ if not contact and not lead:
+ return
last_communication = None
last_issue = None
if contact:
- query_condition = ''
+ query_condition = ""
values = []
- contact = frappe.get_doc('Contact', contact)
+ contact = frappe.get_doc("Contact", contact)
for link in contact.links:
- if link.link_doctype == 'Customer':
+ if link.link_doctype == "Customer":
last_issue = get_last_issue_from_customer(link.link_name)
query_condition += "(`reference_doctype`=%s AND `reference_name`=%s) OR"
values += [link.link_doctype, link.link_name]
@@ -21,65 +22,82 @@ def get_last_interaction(contact=None, lead=None):
if query_condition:
# remove extra appended 'OR'
query_condition = query_condition[:-2]
- last_communication = frappe.db.sql("""
+ last_communication = frappe.db.sql(
+ """
SELECT `name`, `content`
FROM `tabCommunication`
WHERE `sent_or_received`='Received'
AND ({})
ORDER BY `modified`
LIMIT 1
- """.format(query_condition), values, as_dict=1) # nosec
+ """.format(
+ query_condition
+ ),
+ values,
+ as_dict=1,
+ ) # nosec
if lead:
- last_communication = frappe.get_all('Communication', filters={
- 'reference_doctype': 'Lead',
- 'reference_name': lead,
- 'sent_or_received': 'Received'
- }, fields=['name', 'content'], order_by='`creation` DESC', limit=1)
+ last_communication = frappe.get_all(
+ "Communication",
+ filters={"reference_doctype": "Lead", "reference_name": lead, "sent_or_received": "Received"},
+ fields=["name", "content"],
+ order_by="`creation` DESC",
+ limit=1,
+ )
last_communication = last_communication[0] if last_communication else None
- return {
- 'last_communication': last_communication,
- 'last_issue': last_issue
- }
+ return {"last_communication": last_communication, "last_issue": last_issue}
+
def get_last_issue_from_customer(customer_name):
- issues = frappe.get_all('Issue', {
- 'customer': customer_name
- }, ['name', 'subject', 'customer'], order_by='`creation` DESC', limit=1)
+ issues = frappe.get_all(
+ "Issue",
+ {"customer": customer_name},
+ ["name", "subject", "customer"],
+ order_by="`creation` DESC",
+ limit=1,
+ )
return issues[0] if issues else None
def get_scheduled_employees_for_popup(communication_medium):
- if not communication_medium: return []
+ if not communication_medium:
+ return []
now_time = frappe.utils.nowtime()
weekday = frappe.utils.get_weekday()
- available_employee_groups = frappe.get_all("Communication Medium Timeslot", filters={
- 'day_of_week': weekday,
- 'parent': communication_medium,
- 'from_time': ['<=', now_time],
- 'to_time': ['>=', now_time],
- }, fields=['employee_group'])
+ available_employee_groups = frappe.get_all(
+ "Communication Medium Timeslot",
+ filters={
+ "day_of_week": weekday,
+ "parent": communication_medium,
+ "from_time": ["<=", now_time],
+ "to_time": [">=", now_time],
+ },
+ fields=["employee_group"],
+ )
available_employee_groups = tuple([emp.employee_group for emp in available_employee_groups])
- employees = frappe.get_all('Employee Group Table', filters={
- 'parent': ['in', available_employee_groups]
- }, fields=['user_id'])
+ employees = frappe.get_all(
+ "Employee Group Table", filters={"parent": ["in", available_employee_groups]}, fields=["user_id"]
+ )
employee_emails = set([employee.user_id for employee in employees])
return employee_emails
+
def strip_number(number):
- if not number: return
+ if not number:
+ return
# strip + and 0 from the start of the number for proper number comparisions
# eg. +7888383332 should match with 7888383332
# eg. 07888383332 should match with 7888383332
- number = number.lstrip('+')
- number = number.lstrip('0')
+ number = number.lstrip("+")
+ number = number.lstrip("0")
return number
diff --git a/erpnext/crm/report/campaign_efficiency/campaign_efficiency.py b/erpnext/crm/report/campaign_efficiency/campaign_efficiency.py
index 6f3e311f392..be7f5ca29b3 100644
--- a/erpnext/crm/report/campaign_efficiency/campaign_efficiency.py
+++ b/erpnext/crm/report/campaign_efficiency/campaign_efficiency.py
@@ -9,77 +9,40 @@ from frappe.utils import flt
def execute(filters=None):
columns, data = [], []
- columns=get_columns("Campaign Name")
- data=get_lead_data(filters or {}, "Campaign Name")
+ columns = get_columns("Campaign Name")
+ data = get_lead_data(filters or {}, "Campaign Name")
return columns, data
+
def get_columns(based_on):
return [
- {
- "fieldname": frappe.scrub(based_on),
- "label": _(based_on),
- "fieldtype": "Data",
- "width": 150
- },
- {
- "fieldname": "lead_count",
- "label": _("Lead Count"),
- "fieldtype": "Int",
- "width": 80
- },
- {
- "fieldname": "opp_count",
- "label": _("Opp Count"),
- "fieldtype": "Int",
- "width": 80
- },
- {
- "fieldname": "quot_count",
- "label": _("Quot Count"),
- "fieldtype": "Int",
- "width": 80
- },
- {
- "fieldname": "order_count",
- "label": _("Order Count"),
- "fieldtype": "Int",
- "width": 100
- },
- {
- "fieldname": "order_value",
- "label": _("Order Value"),
- "fieldtype": "Float",
- "width": 100
- },
- {
- "fieldname": "opp_lead",
- "label": _("Opp/Lead %"),
- "fieldtype": "Float",
- "width": 100
- },
- {
- "fieldname": "quot_lead",
- "label": _("Quot/Lead %"),
- "fieldtype": "Float",
- "width": 100
- },
- {
- "fieldname": "order_quot",
- "label": _("Order/Quot %"),
- "fieldtype": "Float",
- "width": 100
- }
+ {"fieldname": frappe.scrub(based_on), "label": _(based_on), "fieldtype": "Data", "width": 150},
+ {"fieldname": "lead_count", "label": _("Lead Count"), "fieldtype": "Int", "width": 80},
+ {"fieldname": "opp_count", "label": _("Opp Count"), "fieldtype": "Int", "width": 80},
+ {"fieldname": "quot_count", "label": _("Quot Count"), "fieldtype": "Int", "width": 80},
+ {"fieldname": "order_count", "label": _("Order Count"), "fieldtype": "Int", "width": 100},
+ {"fieldname": "order_value", "label": _("Order Value"), "fieldtype": "Float", "width": 100},
+ {"fieldname": "opp_lead", "label": _("Opp/Lead %"), "fieldtype": "Float", "width": 100},
+ {"fieldname": "quot_lead", "label": _("Quot/Lead %"), "fieldtype": "Float", "width": 100},
+ {"fieldname": "order_quot", "label": _("Order/Quot %"), "fieldtype": "Float", "width": 100},
]
+
def get_lead_data(filters, based_on):
based_on_field = frappe.scrub(based_on)
conditions = get_filter_conditions(filters)
- lead_details = frappe.db.sql("""
+ lead_details = frappe.db.sql(
+ """
select {based_on_field}, name
from `tabLead`
where {based_on_field} is not null and {based_on_field} != '' {conditions}
- """.format(based_on_field=based_on_field, conditions=conditions), filters, as_dict=1)
+ """.format(
+ based_on_field=based_on_field, conditions=conditions
+ ),
+ filters,
+ as_dict=1,
+ )
lead_map = frappe._dict()
for d in lead_details:
@@ -87,11 +50,8 @@ def get_lead_data(filters, based_on):
data = []
for based_on_value, leads in lead_map.items():
- row = {
- based_on_field: based_on_value,
- "lead_count": len(leads)
- }
- row["quot_count"]= get_lead_quotation_count(leads)
+ row = {based_on_field: based_on_value, "lead_count": len(leads)}
+ row["quot_count"] = get_lead_quotation_count(leads)
row["opp_count"] = get_lead_opp_count(leads)
row["order_count"] = get_quotation_ordered_count(leads)
row["order_value"] = get_order_amount(leads) or 0
@@ -105,8 +65,9 @@ def get_lead_data(filters, based_on):
return data
+
def get_filter_conditions(filters):
- conditions=""
+ conditions = ""
if filters.from_date:
conditions += " and date(creation) >= %(from_date)s"
if filters.to_date:
@@ -114,23 +75,45 @@ def get_filter_conditions(filters):
return conditions
+
def get_lead_quotation_count(leads):
- return frappe.db.sql("""select count(name) from `tabQuotation`
- where quotation_to = 'Lead' and party_name in (%s)""" % ', '.join(["%s"]*len(leads)), tuple(leads))[0][0] #nosec
+ return frappe.db.sql(
+ """select count(name) from `tabQuotation`
+ where quotation_to = 'Lead' and party_name in (%s)"""
+ % ", ".join(["%s"] * len(leads)),
+ tuple(leads),
+ )[0][
+ 0
+ ] # nosec
+
def get_lead_opp_count(leads):
- return frappe.db.sql("""select count(name) from `tabOpportunity`
- where opportunity_from = 'Lead' and party_name in (%s)""" % ', '.join(["%s"]*len(leads)), tuple(leads))[0][0]
+ return frappe.db.sql(
+ """select count(name) from `tabOpportunity`
+ where opportunity_from = 'Lead' and party_name in (%s)"""
+ % ", ".join(["%s"] * len(leads)),
+ tuple(leads),
+ )[0][0]
+
def get_quotation_ordered_count(leads):
- return frappe.db.sql("""select count(name)
+ return frappe.db.sql(
+ """select count(name)
from `tabQuotation` where status = 'Ordered' and quotation_to = 'Lead'
- and party_name in (%s)""" % ', '.join(["%s"]*len(leads)), tuple(leads))[0][0]
+ and party_name in (%s)"""
+ % ", ".join(["%s"] * len(leads)),
+ tuple(leads),
+ )[0][0]
+
def get_order_amount(leads):
- return frappe.db.sql("""select sum(base_net_amount)
+ return frappe.db.sql(
+ """select sum(base_net_amount)
from `tabSales Order Item`
where prevdoc_docname in (
select name from `tabQuotation` where status = 'Ordered'
and quotation_to = 'Lead' and party_name in (%s)
- )""" % ', '.join(["%s"]*len(leads)), tuple(leads))[0][0]
+ )"""
+ % ", ".join(["%s"] * len(leads)),
+ tuple(leads),
+ )[0][0]
diff --git a/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.py b/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.py
index ed6cefb2a32..db36581cecd 100644
--- a/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.py
+++ b/erpnext/crm/report/first_response_time_for_opportunity/first_response_time_for_opportunity.py
@@ -3,25 +3,22 @@
import frappe
+from frappe import _
def execute(filters=None):
columns = [
+ {"fieldname": "creation_date", "label": _("Date"), "fieldtype": "Date", "width": 300},
{
- 'fieldname': 'creation_date',
- 'label': 'Date',
- 'fieldtype': 'Date',
- 'width': 300
- },
- {
- 'fieldname': 'first_response_time',
- 'fieldtype': 'Duration',
- 'label': 'First Response Time',
- 'width': 300
+ "fieldname": "first_response_time",
+ "fieldtype": "Duration",
+ "label": "First Response Time",
+ "width": 300,
},
]
- data = frappe.db.sql('''
+ data = frappe.db.sql(
+ """
SELECT
date(creation) as creation_date,
avg(first_response_time) as avg_response_time
@@ -31,6 +28,8 @@ def execute(filters=None):
and first_response_time > 0
GROUP BY creation_date
ORDER BY creation_date desc
- ''', (filters.from_date, filters.to_date))
+ """,
+ (filters.from_date, filters.to_date),
+ )
return columns, data
diff --git a/erpnext/crm/report/lead_conversion_time/lead_conversion_time.py b/erpnext/crm/report/lead_conversion_time/lead_conversion_time.py
index 1f43fa0c476..d7d964d690a 100644
--- a/erpnext/crm/report/lead_conversion_time/lead_conversion_time.py
+++ b/erpnext/crm/report/lead_conversion_time/lead_conversion_time.py
@@ -8,7 +8,8 @@ from frappe.utils import date_diff, flt
def execute(filters=None):
- if not filters: filters = {}
+ if not filters:
+ filters = {}
communication_list = get_communication_details(filters)
columns = get_columns()
@@ -19,8 +20,12 @@ def execute(filters=None):
data = []
for communication in communication_list:
- row = [communication.get('customer'), communication.get('interactions'),\
- communication.get('duration'), communication.get('support_tickets')]
+ row = [
+ communication.get("customer"),
+ communication.get("interactions"),
+ communication.get("duration"),
+ communication.get("support_tickets"),
+ ]
data.append(row)
# add the average row
@@ -32,9 +37,17 @@ def execute(filters=None):
total_interactions += row[1]
total_duration += row[2]
total_tickets += row[3]
- data.append(['Average', total_interactions/len(data), total_duration/len(data), total_tickets/len(data)])
+ data.append(
+ [
+ "Average",
+ total_interactions / len(data),
+ total_duration / len(data),
+ total_tickets / len(data),
+ ]
+ )
return columns, data
+
def get_columns():
return [
{
@@ -42,36 +55,37 @@ def get_columns():
"fieldname": "customer",
"fieldtype": "Link",
"options": "Customer",
- "width": 120
+ "width": 120,
},
{
"label": _("No of Interactions"),
"fieldname": "interactions",
"fieldtype": "Float",
- "width": 120
- },
- {
- "label": _("Duration in Days"),
- "fieldname": "duration",
- "fieldtype": "Float",
- "width": 120
+ "width": 120,
},
+ {"label": _("Duration in Days"), "fieldname": "duration", "fieldtype": "Float", "width": 120},
{
"label": _("Support Tickets"),
"fieldname": "support_tickets",
"fieldtype": "Float",
- "width": 120
- }
+ "width": 120,
+ },
]
+
def get_communication_details(filters):
communication_count = None
communication_list = []
- opportunities = frappe.db.get_values('Opportunity', {'opportunity_from': 'Lead'},\
- ['name', 'customer_name', 'contact_email'], as_dict=1)
+ opportunities = frappe.db.get_values(
+ "Opportunity",
+ {"opportunity_from": "Lead"},
+ ["name", "customer_name", "contact_email"],
+ as_dict=1,
+ )
for d in opportunities:
- invoice = frappe.db.sql('''
+ invoice = frappe.db.sql(
+ """
SELECT
date(creation)
FROM
@@ -81,22 +95,30 @@ def get_communication_details(filters):
ORDER BY
creation
LIMIT 1
- ''', (d.contact_email, filters.from_date, filters.to_date))
+ """,
+ (d.contact_email, filters.from_date, filters.to_date),
+ )
- if not invoice: continue
+ if not invoice:
+ continue
- communication_count = frappe.db.sql('''
+ communication_count = frappe.db.sql(
+ """
SELECT
count(*)
FROM
`tabCommunication`
WHERE
sender = %s AND date(communication_date) <= %s
- ''', (d.contact_email, invoice))[0][0]
+ """,
+ (d.contact_email, invoice),
+ )[0][0]
- if not communication_count: continue
+ if not communication_count:
+ continue
- first_contact = frappe.db.sql('''
+ first_contact = frappe.db.sql(
+ """
SELECT
date(communication_date)
FROM
@@ -106,10 +128,19 @@ def get_communication_details(filters):
ORDER BY
communication_date
LIMIT 1
- ''', (d.contact_email))[0][0]
+ """,
+ (d.contact_email),
+ )[0][0]
duration = flt(date_diff(invoice[0][0], first_contact))
- support_tickets = len(frappe.db.get_all('Issue', {'raised_by': d.contact_email}))
- communication_list.append({'customer': d.customer_name, 'interactions': communication_count, 'duration': duration, 'support_tickets': support_tickets})
+ support_tickets = len(frappe.db.get_all("Issue", {"raised_by": d.contact_email}))
+ communication_list.append(
+ {
+ "customer": d.customer_name,
+ "interactions": communication_count,
+ "duration": duration,
+ "support_tickets": support_tickets,
+ }
+ )
return communication_list
diff --git a/erpnext/crm/report/lead_details/lead_details.py b/erpnext/crm/report/lead_details/lead_details.py
index 09eba7c38a6..8660c733103 100644
--- a/erpnext/crm/report/lead_details/lead_details.py
+++ b/erpnext/crm/report/lead_details/lead_details.py
@@ -10,6 +10,7 @@ def execute(filters=None):
columns, data = get_columns(), get_data(filters)
return columns, data
+
def get_columns():
columns = [
{
@@ -19,101 +20,57 @@ def get_columns():
"options": "Lead",
"width": 150,
},
+ {"label": _("Lead Name"), "fieldname": "lead_name", "fieldtype": "Data", "width": 120},
+ {"fieldname": "status", "label": _("Status"), "fieldtype": "Data", "width": 100},
{
- "label": _("Lead Name"),
- "fieldname": "lead_name",
- "fieldtype": "Data",
- "width": 120
- },
- {
- "fieldname":"status",
- "label": _("Status"),
- "fieldtype": "Data",
- "width": 100
- },
- {
- "fieldname":"lead_owner",
+ "fieldname": "lead_owner",
"label": _("Lead Owner"),
"fieldtype": "Link",
"options": "User",
- "width": 100
+ "width": 100,
},
{
"label": _("Territory"),
"fieldname": "territory",
"fieldtype": "Link",
"options": "Territory",
- "width": 100
- },
- {
- "label": _("Source"),
- "fieldname": "source",
- "fieldtype": "Data",
- "width": 120
- },
- {
- "label": _("Email"),
- "fieldname": "email_id",
- "fieldtype": "Data",
- "width": 120
- },
- {
- "label": _("Mobile"),
- "fieldname": "mobile_no",
- "fieldtype": "Data",
- "width": 120
- },
- {
- "label": _("Phone"),
- "fieldname": "phone",
- "fieldtype": "Data",
- "width": 120
+ "width": 100,
},
+ {"label": _("Source"), "fieldname": "source", "fieldtype": "Data", "width": 120},
+ {"label": _("Email"), "fieldname": "email_id", "fieldtype": "Data", "width": 120},
+ {"label": _("Mobile"), "fieldname": "mobile_no", "fieldtype": "Data", "width": 120},
+ {"label": _("Phone"), "fieldname": "phone", "fieldtype": "Data", "width": 120},
{
"label": _("Owner"),
"fieldname": "owner",
"fieldtype": "Link",
"options": "user",
- "width": 120
+ "width": 120,
},
{
"label": _("Company"),
"fieldname": "company",
"fieldtype": "Link",
"options": "Company",
- "width": 120
+ "width": 120,
},
+ {"fieldname": "address", "label": _("Address"), "fieldtype": "Data", "width": 130},
+ {"fieldname": "state", "label": _("State"), "fieldtype": "Data", "width": 100},
+ {"fieldname": "pincode", "label": _("Postal Code"), "fieldtype": "Data", "width": 90},
{
- "fieldname":"address",
- "label": _("Address"),
- "fieldtype": "Data",
- "width": 130
- },
- {
- "fieldname":"state",
- "label": _("State"),
- "fieldtype": "Data",
- "width": 100
- },
- {
- "fieldname":"pincode",
- "label": _("Postal Code"),
- "fieldtype": "Data",
- "width": 90
- },
- {
- "fieldname":"country",
+ "fieldname": "country",
"label": _("Country"),
"fieldtype": "Link",
"options": "Country",
- "width": 100
+ "width": 100,
},
-
]
return columns
+
def get_data(filters):
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
SELECT
`tabLead`.name,
`tabLead`.lead_name,
@@ -144,9 +101,15 @@ def get_data(filters):
AND `tabLead`.creation BETWEEN %(from_date)s AND %(to_date)s
{conditions}
ORDER BY
- `tabLead`.creation asc """.format(conditions=get_conditions(filters)), filters, as_dict=1)
+ `tabLead`.creation asc """.format(
+ conditions=get_conditions(filters)
+ ),
+ filters,
+ as_dict=1,
+ )
-def get_conditions(filters) :
+
+def get_conditions(filters):
conditions = []
if filters.get("territory"):
diff --git a/erpnext/crm/report/lead_owner_efficiency/lead_owner_efficiency.py b/erpnext/crm/report/lead_owner_efficiency/lead_owner_efficiency.py
index 29322119ee4..996b1b47172 100644
--- a/erpnext/crm/report/lead_owner_efficiency/lead_owner_efficiency.py
+++ b/erpnext/crm/report/lead_owner_efficiency/lead_owner_efficiency.py
@@ -9,10 +9,11 @@ from erpnext.crm.report.campaign_efficiency.campaign_efficiency import get_lead_
def execute(filters=None):
columns, data = [], []
- columns=get_columns()
- data=get_lead_data(filters, "Lead Owner")
+ columns = get_columns()
+ data = get_lead_data(filters, "Lead Owner")
return columns, data
+
def get_columns():
return [
{
@@ -20,54 +21,14 @@ def get_columns():
"label": _("Lead Owner"),
"fieldtype": "Link",
"options": "User",
- "width": "130"
+ "width": "130",
},
- {
- "fieldname": "lead_count",
- "label": _("Lead Count"),
- "fieldtype": "Int",
- "width": "80"
- },
- {
- "fieldname": "opp_count",
- "label": _("Opp Count"),
- "fieldtype": "Int",
- "width": "80"
- },
- {
- "fieldname": "quot_count",
- "label": _("Quot Count"),
- "fieldtype": "Int",
- "width": "80"
- },
- {
- "fieldname": "order_count",
- "label": _("Order Count"),
- "fieldtype": "Int",
- "width": "100"
- },
- {
- "fieldname": "order_value",
- "label": _("Order Value"),
- "fieldtype": "Float",
- "width": "100"
- },
- {
- "fieldname": "opp_lead",
- "label": _("Opp/Lead %"),
- "fieldtype": "Float",
- "width": "100"
- },
- {
- "fieldname": "quot_lead",
- "label": _("Quot/Lead %"),
- "fieldtype": "Float",
- "width": "100"
- },
- {
- "fieldname": "order_quot",
- "label": _("Order/Quot %"),
- "fieldtype": "Float",
- "width": "100"
- }
+ {"fieldname": "lead_count", "label": _("Lead Count"), "fieldtype": "Int", "width": "80"},
+ {"fieldname": "opp_count", "label": _("Opp Count"), "fieldtype": "Int", "width": "80"},
+ {"fieldname": "quot_count", "label": _("Quot Count"), "fieldtype": "Int", "width": "80"},
+ {"fieldname": "order_count", "label": _("Order Count"), "fieldtype": "Int", "width": "100"},
+ {"fieldname": "order_value", "label": _("Order Value"), "fieldtype": "Float", "width": "100"},
+ {"fieldname": "opp_lead", "label": _("Opp/Lead %"), "fieldtype": "Float", "width": "100"},
+ {"fieldname": "quot_lead", "label": _("Quot/Lead %"), "fieldtype": "Float", "width": "100"},
+ {"fieldname": "order_quot", "label": _("Order/Quot %"), "fieldtype": "Float", "width": "100"},
]
diff --git a/erpnext/crm/report/lost_opportunity/lost_opportunity.py b/erpnext/crm/report/lost_opportunity/lost_opportunity.py
index 60d4be85648..a57b44be477 100644
--- a/erpnext/crm/report/lost_opportunity/lost_opportunity.py
+++ b/erpnext/crm/report/lost_opportunity/lost_opportunity.py
@@ -10,6 +10,7 @@ def execute(filters=None):
columns, data = get_columns(), get_data(filters)
return columns, data
+
def get_columns():
columns = [
{
@@ -24,59 +25,56 @@ def get_columns():
"fieldname": "opportunity_from",
"fieldtype": "Link",
"options": "DocType",
- "width": 130
+ "width": 130,
},
{
"label": _("Party"),
- "fieldname":"party_name",
+ "fieldname": "party_name",
"fieldtype": "Dynamic Link",
"options": "opportunity_from",
- "width": 160
+ "width": 160,
},
{
"label": _("Customer/Lead Name"),
- "fieldname":"customer_name",
+ "fieldname": "customer_name",
"fieldtype": "Data",
- "width": 150
+ "width": 150,
},
{
"label": _("Opportunity Type"),
"fieldname": "opportunity_type",
"fieldtype": "Data",
- "width": 130
- },
- {
- "label": _("Lost Reasons"),
- "fieldname": "lost_reason",
- "fieldtype": "Data",
- "width": 220
+ "width": 130,
},
+ {"label": _("Lost Reasons"), "fieldname": "lost_reason", "fieldtype": "Data", "width": 220},
{
"label": _("Sales Stage"),
"fieldname": "sales_stage",
"fieldtype": "Link",
"options": "Sales Stage",
- "width": 150
+ "width": 150,
},
{
"label": _("Territory"),
"fieldname": "territory",
"fieldtype": "Link",
"options": "Territory",
- "width": 150
+ "width": 150,
},
{
"label": _("Next Contact By"),
"fieldname": "contact_by",
"fieldtype": "Link",
"options": "User",
- "width": 150
- }
+ "width": 150,
+ },
]
return columns
+
def get_data(filters):
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
SELECT
`tabOpportunity`.name,
`tabOpportunity`.opportunity_from,
@@ -97,7 +95,12 @@ def get_data(filters):
GROUP BY
`tabOpportunity`.name
ORDER BY
- `tabOpportunity`.creation asc """.format(conditions=get_conditions(filters), join=get_join(filters)), filters, as_dict=1)
+ `tabOpportunity`.creation asc """.format(
+ conditions=get_conditions(filters), join=get_join(filters)
+ ),
+ filters,
+ as_dict=1,
+ )
def get_conditions(filters):
@@ -117,6 +120,7 @@ def get_conditions(filters):
return " ".join(conditions) if conditions else ""
+
def get_join(filters):
join = """LEFT JOIN `tabOpportunity Lost Reason Detail`
ON `tabOpportunity Lost Reason Detail`.parenttype = 'Opportunity' and
@@ -127,6 +131,8 @@ def get_join(filters):
ON `tabOpportunity Lost Reason Detail`.parenttype = 'Opportunity' and
`tabOpportunity Lost Reason Detail`.parent = `tabOpportunity`.name and
`tabOpportunity Lost Reason Detail`.lost_reason = '{0}'
- """.format(filters.get("lost_reason"))
+ """.format(
+ filters.get("lost_reason")
+ )
return join
diff --git a/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.py b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.py
index f53b5bde9ee..3a46fb0879c 100644
--- a/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.py
+++ b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.py
@@ -1,9 +1,9 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import json
+from itertools import groupby
import frappe
-import pandas
from frappe import _
from frappe.utils import flt
@@ -13,8 +13,9 @@ from erpnext.setup.utils import get_exchange_rate
def execute(filters=None):
return OpportunitySummaryBySalesStage(filters).run()
+
class OpportunitySummaryBySalesStage(object):
- def __init__(self,filters=None):
+ def __init__(self, filters=None):
self.filters = frappe._dict(filters or {})
def run(self):
@@ -26,98 +27,95 @@ class OpportunitySummaryBySalesStage(object):
def get_columns(self):
self.columns = []
- if self.filters.get('based_on') == 'Opportunity Owner':
- self.columns.append({
- 'label': _('Opportunity Owner'),
- 'fieldname': 'opportunity_owner',
- 'width': 200
- })
+ if self.filters.get("based_on") == "Opportunity Owner":
+ self.columns.append(
+ {"label": _("Opportunity Owner"), "fieldname": "opportunity_owner", "width": 200}
+ )
- if self.filters.get('based_on') == 'Source':
- self.columns.append({
- 'label': _('Source'),
- 'fieldname': 'source',
- 'fieldtype': 'Link',
- 'options': 'Lead Source',
- 'width': 200
- })
+ if self.filters.get("based_on") == "Source":
+ self.columns.append(
+ {
+ "label": _("Source"),
+ "fieldname": "source",
+ "fieldtype": "Link",
+ "options": "Lead Source",
+ "width": 200,
+ }
+ )
- if self.filters.get('based_on') == 'Opportunity Type':
- self.columns.append({
- 'label': _('Opportunity Type'),
- 'fieldname': 'opportunity_type',
- 'width': 200
- })
+ if self.filters.get("based_on") == "Opportunity Type":
+ self.columns.append(
+ {"label": _("Opportunity Type"), "fieldname": "opportunity_type", "width": 200}
+ )
self.set_sales_stage_columns()
def set_sales_stage_columns(self):
- self.sales_stage_list = frappe.db.get_list('Sales Stage', pluck='name')
+ self.sales_stage_list = frappe.db.get_list("Sales Stage", pluck="name")
for sales_stage in self.sales_stage_list:
- if self.filters.get('data_based_on') == 'Number':
- self.columns.append({
- 'label': _(sales_stage),
- 'fieldname': sales_stage,
- 'fieldtype': 'Int',
- 'width': 150
- })
+ if self.filters.get("data_based_on") == "Number":
+ self.columns.append(
+ {"label": _(sales_stage), "fieldname": sales_stage, "fieldtype": "Int", "width": 150}
+ )
- elif self.filters.get('data_based_on') == 'Amount':
- self.columns.append({
- 'label': _(sales_stage),
- 'fieldname': sales_stage,
- 'fieldtype': 'Currency',
- 'width': 150
- })
+ elif self.filters.get("data_based_on") == "Amount":
+ self.columns.append(
+ {"label": _(sales_stage), "fieldname": sales_stage, "fieldtype": "Currency", "width": 150}
+ )
def get_data(self):
self.data = []
based_on = {
- 'Opportunity Owner': '_assign',
- 'Source': 'source',
- 'Opportunity Type': 'opportunity_type'
- }[self.filters.get('based_on')]
+ "Opportunity Owner": "_assign",
+ "Source": "source",
+ "Opportunity Type": "opportunity_type",
+ }[self.filters.get("based_on")]
data_based_on = {
- 'Number': 'count(name) as count',
- 'Amount': 'opportunity_amount as amount',
- }[self.filters.get('data_based_on')]
+ "Number": "count(name) as count",
+ "Amount": "opportunity_amount as amount",
+ }[self.filters.get("data_based_on")]
self.get_data_query(based_on, data_based_on)
self.get_rows()
def get_data_query(self, based_on, data_based_on):
- if self.filters.get('data_based_on') == 'Number':
- group_by = '{},{}'.format('sales_stage', based_on)
- self.query_result = frappe.db.get_list('Opportunity',
+ if self.filters.get("data_based_on") == "Number":
+ group_by = "{},{}".format("sales_stage", based_on)
+ self.query_result = frappe.db.get_list(
+ "Opportunity",
filters=self.get_conditions(),
- fields=['sales_stage', data_based_on, based_on],
- group_by=group_by
+ fields=["sales_stage", data_based_on, based_on],
+ group_by=group_by,
)
- elif self.filters.get('data_based_on') == 'Amount':
- self.query_result = frappe.db.get_list('Opportunity',
+ elif self.filters.get("data_based_on") == "Amount":
+ self.query_result = frappe.db.get_list(
+ "Opportunity",
filters=self.get_conditions(),
- fields=['sales_stage', based_on, data_based_on, 'currency']
+ fields=["sales_stage", based_on, data_based_on, "currency"],
)
self.convert_to_base_currency()
- dataframe = pandas.DataFrame.from_records(self.query_result)
- dataframe.replace(to_replace=[None], value='Not Assigned', inplace=True)
- result = dataframe.groupby(['sales_stage', based_on], as_index=False)['amount'].sum()
+ for row in self.query_result:
+ if not row.get(based_on):
+ row[based_on] = "Not Assigned"
self.grouped_data = []
- for i in range(len(result['amount'])):
- self.grouped_data.append({
- 'sales_stage': result['sales_stage'][i],
- based_on : result[based_on][i],
- 'amount': result['amount'][i]
- })
+ grouping_key = lambda o: (o["sales_stage"], o[based_on]) # noqa
+ for (sales_stage, _based_on), rows in groupby(self.query_result, grouping_key):
+ self.grouped_data.append(
+ {
+ "sales_stage": sales_stage,
+ based_on: _based_on,
+ "amount": sum(flt(r["amount"]) for r in rows),
+ }
+ )
self.query_result = self.grouped_data
@@ -125,17 +123,17 @@ class OpportunitySummaryBySalesStage(object):
self.data = []
self.get_formatted_data()
- for based_on,data in self.formatted_data.items():
- row_based_on={
- 'Opportunity Owner': 'opportunity_owner',
- 'Source': 'source',
- 'Opportunity Type': 'opportunity_type'
- }[self.filters.get('based_on')]
+ for based_on, data in self.formatted_data.items():
+ row_based_on = {
+ "Opportunity Owner": "opportunity_owner",
+ "Source": "source",
+ "Opportunity Type": "opportunity_type",
+ }[self.filters.get("based_on")]
row = {row_based_on: based_on}
for d in self.query_result:
- sales_stage = d.get('sales_stage')
+ sales_stage = d.get("sales_stage")
row[sales_stage] = data.get(sales_stage)
self.data.append(row)
@@ -144,24 +142,21 @@ class OpportunitySummaryBySalesStage(object):
self.formatted_data = frappe._dict()
for d in self.query_result:
- data_based_on ={
- 'Number': 'count',
- 'Amount': 'amount'
- }[self.filters.get('data_based_on')]
+ data_based_on = {"Number": "count", "Amount": "amount"}[self.filters.get("data_based_on")]
- based_on ={
- 'Opportunity Owner': '_assign',
- 'Source': 'source',
- 'Opportunity Type': 'opportunity_type'
- }[self.filters.get('based_on')]
+ based_on = {
+ "Opportunity Owner": "_assign",
+ "Source": "source",
+ "Opportunity Type": "opportunity_type",
+ }[self.filters.get("based_on")]
- if self.filters.get('based_on') == 'Opportunity Owner':
- if d.get(based_on) == '[]' or d.get(based_on) is None or d.get(based_on) == 'Not Assigned':
- assignments = ['Not Assigned']
+ if self.filters.get("based_on") == "Opportunity Owner":
+ if d.get(based_on) == "[]" or d.get(based_on) is None or d.get(based_on) == "Not Assigned":
+ assignments = ["Not Assigned"]
else:
assignments = json.loads(d.get(based_on))
- sales_stage = d.get('sales_stage')
+ sales_stage = d.get("sales_stage")
count = d.get(data_based_on)
if assignments:
@@ -173,7 +168,7 @@ class OpportunitySummaryBySalesStage(object):
self.set_formatted_data_based_on_sales_stage(assigned_to, sales_stage, count)
else:
value = d.get(based_on)
- sales_stage = d.get('sales_stage')
+ sales_stage = d.get("sales_stage")
count = d.get(data_based_on)
self.set_formatted_data_based_on_sales_stage(value, sales_stage, count)
@@ -184,20 +179,22 @@ class OpportunitySummaryBySalesStage(object):
def get_conditions(self):
filters = []
- if self.filters.get('company'):
- filters.append({'company': self.filters.get('company')})
+ if self.filters.get("company"):
+ filters.append({"company": self.filters.get("company")})
- if self.filters.get('opportunity_type'):
- filters.append({'opportunity_type': self.filters.get('opportunity_type')})
+ if self.filters.get("opportunity_type"):
+ filters.append({"opportunity_type": self.filters.get("opportunity_type")})
- if self.filters.get('opportunity_source'):
- filters.append({'source': self.filters.get('opportunity_source')})
+ if self.filters.get("opportunity_source"):
+ filters.append({"source": self.filters.get("opportunity_source")})
- if self.filters.get('status'):
- filters.append({'status': ('in',self.filters.get('status'))})
+ if self.filters.get("status"):
+ filters.append({"status": ("in", self.filters.get("status"))})
- if self.filters.get('from_date') and self.filters.get('to_date'):
- filters.append(['transaction_date', 'between', [self.filters.get('from_date'), self.filters.get('to_date')]])
+ if self.filters.get("from_date") and self.filters.get("to_date"):
+ filters.append(
+ ["transaction_date", "between", [self.filters.get("from_date"), self.filters.get("to_date")]]
+ )
return filters
@@ -209,45 +206,36 @@ class OpportunitySummaryBySalesStage(object):
for sales_stage in self.sales_stage_list:
labels.append(sales_stage)
- options = {
- 'Number': 'count',
- 'Amount': 'amount'
- }[self.filters.get('data_based_on')]
+ options = {"Number": "count", "Amount": "amount"}[self.filters.get("data_based_on")]
for data in self.query_result:
for count in range(len(values)):
- if data['sales_stage'] == labels[count]:
+ if data["sales_stage"] == labels[count]:
values[count] = values[count] + data[options]
- datasets.append({'name':options, 'values':values})
+ datasets.append({"name": options, "values": values})
- self.chart = {
- 'data':{
- 'labels': labels,
- 'datasets': datasets
- },
- 'type':'line'
- }
+ self.chart = {"data": {"labels": labels, "datasets": datasets}, "type": "line"}
- def currency_conversion(self,from_currency,to_currency):
+ def currency_conversion(self, from_currency, to_currency):
cacheobj = frappe.cache()
if cacheobj.get(from_currency):
- return flt(str(cacheobj.get(from_currency),'UTF-8'))
+ return flt(str(cacheobj.get(from_currency), "UTF-8"))
else:
- value = get_exchange_rate(from_currency,to_currency)
- cacheobj.set(from_currency,value)
- return flt(str(cacheobj.get(from_currency),'UTF-8'))
+ value = get_exchange_rate(from_currency, to_currency)
+ cacheobj.set(from_currency, value)
+ return flt(str(cacheobj.get(from_currency), "UTF-8"))
def get_default_currency(self):
- company = self.filters.get('company')
- return frappe.db.get_value('Company', company, 'default_currency')
+ company = self.filters.get("company")
+ return frappe.db.get_value("Company", company, "default_currency")
def convert_to_base_currency(self):
default_currency = self.get_default_currency()
for data in self.query_result:
- if data.get('currency') != default_currency:
- opportunity_currency = data.get('currency')
- value = self.currency_conversion(opportunity_currency,default_currency)
- data['amount'] = data['amount'] * value
+ if data.get("currency") != default_currency:
+ opportunity_currency = data.get("currency")
+ value = self.currency_conversion(opportunity_currency, default_currency)
+ data["amount"] = data["amount"] * value
diff --git a/erpnext/crm/report/opportunity_summary_by_sales_stage/test_opportunity_summary_by_sales_stage.py b/erpnext/crm/report/opportunity_summary_by_sales_stage/test_opportunity_summary_by_sales_stage.py
index 13859d9e0b4..ffc612df64b 100644
--- a/erpnext/crm/report/opportunity_summary_by_sales_stage/test_opportunity_summary_by_sales_stage.py
+++ b/erpnext/crm/report/opportunity_summary_by_sales_stage/test_opportunity_summary_by_sales_stage.py
@@ -27,68 +27,44 @@ class TestOpportunitySummaryBySalesStage(unittest.TestCase):
self.check_all_filters()
def check_for_opportunity_owner(self):
- filters = {
- 'based_on': "Opportunity Owner",
- 'data_based_on': "Number",
- 'company': "Best Test"
- }
+ filters = {"based_on": "Opportunity Owner", "data_based_on": "Number", "company": "Best Test"}
report = execute(filters)
- expected_data = [{
- 'opportunity_owner': "Not Assigned",
- 'Prospecting': 1
- }]
+ expected_data = [{"opportunity_owner": "Not Assigned", "Prospecting": 1}]
self.assertEqual(expected_data, report[1])
def check_for_source(self):
- filters = {
- 'based_on': "Source",
- 'data_based_on': "Number",
- 'company': "Best Test"
- }
+ filters = {"based_on": "Source", "data_based_on": "Number", "company": "Best Test"}
report = execute(filters)
- expected_data = [{
- 'source': 'Cold Calling',
- 'Prospecting': 1
- }]
+ expected_data = [{"source": "Cold Calling", "Prospecting": 1}]
self.assertEqual(expected_data, report[1])
def check_for_opportunity_type(self):
- filters = {
- 'based_on': "Opportunity Type",
- 'data_based_on': "Number",
- 'company': "Best Test"
- }
+ filters = {"based_on": "Opportunity Type", "data_based_on": "Number", "company": "Best Test"}
report = execute(filters)
- expected_data = [{
- 'opportunity_type': 'Sales',
- 'Prospecting': 1
- }]
+ expected_data = [{"opportunity_type": "Sales", "Prospecting": 1}]
self.assertEqual(expected_data, report[1])
def check_all_filters(self):
filters = {
- 'based_on': "Opportunity Type",
- 'data_based_on': "Number",
- 'company': "Best Test",
- 'opportunity_source': "Cold Calling",
- 'opportunity_type': "Sales",
- 'status': ["Open"]
+ "based_on": "Opportunity Type",
+ "data_based_on": "Number",
+ "company": "Best Test",
+ "opportunity_source": "Cold Calling",
+ "opportunity_type": "Sales",
+ "status": ["Open"],
}
report = execute(filters)
- expected_data = [{
- 'opportunity_type': 'Sales',
- 'Prospecting': 1
- }]
+ expected_data = [{"opportunity_type": "Sales", "Prospecting": 1}]
- self.assertEqual(expected_data, report[1])
\ No newline at end of file
+ self.assertEqual(expected_data, report[1])
diff --git a/erpnext/crm/report/prospects_engaged_but_not_converted/prospects_engaged_but_not_converted.py b/erpnext/crm/report/prospects_engaged_but_not_converted/prospects_engaged_but_not_converted.py
index 41cb4422a59..50c42efe3c5 100644
--- a/erpnext/crm/report/prospects_engaged_but_not_converted/prospects_engaged_but_not_converted.py
+++ b/erpnext/crm/report/prospects_engaged_but_not_converted/prospects_engaged_but_not_converted.py
@@ -15,62 +15,58 @@ def execute(filters=None):
return columns, data
+
def set_defaut_value_for_filters(filters):
- if not filters.get('no_of_interaction'): filters["no_of_interaction"] = 1
- if not filters.get('lead_age'): filters["lead_age"] = 60
+ if not filters.get("no_of_interaction"):
+ filters["no_of_interaction"] = 1
+ if not filters.get("lead_age"):
+ filters["lead_age"] = 60
+
def get_columns():
- columns = [{
- "label": _("Lead"),
- "fieldname": "lead",
- "fieldtype": "Link",
- "options": "Lead",
- "width": 130
- },
- {
- "label": _("Name"),
- "fieldname": "name",
- "width": 120
- },
- {
- "label": _("Organization"),
- "fieldname": "organization",
- "width": 120
- },
+ columns = [
+ {"label": _("Lead"), "fieldname": "lead", "fieldtype": "Link", "options": "Lead", "width": 130},
+ {"label": _("Name"), "fieldname": "name", "width": 120},
+ {"label": _("Organization"), "fieldname": "organization", "width": 120},
{
"label": _("Reference Document Type"),
"fieldname": "reference_document_type",
"fieldtype": "Link",
"options": "Doctype",
- "width": 100
+ "width": 100,
},
{
"label": _("Reference Name"),
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
"options": "reference_document_type",
- "width": 140
+ "width": 140,
},
{
"label": _("Last Communication"),
"fieldname": "last_communication",
"fieldtype": "Data",
- "width": 200
+ "width": 200,
},
{
"label": _("Last Communication Date"),
"fieldname": "last_communication_date",
"fieldtype": "Date",
- "width": 100
- }]
+ "width": 100,
+ },
+ ]
return columns
+
def get_data(filters):
lead_details = []
lead_filters = get_lead_filters(filters)
- for lead in frappe.get_all('Lead', fields = ['name', 'lead_name', 'company_name'], filters=lead_filters):
- data = frappe.db.sql("""
+ for lead in frappe.get_all(
+ "Lead", fields=["name", "lead_name", "company_name"], filters=lead_filters
+ ):
+ data = frappe.db.sql(
+ """
select
`tabCommunication`.reference_doctype, `tabCommunication`.reference_name,
`tabCommunication`.content, `tabCommunication`.communication_date
@@ -90,7 +86,8 @@ def get_data(filters):
`tabCommunication`.sent_or_received = 'Received'
order by
ref_document.lead, `tabCommunication`.creation desc limit %(limit)s""",
- {'lead': lead.name, 'limit': filters.get('no_of_interaction')})
+ {"lead": lead.name, "limit": filters.get("no_of_interaction")},
+ )
for lead_info in data:
lead_data = [lead.name, lead.lead_name, lead.company_name] + list(lead_info)
@@ -98,13 +95,15 @@ def get_data(filters):
return lead_details
+
def get_lead_filters(filters):
lead_creation_date = get_creation_date_based_on_lead_age(filters)
lead_filters = [["status", "!=", "Converted"], ["creation", ">", lead_creation_date]]
- if filters.get('lead'):
- lead_filters.append(["name", "=", filters.get('lead')])
+ if filters.get("lead"):
+ lead_filters.append(["name", "=", filters.get("lead")])
return lead_filters
+
def get_creation_date_based_on_lead_age(filters):
- return add_days(now(), (filters.get('lead_age') * -1))
+ return add_days(now(), (filters.get("lead_age") * -1))
diff --git a/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py
index 1c7846b120d..d23a22ac46d 100644
--- a/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py
+++ b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py
@@ -3,9 +3,9 @@
import json
from datetime import date
+from itertools import groupby
import frappe
-import pandas
from dateutil.relativedelta import relativedelta
from frappe import _
from frappe.utils import cint, flt
@@ -16,6 +16,7 @@ from erpnext.setup.utils import get_exchange_rate
def execute(filters=None):
return SalesPipelineAnalytics(filters).run()
+
class SalesPipelineAnalytics(object):
def __init__(self, filters=None):
self.filters = frappe._dict(filters or {})
@@ -34,113 +35,91 @@ class SalesPipelineAnalytics(object):
self.set_pipeline_based_on_column()
def set_range_columns(self):
- based_on = {
- 'Number': 'Int',
- 'Amount': 'Currency'
- }[self.filters.get('based_on')]
+ based_on = {"Number": "Int", "Amount": "Currency"}[self.filters.get("based_on")]
- if self.filters.get('range') == 'Monthly':
+ if self.filters.get("range") == "Monthly":
month_list = self.get_month_list()
for month in month_list:
- self.columns.append({
- 'fieldname': month,
- 'fieldtype': based_on,
- 'label': month,
- 'width': 200
- })
+ self.columns.append({"fieldname": month, "fieldtype": based_on, "label": month, "width": 200})
- elif self.filters.get('range') == 'Quarterly':
+ elif self.filters.get("range") == "Quarterly":
for quarter in range(1, 5):
- self.columns.append({
- 'fieldname': f'Q{quarter}',
- 'fieldtype': based_on,
- 'label': f'Q{quarter}',
- 'width': 200
- })
+ self.columns.append(
+ {"fieldname": f"Q{quarter}", "fieldtype": based_on, "label": f"Q{quarter}", "width": 200}
+ )
def set_pipeline_based_on_column(self):
- if self.filters.get('pipeline_by') == 'Owner':
- self.columns.insert(0, {
- 'fieldname': 'opportunity_owner',
- 'label': _('Opportunity Owner'),
- 'width': 200
- })
+ if self.filters.get("pipeline_by") == "Owner":
+ self.columns.insert(
+ 0, {"fieldname": "opportunity_owner", "label": _("Opportunity Owner"), "width": 200}
+ )
- elif self.filters.get('pipeline_by') == 'Sales Stage':
- self.columns.insert(0, {
- 'fieldname': 'sales_stage',
- 'label': _('Sales Stage'),
- 'width': 200
- })
+ elif self.filters.get("pipeline_by") == "Sales Stage":
+ self.columns.insert(0, {"fieldname": "sales_stage", "label": _("Sales Stage"), "width": 200})
def get_fields(self):
- self.based_on ={
- 'Owner': '_assign as opportunity_owner',
- 'Sales Stage': 'sales_stage'
- }[self.filters.get('pipeline_by')]
+ self.based_on = {"Owner": "_assign as opportunity_owner", "Sales Stage": "sales_stage"}[
+ self.filters.get("pipeline_by")
+ ]
- self.data_based_on ={
- 'Number': 'count(name) as count',
- 'Amount': 'opportunity_amount as amount'
- }[self.filters.get('based_on')]
+ self.data_based_on = {
+ "Number": "count(name) as count",
+ "Amount": "opportunity_amount as amount",
+ }[self.filters.get("based_on")]
- self.group_by_based_on = {
- 'Owner': '_assign',
- 'Sales Stage': 'sales_stage'
- }[self.filters.get('pipeline_by')]
+ self.group_by_based_on = {"Owner": "_assign", "Sales Stage": "sales_stage"}[
+ self.filters.get("pipeline_by")
+ ]
self.group_by_period = {
- 'Monthly': 'month(expected_closing)',
- 'Quarterly': 'QUARTER(expected_closing)'
- }[self.filters.get('range')]
+ "Monthly": "month(expected_closing)",
+ "Quarterly": "QUARTER(expected_closing)",
+ }[self.filters.get("range")]
- self.pipeline_by = {
- 'Owner': 'opportunity_owner',
- 'Sales Stage': 'sales_stage'
- }[self.filters.get('pipeline_by')]
+ self.pipeline_by = {"Owner": "opportunity_owner", "Sales Stage": "sales_stage"}[
+ self.filters.get("pipeline_by")
+ ]
self.duration = {
- 'Monthly': 'monthname(expected_closing) as month',
- 'Quarterly': 'QUARTER(expected_closing) as quarter'
- }[self.filters.get('range')]
+ "Monthly": "monthname(expected_closing) as month",
+ "Quarterly": "QUARTER(expected_closing) as quarter",
+ }[self.filters.get("range")]
- self.period_by = {
- 'Monthly': 'month',
- 'Quarterly': 'quarter'
- }[self.filters.get('range')]
+ self.period_by = {"Monthly": "month", "Quarterly": "quarter"}[self.filters.get("range")]
def get_data(self):
self.get_fields()
- if self.filters.get('based_on') == 'Number':
- self.query_result = frappe.db.get_list('Opportunity',
+ if self.filters.get("based_on") == "Number":
+ self.query_result = frappe.db.get_list(
+ "Opportunity",
filters=self.get_conditions(),
fields=[self.based_on, self.data_based_on, self.duration],
- group_by='{},{}'.format(self.group_by_based_on, self.group_by_period),
- order_by=self.group_by_period
+ group_by="{},{}".format(self.group_by_based_on, self.group_by_period),
+ order_by=self.group_by_period,
)
- if self.filters.get('based_on') == 'Amount':
- self.query_result = frappe.db.get_list('Opportunity',
+ if self.filters.get("based_on") == "Amount":
+ self.query_result = frappe.db.get_list(
+ "Opportunity",
filters=self.get_conditions(),
- fields=[self.based_on, self.data_based_on, self.duration, 'currency']
+ fields=[self.based_on, self.data_based_on, self.duration, "currency"],
)
self.convert_to_base_currency()
- dataframe = pandas.DataFrame.from_records(self.query_result)
- dataframe.replace(to_replace=[None], value='Not Assigned', inplace=True)
- result = dataframe.groupby([self.pipeline_by, self.period_by], as_index=False)['amount'].sum()
-
self.grouped_data = []
- for i in range(len(result['amount'])):
- self.grouped_data.append({
- self.pipeline_by : result[self.pipeline_by][i],
- self.period_by : result[self.period_by][i],
- 'amount': result['amount'][i]
- })
+ grouping_key = lambda o: (o.get(self.pipeline_by) or "Not Assigned", o[self.period_by]) # noqa
+ for (pipeline_by, period_by), rows in groupby(self.query_result, grouping_key):
+ self.grouped_data.append(
+ {
+ self.pipeline_by: pipeline_by,
+ self.period_by: period_by,
+ "amount": sum(flt(r["amount"]) for r in rows),
+ }
+ )
self.query_result = self.grouped_data
@@ -150,21 +129,22 @@ class SalesPipelineAnalytics(object):
def get_conditions(self):
conditions = []
- if self.filters.get('opportunity_source'):
- conditions.append({'source': self.filters.get('opportunity_source')})
+ if self.filters.get("opportunity_source"):
+ conditions.append({"source": self.filters.get("opportunity_source")})
- if self.filters.get('opportunity_type'):
- conditions.append({'opportunity_type': self.filters.get('opportunity_type')})
+ if self.filters.get("opportunity_type"):
+ conditions.append({"opportunity_type": self.filters.get("opportunity_type")})
- if self.filters.get('status'):
- conditions.append({'status': self.filters.get('status')})
+ if self.filters.get("status"):
+ conditions.append({"status": self.filters.get("status")})
- if self.filters.get('company'):
- conditions.append({'company': self.filters.get('company')})
+ if self.filters.get("company"):
+ conditions.append({"company": self.filters.get("company")})
- if self.filters.get('from_date') and self.filters.get('to_date'):
- conditions.append(['expected_closing', 'between',
- [self.filters.get('from_date'), self.filters.get('to_date')]])
+ if self.filters.get("from_date") and self.filters.get("to_date"):
+ conditions.append(
+ ["expected_closing", "between", [self.filters.get("from_date"), self.filters.get("to_date")]]
+ )
return conditions
@@ -175,49 +155,36 @@ class SalesPipelineAnalytics(object):
self.append_to_dataset(datasets)
for column in self.columns:
- if column['fieldname'] != 'opportunity_owner' and column['fieldname'] != 'sales_stage':
- labels.append(column['fieldname'])
+ if column["fieldname"] != "opportunity_owner" and column["fieldname"] != "sales_stage":
+ labels.append(column["fieldname"])
- self.chart = {
- 'data':{
- 'labels': labels,
- 'datasets': datasets
- },
- 'type':'line'
- }
+ self.chart = {"data": {"labels": labels, "datasets": datasets}, "type": "line"}
return self.chart
def get_periodic_data(self):
self.periodic_data = frappe._dict()
- based_on = {
- 'Number': 'count',
- 'Amount': 'amount'
- }[self.filters.get('based_on')]
+ based_on = {"Number": "count", "Amount": "amount"}[self.filters.get("based_on")]
- pipeline_by = {
- 'Owner': 'opportunity_owner',
- 'Sales Stage': 'sales_stage'
- }[self.filters.get('pipeline_by')]
+ pipeline_by = {"Owner": "opportunity_owner", "Sales Stage": "sales_stage"}[
+ self.filters.get("pipeline_by")
+ ]
- frequency = {
- 'Monthly': 'month',
- 'Quarterly': 'quarter'
- }[self.filters.get('range')]
+ frequency = {"Monthly": "month", "Quarterly": "quarter"}[self.filters.get("range")]
for info in self.query_result:
- if self.filters.get('range') == 'Monthly':
+ if self.filters.get("range") == "Monthly":
period = info.get(frequency)
- if self.filters.get('range') == 'Quarterly':
+ if self.filters.get("range") == "Quarterly":
period = f'Q{cint(info.get("quarter"))}'
value = info.get(pipeline_by)
count_or_amount = info.get(based_on)
- if self.filters.get('pipeline_by') == 'Owner':
- if value == 'Not Assigned' or value == '[]' or value is None:
- assigned_to = ['Not Assigned']
+ if self.filters.get("pipeline_by") == "Owner":
+ if value == "Not Assigned" or value == "[]" or value is None:
+ assigned_to = ["Not Assigned"]
else:
assigned_to = json.loads(value)
self.check_for_assigned_to(period, value, count_or_amount, assigned_to, info)
@@ -228,9 +195,9 @@ class SalesPipelineAnalytics(object):
def set_formatted_data(self, period, value, count_or_amount, assigned_to):
if assigned_to:
if len(assigned_to) > 1:
- if self.filters.get('assigned_to'):
+ if self.filters.get("assigned_to"):
for user in assigned_to:
- if self.filters.get('assigned_to') == user:
+ if self.filters.get("assigned_to") == user:
value = user
self.periodic_data.setdefault(value, frappe._dict()).setdefault(period, 0)
self.periodic_data[value][period] += count_or_amount
@@ -249,40 +216,34 @@ class SalesPipelineAnalytics(object):
self.periodic_data[value][period] += count_or_amount
def check_for_assigned_to(self, period, value, count_or_amount, assigned_to, info):
- if self.filters.get('assigned_to'):
- for data in json.loads(info.get('opportunity_owner')):
- if data == self.filters.get('assigned_to'):
+ if self.filters.get("assigned_to"):
+ for data in json.loads(info.get("opportunity_owner")):
+ if data == self.filters.get("assigned_to"):
self.set_formatted_data(period, data, count_or_amount, assigned_to)
else:
self.set_formatted_data(period, value, count_or_amount, assigned_to)
def get_month_list(self):
- month_list= []
+ month_list = []
current_date = date.today()
month_number = date.today().month
- for month in range(month_number,13):
- month_list.append(current_date.strftime('%B'))
+ for month in range(month_number, 13):
+ month_list.append(current_date.strftime("%B"))
current_date = current_date + relativedelta(months=1)
return month_list
def append_to_dataset(self, datasets):
- range_by = {
- 'Monthly': 'month',
- 'Quarterly': 'quarter'
- }[self.filters.get('range')]
+ range_by = {"Monthly": "month", "Quarterly": "quarter"}[self.filters.get("range")]
- based_on = {
- 'Amount': 'amount',
- 'Number': 'count'
- }[self.filters.get('based_on')]
+ based_on = {"Amount": "amount", "Number": "count"}[self.filters.get("based_on")]
- if self.filters.get('range') == 'Quarterly':
- frequency_list = [1,2,3,4]
+ if self.filters.get("range") == "Quarterly":
+ frequency_list = [1, 2, 3, 4]
count = [0] * 4
- if self.filters.get('range') == 'Monthly':
+ if self.filters.get("range") == "Monthly":
frequency_list = self.get_month_list()
count = [0] * 12
@@ -290,43 +251,43 @@ class SalesPipelineAnalytics(object):
for i in range(len(frequency_list)):
if info[range_by] == frequency_list[i]:
count[i] = count[i] + info[based_on]
- datasets.append({'name': based_on, 'values': count})
+ datasets.append({"name": based_on, "values": count})
def append_data(self, pipeline_by, period_by):
self.data = []
- for pipeline,period_data in self.periodic_data.items():
- row = {pipeline_by : pipeline}
+ for pipeline, period_data in self.periodic_data.items():
+ row = {pipeline_by: pipeline}
for info in self.query_result:
- if self.filters.get('range') == 'Monthly':
+ if self.filters.get("range") == "Monthly":
period = info.get(period_by)
- if self.filters.get('range') == 'Quarterly':
- period = f'Q{cint(info.get(period_by))}'
+ if self.filters.get("range") == "Quarterly":
+ period = f"Q{cint(info.get(period_by))}"
- count = period_data.get(period,0.0)
+ count = period_data.get(period, 0.0)
row[period] = count
self.data.append(row)
def get_default_currency(self):
- company = self.filters.get('company')
- return frappe.db.get_value('Company',company,['default_currency'])
+ company = self.filters.get("company")
+ return frappe.db.get_value("Company", company, ["default_currency"])
def get_currency_rate(self, from_currency, to_currency):
cacheobj = frappe.cache()
if cacheobj.get(from_currency):
- return flt(str(cacheobj.get(from_currency),'UTF-8'))
+ return flt(str(cacheobj.get(from_currency), "UTF-8"))
else:
value = get_exchange_rate(from_currency, to_currency)
cacheobj.set(from_currency, value)
- return flt(str(cacheobj.get(from_currency),'UTF-8'))
+ return flt(str(cacheobj.get(from_currency), "UTF-8"))
def convert_to_base_currency(self):
default_currency = self.get_default_currency()
for data in self.query_result:
- if data.get('currency') != default_currency:
- opportunity_currency = data.get('currency')
- value = self.get_currency_rate(opportunity_currency,default_currency)
- data['amount'] = data['amount'] * value
+ if data.get("currency") != default_currency:
+ opportunity_currency = data.get("currency")
+ value = self.get_currency_rate(opportunity_currency, default_currency)
+ data["amount"] = data["amount"] * value
diff --git a/erpnext/crm/report/sales_pipeline_analytics/test_sales_pipeline_analytics.py b/erpnext/crm/report/sales_pipeline_analytics/test_sales_pipeline_analytics.py
index 24c3839d2d9..02d82b637e8 100644
--- a/erpnext/crm/report/sales_pipeline_analytics/test_sales_pipeline_analytics.py
+++ b/erpnext/crm/report/sales_pipeline_analytics/test_sales_pipeline_analytics.py
@@ -22,217 +22,175 @@ class TestSalesPipelineAnalytics(unittest.TestCase):
def check_for_monthly_and_number(self):
filters = {
- 'pipeline_by':"Owner",
- 'range':"Monthly",
- 'based_on':"Number",
- 'status':"Open",
- 'opportunity_type':"Sales",
- 'company':"Best Test"
+ "pipeline_by": "Owner",
+ "range": "Monthly",
+ "based_on": "Number",
+ "status": "Open",
+ "opportunity_type": "Sales",
+ "company": "Best Test",
}
report = execute(filters)
- expected_data = [
- {
- 'opportunity_owner':'Not Assigned',
- 'August':1
- }
- ]
+ expected_data = [{"opportunity_owner": "Not Assigned", "August": 1}]
- self.assertEqual(expected_data,report[1])
+ self.assertEqual(expected_data, report[1])
filters = {
- 'pipeline_by':"Sales Stage",
- 'range':"Monthly",
- 'based_on':"Number",
- 'status':"Open",
- 'opportunity_type':"Sales",
- 'company':"Best Test"
+ "pipeline_by": "Sales Stage",
+ "range": "Monthly",
+ "based_on": "Number",
+ "status": "Open",
+ "opportunity_type": "Sales",
+ "company": "Best Test",
}
report = execute(filters)
- expected_data = [
- {
- 'sales_stage':'Prospecting',
- 'August':1
- }
- ]
+ expected_data = [{"sales_stage": "Prospecting", "August": 1}]
- self.assertEqual(expected_data,report[1])
+ self.assertEqual(expected_data, report[1])
def check_for_monthly_and_amount(self):
filters = {
- 'pipeline_by':"Owner",
- 'range':"Monthly",
- 'based_on':"Amount",
- 'status':"Open",
- 'opportunity_type':"Sales",
- 'company':"Best Test"
+ "pipeline_by": "Owner",
+ "range": "Monthly",
+ "based_on": "Amount",
+ "status": "Open",
+ "opportunity_type": "Sales",
+ "company": "Best Test",
}
report = execute(filters)
- expected_data = [
- {
- 'opportunity_owner':'Not Assigned',
- 'August':150000
- }
- ]
+ expected_data = [{"opportunity_owner": "Not Assigned", "August": 150000}]
- self.assertEqual(expected_data,report[1])
+ self.assertEqual(expected_data, report[1])
filters = {
- 'pipeline_by':"Sales Stage",
- 'range':"Monthly",
- 'based_on':"Amount",
- 'status':"Open",
- 'opportunity_type':"Sales",
- 'company':"Best Test"
+ "pipeline_by": "Sales Stage",
+ "range": "Monthly",
+ "based_on": "Amount",
+ "status": "Open",
+ "opportunity_type": "Sales",
+ "company": "Best Test",
}
report = execute(filters)
- expected_data = [
- {
- 'sales_stage':'Prospecting',
- 'August':150000
- }
- ]
+ expected_data = [{"sales_stage": "Prospecting", "August": 150000}]
- self.assertEqual(expected_data,report[1])
+ self.assertEqual(expected_data, report[1])
def check_for_quarterly_and_number(self):
filters = {
- 'pipeline_by':"Owner",
- 'range':"Quarterly",
- 'based_on':"Number",
- 'status':"Open",
- 'opportunity_type':"Sales",
- 'company':"Best Test"
+ "pipeline_by": "Owner",
+ "range": "Quarterly",
+ "based_on": "Number",
+ "status": "Open",
+ "opportunity_type": "Sales",
+ "company": "Best Test",
}
report = execute(filters)
- expected_data = [
- {
- 'opportunity_owner':'Not Assigned',
- 'Q3':1
- }
- ]
+ expected_data = [{"opportunity_owner": "Not Assigned", "Q3": 1}]
- self.assertEqual(expected_data,report[1])
+ self.assertEqual(expected_data, report[1])
filters = {
- 'pipeline_by':"Sales Stage",
- 'range':"Quarterly",
- 'based_on':"Number",
- 'status':"Open",
- 'opportunity_type':"Sales",
- 'company':"Best Test"
+ "pipeline_by": "Sales Stage",
+ "range": "Quarterly",
+ "based_on": "Number",
+ "status": "Open",
+ "opportunity_type": "Sales",
+ "company": "Best Test",
}
report = execute(filters)
- expected_data = [
- {
- 'sales_stage':'Prospecting',
- 'Q3':1
- }
- ]
+ expected_data = [{"sales_stage": "Prospecting", "Q3": 1}]
- self.assertEqual(expected_data,report[1])
+ self.assertEqual(expected_data, report[1])
def check_for_quarterly_and_amount(self):
filters = {
- 'pipeline_by':"Owner",
- 'range':"Quarterly",
- 'based_on':"Amount",
- 'status':"Open",
- 'opportunity_type':"Sales",
- 'company':"Best Test"
+ "pipeline_by": "Owner",
+ "range": "Quarterly",
+ "based_on": "Amount",
+ "status": "Open",
+ "opportunity_type": "Sales",
+ "company": "Best Test",
}
report = execute(filters)
- expected_data = [
- {
- 'opportunity_owner':'Not Assigned',
- 'Q3':150000
- }
- ]
+ expected_data = [{"opportunity_owner": "Not Assigned", "Q3": 150000}]
- self.assertEqual(expected_data,report[1])
+ self.assertEqual(expected_data, report[1])
filters = {
- 'pipeline_by':"Sales Stage",
- 'range':"Quarterly",
- 'based_on':"Amount",
- 'status':"Open",
- 'opportunity_type':"Sales",
- 'company':"Best Test"
+ "pipeline_by": "Sales Stage",
+ "range": "Quarterly",
+ "based_on": "Amount",
+ "status": "Open",
+ "opportunity_type": "Sales",
+ "company": "Best Test",
}
report = execute(filters)
- expected_data = [
- {
- 'sales_stage':'Prospecting',
- 'Q3':150000
- }
- ]
+ expected_data = [{"sales_stage": "Prospecting", "Q3": 150000}]
- self.assertEqual(expected_data,report[1])
+ self.assertEqual(expected_data, report[1])
def check_for_all_filters(self):
filters = {
- 'pipeline_by':"Owner",
- 'range':"Monthly",
- 'based_on':"Number",
- 'status':"Open",
- 'opportunity_type':"Sales",
- 'company':"Best Test",
- 'opportunity_source':'Cold Calling',
- 'from_date': '2021-08-01',
- 'to_date':'2021-08-31'
+ "pipeline_by": "Owner",
+ "range": "Monthly",
+ "based_on": "Number",
+ "status": "Open",
+ "opportunity_type": "Sales",
+ "company": "Best Test",
+ "opportunity_source": "Cold Calling",
+ "from_date": "2021-08-01",
+ "to_date": "2021-08-31",
}
report = execute(filters)
- expected_data = [
- {
- 'opportunity_owner':'Not Assigned',
- 'August': 1
- }
- ]
+ expected_data = [{"opportunity_owner": "Not Assigned", "August": 1}]
+
+ self.assertEqual(expected_data, report[1])
- self.assertEqual(expected_data,report[1])
def create_company():
- doc = frappe.db.exists('Company','Best Test')
+ doc = frappe.db.exists("Company", "Best Test")
if not doc:
- doc = frappe.new_doc('Company')
- doc.company_name = 'Best Test'
+ doc = frappe.new_doc("Company")
+ doc.company_name = "Best Test"
doc.default_currency = "INR"
doc.insert()
+
def create_customer():
- doc = frappe.db.exists("Customer","_Test NC")
+ doc = frappe.db.exists("Customer", "_Test NC")
if not doc:
doc = frappe.new_doc("Customer")
- doc.customer_name = '_Test NC'
+ doc.customer_name = "_Test NC"
doc.insert()
+
def create_opportunity():
- doc = frappe.db.exists({"doctype":"Opportunity","party_name":"_Test NC"})
+ doc = frappe.db.exists({"doctype": "Opportunity", "party_name": "_Test NC"})
if not doc:
doc = frappe.new_doc("Opportunity")
doc.opportunity_from = "Customer"
- customer_name = frappe.db.get_value("Customer",{"customer_name":'_Test NC'},['customer_name'])
+ customer_name = frappe.db.get_value("Customer", {"customer_name": "_Test NC"}, ["customer_name"])
doc.party_name = customer_name
doc.opportunity_amount = 150000
doc.source = "Cold Calling"
doc.currency = "INR"
doc.expected_closing = "2021-08-31"
- doc.company = 'Best Test'
- doc.insert()
\ No newline at end of file
+ doc.company = "Best Test"
+ doc.insert()
diff --git a/erpnext/crm/utils.py b/erpnext/crm/utils.py
index a4576a287e1..5783b2c6611 100644
--- a/erpnext/crm/utils.py
+++ b/erpnext/crm/utils.py
@@ -9,12 +9,16 @@ def update_lead_phone_numbers(contact, method):
if len(contact.phone_nos) > 1:
# get the default phone number
- primary_phones = [phone_doc.phone for phone_doc in contact.phone_nos if phone_doc.is_primary_phone]
+ primary_phones = [
+ phone_doc.phone for phone_doc in contact.phone_nos if phone_doc.is_primary_phone
+ ]
if primary_phones:
phone = primary_phones[0]
# get the default mobile number
- primary_mobile_nos = [phone_doc.phone for phone_doc in contact.phone_nos if phone_doc.is_primary_mobile_no]
+ primary_mobile_nos = [
+ phone_doc.phone for phone_doc in contact.phone_nos if phone_doc.is_primary_mobile_no
+ ]
if primary_mobile_nos:
mobile_no = primary_mobile_nos[0]
@@ -22,15 +26,21 @@ def update_lead_phone_numbers(contact, method):
lead.db_set("phone", phone)
lead.db_set("mobile_no", mobile_no)
+
def copy_comments(doctype, docname, doc):
- comments = frappe.db.get_values("Comment", filters={"reference_doctype": doctype, "reference_name": docname, "comment_type": "Comment"}, fieldname="*")
+ comments = frappe.db.get_values(
+ "Comment",
+ filters={"reference_doctype": doctype, "reference_name": docname, "comment_type": "Comment"},
+ fieldname="*",
+ )
for comment in comments:
- comment = frappe.get_doc(comment.update({"doctype":"Comment"}))
+ comment = frappe.get_doc(comment.update({"doctype": "Comment"}))
comment.name = None
comment.reference_doctype = doc.doctype
comment.reference_name = doc.name
comment.insert()
+
def add_link_in_communication(doctype, docname, doc):
communication_list = get_linked_communication_list(doctype, docname)
@@ -38,13 +48,15 @@ def add_link_in_communication(doctype, docname, doc):
communication_doc = frappe.get_doc("Communication", communication)
communication_doc.add_link(doc.doctype, doc.name, autosave=True)
+
def get_linked_communication_list(doctype, docname):
- communications = frappe.get_all("Communication", filters={"reference_doctype": doctype, "reference_name": docname}, pluck='name')
- communication_links = frappe.get_all('Communication Link',
- {
- "link_doctype": doctype,
- "link_name": docname,
- "parent": ("not in", communications)
- }, pluck="parent")
+ communications = frappe.get_all(
+ "Communication", filters={"reference_doctype": doctype, "reference_name": docname}, pluck="name"
+ )
+ communication_links = frappe.get_all(
+ "Communication Link",
+ {"link_doctype": doctype, "link_name": docname, "parent": ("not in", communications)},
+ pluck="parent",
+ )
return communications + communication_links
diff --git a/erpnext/domains/distribution.py b/erpnext/domains/distribution.py
index 020ab3b83b1..5953c4e2a42 100644
--- a/erpnext/domains/distribution.py
+++ b/erpnext/domains/distribution.py
@@ -1,18 +1,16 @@
data = {
- 'desktop_icons': [
- 'Item',
- 'Customer',
- 'Supplier',
- 'Lead',
- 'Sales Order',
- 'Purchase Order',
- 'Task',
- 'Sales Invoice',
- 'CRM',
- 'ToDo'
+ "desktop_icons": [
+ "Item",
+ "Customer",
+ "Supplier",
+ "Lead",
+ "Sales Order",
+ "Purchase Order",
+ "Task",
+ "Sales Invoice",
+ "CRM",
+ "ToDo",
],
- 'set_value': [
- ['Stock Settings', None, 'show_barcode_field', 1]
- ],
- 'default_portal_role': 'Customer'
+ "set_value": [["Stock Settings", None, "show_barcode_field", 1]],
+ "default_portal_role": "Customer",
}
diff --git a/erpnext/domains/education.py b/erpnext/domains/education.py
index 11ea9b4022e..23b8258baf6 100644
--- a/erpnext/domains/education.py
+++ b/erpnext/domains/education.py
@@ -1,27 +1,19 @@
data = {
- 'desktop_icons': [
- 'Student',
- 'Program',
- 'Course',
- 'Student Group',
- 'Instructor',
- 'Fees',
- 'Task',
- 'ToDo',
- 'Education',
- 'Student Attendance Tool',
- 'Student Applicant'
+ "desktop_icons": [
+ "Student",
+ "Program",
+ "Course",
+ "Student Group",
+ "Instructor",
+ "Fees",
+ "Task",
+ "ToDo",
+ "Education",
+ "Student Attendance Tool",
+ "Student Applicant",
],
- 'default_portal_role': 'Student',
- 'restricted_roles': [
- 'Student',
- 'Instructor',
- 'Academics User',
- 'Education Manager'
- ],
- 'modules': [
- 'Education'
- ],
- 'on_setup': 'erpnext.education.setup.setup_education'
-
+ "default_portal_role": "Student",
+ "restricted_roles": ["Student", "Instructor", "Academics User", "Education Manager"],
+ "modules": ["Education"],
+ "on_setup": "erpnext.education.setup.setup_education",
}
diff --git a/erpnext/domains/manufacturing.py b/erpnext/domains/manufacturing.py
index 96ce1945a48..08ed3cf92b5 100644
--- a/erpnext/domains/manufacturing.py
+++ b/erpnext/domains/manufacturing.py
@@ -1,22 +1,25 @@
data = {
- 'desktop_icons': [
- 'Item',
- 'BOM',
- 'Customer',
- 'Supplier',
- 'Sales Order',
- 'Purchase Order',
- 'Work Order',
- 'Task',
- 'Accounts',
- 'HR',
- 'ToDo'
- ],
- 'properties': [
- {'doctype': 'Item', 'fieldname': 'manufacturing', 'property': 'collapsible_depends_on', 'value': 'is_stock_item'},
+ "desktop_icons": [
+ "Item",
+ "BOM",
+ "Customer",
+ "Supplier",
+ "Sales Order",
+ "Purchase Order",
+ "Work Order",
+ "Task",
+ "Accounts",
+ "HR",
+ "ToDo",
],
- 'set_value': [
- ['Stock Settings', None, 'show_barcode_field', 1]
+ "properties": [
+ {
+ "doctype": "Item",
+ "fieldname": "manufacturing",
+ "property": "collapsible_depends_on",
+ "value": "is_stock_item",
+ },
],
- 'default_portal_role': 'Customer'
+ "set_value": [["Stock Settings", None, "show_barcode_field", 1]],
+ "default_portal_role": "Customer",
}
diff --git a/erpnext/domains/non_profit.py b/erpnext/domains/non_profit.py
deleted file mode 100644
index d9fc5e5df01..00000000000
--- a/erpnext/domains/non_profit.py
+++ /dev/null
@@ -1,22 +0,0 @@
-data = {
- 'desktop_icons': [
- 'Non Profit',
- 'Member',
- 'Donor',
- 'Volunteer',
- 'Grant Application',
- 'Accounts',
- 'Buying',
- 'HR',
- 'ToDo'
- ],
- 'restricted_roles': [
- 'Non Profit Manager',
- 'Non Profit Member',
- 'Non Profit Portal User'
- ],
- 'modules': [
- 'Non Profit'
- ],
- 'default_portal_role': 'Non Profit Manager'
-}
diff --git a/erpnext/domains/retail.py b/erpnext/domains/retail.py
index 07b2e2781ec..8ea85f10407 100644
--- a/erpnext/domains/retail.py
+++ b/erpnext/domains/retail.py
@@ -1,16 +1,14 @@
data = {
- 'desktop_icons': [
- 'POS',
- 'Item',
- 'Customer',
- 'Sales Invoice',
- 'Purchase Order',
- 'Accounts',
- 'Task',
- 'ToDo'
+ "desktop_icons": [
+ "POS",
+ "Item",
+ "Customer",
+ "Sales Invoice",
+ "Purchase Order",
+ "Accounts",
+ "Task",
+ "ToDo",
],
- 'set_value': [
- ['Stock Settings', None, 'show_barcode_field', 1]
- ],
- 'default_portal_role': 'Customer'
+ "set_value": [["Stock Settings", None, "show_barcode_field", 1]],
+ "default_portal_role": "Customer",
}
diff --git a/erpnext/domains/services.py b/erpnext/domains/services.py
index 6035afbcf04..8595c69aff4 100644
--- a/erpnext/domains/services.py
+++ b/erpnext/domains/services.py
@@ -1,19 +1,17 @@
data = {
- 'desktop_icons': [
- 'Project',
- 'Timesheet',
- 'Customer',
- 'Sales Order',
- 'Sales Invoice',
- 'CRM',
- 'Task',
- 'Expense Claim',
- 'Employee',
- 'HR',
- 'ToDo'
+ "desktop_icons": [
+ "Project",
+ "Timesheet",
+ "Customer",
+ "Sales Order",
+ "Sales Invoice",
+ "CRM",
+ "Task",
+ "Expense Claim",
+ "Employee",
+ "HR",
+ "ToDo",
],
- 'set_value': [
- ['Stock Settings', None, 'show_barcode_field', 0]
- ],
- 'default_portal_role': 'Customer'
+ "set_value": [["Stock Settings", None, "show_barcode_field", 0]],
+ "default_portal_role": "Customer",
}
diff --git a/erpnext/e_commerce/api.py b/erpnext/e_commerce/api.py
index 43cb36ca2e2..179829c2b74 100644
--- a/erpnext/e_commerce/api.py
+++ b/erpnext/e_commerce/api.py
@@ -15,16 +15,16 @@ from erpnext.setup.doctype.item_group.item_group import get_child_groups_for_web
@frappe.whitelist(allow_guest=True)
def get_product_filter_data(query_args=None):
"""
- Returns filtered products and discount filters.
- :param query_args (dict): contains filters to get products list
+ Returns filtered products and discount filters.
+ :param query_args (dict): contains filters to get products list
- Query Args filters:
- search (str): Search Term.
- field_filters (dict): Keys include item_group, brand, etc.
- attribute_filters(dict): Keys include Color, Size, etc.
- start (int): Offset items by
- item_group (str): Valid Item Group
- from_filters (bool): Set as True to jump to page 1
+ Query Args filters:
+ search (str): Search Term.
+ field_filters (dict): Keys include item_group, brand, etc.
+ attribute_filters(dict): Keys include Color, Size, etc.
+ start (int): Offset items by
+ item_group (str): Valid Item Group
+ from_filters (bool): Set as True to jump to page 1
"""
if isinstance(query_args, str):
query_args = json.loads(query_args)
@@ -48,17 +48,12 @@ def get_product_filter_data(query_args=None):
sub_categories = []
if item_group:
- field_filters['item_group'] = item_group
sub_categories = get_child_groups_for_website(item_group, immediate=True)
engine = ProductQuery()
try:
result = engine.query(
- attribute_filters,
- field_filters,
- search_term=search,
- start=start,
- item_group=item_group
+ attribute_filters, field_filters, search_term=search, start=start, item_group=item_group
)
except Exception:
traceback = frappe.get_traceback()
@@ -78,9 +73,10 @@ def get_product_filter_data(query_args=None):
"filters": filters,
"settings": engine.settings,
"sub_categories": sub_categories,
- "items_count": result["items_count"]
+ "items_count": result["items_count"],
}
+
@frappe.whitelist(allow_guest=True)
def get_guest_redirect_on_action():
- return frappe.db.get_single_value("E Commerce Settings", "redirect_on_action")
\ No newline at end of file
+ return frappe.db.get_single_value("E Commerce Settings", "redirect_on_action")
diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json
index d5fb9697f89..e6f08f708a8 100644
--- a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json
+++ b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json
@@ -47,7 +47,7 @@
"item_search_settings_section",
"redisearch_warning",
"search_index_fields",
- "show_categories_in_search_autocomplete",
+ "is_redisearch_enabled",
"is_redisearch_loaded",
"shop_by_category_section",
"slideshow",
@@ -293,6 +293,7 @@
"fieldname": "search_index_fields",
"fieldtype": "Small Text",
"label": "Search Index Fields",
+ "mandatory_depends_on": "is_redisearch_enabled",
"read_only_depends_on": "eval:!doc.is_redisearch_loaded"
},
{
@@ -301,13 +302,6 @@
"fieldtype": "Section Break",
"label": "Item Search Settings"
},
- {
- "default": "1",
- "fieldname": "show_categories_in_search_autocomplete",
- "fieldtype": "Check",
- "label": "Show Categories in Search Autocomplete",
- "read_only_depends_on": "eval:!doc.is_redisearch_loaded"
- },
{
"default": "0",
"fieldname": "is_redisearch_loaded",
@@ -365,12 +359,19 @@
"fieldname": "show_price_in_quotation",
"fieldtype": "Check",
"label": "Show Price in Quotation"
+ },
+ {
+ "default": "0",
+ "fieldname": "is_redisearch_enabled",
+ "fieldtype": "Check",
+ "label": "Enable Redisearch",
+ "read_only_depends_on": "eval:!doc.is_redisearch_loaded"
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2021-09-02 14:02:44.785824",
+ "modified": "2022-04-01 18:35:56.106756",
"modified_by": "Administrator",
"module": "E-commerce",
"name": "E Commerce Settings",
@@ -389,5 +390,6 @@
],
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py
index dd7b114289d..f85667e1b20 100644
--- a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py
+++ b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py
@@ -9,16 +9,21 @@ from frappe.utils import comma_and, flt, unique
from erpnext.e_commerce.redisearch_utils import (
create_website_items_index,
+ define_autocomplete_dictionary,
get_indexable_web_fields,
is_search_module_loaded,
)
-class ShoppingCartSetupError(frappe.ValidationError): pass
+class ShoppingCartSetupError(frappe.ValidationError):
+ pass
+
class ECommerceSettings(Document):
def onload(self):
self.get("__onload").quotation_series = frappe.get_meta("Quotation").get_options("naming_series")
+
+ # flag >> if redisearch is installed and loaded
self.is_redisearch_loaded = is_search_module_loaded()
def validate(self):
@@ -32,16 +37,36 @@ class ECommerceSettings(Document):
frappe.clear_document_cache("E Commerce Settings", "E Commerce Settings")
+ self.is_redisearch_enabled_pre_save = frappe.db.get_single_value(
+ "E Commerce Settings", "is_redisearch_enabled"
+ )
+
+ def after_save(self):
+ self.create_redisearch_indexes()
+
+ def create_redisearch_indexes(self):
+ # if redisearch is enabled (value changed) create indexes and dictionary
+ value_changed = self.is_redisearch_enabled != self.is_redisearch_enabled_pre_save
+ if self.is_redisearch_loaded and self.is_redisearch_enabled and value_changed:
+ define_autocomplete_dictionary()
+ create_website_items_index()
+
def validate_field_filters(self):
if not (self.enable_field_filters and self.filter_fields):
return
item_meta = frappe.get_meta("Item")
- valid_fields = [df.fieldname for df in item_meta.fields if df.fieldtype in ["Link", "Table MultiSelect"]]
+ valid_fields = [
+ df.fieldname for df in item_meta.fields if df.fieldtype in ["Link", "Table MultiSelect"]
+ ]
for f in self.filter_fields:
if f.fieldname not in valid_fields:
- frappe.throw(_("Filter Fields Row #{0}: Fieldname {1} must be of type 'Link' or 'Table MultiSelect'").format(f.idx, f.fieldname))
+ frappe.throw(
+ _(
+ "Filter Fields Row #{0}: Fieldname {1} must be of type 'Link' or 'Table MultiSelect'"
+ ).format(f.idx, f.fieldname)
+ )
def validate_attribute_filters(self):
if not (self.enable_attribute_filters and self.filter_attributes):
@@ -58,8 +83,8 @@ class ECommerceSettings(Document):
if not self.search_index_fields:
return
- fields = self.search_index_fields.replace(' ', '')
- fields = unique(fields.strip(',').split(',')) # Remove extra ',' and remove duplicates
+ fields = self.search_index_fields.replace(" ", "")
+ fields = unique(fields.strip(",").split(",")) # Remove extra ',' and remove duplicates
# All fields should be indexable
allowed_indexable_fields = get_indexable_web_fields()
@@ -70,18 +95,22 @@ class ECommerceSettings(Document):
invalid_fields = comma_and(invalid_fields)
if num_invalid_fields > 1:
- frappe.throw(_("{0} are not valid options for Search Index Field.").format(frappe.bold(invalid_fields)))
+ frappe.throw(
+ _("{0} are not valid options for Search Index Field.").format(frappe.bold(invalid_fields))
+ )
else:
- frappe.throw(_("{0} is not a valid option for Search Index Field.").format(frappe.bold(invalid_fields)))
+ frappe.throw(
+ _("{0} is not a valid option for Search Index Field.").format(frappe.bold(invalid_fields))
+ )
- self.search_index_fields = ','.join(fields)
+ self.search_index_fields = ",".join(fields)
def validate_price_list_exchange_rate(self):
"Check if exchange rate exists for Price List currency (to Company's currency)."
from erpnext.setup.utils import get_exchange_rate
if not self.enabled or not self.company or not self.price_list:
- return # this function is also called from hooks, check values again
+ return # this function is also called from hooks, check values again
company_currency = frappe.get_cached_value("Company", self.company, "default_currency")
price_list_currency = frappe.db.get_value("Price List", self.price_list, "currency")
@@ -105,12 +134,13 @@ class ECommerceSettings(Document):
frappe.throw(_(msg), title=_("Missing"), exc=ShoppingCartSetupError)
def validate_tax_rule(self):
- if not frappe.db.get_value("Tax Rule", {"use_for_shopping_cart" : 1}, "name"):
+ if not frappe.db.get_value("Tax Rule", {"use_for_shopping_cart": 1}, "name"):
frappe.throw(frappe._("Set Tax Rule for shopping cart"), ShoppingCartSetupError)
def get_tax_master(self, billing_territory):
- tax_master = self.get_name_from_territory(billing_territory, "sales_taxes_and_charges_masters",
- "sales_taxes_and_charges_master")
+ tax_master = self.get_name_from_territory(
+ billing_territory, "sales_taxes_and_charges_masters", "sales_taxes_and_charges_master"
+ )
return tax_master and tax_master[0] or None
def get_shipping_rules(self, shipping_territory):
@@ -127,25 +157,28 @@ class ECommerceSettings(Document):
if not (new_fields == old_fields):
create_website_items_index()
+
def validate_cart_settings(doc=None, method=None):
frappe.get_doc("E Commerce Settings", "E Commerce Settings").run_method("validate")
-def get_shopping_cart_settings():
- if not getattr(frappe.local, "shopping_cart_settings", None):
- frappe.local.shopping_cart_settings = frappe.get_doc("E Commerce Settings", "E Commerce Settings")
- return frappe.local.shopping_cart_settings
+def get_shopping_cart_settings():
+ return frappe.get_cached_doc("E Commerce Settings")
+
@frappe.whitelist(allow_guest=True)
def is_cart_enabled():
return get_shopping_cart_settings().enabled
+
def show_quantity_in_website():
return get_shopping_cart_settings().show_quantity_in_website
+
def check_shopping_cart_enabled():
if not get_shopping_cart_settings().enabled:
frappe.throw(_("You need to enable Shopping Cart"), ShoppingCartSetupError)
+
def show_attachments():
return get_shopping_cart_settings().show_attachments
diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/test_e_commerce_settings.py b/erpnext/e_commerce/doctype/e_commerce_settings/test_e_commerce_settings.py
index 86cef30d985..c4c958bd443 100644
--- a/erpnext/e_commerce/doctype/e_commerce_settings/test_e_commerce_settings.py
+++ b/erpnext/e_commerce/doctype/e_commerce_settings/test_e_commerce_settings.py
@@ -15,8 +15,7 @@ class TestECommerceSettings(unittest.TestCase):
frappe.db.sql("""delete from `tabSingles` where doctype="Shipping Cart Settings" """)
def get_cart_settings(self):
- return frappe.get_doc({"doctype": "E Commerce Settings",
- "company": "_Test Company"})
+ return frappe.get_doc({"doctype": "E Commerce Settings", "company": "_Test Company"})
# NOTE: Exchangrate API has all enabled currencies that ERPNext supports.
# We aren't checking just currency exchange record anymore
@@ -41,7 +40,7 @@ class TestECommerceSettings(unittest.TestCase):
def test_tax_rule_validation(self):
frappe.db.sql("update `tabTax Rule` set use_for_shopping_cart = 0")
- frappe.db.commit() # nosemgrep
+ frappe.db.commit() # nosemgrep
cart_settings = self.get_cart_settings()
cart_settings.enabled = 1
@@ -50,6 +49,7 @@ class TestECommerceSettings(unittest.TestCase):
frappe.db.sql("update `tabTax Rule` set use_for_shopping_cart = 1")
+
def setup_e_commerce_settings(values_dict):
"Accepts a dict of values that updates E Commerce Settings."
if not values_dict:
@@ -59,4 +59,5 @@ def setup_e_commerce_settings(values_dict):
doc.update(values_dict)
doc.save()
+
test_dependencies = ["Tax Rule"]
diff --git a/erpnext/e_commerce/doctype/item_review/item_review.py b/erpnext/e_commerce/doctype/item_review/item_review.py
index 966ec350e75..3e540e38853 100644
--- a/erpnext/e_commerce/doctype/item_review/item_review.py
+++ b/erpnext/e_commerce/doctype/item_review/item_review.py
@@ -18,6 +18,7 @@ from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import (
class UnverifiedReviewer(frappe.ValidationError):
pass
+
class ItemReview(Document):
def after_insert(self):
# regenerate cache on review creation
@@ -54,12 +55,13 @@ def get_item_reviews(web_item, start=0, end=10, data=None):
return data
+
def get_queried_reviews(web_item, start=0, end=10, data=None):
"""
- Query Website Item wise reviews and cache if needed.
- Cache stores only first page of reviews i.e. 10 reviews maximum.
- Returns:
- dict: Containing reviews, average ratings, % of reviews per rating and total reviews.
+ Query Website Item wise reviews and cache if needed.
+ Cache stores only first page of reviews i.e. 10 reviews maximum.
+ Returns:
+ dict: Containing reviews, average ratings, % of reviews per rating and total reviews.
"""
if not data:
data = frappe._dict()
@@ -69,13 +71,13 @@ def get_queried_reviews(web_item, start=0, end=10, data=None):
filters={"website_item": web_item},
fields=["*"],
limit_start=start,
- limit_page_length=end
+ limit_page_length=end,
)
rating_data = frappe.db.get_all(
"Item Review",
filters={"website_item": web_item},
- fields=["avg(rating) as average, count(*) as total"]
+ fields=["avg(rating) as average, count(*) as total"],
)[0]
data.average_rating = flt(rating_data.average, 1)
@@ -83,11 +85,9 @@ def get_queried_reviews(web_item, start=0, end=10, data=None):
# get % of reviews per rating
reviews_per_rating = []
- for i in range(1,6):
+ for i in range(1, 6):
count = frappe.db.get_all(
- "Item Review",
- filters={"website_item": web_item, "rating": i},
- fields=["count(*) as count"]
+ "Item Review", filters={"website_item": web_item, "rating": i}, fields=["count(*) as count"]
)[0].count
percent = flt((count / rating_data.total or 1) * 100, 0) if count else 0
@@ -98,40 +98,45 @@ def get_queried_reviews(web_item, start=0, end=10, data=None):
return data
+
def set_reviews_in_cache(web_item, reviews_dict):
frappe.cache().hset("item_reviews", web_item, reviews_dict)
+
@frappe.whitelist()
def add_item_review(web_item, title, rating, comment=None):
- """ Add an Item Review by a user if non-existent. """
+ """Add an Item Review by a user if non-existent."""
if frappe.session.user == "Guest":
# guest user should not reach here ideally in the case they do via an API, throw error
frappe.throw(_("You are not verified to write a review yet."), exc=UnverifiedReviewer)
if not frappe.db.exists("Item Review", {"user": frappe.session.user, "website_item": web_item}):
- doc = frappe.get_doc({
- "doctype": "Item Review",
- "user": frappe.session.user,
- "customer": get_customer(),
- "website_item": web_item,
- "item": frappe.db.get_value("Website Item", web_item, "item_code"),
- "review_title": title,
- "rating": rating,
- "comment": comment
- })
+ doc = frappe.get_doc(
+ {
+ "doctype": "Item Review",
+ "user": frappe.session.user,
+ "customer": get_customer(),
+ "website_item": web_item,
+ "item": frappe.db.get_value("Website Item", web_item, "item_code"),
+ "review_title": title,
+ "rating": rating,
+ "comment": comment,
+ }
+ )
doc.published_on = datetime.today().strftime("%d %B %Y")
doc.insert()
+
def get_customer(silent=False):
"""
- silent: Return customer if exists else return nothing. Dont throw error.
+ silent: Return customer if exists else return nothing. Dont throw error.
"""
user = frappe.session.user
contact_name = get_contact_name(user)
customer = None
if contact_name:
- contact = frappe.get_doc('Contact', contact_name)
+ contact = frappe.get_doc("Contact", contact_name)
for link in contact.links:
if link.link_doctype == "Customer":
customer = link.link_name
@@ -143,5 +148,6 @@ def get_customer(silent=False):
return None
else:
# should not reach here unless via an API
- frappe.throw(_("You are not a verified customer yet. Please contact us to proceed."),
- exc=UnverifiedReviewer)
+ frappe.throw(
+ _("You are not a verified customer yet. Please contact us to proceed."), exc=UnverifiedReviewer
+ )
diff --git a/erpnext/e_commerce/doctype/website_item/test_website_item.py b/erpnext/e_commerce/doctype/website_item/test_website_item.py
index b39e4dfb514..828c655e793 100644
--- a/erpnext/e_commerce/doctype/website_item/test_website_item.py
+++ b/erpnext/e_commerce/doctype/website_item/test_website_item.py
@@ -19,17 +19,23 @@ from erpnext.stock.doctype.item.item import DataValidationError
from erpnext.stock.doctype.item.test_item import make_item
WEBITEM_DESK_TESTS = ("test_website_item_desk_item_sync", "test_publish_variant_and_template")
-WEBITEM_PRICE_TESTS = ('test_website_item_price_for_logged_in_user', 'test_website_item_price_for_guest_user')
+WEBITEM_PRICE_TESTS = (
+ "test_website_item_price_for_logged_in_user",
+ "test_website_item_price_for_guest_user",
+)
+
class TestWebsiteItem(unittest.TestCase):
@classmethod
def setUpClass(cls):
- setup_e_commerce_settings({
- "company": "_Test Company",
- "enabled": 1,
- "default_customer_group": "_Test Customer Group",
- "price_list": "_Test Price List India"
- })
+ setup_e_commerce_settings(
+ {
+ "company": "_Test Company",
+ "enabled": 1,
+ "default_customer_group": "_Test Customer Group",
+ "price_list": "_Test Price List India",
+ }
+ )
@classmethod
def tearDownClass(cls):
@@ -37,40 +43,42 @@ class TestWebsiteItem(unittest.TestCase):
def setUp(self):
if self._testMethodName in WEBITEM_DESK_TESTS:
- make_item("Test Web Item", {
- "has_variant": 1,
- "variant_based_on": "Item Attribute",
- "attributes": [
- {
- "attribute": "Test Size"
- }
- ]
- })
+ make_item(
+ "Test Web Item",
+ {
+ "has_variant": 1,
+ "variant_based_on": "Item Attribute",
+ "attributes": [{"attribute": "Test Size"}],
+ },
+ )
elif self._testMethodName in WEBITEM_PRICE_TESTS:
- create_user_and_customer_if_not_exists("test_contact_customer@example.com", "_Test Contact For _Test Customer")
+ create_user_and_customer_if_not_exists(
+ "test_contact_customer@example.com", "_Test Contact For _Test Customer"
+ )
create_regular_web_item()
make_web_item_price(item_code="Test Mobile Phone")
# Note: When testing web item pricing rule logged-in user pricing rule must differ from guest pricing rule or test will falsely pass.
- # This is because make_web_pricing_rule creates a pricing rule "selling": 1, without specifying "applicable_for". Therefor,
- # when testing for logged-in user the test will get the previous pricing rule because "selling" is still true.
+ # This is because make_web_pricing_rule creates a pricing rule "selling": 1, without specifying "applicable_for". Therefor,
+ # when testing for logged-in user the test will get the previous pricing rule because "selling" is still true.
#
# I've attempted to mitigate this by setting applicable_for=Customer, and customer=Guest however, this only results in PermissionError failing the test.
make_web_pricing_rule(
- title="Test Pricing Rule for Test Mobile Phone",
- item_code="Test Mobile Phone",
- selling=1)
+ title="Test Pricing Rule for Test Mobile Phone", item_code="Test Mobile Phone", selling=1
+ )
make_web_pricing_rule(
title="Test Pricing Rule for Test Mobile Phone (Customer)",
item_code="Test Mobile Phone",
selling=1,
discount_percentage="25",
applicable_for="Customer",
- customer="_Test Customer")
+ customer="_Test Customer",
+ )
def test_index_creation(self):
"Check if index is getting created in db."
from erpnext.e_commerce.doctype.website_item.website_item import on_doctype_update
+
on_doctype_update()
indices = frappe.db.sql("show index from `tabWebsite Item`", as_dict=1)
@@ -84,7 +92,7 @@ class TestWebsiteItem(unittest.TestCase):
def test_website_item_desk_item_sync(self):
"Check creation/updation/deletion of Website Item and its impact on Item master."
web_item = None
- item = make_item("Test Web Item") # will return item if exists
+ item = make_item("Test Web Item") # will return item if exists
try:
web_item = make_website_item(item, save=False)
web_item.save()
@@ -97,7 +105,7 @@ class TestWebsiteItem(unittest.TestCase):
item.reload()
self.assertEqual(web_item.published, 1)
- self.assertEqual(item.published_in_website, 1) # check if item was back updated
+ self.assertEqual(item.published_in_website, 1) # check if item was back updated
self.assertEqual(web_item.item_group, item.item_group)
# check if changing item data changes it in website item
@@ -170,9 +178,12 @@ class TestWebsiteItem(unittest.TestCase):
from erpnext.setup.doctype.item_group.item_group import get_parent_item_groups
item_code = "Test Breadcrumb Item"
- item = make_item(item_code, {
- "item_group": "_Test Item Group B - 1",
- })
+ item = make_item(
+ item_code,
+ {
+ "item_group": "_Test Item Group B - 1",
+ },
+ )
if not frappe.db.exists("Website Item", {"item_code": item_code}):
web_item = make_website_item(item, save=False)
@@ -187,7 +198,7 @@ class TestWebsiteItem(unittest.TestCase):
self.assertEqual(breadcrumbs[0]["name"], "Home")
self.assertEqual(breadcrumbs[1]["name"], "Shop by Category")
- self.assertEqual(breadcrumbs[2]["name"], "_Test Item Group B") # parent item group
+ self.assertEqual(breadcrumbs[2]["name"], "_Test Item Group B") # parent item group
self.assertEqual(breadcrumbs[3]["name"], "_Test Item Group B - 1")
# tear down
@@ -236,10 +247,7 @@ class TestWebsiteItem(unittest.TestCase):
item_code = "Test Mobile Phone"
# show price for guest user in e commerce settings
- setup_e_commerce_settings({
- "show_price": 1,
- "hide_price_for_guest": 0
- })
+ setup_e_commerce_settings({"show_price": 1, "hide_price_for_guest": 0})
# price and pricing rule added via setUp
@@ -270,11 +278,11 @@ class TestWebsiteItem(unittest.TestCase):
def test_website_item_stock_when_out_of_stock(self):
"""
- Check if stock details are fetched correctly for empty inventory when:
- 1) Showing stock availability enabled:
- - Warehouse unset
- - Warehouse set
- 2) Showing stock availability disabled
+ Check if stock details are fetched correctly for empty inventory when:
+ 1) Showing stock availability enabled:
+ - Warehouse unset
+ - Warehouse set
+ 2) Showing stock availability disabled
"""
item_code = "Test Mobile Phone"
create_regular_web_item()
@@ -288,7 +296,9 @@ class TestWebsiteItem(unittest.TestCase):
self.assertFalse(bool(data.product_info["stock_qty"]))
# set warehouse
- frappe.db.set_value("Website Item", {"item_code": item_code}, "website_warehouse", "_Test Warehouse - _TC")
+ frappe.db.set_value(
+ "Website Item", {"item_code": item_code}, "website_warehouse", "_Test Warehouse - _TC"
+ )
# check if stock details are fetched and item not in stock with warehouse set
data = get_product_info_for_website(item_code, skip_quotation_creation=True)
@@ -310,11 +320,11 @@ class TestWebsiteItem(unittest.TestCase):
def test_website_item_stock_when_in_stock(self):
"""
- Check if stock details are fetched correctly for available inventory when:
- 1) Showing stock availability enabled:
- - Warehouse set
- - Warehouse unset
- 2) Showing stock availability disabled
+ Check if stock details are fetched correctly for available inventory when:
+ 1) Showing stock availability enabled:
+ - Warehouse set
+ - Warehouse unset
+ 2) Showing stock availability disabled
"""
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
@@ -324,10 +334,14 @@ class TestWebsiteItem(unittest.TestCase):
frappe.local.shopping_cart_settings = None
# set warehouse
- frappe.db.set_value("Website Item", {"item_code": item_code}, "website_warehouse", "_Test Warehouse - _TC")
+ frappe.db.set_value(
+ "Website Item", {"item_code": item_code}, "website_warehouse", "_Test Warehouse - _TC"
+ )
# stock up item
- stock_entry = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=2, rate=100)
+ stock_entry = make_stock_entry(
+ item_code=item_code, target="_Test Warehouse - _TC", qty=2, rate=100
+ )
# check if stock details are fetched and item is in stock with warehouse set
data = get_product_info_for_website(item_code, skip_quotation_creation=True)
@@ -362,10 +376,7 @@ class TestWebsiteItem(unittest.TestCase):
item_code = "Test Mobile Phone"
web_item = create_regular_web_item(item_code)
- setup_e_commerce_settings({
- "enable_recommendations": 1,
- "show_price": 1
- })
+ setup_e_commerce_settings({"enable_recommendations": 1, "show_price": 1})
# create recommended web item and price for it
recommended_web_item = create_regular_web_item("Test Mobile Phone 1")
@@ -383,7 +394,7 @@ class TestWebsiteItem(unittest.TestCase):
self.assertEqual(len(recommended_items), 1)
recomm_item = recommended_items[0]
self.assertEqual(recomm_item.get("website_item_name"), "Test Mobile Phone 1")
- self.assertTrue(bool(recomm_item.get("price_info"))) # price fetched
+ self.assertTrue(bool(recomm_item.get("price_info"))) # price fetched
price_info = recomm_item.get("price_info")
self.assertEqual(price_info.get("price_list_rate"), 1000)
@@ -397,7 +408,7 @@ class TestWebsiteItem(unittest.TestCase):
recommended_items = web_item.get_recommended_items(e_commerce_settings)
self.assertEqual(len(recommended_items), 1)
- self.assertFalse(bool(recommended_items[0].get("price_info"))) # price not fetched
+ self.assertFalse(bool(recommended_items[0].get("price_info"))) # price not fetched
# tear down
web_item.delete()
@@ -410,11 +421,9 @@ class TestWebsiteItem(unittest.TestCase):
web_item = create_regular_web_item(item_code)
# price visible to guests
- setup_e_commerce_settings({
- "enable_recommendations": 1,
- "show_price": 1,
- "hide_price_for_guest": 0
- })
+ setup_e_commerce_settings(
+ {"enable_recommendations": 1, "show_price": 1, "hide_price_for_guest": 0}
+ )
# create recommended web item and price for it
recommended_web_item = create_regular_web_item("Test Mobile Phone 1")
@@ -432,7 +441,7 @@ class TestWebsiteItem(unittest.TestCase):
# test results if show price is enabled
self.assertEqual(len(recommended_items), 1)
- self.assertTrue(bool(recommended_items[0].get("price_info"))) # price fetched
+ self.assertTrue(bool(recommended_items[0].get("price_info"))) # price fetched
# price hidden from guests
frappe.set_user("Administrator")
@@ -445,7 +454,7 @@ class TestWebsiteItem(unittest.TestCase):
# test results if show price is enabled
self.assertEqual(len(recommended_items), 1)
- self.assertFalse(bool(recommended_items[0].get("price_info"))) # price fetched
+ self.assertFalse(bool(recommended_items[0].get("price_info"))) # price fetched
# tear down
frappe.set_user("Administrator")
@@ -453,6 +462,7 @@ class TestWebsiteItem(unittest.TestCase):
recommended_web_item.delete()
frappe.get_cached_doc("Item", "Test Mobile Phone 1").delete()
+
def create_regular_web_item(item_code=None, item_args=None, web_args=None):
"Create Regular Item and Website Item."
item_code = item_code or "Test Mobile Phone"
@@ -468,47 +478,51 @@ def create_regular_web_item(item_code=None, item_args=None, web_args=None):
return web_item
+
def make_web_item_price(**kwargs):
item_code = kwargs.get("item_code")
if not item_code:
return
if not frappe.db.exists("Item Price", {"item_code": item_code}):
- item_price = frappe.get_doc({
- "doctype": "Item Price",
- "item_code": item_code,
- "price_list": kwargs.get("price_list") or "_Test Price List India",
- "price_list_rate": kwargs.get("price_list_rate") or 1000
- })
+ item_price = frappe.get_doc(
+ {
+ "doctype": "Item Price",
+ "item_code": item_code,
+ "price_list": kwargs.get("price_list") or "_Test Price List India",
+ "price_list_rate": kwargs.get("price_list_rate") or 1000,
+ }
+ )
item_price.insert()
else:
item_price = frappe.get_cached_doc("Item Price", {"item_code": item_code})
return item_price
+
def make_web_pricing_rule(**kwargs):
title = kwargs.get("title")
if not title:
return
if not frappe.db.exists("Pricing Rule", title):
- pricing_rule = frappe.get_doc({
- "doctype": "Pricing Rule",
- "title": title,
- "apply_on": kwargs.get("apply_on") or "Item Code",
- "items": [{
- "item_code": kwargs.get("item_code")
- }],
- "selling": kwargs.get("selling") or 0,
- "buying": kwargs.get("buying") or 0,
- "rate_or_discount": kwargs.get("rate_or_discount") or "Discount Percentage",
- "discount_percentage": kwargs.get("discount_percentage") or 10,
- "company": kwargs.get("company") or "_Test Company",
- "currency": kwargs.get("currency") or "INR",
- "for_price_list": kwargs.get("price_list") or "_Test Price List India",
- "applicable_for": kwargs.get("applicable_for") or "",
- "customer": kwargs.get("customer") or "",
- })
+ pricing_rule = frappe.get_doc(
+ {
+ "doctype": "Pricing Rule",
+ "title": title,
+ "apply_on": kwargs.get("apply_on") or "Item Code",
+ "items": [{"item_code": kwargs.get("item_code")}],
+ "selling": kwargs.get("selling") or 0,
+ "buying": kwargs.get("buying") or 0,
+ "rate_or_discount": kwargs.get("rate_or_discount") or "Discount Percentage",
+ "discount_percentage": kwargs.get("discount_percentage") or 10,
+ "company": kwargs.get("company") or "_Test Company",
+ "currency": kwargs.get("currency") or "INR",
+ "for_price_list": kwargs.get("price_list") or "_Test Price List India",
+ "applicable_for": kwargs.get("applicable_for") or "",
+ "customer": kwargs.get("customer") or "",
+ }
+ )
pricing_rule.insert()
else:
pricing_rule = frappe.get_doc("Pricing Rule", {"title": title})
@@ -516,23 +530,26 @@ def make_web_pricing_rule(**kwargs):
return pricing_rule
-def create_user_and_customer_if_not_exists(email, first_name = None):
+def create_user_and_customer_if_not_exists(email, first_name=None):
if frappe.db.exists("User", email):
return
- frappe.get_doc({
- "doctype": "User",
- "user_type": "Website User",
- "email": email,
- "send_welcome_email": 0,
- "first_name": first_name or email.split("@")[0]
- }).insert(ignore_permissions=True)
+ frappe.get_doc(
+ {
+ "doctype": "User",
+ "user_type": "Website User",
+ "email": email,
+ "send_welcome_email": 0,
+ "first_name": first_name or email.split("@")[0],
+ }
+ ).insert(ignore_permissions=True)
contact = frappe.get_last_doc("Contact", filters={"email_id": email})
- link = contact.append('links', {})
+ link = contact.append("links", {})
link.link_doctype = "Customer"
link.link_name = "_Test Customer"
link.link_title = "_Test Customer"
contact.save()
+
test_dependencies = ["Price List", "Item Price", "Customer", "Contact", "Item"]
diff --git a/erpnext/e_commerce/doctype/website_item/website_item.js b/erpnext/e_commerce/doctype/website_item/website_item.js
index 741e78f4a55..7295e4b56a0 100644
--- a/erpnext/e_commerce/doctype/website_item/website_item.js
+++ b/erpnext/e_commerce/doctype/website_item/website_item.js
@@ -2,23 +2,46 @@
// For license information, please see license.txt
frappe.ui.form.on('Website Item', {
- onload: function(frm) {
+ onload: (frm) => {
// should never check Private
frm.fields_dict["website_image"].df.is_private = 0;
+
+ frm.set_query("website_warehouse", () => {
+ return {
+ filters: {"is_group": 0}
+ };
+ });
},
- image: function() {
+ refresh: (frm) => {
+ frm.add_custom_button(__("Prices"), function() {
+ frappe.set_route("List", "Item Price", {"item_code": frm.doc.item_code});
+ }, __("View"));
+
+ frm.add_custom_button(__("Stock"), function() {
+ frappe.route_options = {
+ "item_code": frm.doc.item_code
+ };
+ frappe.set_route("query-report", "Stock Balance");
+ }, __("View"));
+
+ frm.add_custom_button(__("E Commerce Settings"), function() {
+ frappe.set_route("Form", "E Commerce Settings");
+ }, __("View"));
+ },
+
+ image: () => {
refresh_field("image_view");
},
- copy_from_item_group: function(frm) {
+ copy_from_item_group: (frm) => {
return frm.call({
doc: frm.doc,
method: "copy_specification_from_item_group"
});
},
- set_meta_tags(frm) {
+ set_meta_tags: (frm) => {
frappe.utils.set_meta_tag(frm.doc.route);
}
});
diff --git a/erpnext/e_commerce/doctype/website_item/website_item.py b/erpnext/e_commerce/doctype/website_item/website_item.py
index 62f7f49b2ef..02ec3bf1f3f 100644
--- a/erpnext/e_commerce/doctype/website_item/website_item.py
+++ b/erpnext/e_commerce/doctype/website_item/website_item.py
@@ -29,7 +29,7 @@ class WebsiteItem(WebsiteGenerator):
page_title_field="web_item_name",
condition_field="published",
template="templates/generators/item/item.html",
- no_cache=1
+ no_cache=1,
)
def autoname(self):
@@ -88,14 +88,17 @@ class WebsiteItem(WebsiteGenerator):
def publish_unpublish_desk_item(self, publish=True):
if frappe.db.get_value("Item", self.item_code, "published_in_website") and publish:
- return # if already published don't publish again
+ return # if already published don't publish again
frappe.db.set_value("Item", self.item_code, "published_in_website", publish)
def make_route(self):
"""Called from set_route in WebsiteGenerator."""
if not self.route:
- return cstr(frappe.db.get_value('Item Group', self.item_group,
- 'route')) + '/' + self.scrub((self.item_name if self.item_name else self.item_code) + '-' + random_string(5))
+ return (
+ cstr(frappe.db.get_value("Item Group", self.item_group, "route"))
+ + "/"
+ + self.scrub((self.item_name if self.item_name else self.item_code) + "-" + random_string(5))
+ )
def update_template_item(self):
"""Publish Template Item if Variant is published."""
@@ -124,12 +127,10 @@ class WebsiteItem(WebsiteGenerator):
# find if website image url exists as public
file_doc = frappe.get_all(
"File",
- filters={
- "file_url": self.website_image
- },
+ filters={"file_url": self.website_image},
fields=["name", "is_private"],
order_by="is_private asc",
- limit_page_length=1
+ limit_page_length=1,
)
if file_doc:
@@ -137,7 +138,11 @@ class WebsiteItem(WebsiteGenerator):
if not file_doc:
if not auto_set_website_image:
- frappe.msgprint(_("Website Image {0} attached to Item {1} cannot be found").format(self.website_image, self.name))
+ frappe.msgprint(
+ _("Website Image {0} attached to Item {1} cannot be found").format(
+ self.website_image, self.name
+ )
+ )
self.website_image = None
@@ -154,18 +159,23 @@ class WebsiteItem(WebsiteGenerator):
import requests.exceptions
- if not self.is_new() and self.website_image != frappe.db.get_value(self.doctype, self.name, "website_image"):
+ if not self.is_new() and self.website_image != frappe.db.get_value(
+ self.doctype, self.name, "website_image"
+ ):
self.thumbnail = None
if self.website_image and not self.thumbnail:
file_doc = None
try:
- file_doc = frappe.get_doc("File", {
- "file_url": self.website_image,
- "attached_to_doctype": "Website Item",
- "attached_to_name": self.name
- })
+ file_doc = frappe.get_doc(
+ "File",
+ {
+ "file_url": self.website_image,
+ "attached_to_doctype": "Website Item",
+ "attached_to_name": self.name,
+ },
+ )
except frappe.DoesNotExistError:
pass
# cleanup
@@ -177,18 +187,21 @@ class WebsiteItem(WebsiteGenerator):
except requests.exceptions.SSLError:
frappe.msgprint(
- _("Warning: Invalid SSL certificate on attachment {0}").format(self.website_image))
+ _("Warning: Invalid SSL certificate on attachment {0}").format(self.website_image)
+ )
self.website_image = None
# for CSV import
if self.website_image and not file_doc:
try:
- file_doc = frappe.get_doc({
- "doctype": "File",
- "file_url": self.website_image,
- "attached_to_doctype": "Website Item",
- "attached_to_name": self.name
- }).save()
+ file_doc = frappe.get_doc(
+ {
+ "doctype": "File",
+ "file_url": self.website_image,
+ "attached_to_doctype": "Website Item",
+ "attached_to_name": self.name,
+ }
+ ).save()
except IOError:
self.website_image = None
@@ -204,11 +217,11 @@ class WebsiteItem(WebsiteGenerator):
context.search_link = "/search"
context.body_class = "product-page"
- context.parents = get_parent_item_groups(self.item_group, from_item=True) # breadcumbs
+ context.parents = get_parent_item_groups(self.item_group, from_item=True) # breadcumbs
self.attributes = frappe.get_all(
"Item Variant Attribute",
fields=["attribute", "attribute_value"],
- filters={"parent": self.item_code}
+ filters={"parent": self.item_code},
)
if self.slideshow:
@@ -227,7 +240,9 @@ class WebsiteItem(WebsiteGenerator):
context.reviews = context.reviews[:4]
context.wished = False
- if frappe.db.exists("Wishlist Item", {"item_code": self.item_code, "parent": frappe.session.user}):
+ if frappe.db.exists(
+ "Wishlist Item", {"item_code": self.item_code, "parent": frappe.session.user}
+ ):
context.wished = True
context.user_is_customer = check_if_user_is_customer()
@@ -243,11 +258,12 @@ class WebsiteItem(WebsiteGenerator):
variant.attributes = frappe.get_all(
"Item Variant Attribute",
filters={"parent": variant.name},
- fields=["attribute", "attribute_value as value"])
+ fields=["attribute", "attribute_value as value"],
+ )
# make an attribute-value map for easier access in templates
variant.attribute_map = frappe._dict(
- {attr.attribute : attr.value for attr in variant.attributes}
+ {attr.attribute: attr.value for attr in variant.attributes}
)
for attr in variant.attributes:
@@ -267,9 +283,12 @@ class WebsiteItem(WebsiteGenerator):
values.append(val)
else:
# get list of values defined (for sequence)
- for attr_value in frappe.db.get_all("Item Attribute Value",
+ for attr_value in frappe.db.get_all(
+ "Item Attribute Value",
fields=["attribute_value"],
- filters={"parent": attr.attribute}, order_by="idx asc"):
+ filters={"parent": attr.attribute},
+ order_by="idx asc",
+ ):
if attr_value.attribute_value in attribute_values_available.get(attr.attribute, []):
values.append(attr_value.attribute_value)
@@ -279,10 +298,10 @@ class WebsiteItem(WebsiteGenerator):
safe_description = frappe.utils.to_markdown(self.description)
- context.metatags.url = frappe.utils.get_url() + '/' + context.route
+ context.metatags.url = frappe.utils.get_url() + "/" + context.route
if context.website_image:
- if context.website_image.startswith('http'):
+ if context.website_image.startswith("http"):
url = context.website_image
else:
url = frappe.utils.get_url() + context.website_image
@@ -292,24 +311,28 @@ class WebsiteItem(WebsiteGenerator):
context.metatags.title = self.web_item_name or self.item_name or self.item_code
- context.metatags['og:type'] = 'product'
- context.metatags['og:site_name'] = 'ERPNext'
+ context.metatags["og:type"] = "product"
+ context.metatags["og:site_name"] = "ERPNext"
def set_shopping_cart_data(self, context):
from erpnext.e_commerce.shopping_cart.product_info import get_product_info_for_website
- context.shopping_cart = get_product_info_for_website(self.item_code, skip_quotation_creation=True)
+
+ context.shopping_cart = get_product_info_for_website(
+ self.item_code, skip_quotation_creation=True
+ )
def copy_specification_from_item_group(self):
self.set("website_specifications", [])
if self.item_group:
- for label, desc in frappe.db.get_values("Item Website Specification",
- {"parent": self.item_group}, ["label", "description"]):
+ for label, desc in frappe.db.get_values(
+ "Item Website Specification", {"parent": self.item_group}, ["label", "description"]
+ ):
row = self.append("website_specifications")
row.label = label
row.description = desc
def get_product_details_section(self, context):
- """ Get section with tabs or website specifications. """
+ """Get section with tabs or website specifications."""
context.show_tabs = self.show_tabbed_section
if self.show_tabbed_section and (self.tabs or self.website_specifications):
context.tabs = self.get_tabs()
@@ -321,10 +344,8 @@ class WebsiteItem(WebsiteGenerator):
tab_values["tab_1_title"] = "Product Details"
tab_values["tab_1_content"] = frappe.render_template(
"templates/generators/item/item_specifications.html",
- {
- "website_specifications": self.website_specifications,
- "show_tabs": self.show_tabbed_section
- })
+ {"website_specifications": self.website_specifications, "show_tabs": self.show_tabbed_section},
+ )
for row in self.tabs:
tab_values[f"tab_{row.idx + 1}_title"] = _(row.label)
@@ -338,15 +359,11 @@ class WebsiteItem(WebsiteGenerator):
query = (
frappe.qb.from_(ri)
- .join(wi).on(ri.item_code == wi.item_code)
- .select(
- ri.item_code, ri.route,
- ri.website_item_name,
- ri.website_item_thumbnail
- ).where(
- (ri.parent == self.name)
- & (wi.published == 1)
- ).orderby(ri.idx)
+ .join(wi)
+ .on(ri.item_code == wi.item_code)
+ .select(ri.item_code, ri.route, ri.website_item_name, ri.website_item_thumbnail)
+ .where((ri.parent == self.name) & (wi.published == 1))
+ .orderby(ri.idx)
)
items = query.run(as_dict=True)
@@ -360,22 +377,24 @@ class WebsiteItem(WebsiteGenerator):
selling_price_list = _set_price_list(settings, None)
for item in items:
item.price_info = get_price(
- item.item_code,
- selling_price_list,
- settings.default_customer_group,
- settings.company
+ item.item_code, selling_price_list, settings.default_customer_group, settings.company
)
return items
+
def invalidate_cache_for_web_item(doc):
"""Invalidate Website Item Group cache and rebuild ItemVariantsCacheManager."""
from erpnext.stock.doctype.item.item import invalidate_item_variants_cache_for_website
invalidate_cache_for(doc, doc.item_group)
- website_item_groups = list(set((doc.get("old_website_item_groups") or [])
- + [d.item_group for d in doc.get({"doctype": "Website Item Group"}) if d.item_group]))
+ website_item_groups = list(
+ set(
+ (doc.get("old_website_item_groups") or [])
+ + [d.item_group for d in doc.get({"doctype": "Website Item Group"}) if d.item_group]
+ )
+ )
for item_group in website_item_groups:
invalidate_cache_for(doc, item_group)
@@ -385,6 +404,7 @@ def invalidate_cache_for_web_item(doc):
invalidate_item_variants_cache_for_website(doc)
+
def on_doctype_update():
# since route is a Text column, it needs a length for indexing
frappe.db.add_index("Website Item", ["route(500)"])
@@ -392,6 +412,7 @@ def on_doctype_update():
frappe.db.add_index("Website Item", ["item_group"])
frappe.db.add_index("Website Item", ["brand"])
+
def check_if_user_is_customer(user=None):
from frappe.contacts.doctype.contact.contact import get_contact_name
@@ -402,7 +423,7 @@ def check_if_user_is_customer(user=None):
customer = None
if contact_name:
- contact = frappe.get_doc('Contact', contact_name)
+ contact = frappe.get_doc("Contact", contact_name)
for link in contact.links:
if link.link_doctype == "Customer":
customer = link.link_name
@@ -410,6 +431,7 @@ def check_if_user_is_customer(user=None):
return True if customer else False
+
@frappe.whitelist()
def make_website_item(doc, save=True):
if not doc:
@@ -425,8 +447,17 @@ def make_website_item(doc, save=True):
website_item = frappe.new_doc("Website Item")
website_item.web_item_name = doc.get("item_name")
- fields_to_map = ["item_code", "item_name", "item_group", "stock_uom", "brand", "image",
- "has_variants", "variant_of", "description"]
+ fields_to_map = [
+ "item_code",
+ "item_name",
+ "item_group",
+ "stock_uom",
+ "brand",
+ "image",
+ "has_variants",
+ "variant_of",
+ "description",
+ ]
for field in fields_to_map:
website_item.update({field: doc.get(field)})
@@ -438,4 +469,4 @@ def make_website_item(doc, save=True):
# Add to search cache
insert_item_to_index(website_item)
- return [website_item.name, website_item.web_item_name]
\ No newline at end of file
+ return [website_item.name, website_item.web_item_name]
diff --git a/erpnext/e_commerce/doctype/website_offer/website_offer.py b/erpnext/e_commerce/doctype/website_offer/website_offer.py
index d73c132b0e9..8c92f75a1e9 100644
--- a/erpnext/e_commerce/doctype/website_offer/website_offer.py
+++ b/erpnext/e_commerce/doctype/website_offer/website_offer.py
@@ -9,6 +9,7 @@ from frappe.model.document import Document
class WebsiteOffer(Document):
pass
+
@frappe.whitelist(allow_guest=True)
def get_offer_details(offer_id):
- return frappe.db.get_value('Website Offer', {'name': offer_id}, ['offer_details'])
+ return frappe.db.get_value("Website Offer", {"name": offer_id}, ["offer_details"])
diff --git a/erpnext/e_commerce/doctype/wishlist/test_wishlist.py b/erpnext/e_commerce/doctype/wishlist/test_wishlist.py
index 504bb658113..9d27126fdb2 100644
--- a/erpnext/e_commerce/doctype/wishlist/test_wishlist.py
+++ b/erpnext/e_commerce/doctype/wishlist/test_wishlist.py
@@ -34,14 +34,16 @@ class TestWishlist(unittest.TestCase):
# check if wishlist was created and item was added
self.assertTrue(frappe.db.exists("Wishlist", {"user": frappe.session.user}))
- self.assertTrue(frappe.db.exists("Wishlist Item", {"item_code": "Test Phone Series X", "parent": frappe.session.user}))
+ self.assertTrue(
+ frappe.db.exists(
+ "Wishlist Item", {"item_code": "Test Phone Series X", "parent": frappe.session.user}
+ )
+ )
# add second item to wishlist
add_to_wishlist("Test Phone Series Y")
wishlist_length = frappe.db.get_value(
- "Wishlist Item",
- {"parent": frappe.session.user},
- "count(*)"
+ "Wishlist Item", {"parent": frappe.session.user}, "count(*)"
)
self.assertEqual(wishlist_length, 2)
@@ -49,9 +51,7 @@ class TestWishlist(unittest.TestCase):
remove_from_wishlist("Test Phone Series Y")
wishlist_length = frappe.db.get_value(
- "Wishlist Item",
- {"parent": frappe.session.user},
- "count(*)"
+ "Wishlist Item", {"parent": frappe.session.user}, "count(*)"
)
self.assertIsNone(frappe.db.exists("Wishlist Item", {"parent": frappe.session.user}))
self.assertEqual(wishlist_length, 0)
@@ -74,29 +74,44 @@ class TestWishlist(unittest.TestCase):
# check wishlist and its content for users
self.assertTrue(frappe.db.exists("Wishlist", {"user": test_user.name}))
- self.assertTrue(frappe.db.exists("Wishlist Item",
- {"item_code": "Test Phone Series X", "parent": test_user.name}))
+ self.assertTrue(
+ frappe.db.exists(
+ "Wishlist Item", {"item_code": "Test Phone Series X", "parent": test_user.name}
+ )
+ )
self.assertTrue(frappe.db.exists("Wishlist", {"user": test_user_1.name}))
- self.assertTrue(frappe.db.exists("Wishlist Item",
- {"item_code": "Test Phone Series X", "parent": test_user_1.name}))
+ self.assertTrue(
+ frappe.db.exists(
+ "Wishlist Item", {"item_code": "Test Phone Series X", "parent": test_user_1.name}
+ )
+ )
# remove item for second user
remove_from_wishlist("Test Phone Series X")
# make sure item was removed for second user and not first
- self.assertFalse(frappe.db.exists("Wishlist Item",
- {"item_code": "Test Phone Series X", "parent": test_user_1.name}))
- self.assertTrue(frappe.db.exists("Wishlist Item",
- {"item_code": "Test Phone Series X", "parent": test_user.name}))
+ self.assertFalse(
+ frappe.db.exists(
+ "Wishlist Item", {"item_code": "Test Phone Series X", "parent": test_user_1.name}
+ )
+ )
+ self.assertTrue(
+ frappe.db.exists(
+ "Wishlist Item", {"item_code": "Test Phone Series X", "parent": test_user.name}
+ )
+ )
# remove item for first user
frappe.set_user(test_user.name)
remove_from_wishlist("Test Phone Series X")
- self.assertFalse(frappe.db.exists("Wishlist Item",
- {"item_code": "Test Phone Series X", "parent": test_user.name}))
+ self.assertFalse(
+ frappe.db.exists(
+ "Wishlist Item", {"item_code": "Test Phone Series X", "parent": test_user.name}
+ )
+ )
# tear down
frappe.set_user("Administrator")
frappe.get_doc("Wishlist", {"user": test_user.name}).delete()
- frappe.get_doc("Wishlist", {"user": test_user_1.name}).delete()
\ No newline at end of file
+ frappe.get_doc("Wishlist", {"user": test_user_1.name}).delete()
diff --git a/erpnext/e_commerce/doctype/wishlist/wishlist.py b/erpnext/e_commerce/doctype/wishlist/wishlist.py
index 50e3d3a3392..5d6ab41db01 100644
--- a/erpnext/e_commerce/doctype/wishlist/wishlist.py
+++ b/erpnext/e_commerce/doctype/wishlist/wishlist.py
@@ -9,6 +9,7 @@ from frappe.model.document import Document
class Wishlist(Document):
pass
+
@frappe.whitelist()
def add_to_wishlist(item_code):
"""Insert Item into wishlist."""
@@ -20,7 +21,8 @@ def add_to_wishlist(item_code):
"Website Item",
{"item_code": item_code},
["image", "website_warehouse", "name", "web_item_name", "item_name", "item_group", "route"],
- as_dict=1)
+ as_dict=1,
+ )
wished_item_dict = {
"item_code": item_code,
@@ -30,7 +32,7 @@ def add_to_wishlist(item_code):
"web_item_name": web_item_data.get("web_item_name"),
"image": web_item_data.get("image"),
"warehouse": web_item_data.get("website_warehouse"),
- "route": web_item_data.get("route")
+ "route": web_item_data.get("route"),
}
if not frappe.db.exists("Wishlist", frappe.session.user):
@@ -41,28 +43,20 @@ def add_to_wishlist(item_code):
wishlist.save(ignore_permissions=True)
else:
wishlist = frappe.get_doc("Wishlist", frappe.session.user)
- item = wishlist.append('items', wished_item_dict)
+ item = wishlist.append("items", wished_item_dict)
item.db_insert()
if hasattr(frappe.local, "cookie_manager"):
frappe.local.cookie_manager.set_cookie("wish_count", str(len(wishlist.items)))
+
@frappe.whitelist()
def remove_from_wishlist(item_code):
if frappe.db.exists("Wishlist Item", {"item_code": item_code, "parent": frappe.session.user}):
- frappe.db.delete(
- "Wishlist Item",
- {
- "item_code": item_code,
- "parent": frappe.session.user
- }
- )
- frappe.db.commit() # nosemgrep
+ frappe.db.delete("Wishlist Item", {"item_code": item_code, "parent": frappe.session.user})
+ frappe.db.commit() # nosemgrep
- wishlist_items = frappe.db.get_values(
- "Wishlist Item",
- filters={"parent": frappe.session.user}
- )
+ wishlist_items = frappe.db.get_values("Wishlist Item", filters={"parent": frappe.session.user})
if hasattr(frappe.local, "cookie_manager"):
- frappe.local.cookie_manager.set_cookie("wish_count", str(len(wishlist_items)))
\ No newline at end of file
+ frappe.local.cookie_manager.set_cookie("wish_count", str(len(wishlist_items)))
diff --git a/erpnext/e_commerce/legacy_search.py b/erpnext/e_commerce/legacy_search.py
index 752c33e92ee..ef8e86d4428 100644
--- a/erpnext/e_commerce/legacy_search.py
+++ b/erpnext/e_commerce/legacy_search.py
@@ -9,8 +9,9 @@ from whoosh.query import Prefix
# TODO: Make obsolete
INDEX_NAME = "products"
+
class ProductSearch(FullTextSearch):
- """ Wrapper for WebsiteSearch """
+ """Wrapper for WebsiteSearch"""
def get_schema(self):
return Schema(
@@ -29,7 +30,7 @@ class ProductSearch(FullTextSearch):
in www/ and routes from published documents
Returns:
- self (object): FullTextSearch Instance
+ self (object): FullTextSearch Instance
"""
items = get_all_published_items()
documents = [self.get_document_to_index(item) for item in items]
@@ -69,12 +70,12 @@ class ProductSearch(FullTextSearch):
"""Search from the current index
Args:
- text (str): String to search for
- scope (str, optional): Scope to limit the search. Defaults to None.
- limit (int, optional): Limit number of search results. Defaults to 20.
+ text (str): String to search for
+ scope (str, optional): Scope to limit the search. Defaults to None.
+ limit (int, optional): Limit number of search results. Defaults to 20.
Returns:
- [List(_dict)]: Search results
+ [List(_dict)]: Search results
"""
ix = self.get_index()
@@ -111,17 +112,23 @@ class ProductSearch(FullTextSearch):
keyword_highlights=keyword_highlights,
)
+
def get_all_published_items():
- return frappe.get_all("Website Item", filters={"variant_of": "", "published": 1}, pluck="item_code")
+ return frappe.get_all(
+ "Website Item", filters={"variant_of": "", "published": 1}, pluck="item_code"
+ )
+
def update_index_for_path(path):
search = ProductSearch(INDEX_NAME)
return search.update_index_by_name(path)
+
def remove_document_from_index(path):
search = ProductSearch(INDEX_NAME)
return search.remove_document_from_index(path)
+
def build_index_for_all_routes():
search = ProductSearch(INDEX_NAME)
return search.build()
diff --git a/erpnext/e_commerce/product_data_engine/filters.py b/erpnext/e_commerce/product_data_engine/filters.py
index c4a3cb9fbef..feb89c18306 100644
--- a/erpnext/e_commerce/product_data_engine/filters.py
+++ b/erpnext/e_commerce/product_data_engine/filters.py
@@ -14,39 +14,53 @@ class ProductFiltersBuilder:
self.item_group = item_group
def get_field_filters(self):
+ from erpnext.setup.doctype.item_group.item_group import get_child_groups_for_website
+
if not self.item_group and not self.doc.enable_field_filters:
return
fields, filter_data = [], []
- filter_fields = [row.fieldname for row in self.doc.filter_fields] # fields in settings
+ filter_fields = [row.fieldname for row in self.doc.filter_fields] # fields in settings
# filter valid field filters i.e. those that exist in Item
- item_meta = frappe.get_meta('Item', cached=True)
+ item_meta = frappe.get_meta("Item", cached=True)
fields = [item_meta.get_field(field) for field in filter_fields if item_meta.has_field(field)]
for df in fields:
- item_filters, item_or_filters = {}, []
+ item_filters, item_or_filters = {"published_in_website": 1}, []
link_doctype_values = self.get_filtered_link_doctype_records(df)
if df.fieldtype == "Link":
if self.item_group:
- item_or_filters.extend([
- ["item_group", "=", self.item_group],
- ["Website Item Group", "item_group", "=", self.item_group] # consider website item groups
- ])
+ include_child = frappe.db.get_value("Item Group", self.item_group, "include_descendants")
+ if include_child:
+ include_groups = get_child_groups_for_website(self.item_group, include_self=True)
+ include_groups = [x.name for x in include_groups]
+ item_or_filters.extend(
+ [
+ ["item_group", "in", include_groups],
+ ["Website Item Group", "item_group", "=", self.item_group], # consider website item groups
+ ]
+ )
+ else:
+ item_or_filters.extend(
+ [
+ ["item_group", "=", self.item_group],
+ ["Website Item Group", "item_group", "=", self.item_group], # consider website item groups
+ ]
+ )
# Get link field values attached to published items
- item_filters['published_in_website'] = 1
item_values = frappe.get_all(
"Item",
fields=[df.fieldname],
filters=item_filters,
or_filters=item_or_filters,
distinct="True",
- pluck=df.fieldname
+ pluck=df.fieldname,
)
- values = list(set(item_values) & link_doctype_values) # intersection of both
+ values = list(set(item_values) & link_doctype_values) # intersection of both
else:
# table multiselect
values = list(link_doctype_values)
@@ -62,10 +76,10 @@ class ProductFiltersBuilder:
def get_filtered_link_doctype_records(self, field):
"""
- Get valid link doctype records depending on filters.
- Apply enable/disable/show_in_website filter.
- Returns:
- set: A set containing valid record names
+ Get valid link doctype records depending on filters.
+ Apply enable/disable/show_in_website filter.
+ Returns:
+ set: A set containing valid record names
"""
link_doctype = field.get_link_doctype()
meta = frappe.get_meta(link_doctype, cached=True) if link_doctype else None
@@ -81,12 +95,12 @@ class ProductFiltersBuilder:
if not meta:
return filters
- if meta.has_field('enabled'):
- filters['enabled'] = 1
- if meta.has_field('disabled'):
- filters['disabled'] = 0
- if meta.has_field('show_in_website'):
- filters['show_in_website'] = 1
+ if meta.has_field("enabled"):
+ filters["enabled"] = 1
+ if meta.has_field("disabled"):
+ filters["disabled"] = 0
+ if meta.has_field("show_in_website"):
+ filters["show_in_website"] = 1
return filters
@@ -101,12 +115,9 @@ class ProductFiltersBuilder:
result = frappe.get_all(
"Item Variant Attribute",
- filters={
- "attribute": ["in", attributes],
- "attribute_value": ["is", "set"]
- },
+ filters={"attribute": ["in", attributes], "attribute_value": ["is", "set"]},
fields=["attribute", "attribute_value"],
- distinct=True
+ distinct=True,
)
attribute_value_map = {}
@@ -126,11 +137,13 @@ class ProductFiltersBuilder:
# [25, 60] rounded min max
min_range_absolute, max_range_absolute = floor(min_discount), floor(max_discount)
- min_range = int(min_discount - (min_range_absolute % 10)) # 20
- max_range = int(max_discount - (max_range_absolute % 10)) # 60
+ min_range = int(min_discount - (min_range_absolute % 10)) # 20
+ max_range = int(max_discount - (max_range_absolute % 10)) # 60
- min_range = (min_range + 10) if min_range != min_range_absolute else min_range # 30 (upper limit of 25.89 in range of 10)
- max_range = (max_range + 10) if max_range != max_range_absolute else max_range # 60
+ min_range = (
+ (min_range + 10) if min_range != min_range_absolute else min_range
+ ) # 30 (upper limit of 25.89 in range of 10)
+ max_range = (max_range + 10) if max_range != max_range_absolute else max_range # 60
for discount in range(min_range, (max_range + 1), 10):
label = f"{discount}% and below"
diff --git a/erpnext/e_commerce/product_data_engine/query.py b/erpnext/e_commerce/product_data_engine/query.py
index 007bf8b3489..5a753822d4b 100644
--- a/erpnext/e_commerce/product_data_engine/query.py
+++ b/erpnext/e_commerce/product_data_engine/query.py
@@ -13,12 +13,13 @@ class ProductQuery:
"""Query engine for product listing
Attributes:
- fields (list): Fields to fetch in query
- conditions (string): Conditions for query building
- or_conditions (string): Search conditions
- page_length (Int): Length of page for the query
- settings (Document): E Commerce Settings DocType
+ fields (list): Fields to fetch in query
+ conditions (string): Conditions for query building
+ or_conditions (string): Search conditions
+ page_length (Int): Length of page for the query
+ settings (Document): E Commerce Settings DocType
"""
+
def __init__(self):
self.settings = frappe.get_doc("E Commerce Settings")
self.page_length = self.settings.products_per_page or 20
@@ -26,30 +27,42 @@ class ProductQuery:
self.or_filters = []
self.filters = [["published", "=", 1]]
self.fields = [
- "web_item_name", "name", "item_name", "item_code", "website_image",
- "variant_of", "has_variants", "item_group", "image", "web_long_description",
- "short_description", "route", "website_warehouse", "ranking", "on_backorder"
+ "web_item_name",
+ "name",
+ "item_name",
+ "item_code",
+ "website_image",
+ "variant_of",
+ "has_variants",
+ "item_group",
+ "image",
+ "web_long_description",
+ "short_description",
+ "route",
+ "website_warehouse",
+ "ranking",
+ "on_backorder",
]
def query(self, attributes=None, fields=None, search_term=None, start=0, item_group=None):
"""
Args:
- attributes (dict, optional): Item Attribute filters
- fields (dict, optional): Field level filters
- search_term (str, optional): Search term to lookup
- start (int, optional): Page start
+ attributes (dict, optional): Item Attribute filters
+ fields (dict, optional): Field level filters
+ search_term (str, optional): Search term to lookup
+ start (int, optional): Page start
Returns:
- dict: Dict containing items, item count & discount range
+ dict: Dict containing items, item count & discount range
"""
# track if discounts included in field filters
self.filter_with_discount = bool(fields.get("discount"))
result, discount_list, website_item_groups, cart_items, count = [], [], [], [], 0
- website_item_groups = self.get_website_item_group_results(item_group, website_item_groups)
-
if fields:
self.build_fields_filters(fields)
+ if item_group:
+ self.build_item_group_filters(item_group)
if search_term:
self.build_search_filters(search_term)
if self.settings.hide_variants:
@@ -61,8 +74,6 @@ class ProductQuery:
else:
result, count = self.query_items(start=start)
- result = self.combine_web_item_group_results(item_group, result, website_item_groups)
-
# sort combined results by ranking
result = sorted(result, key=lambda x: x.get("ranking"), reverse=True)
@@ -77,11 +88,7 @@ class ProductQuery:
result = self.filter_results_by_discount(fields, result)
- return {
- "items": result,
- "items_count": count,
- "discounts": discounts
- }
+ return {"items": result, "items_count": count, "discounts": discounts}
def query_items(self, start=0):
"""Build a query to fetch Website Items based on field filters."""
@@ -93,8 +100,9 @@ class ProductQuery:
filters=self.filters,
or_filters=self.or_filters,
limit_page_length=184467440737095516,
- limit_start=start, # get all items from this offset for total count ahead
- order_by="ranking desc")
+ limit_start=start, # get all items from this offset for total count ahead
+ order_by="ranking desc",
+ )
count = len(count_items)
# If discounts included, return all rows.
@@ -110,7 +118,8 @@ class ProductQuery:
or_filters=self.or_filters,
limit_page_length=page_length,
limit_start=start,
- order_by="ranking desc")
+ order_by="ranking desc",
+ )
return items, count
@@ -129,8 +138,9 @@ class ProductQuery:
filters=[
["published_in_website", "=", 1],
["Item Variant Attribute", "attribute", "=", attribute],
- ["Item Variant Attribute", "attribute_value", "in", values]
- ])
+ ["Item Variant Attribute", "attribute_value", "in", values],
+ ],
+ )
item_codes.append({x.item_code for x in item_code_list})
if item_codes:
@@ -145,21 +155,21 @@ class ProductQuery:
"""Build filters for field values
Args:
- filters (dict): Filters
+ filters (dict): Filters
"""
for field, values in filters.items():
if not values or field == "discount":
continue
# handle multiselect fields in filter addition
- meta = frappe.get_meta('Website Item', cached=True)
+ meta = frappe.get_meta("Website Item", cached=True)
df = meta.get_field(field)
- if df.fieldtype == 'Table MultiSelect':
+ if df.fieldtype == "Table MultiSelect":
child_doctype = df.options
child_meta = frappe.get_meta(child_doctype, cached=True)
fields = child_meta.get("fields")
if fields:
- self.filters.append([child_doctype, fields[0].fieldname, 'IN', values])
+ self.filters.append([child_doctype, fields[0].fieldname, "IN", values])
elif isinstance(values, list):
# If value is a list use `IN` query
self.filters.append([field, "in", values])
@@ -167,14 +177,34 @@ class ProductQuery:
# `=` will be faster than `IN` for most cases
self.filters.append([field, "=", values])
+ def build_item_group_filters(self, item_group):
+ "Add filters for Item group page and include Website Item Groups."
+ from erpnext.setup.doctype.item_group.item_group import get_child_groups_for_website
+
+ item_group_filters = []
+
+ item_group_filters.append(["Website Item", "item_group", "=", item_group])
+ # Consider Website Item Groups
+ item_group_filters.append(["Website Item Group", "item_group", "=", item_group])
+
+ if frappe.db.get_value("Item Group", item_group, "include_descendants"):
+ # include child item group's items as well
+ # eg. Group Node A, will show items of child 1 and child 2 as well
+ # on it's web page
+ include_groups = get_child_groups_for_website(item_group, include_self=True)
+ include_groups = [x.name for x in include_groups]
+ item_group_filters.append(["Website Item", "item_group", "in", include_groups])
+
+ self.or_filters.extend(item_group_filters)
+
def build_search_filters(self, search_term):
"""Query search term in specified fields
Args:
- search_term (str): Search candidate
+ search_term (str): Search candidate
"""
# Default fields to search from
- default_fields = {'item_code', 'item_name', 'web_long_description', 'item_group'}
+ default_fields = {"item_code", "item_name", "web_long_description", "item_group"}
# Get meta search fields
meta = frappe.get_meta("Website Item")
@@ -182,35 +212,24 @@ class ProductQuery:
# Join the meta fields and default fields set
search_fields = default_fields.union(meta_fields)
- if frappe.db.count('Website Item', cache=True) > 50000:
- search_fields.discard('web_long_description')
+ if frappe.db.count("Website Item", cache=True) > 50000:
+ search_fields.discard("web_long_description")
# Build or filters for query
- search = '%{}%'.format(search_term)
+ search = "%{}%".format(search_term)
for field in search_fields:
self.or_filters.append([field, "like", search])
- def get_website_item_group_results(self, item_group, website_item_groups):
- """Get Web Items for Item Group Page via Website Item Groups."""
- if item_group:
- website_item_groups = frappe.db.get_all(
- "Website Item",
- fields=self.fields + ["`tabWebsite Item Group`.parent as wig_parent"],
- filters=[
- ["Website Item Group", "item_group", "=", item_group],
- ["published", "=", 1]
- ]
- )
- return website_item_groups
-
def add_display_details(self, result, discount_list, cart_items):
"""Add price and availability details in result."""
for item in result:
- product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get('product_info')
+ product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get(
+ "product_info"
+ )
- if product_info and product_info['price']:
+ if product_info and product_info["price"]:
# update/mutate item and discount_list objects
- self.get_price_discount_info(item, product_info['price'], discount_list)
+ self.get_price_discount_info(item, product_info["price"], discount_list)
if self.settings.show_stock_availability:
self.get_stock_availability(item)
@@ -218,7 +237,9 @@ class ProductQuery:
item.in_cart = item.item_code in cart_items
item.wished = False
- if frappe.db.exists("Wishlist Item", {"item_code": item.item_code, "parent": frappe.session.user}):
+ if frappe.db.exists(
+ "Wishlist Item", {"item_code": item.item_code, "parent": frappe.session.user}
+ ):
item.wished = True
return result, discount_list
@@ -229,13 +250,14 @@ class ProductQuery:
for field in fields:
item[field] = price_object.get(field)
- if price_object.get('discount_percent'):
+ if price_object.get("discount_percent"):
item.discount_percent = flt(price_object.discount_percent)
discount_list.append(price_object.discount_percent)
if item.formatted_mrp:
- item.discount = price_object.get('formatted_discount_percent') or \
- price_object.get('formatted_discount_rate')
+ item.discount = price_object.get("formatted_discount_percent") or price_object.get(
+ "formatted_discount_rate"
+ )
def get_stock_availability(self, item):
"""Modify item object and add stock details."""
@@ -255,47 +277,46 @@ class ProductQuery:
elif warehouse:
# stock item and has warehouse
actual_qty = frappe.db.get_value(
- "Bin",
- {"item_code": item.item_code,"warehouse": item.get("website_warehouse")},
- "actual_qty")
+ "Bin", {"item_code": item.item_code, "warehouse": item.get("website_warehouse")}, "actual_qty"
+ )
item.in_stock = bool(flt(actual_qty))
def get_cart_items(self):
customer = get_customer(silent=True)
if customer:
- quotation = frappe.get_all("Quotation", fields=["name"], filters=
- {"party_name": customer, "order_type": "Shopping Cart", "docstatus": 0},
- order_by="modified desc", limit_page_length=1)
+ quotation = frappe.get_all(
+ "Quotation",
+ fields=["name"],
+ filters={
+ "party_name": customer,
+ "contact_email": frappe.session.user,
+ "order_type": "Shopping Cart",
+ "docstatus": 0,
+ },
+ order_by="modified desc",
+ limit_page_length=1,
+ )
if quotation:
items = frappe.get_all(
- "Quotation Item",
- fields=["item_code"],
- filters={
- "parent": quotation[0].get("name")
- })
+ "Quotation Item", fields=["item_code"], filters={"parent": quotation[0].get("name")}
+ )
items = [row.item_code for row in items]
return items
return []
- def combine_web_item_group_results(self, item_group, result, website_item_groups):
- """Combine results with context of website item groups into item results."""
- if item_group and website_item_groups:
- items_list = {row.name for row in result}
- for row in website_item_groups:
- if row.wig_parent not in items_list:
- result.append(row)
-
- return result
-
def filter_results_by_discount(self, fields, result):
if fields and fields.get("discount"):
discount_percent = frappe.utils.flt(fields["discount"][0])
- result = [row for row in result if row.get("discount_percent") and row.discount_percent <= discount_percent]
+ result = [
+ row
+ for row in result
+ if row.get("discount_percent") and row.discount_percent <= discount_percent
+ ]
if self.filter_with_discount:
# no limit was added to results while querying
# slice results manually
- result[:self.page_length]
+ result[: self.page_length]
- return result
\ No newline at end of file
+ return result
diff --git a/erpnext/e_commerce/product_data_engine/test_item_group_product_data_engine.py b/erpnext/e_commerce/product_data_engine/test_item_group_product_data_engine.py
index f0f7918d00e..45bc20ece6e 100644
--- a/erpnext/e_commerce/product_data_engine/test_item_group_product_data_engine.py
+++ b/erpnext/e_commerce/product_data_engine/test_item_group_product_data_engine.py
@@ -10,17 +10,17 @@ from erpnext.e_commerce.doctype.website_item.test_website_item import create_reg
test_dependencies = ["Item", "Item Group"]
+
class TestItemGroupProductDataEngine(unittest.TestCase):
"Test Products & Sub-Category Querying for Product Listing on Item Group Page."
- @classmethod
- def setUpClass(cls):
+ def setUp(self):
item_codes = [
("Test Mobile A", "_Test Item Group B"),
("Test Mobile B", "_Test Item Group B"),
("Test Mobile C", "_Test Item Group B - 1"),
("Test Mobile D", "_Test Item Group B - 1"),
- ("Test Mobile E", "_Test Item Group B - 2")
+ ("Test Mobile E", "_Test Item Group B - 2"),
]
for item in item_codes:
item_code = item[0]
@@ -28,18 +28,22 @@ class TestItemGroupProductDataEngine(unittest.TestCase):
if not frappe.db.exists("Website Item", {"item_code": item_code}):
create_regular_web_item(item_code, item_args=item_args)
- @classmethod
- def tearDownClass(cls):
+ frappe.db.set_value("Item Group", "_Test Item Group B - 1", "show_in_website", 1)
+ frappe.db.set_value("Item Group", "_Test Item Group B - 2", "show_in_website", 1)
+
+ def tearDown(self):
frappe.db.rollback()
def test_product_listing_in_item_group(self):
"Test if only products belonging to the Item Group are fetched."
- result = get_product_filter_data(query_args={
- "field_filters": {},
- "attribute_filters": {},
- "start": 0,
- "item_group": "_Test Item Group B"
- })
+ result = get_product_filter_data(
+ query_args={
+ "field_filters": {},
+ "attribute_filters": {},
+ "start": 0,
+ "item_group": "_Test Item Group B",
+ }
+ )
items = result.get("items")
item_codes = [item.get("item_code") for item in items]
@@ -53,49 +57,52 @@ class TestItemGroupProductDataEngine(unittest.TestCase):
website_item = frappe.get_doc("Website Item", {"item_code": "Test Mobile E"})
# show item belonging to '_Test Item Group B - 2' in '_Test Item Group B - 1' as well
- website_item.append("website_item_groups", {
- "item_group": "_Test Item Group B - 1"
- })
+ website_item.append("website_item_groups", {"item_group": "_Test Item Group B - 1"})
website_item.save()
- result = get_product_filter_data(query_args={
- "field_filters": {},
- "attribute_filters": {},
- "start": 0,
- "item_group": "_Test Item Group B - 1"
- })
+ result = get_product_filter_data(
+ query_args={
+ "field_filters": {},
+ "attribute_filters": {},
+ "start": 0,
+ "item_group": "_Test Item Group B - 1",
+ }
+ )
items = result.get("items")
item_codes = [item.get("item_code") for item in items]
self.assertEqual(len(items), 3)
- self.assertIn("Test Mobile E", item_codes) # visible in other item groups
+ self.assertIn("Test Mobile E", item_codes) # visible in other item groups
self.assertIn("Test Mobile C", item_codes)
self.assertIn("Test Mobile D", item_codes)
- result = get_product_filter_data(query_args={
- "field_filters": {},
- "attribute_filters": {},
- "start": 0,
- "item_group": "_Test Item Group B - 2"
- })
+ result = get_product_filter_data(
+ query_args={
+ "field_filters": {},
+ "attribute_filters": {},
+ "start": 0,
+ "item_group": "_Test Item Group B - 2",
+ }
+ )
items = result.get("items")
self.assertEqual(len(items), 1)
- self.assertEqual(items[0].get("item_code"), "Test Mobile E") # visible in own item group
+ self.assertEqual(items[0].get("item_code"), "Test Mobile E") # visible in own item group
def test_item_group_with_sub_groups(self):
"Test Valid Sub Item Groups in Item Group Page."
- frappe.db.set_value("Item Group", "_Test Item Group B - 1", "show_in_website", 1)
frappe.db.set_value("Item Group", "_Test Item Group B - 2", "show_in_website", 0)
- result = get_product_filter_data(query_args={
- "field_filters": {},
- "attribute_filters": {},
- "start": 0,
- "item_group": "_Test Item Group B"
- })
+ result = get_product_filter_data(
+ query_args={
+ "field_filters": {},
+ "attribute_filters": {},
+ "start": 0,
+ "item_group": "_Test Item Group B",
+ }
+ )
self.assertTrue(bool(result.get("sub_categories")))
@@ -104,14 +111,60 @@ class TestItemGroupProductDataEngine(unittest.TestCase):
self.assertIn("_Test Item Group B - 1", child_groups)
frappe.db.set_value("Item Group", "_Test Item Group B - 2", "show_in_website", 1)
- result = get_product_filter_data(query_args={
- "field_filters": {},
- "attribute_filters": {},
- "start": 0,
- "item_group": "_Test Item Group B"
- })
+ result = get_product_filter_data(
+ query_args={
+ "field_filters": {},
+ "attribute_filters": {},
+ "start": 0,
+ "item_group": "_Test Item Group B",
+ }
+ )
child_groups = [d.name for d in result.get("sub_categories")]
# check if child group is fetched if shown in website
self.assertIn("_Test Item Group B - 1", child_groups)
- self.assertIn("_Test Item Group B - 2", child_groups)
\ No newline at end of file
+ self.assertIn("_Test Item Group B - 2", child_groups)
+
+ def test_item_group_page_with_descendants_included(self):
+ """
+ Test if 'include_descendants' pulls Items belonging to descendant Item Groups (Level 2 & 3).
+ > _Test Item Group B [Level 1]
+ > _Test Item Group B - 1 [Level 2]
+ > _Test Item Group B - 1 - 1 [Level 3]
+ """
+ frappe.get_doc(
+ { # create Level 3 nested child group
+ "doctype": "Item Group",
+ "is_group": 1,
+ "item_group_name": "_Test Item Group B - 1 - 1",
+ "parent_item_group": "_Test Item Group B - 1",
+ }
+ ).insert()
+
+ create_regular_web_item( # create an item belonging to level 3 item group
+ "Test Mobile F", item_args={"item_group": "_Test Item Group B - 1 - 1"}
+ )
+
+ frappe.db.set_value("Item Group", "_Test Item Group B - 1 - 1", "show_in_website", 1)
+
+ # enable 'include descendants' in Level 1
+ frappe.db.set_value("Item Group", "_Test Item Group B", "include_descendants", 1)
+
+ result = get_product_filter_data(
+ query_args={
+ "field_filters": {},
+ "attribute_filters": {},
+ "start": 0,
+ "item_group": "_Test Item Group B",
+ }
+ )
+
+ items = result.get("items")
+ item_codes = [item.get("item_code") for item in items]
+
+ # check if all sub groups' items are pulled
+ self.assertEqual(len(items), 6)
+ self.assertIn("Test Mobile A", item_codes)
+ self.assertIn("Test Mobile C", item_codes)
+ self.assertIn("Test Mobile E", item_codes)
+ self.assertIn("Test Mobile F", item_codes)
diff --git a/erpnext/e_commerce/product_data_engine/test_product_data_engine.py b/erpnext/e_commerce/product_data_engine/test_product_data_engine.py
index 9ec336d1566..ab958d14866 100644
--- a/erpnext/e_commerce/product_data_engine/test_product_data_engine.py
+++ b/erpnext/e_commerce/product_data_engine/test_product_data_engine.py
@@ -14,19 +14,20 @@ from erpnext.e_commerce.product_data_engine.query import ProductQuery
test_dependencies = ["Item", "Item Group"]
+
class TestProductDataEngine(unittest.TestCase):
"Test Products Querying and Filters for Product Listing."
@classmethod
def setUpClass(cls):
item_codes = [
- ("Test 11I Laptop", "Products"), # rank 1
- ("Test 12I Laptop", "Products"), # rank 2
- ("Test 13I Laptop", "Products"), # rank 3
- ("Test 14I Laptop", "Raw Material"), # rank 4
- ("Test 15I Laptop", "Raw Material"), # rank 5
- ("Test 16I Laptop", "Raw Material"), # rank 6
- ("Test 17I Laptop", "Products") # rank 7
+ ("Test 11I Laptop", "Products"), # rank 1
+ ("Test 12I Laptop", "Products"), # rank 2
+ ("Test 13I Laptop", "Products"), # rank 3
+ ("Test 14I Laptop", "Raw Material"), # rank 4
+ ("Test 15I Laptop", "Raw Material"), # rank 5
+ ("Test 16I Laptop", "Raw Material"), # rank 6
+ ("Test 17I Laptop", "Products"), # rank 7
]
for index, item in enumerate(item_codes, start=1):
item_code = item[0]
@@ -35,17 +36,19 @@ class TestProductDataEngine(unittest.TestCase):
if not frappe.db.exists("Website Item", {"item_code": item_code}):
create_regular_web_item(item_code, item_args=item_args, web_args=web_args)
- setup_e_commerce_settings({
- "products_per_page": 4,
- "enable_field_filters": 1,
- "filter_fields": [{"fieldname": "item_group"}],
- "enable_attribute_filters": 1,
- "filter_attributes": [{"attribute": "Test Size"}],
- "company": "_Test Company",
- "enabled": 1,
- "default_customer_group": "_Test Customer Group",
- "price_list": "_Test Price List India"
- })
+ setup_e_commerce_settings(
+ {
+ "products_per_page": 4,
+ "enable_field_filters": 1,
+ "filter_fields": [{"fieldname": "item_group"}],
+ "enable_attribute_filters": 1,
+ "filter_attributes": [{"attribute": "Test Size"}],
+ "company": "_Test Company",
+ "enabled": 1,
+ "default_customer_group": "_Test Customer Group",
+ "price_list": "_Test Price List India",
+ }
+ )
frappe.local.shopping_cart_settings = None
@classmethod
@@ -55,13 +58,7 @@ class TestProductDataEngine(unittest.TestCase):
def test_product_list_ordering_and_paging(self):
"Test if website items appear by ranking on different pages."
engine = ProductQuery()
- result = engine.query(
- attributes={},
- fields={},
- search_term=None,
- start=0,
- item_group=None
- )
+ result = engine.query(attributes={}, fields={}, search_term=None, start=0, item_group=None)
items = result.get("items")
self.assertIsNotNone(items)
@@ -75,13 +72,7 @@ class TestProductDataEngine(unittest.TestCase):
self.assertEqual(items[3].get("item_code"), "Test 14I Laptop")
# check next page
- result = engine.query(
- attributes={},
- fields={},
- search_term=None,
- start=4,
- item_group=None
- )
+ result = engine.query(attributes={}, fields={}, search_term=None, start=4, item_group=None)
items = result.get("items")
# check if items appear as per ranking set in setUpClass on next page
@@ -101,13 +92,7 @@ class TestProductDataEngine(unittest.TestCase):
frappe.db.set_value("Website Item", {"item_code": item_code}, "ranking", 10)
engine = ProductQuery()
- result = engine.query(
- attributes={},
- fields={},
- search_term=None,
- start=0,
- item_group=None
- )
+ result = engine.query(attributes={}, fields={}, search_term=None, start=0, item_group=None)
items = result.get("items")
# check if item is the first item on the first page
@@ -152,11 +137,7 @@ class TestProductDataEngine(unittest.TestCase):
engine = ProductQuery()
result = engine.query(
- attributes={},
- fields=field_filters,
- search_term=None,
- start=0,
- item_group=None
+ attributes={}, fields=field_filters, search_term=None, start=0, item_group=None
)
items = result.get("items")
@@ -188,11 +169,7 @@ class TestProductDataEngine(unittest.TestCase):
attribute_filters = {"Test Size": ["Large"]}
engine = ProductQuery()
result = engine.query(
- attributes=attribute_filters,
- fields={},
- search_term=None,
- start=0,
- item_group=None
+ attributes=attribute_filters, fields={}, search_term=None, start=0, item_group=None
)
items = result.get("items")
@@ -209,24 +186,13 @@ class TestProductDataEngine(unittest.TestCase):
item_code = "Test 12I Laptop"
make_web_item_price(item_code=item_code)
- make_web_pricing_rule(
- title=f"Test Pricing Rule for {item_code}",
- item_code=item_code,
- selling=1
- )
+ make_web_pricing_rule(title=f"Test Pricing Rule for {item_code}", item_code=item_code, selling=1)
setup_e_commerce_settings({"show_price": 1})
frappe.local.shopping_cart_settings = None
-
engine = ProductQuery()
- result = engine.query(
- attributes={},
- fields={},
- search_term=None,
- start=4,
- item_group=None
- )
+ result = engine.query(attributes={}, fields={}, search_term=None, start=4, item_group=None)
self.assertTrue(bool(result.get("discounts")))
filter_engine = ProductFiltersBuilder()
@@ -247,16 +213,16 @@ class TestProductDataEngine(unittest.TestCase):
make_web_item_price(item_code="Test 12I Laptop")
make_web_pricing_rule(
- title="Test Pricing Rule for Test 12I Laptop", # 10% discount
+ title="Test Pricing Rule for Test 12I Laptop", # 10% discount
item_code="Test 12I Laptop",
- selling=1
+ selling=1,
)
make_web_item_price(item_code="Test 13I Laptop")
make_web_pricing_rule(
- title="Test Pricing Rule for Test 13I Laptop", # 15% discount
+ title="Test Pricing Rule for Test 13I Laptop", # 15% discount
item_code="Test 13I Laptop",
discount_percentage=15,
- selling=1
+ selling=1,
)
setup_e_commerce_settings({"show_price": 1})
@@ -264,11 +230,7 @@ class TestProductDataEngine(unittest.TestCase):
engine = ProductQuery()
result = engine.query(
- attributes={},
- fields=field_filters,
- search_term=None,
- start=0,
- item_group=None
+ attributes={}, fields=field_filters, search_term=None, start=0, item_group=None
)
items = result.get("items")
@@ -282,15 +244,13 @@ class TestProductDataEngine(unittest.TestCase):
create_variant_web_item()
- result = get_product_filter_data(query_args={
- "field_filters": {
- "item_group": "Products"
- },
- "attribute_filters": {
- "Test Size": ["Large"]
- },
- "start": 0
- })
+ result = get_product_filter_data(
+ query_args={
+ "field_filters": {"item_group": "Products"},
+ "attribute_filters": {"Test Size": ["Large"]},
+ "start": 0,
+ }
+ )
items = result.get("items")
@@ -301,20 +261,13 @@ class TestProductDataEngine(unittest.TestCase):
"Test if variants are hideen on hiding variants in settings."
create_variant_web_item()
- setup_e_commerce_settings({
- "enable_attribute_filters": 0,
- "hide_variants": 1
- })
+ setup_e_commerce_settings({"enable_attribute_filters": 0, "hide_variants": 1})
frappe.local.shopping_cart_settings = None
attribute_filters = {"Test Size": ["Large"]}
engine = ProductQuery()
result = engine.query(
- attributes=attribute_filters,
- fields={},
- search_term=None,
- start=0,
- item_group=None
+ attributes=attribute_filters, fields={}, search_term=None, start=0, item_group=None
)
items = result.get("items")
@@ -322,10 +275,8 @@ class TestProductDataEngine(unittest.TestCase):
self.assertEqual(len(items), 0)
# tear down
- setup_e_commerce_settings({
- "enable_attribute_filters": 1,
- "hide_variants": 0
- })
+ setup_e_commerce_settings({"enable_attribute_filters": 1, "hide_variants": 0})
+
def create_variant_web_item():
"Create Variant and Template Website Items."
@@ -333,18 +284,17 @@ def create_variant_web_item():
from erpnext.e_commerce.doctype.website_item.website_item import make_website_item
from erpnext.stock.doctype.item.test_item import make_item
- make_item("Test Web Item", {
- "has_variant": 1,
- "variant_based_on": "Item Attribute",
- "attributes": [
- {
- "attribute": "Test Size"
- }
- ]
- })
+ make_item(
+ "Test Web Item",
+ {
+ "has_variant": 1,
+ "variant_based_on": "Item Attribute",
+ "attributes": [{"attribute": "Test Size"}],
+ },
+ )
if not frappe.db.exists("Item", "Test Web Item-L"):
variant = create_variant("Test Web Item", {"Test Size": "Large"})
variant.save()
if not frappe.db.exists("Website Item", {"variant_of": "Test Web Item"}):
- make_website_item(variant, save=True)
\ No newline at end of file
+ make_website_item(variant, save=True)
diff --git a/erpnext/e_commerce/product_ui/views.js b/erpnext/e_commerce/product_ui/views.js
index 1b5c44038f3..fb63b21a083 100644
--- a/erpnext/e_commerce/product_ui/views.js
+++ b/erpnext/e_commerce/product_ui/views.js
@@ -418,6 +418,22 @@ erpnext.ProductView = class {
me.change_route_with_filters();
});
+
+ // bind filter lookup input box
+ $('.filter-lookup-input').on('keydown', frappe.utils.debounce((e) => {
+ const $input = $(e.target);
+ const keyword = ($input.val() || '').toLowerCase();
+ const $filter_options = $input.next('.filter-options');
+
+ $filter_options.find('.filter-lookup-wrapper').show();
+ $filter_options.find('.filter-lookup-wrapper').each((i, el) => {
+ const $el = $(el);
+ const value = $el.data('value').toLowerCase();
+ if (!value.includes(keyword)) {
+ $el.hide();
+ }
+ });
+ }, 300));
}
change_route_with_filters() {
@@ -495,7 +511,7 @@ erpnext.ProductView = class {
categories.forEach(category => {
sub_group_html += `
-
+
${ category.name }
diff --git a/erpnext/e_commerce/redisearch_utils.py b/erpnext/e_commerce/redisearch_utils.py
index 59c7f32fd46..f2dd796f2c5 100644
--- a/erpnext/e_commerce/redisearch_utils.py
+++ b/erpnext/e_commerce/redisearch_utils.py
@@ -1,73 +1,90 @@
-# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
+import json
+
import frappe
+from frappe import _
from frappe.utils.redis_wrapper import RedisWrapper
+from redis import ResponseError
from redisearch import AutoCompleter, Client, IndexDefinition, Suggestion, TagField, TextField
-WEBSITE_ITEM_INDEX = 'website_items_index'
-WEBSITE_ITEM_KEY_PREFIX = 'website_item:'
-WEBSITE_ITEM_NAME_AUTOCOMPLETE = 'website_items_name_dict'
-WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE = 'website_items_category_dict'
+WEBSITE_ITEM_INDEX = "website_items_index"
+WEBSITE_ITEM_KEY_PREFIX = "website_item:"
+WEBSITE_ITEM_NAME_AUTOCOMPLETE = "website_items_name_dict"
+WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE = "website_items_category_dict"
+
def get_indexable_web_fields():
"Return valid fields from Website Item that can be searched for."
web_item_meta = frappe.get_meta("Website Item", cached=True)
valid_fields = filter(
lambda df: df.fieldtype in ("Link", "Table MultiSelect", "Data", "Small Text", "Text Editor"),
- web_item_meta.fields)
+ web_item_meta.fields,
+ )
return [df.fieldname for df in valid_fields]
+
+def is_redisearch_enabled():
+ "Return True only if redisearch is loaded and enabled."
+ is_redisearch_enabled = frappe.db.get_single_value("E Commerce Settings", "is_redisearch_enabled")
+ return is_search_module_loaded() and is_redisearch_enabled
+
+
def is_search_module_loaded():
try:
cache = frappe.cache()
- out = cache.execute_command('MODULE LIST')
+ out = cache.execute_command("MODULE LIST")
parsed_output = " ".join(
(" ".join([s.decode() for s in o if not isinstance(s, int)]) for o in out)
)
return "search" in parsed_output
except Exception:
- return False
+ return False # handling older redis versions
+
+
+def if_redisearch_enabled(function):
+ "Decorator to check if Redisearch is enabled."
-def if_redisearch_loaded(function):
- "Decorator to check if Redisearch is loaded."
def wrapper(*args, **kwargs):
- if is_search_module_loaded():
+ if is_redisearch_enabled():
func = function(*args, **kwargs)
return func
return
return wrapper
-def make_key(key):
- return "{0}|{1}".format(frappe.conf.db_name, key).encode('utf-8')
-@if_redisearch_loaded
+def make_key(key):
+ return "{0}|{1}".format(frappe.conf.db_name, key).encode("utf-8")
+
+
+@if_redisearch_enabled
def create_website_items_index():
"Creates Index Definition."
# CREATE index
client = Client(make_key(WEBSITE_ITEM_INDEX), conn=frappe.cache())
- # DROP if already exists
try:
- client.drop_index()
- except Exception:
+ client.drop_index() # drop if already exists
+ except ResponseError:
+ # will most likely raise a ResponseError if index does not exist
+ # ignore and create index
pass
+ except Exception:
+ raise_redisearch_error()
idx_def = IndexDefinition([make_key(WEBSITE_ITEM_KEY_PREFIX)])
- # Based on e-commerce settings
- idx_fields = frappe.db.get_single_value(
- 'E Commerce Settings',
- 'search_index_fields'
- )
- idx_fields = idx_fields.split(',') if idx_fields else []
+ # Index fields mentioned in e-commerce settings
+ idx_fields = frappe.db.get_single_value("E Commerce Settings", "search_index_fields")
+ idx_fields = idx_fields.split(",") if idx_fields else []
- if 'web_item_name' in idx_fields:
- idx_fields.remove('web_item_name')
+ if "web_item_name" in idx_fields:
+ idx_fields.remove("web_item_name")
idx_fields = list(map(to_search_field, idx_fields))
@@ -79,45 +96,51 @@ def create_website_items_index():
reindex_all_web_items()
define_autocomplete_dictionary()
+
def to_search_field(field):
if field == "tags":
return TagField("tags", separator=",")
return TextField(field)
-@if_redisearch_loaded
+
+@if_redisearch_enabled
def insert_item_to_index(website_item_doc):
# Insert item to index
key = get_cache_key(website_item_doc.name)
cache = frappe.cache()
web_item = create_web_item_map(website_item_doc)
- for k, v in web_item.items():
- super(RedisWrapper, cache).hset(make_key(key), k, v)
+ for field, value in web_item.items():
+ super(RedisWrapper, cache).hset(make_key(key), field, value)
insert_to_name_ac(website_item_doc.web_item_name, website_item_doc.name)
-@if_redisearch_loaded
+
+@if_redisearch_enabled
def insert_to_name_ac(web_name, doc_name):
ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=frappe.cache())
ac.add_suggestions(Suggestion(web_name, payload=doc_name))
+
def create_web_item_map(website_item_doc):
fields_to_index = get_fields_indexed()
web_item = {}
- for f in fields_to_index:
- web_item[f] = website_item_doc.get(f) or ''
+ for field in fields_to_index:
+ web_item[field] = website_item_doc.get(field) or ""
return web_item
-@if_redisearch_loaded
+
+@if_redisearch_enabled
def update_index_for_item(website_item_doc):
# Reinsert to Cache
insert_item_to_index(website_item_doc)
define_autocomplete_dictionary()
-@if_redisearch_loaded
+
+@if_redisearch_enabled
def delete_item_from_index(website_item_doc):
cache = frappe.cache()
key = get_cache_key(website_item_doc.name)
@@ -125,86 +148,107 @@ def delete_item_from_index(website_item_doc):
try:
cache.delete(key)
except Exception:
- return False
+ raise_redisearch_error()
delete_from_ac_dict(website_item_doc)
return True
-@if_redisearch_loaded
+
+@if_redisearch_enabled
def delete_from_ac_dict(website_item_doc):
- '''Removes this items's name from autocomplete dictionary'''
+ """Removes this items's name from autocomplete dictionary"""
cache = frappe.cache()
name_ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=cache)
name_ac.delete(website_item_doc.web_item_name)
-@if_redisearch_loaded
+
+@if_redisearch_enabled
def define_autocomplete_dictionary():
- """Creates an autocomplete search dictionary for `name`.
- Also creats autocomplete dictionary for `categories` if
- checked in E Commerce Settings"""
+ """
+ Defines/Redefines an autocomplete search dictionary for Website Item Name.
+ Also creats autocomplete dictionary for Published Item Groups.
+ """
cache = frappe.cache()
- name_ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=cache)
- cat_ac = AutoCompleter(make_key(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE), conn=cache)
-
- ac_categories = frappe.db.get_single_value(
- 'E Commerce Settings',
- 'show_categories_in_search_autocomplete'
- )
+ item_ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=cache)
+ item_group_ac = AutoCompleter(make_key(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE), conn=cache)
# Delete both autocomplete dicts
try:
cache.delete(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE))
cache.delete(make_key(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE))
except Exception:
- return False
+ raise_redisearch_error()
+ create_items_autocomplete_dict(autocompleter=item_ac)
+ create_item_groups_autocomplete_dict(autocompleter=item_group_ac)
+
+
+@if_redisearch_enabled
+def create_items_autocomplete_dict(autocompleter):
+ "Add items as suggestions in Autocompleter."
items = frappe.get_all(
- 'Website Item',
- fields=['web_item_name', 'item_group'],
- filters={"published": 1}
+ "Website Item", fields=["web_item_name", "item_group"], filters={"published": 1}
)
for item in items:
- name_ac.add_suggestions(Suggestion(item.web_item_name))
- if ac_categories and item.item_group:
- cat_ac.add_suggestions(Suggestion(item.item_group))
+ autocompleter.add_suggestions(Suggestion(item.web_item_name))
- return True
-@if_redisearch_loaded
-def reindex_all_web_items():
- items = frappe.get_all(
- 'Website Item',
- fields=get_fields_indexed(),
- filters={"published": True}
+@if_redisearch_enabled
+def create_item_groups_autocomplete_dict(autocompleter):
+ "Add item groups with weightage as suggestions in Autocompleter."
+ published_item_groups = frappe.get_all(
+ "Item Group", fields=["name", "route", "weightage"], filters={"show_in_website": 1}
)
+ if not published_item_groups:
+ return
+
+ for item_group in published_item_groups:
+ payload = json.dumps({"name": item_group.name, "route": item_group.route})
+ autocompleter.add_suggestions(
+ Suggestion(
+ string=item_group.name,
+ score=frappe.utils.flt(item_group.weightage) or 1.0,
+ payload=payload, # additional info that can be retrieved later
+ )
+ )
+
+
+@if_redisearch_enabled
+def reindex_all_web_items():
+ items = frappe.get_all("Website Item", fields=get_fields_indexed(), filters={"published": True})
cache = frappe.cache()
for item in items:
web_item = create_web_item_map(item)
key = make_key(get_cache_key(item.name))
- for k, v in web_item.items():
- super(RedisWrapper, cache).hset(key, k, v)
+ for field, value in web_item.items():
+ super(RedisWrapper, cache).hset(key, field, value)
+
def get_cache_key(name):
name = frappe.scrub(name)
return f"{WEBSITE_ITEM_KEY_PREFIX}{name}"
-def get_fields_indexed():
- fields_to_index = frappe.db.get_single_value(
- 'E Commerce Settings',
- 'search_index_fields'
- )
- fields_to_index = fields_to_index.split(',') if fields_to_index else []
- mandatory_fields = ['name', 'web_item_name', 'route', 'thumbnail', 'ranking']
+def get_fields_indexed():
+ fields_to_index = frappe.db.get_single_value("E Commerce Settings", "search_index_fields")
+ fields_to_index = fields_to_index.split(",") if fields_to_index else []
+
+ mandatory_fields = ["name", "web_item_name", "route", "thumbnail", "ranking"]
fields_to_index = fields_to_index + mandatory_fields
return fields_to_index
-# TODO: Remove later
-# # Figure out a way to run this at startup
-define_autocomplete_dictionary()
-create_website_items_index()
+
+def raise_redisearch_error():
+ "Create an Error Log and raise error."
+ traceback = frappe.get_traceback()
+ log = frappe.log_error(traceback, frappe._("Redisearch Error"))
+ log_link = frappe.utils.get_link_to_form("Error Log", log.name)
+
+ frappe.throw(
+ msg=_("Something went wrong. Check {0}").format(log_link), title=_("Redisearch Error")
+ )
diff --git a/erpnext/e_commerce/shopping_cart/cart.py b/erpnext/e_commerce/shopping_cart/cart.py
index 458cf69af7e..4c823936848 100644
--- a/erpnext/e_commerce/shopping_cart/cart.py
+++ b/erpnext/e_commerce/shopping_cart/cart.py
@@ -19,6 +19,7 @@ from erpnext.utilities.product import get_web_item_qty_in_stock
class WebsitePriceListMissingError(frappe.ValidationError):
pass
+
def set_cart_count(quotation=None):
if cint(frappe.db.get_singles_value("E Commerce Settings", "enabled")):
if not quotation:
@@ -28,6 +29,7 @@ def set_cart_count(quotation=None):
if hasattr(frappe.local, "cookie_manager"):
frappe.local.cookie_manager.set_cookie("cart_count", cart_count)
+
@frappe.whitelist()
def get_cart_quotation(doc=None):
party = get_party()
@@ -47,38 +49,46 @@ def get_cart_quotation(doc=None):
"shipping_addresses": get_shipping_addresses(party),
"billing_addresses": get_billing_addresses(party),
"shipping_rules": get_applicable_shipping_rules(party),
- "cart_settings": frappe.get_cached_doc("E Commerce Settings")
+ "cart_settings": frappe.get_cached_doc("E Commerce Settings"),
}
+
@frappe.whitelist()
def get_shipping_addresses(party=None):
if not party:
party = get_party()
addresses = get_address_docs(party=party)
- return [{"name": address.name, "title": address.address_title, "display": address.display}
- for address in addresses if address.address_type == "Shipping"
+ return [
+ {"name": address.name, "title": address.address_title, "display": address.display}
+ for address in addresses
+ if address.address_type == "Shipping"
]
+
@frappe.whitelist()
def get_billing_addresses(party=None):
if not party:
party = get_party()
addresses = get_address_docs(party=party)
- return [{"name": address.name, "title": address.address_title, "display": address.display}
- for address in addresses if address.address_type == "Billing"
+ return [
+ {"name": address.name, "title": address.address_title, "display": address.display}
+ for address in addresses
+ if address.address_type == "Billing"
]
+
@frappe.whitelist()
def place_order():
quotation = _get_cart_quotation()
- cart_settings = frappe.db.get_value("E Commerce Settings", None,
- ["company", "allow_items_not_in_stock"], as_dict=1)
+ cart_settings = frappe.db.get_value(
+ "E Commerce Settings", None, ["company", "allow_items_not_in_stock"], as_dict=1
+ )
quotation.company = cart_settings.company
quotation.flags.ignore_permissions = True
quotation.submit()
- if quotation.quotation_to == 'Lead' and quotation.party_name:
+ if quotation.quotation_to == "Lead" and quotation.party_name:
# company used to create customer accounts
frappe.defaults.set_user_default("company", quotation.company)
@@ -86,17 +96,14 @@ def place_order():
frappe.throw(_("Set Shipping Address or Billing Address"))
from erpnext.selling.doctype.quotation.quotation import _make_sales_order
+
sales_order = frappe.get_doc(_make_sales_order(quotation.name, ignore_permissions=True))
sales_order.payment_schedule = []
if not cint(cart_settings.allow_items_not_in_stock):
for item in sales_order.get("items"):
item.warehouse = frappe.db.get_value(
- "Website Item",
- {
- "item_code": item.item_code
- },
- "website_warehouse"
+ "Website Item", {"item_code": item.item_code}, "website_warehouse"
)
is_stock_item = frappe.db.get_value("Item", item.item_code, "is_stock_item")
@@ -116,13 +123,19 @@ def place_order():
return sales_order.name
+
@frappe.whitelist()
def request_for_quotation():
quotation = _get_cart_quotation()
quotation.flags.ignore_permissions = True
- quotation.submit()
+
+ if get_shopping_cart_settings().save_quotations_as_draft:
+ quotation.save()
+ else:
+ quotation.submit()
return quotation.name
+
@frappe.whitelist()
def update_cart(item_code, qty, additional_notes=None, with_items=False):
quotation = _get_cart_quotation()
@@ -139,12 +152,15 @@ def update_cart(item_code, qty, additional_notes=None, with_items=False):
else:
quotation_items = quotation.get("items", {"item_code": item_code})
if not quotation_items:
- quotation.append("items", {
- "doctype": "Quotation Item",
- "item_code": item_code,
- "qty": qty,
- "additional_notes": additional_notes
- })
+ quotation.append(
+ "items",
+ {
+ "doctype": "Quotation Item",
+ "item_code": item_code,
+ "qty": qty,
+ "additional_notes": additional_notes,
+ },
+ )
else:
quotation_items[0].qty = qty
quotation_items[0].additional_notes = additional_notes
@@ -164,73 +180,75 @@ def update_cart(item_code, qty, additional_notes=None, with_items=False):
if cint(with_items):
context = get_cart_quotation(quotation)
return {
- "items": frappe.render_template("templates/includes/cart/cart_items.html",
- context),
- "total": frappe.render_template("templates/includes/cart/cart_items_total.html",
- context),
- "taxes_and_totals": frappe.render_template("templates/includes/cart/cart_payment_summary.html",
- context)
+ "items": frappe.render_template("templates/includes/cart/cart_items.html", context),
+ "total": frappe.render_template("templates/includes/cart/cart_items_total.html", context),
+ "taxes_and_totals": frappe.render_template(
+ "templates/includes/cart/cart_payment_summary.html", context
+ ),
}
else:
- return {
- 'name': quotation.name
- }
+ return {"name": quotation.name}
+
@frappe.whitelist()
def get_shopping_cart_menu(context=None):
if not context:
context = get_cart_quotation()
- return frappe.render_template('templates/includes/cart/cart_dropdown.html', context)
+ return frappe.render_template("templates/includes/cart/cart_dropdown.html", context)
@frappe.whitelist()
def add_new_address(doc):
doc = frappe.parse_json(doc)
- doc.update({
- 'doctype': 'Address'
- })
+ doc.update({"doctype": "Address"})
address = frappe.get_doc(doc)
address.save(ignore_permissions=True)
return address
+
@frappe.whitelist(allow_guest=True)
def create_lead_for_item_inquiry(lead, subject, message):
lead = frappe.parse_json(lead)
- lead_doc = frappe.new_doc('Lead')
+ lead_doc = frappe.new_doc("Lead")
for fieldname in ("lead_name", "company_name", "email_id", "phone"):
lead_doc.set(fieldname, lead.get(fieldname))
- lead_doc.set('lead_owner', '')
+ lead_doc.set("lead_owner", "")
- if not frappe.db.exists('Lead Source', 'Product Inquiry'):
- frappe.get_doc({
- 'doctype': 'Lead Source',
- 'source_name' : 'Product Inquiry'
- }).insert(ignore_permissions=True)
+ if not frappe.db.exists("Lead Source", "Product Inquiry"):
+ frappe.get_doc({"doctype": "Lead Source", "source_name": "Product Inquiry"}).insert(
+ ignore_permissions=True
+ )
- lead_doc.set('source', 'Product Inquiry')
+ lead_doc.set("source", "Product Inquiry")
try:
lead_doc.save(ignore_permissions=True)
except frappe.exceptions.DuplicateEntryError:
frappe.clear_messages()
- lead_doc = frappe.get_doc('Lead', {'email_id': lead['email_id']})
+ lead_doc = frappe.get_doc("Lead", {"email_id": lead["email_id"]})
- lead_doc.add_comment('Comment', text='''
+ lead_doc.add_comment(
+ "Comment",
+ text="""
- '''.format(subject=subject, message=message))
+ """.format(
+ subject=subject, message=message
+ ),
+ )
return lead_doc
@frappe.whitelist()
def get_terms_and_conditions(terms_name):
- return frappe.db.get_value('Terms and Conditions', terms_name, 'terms')
+ return frappe.db.get_value("Terms and Conditions", terms_name, "terms")
+
@frappe.whitelist()
def update_cart_address(address_type, address_name):
@@ -247,31 +265,35 @@ def update_cart_address(address_type, address_name):
quotation.shipping_address_name = address_name
quotation.shipping_address = address_display
quotation.customer_address = quotation.customer_address or address_name
- address_doc = next((doc for doc in get_shipping_addresses() if doc["name"] == address_name), None)
+ address_doc = next(
+ (doc for doc in get_shipping_addresses() if doc["name"] == address_name), None
+ )
apply_cart_settings(quotation=quotation)
quotation.flags.ignore_permissions = True
quotation.save()
context = get_cart_quotation(quotation)
- context['address'] = address_doc
+ context["address"] = address_doc
return {
- "taxes": frappe.render_template("templates/includes/order/order_taxes.html",
- context),
- "address": frappe.render_template("templates/includes/cart/address_card.html",
- context)
+ "taxes": frappe.render_template("templates/includes/order/order_taxes.html", context),
+ "address": frappe.render_template("templates/includes/cart/address_card.html", context),
}
+
def guess_territory():
territory = None
geoip_country = frappe.session.get("session_country")
if geoip_country:
territory = frappe.db.get_value("Territory", geoip_country)
- return territory or \
- frappe.db.get_value("E Commerce Settings", None, "territory") or \
- get_root_of("Territory")
+ return (
+ territory
+ or frappe.db.get_value("E Commerce Settings", None, "territory")
+ or get_root_of("Territory")
+ )
+
def decorate_quotation_doc(doc):
for d in doc.get("items", []):
@@ -284,50 +306,56 @@ def decorate_quotation_doc(doc):
"Item",
filters={"item_code": item_code},
fieldname=["variant_of", "item_name", "image"],
- as_dict=True
+ as_dict=True,
)[0]
item_code = variant_data.variant_of
fields = fields[1:]
d.web_item_name = variant_data.item_name
- if variant_data.image: # get image from variant or template web item
+ if variant_data.image: # get image from variant or template web item
d.thumbnail = variant_data.image
fields = fields[2:]
- d.update(frappe.db.get_value(
- "Website Item",
- {"item_code": item_code},
- fields,
- as_dict=True)
- )
+ d.update(frappe.db.get_value("Website Item", {"item_code": item_code}, fields, as_dict=True))
return doc
def _get_cart_quotation(party=None):
- '''Return the open Quotation of type "Shopping Cart" or make a new one'''
+ """Return the open Quotation of type "Shopping Cart" or make a new one"""
if not party:
party = get_party()
- quotation = frappe.get_all("Quotation", fields=["name"], filters=
- {"party_name": party.name, "order_type": "Shopping Cart", "docstatus": 0},
- order_by="modified desc", limit_page_length=1)
+ quotation = frappe.get_all(
+ "Quotation",
+ fields=["name"],
+ filters={
+ "party_name": party.name,
+ "contact_email": frappe.session.user,
+ "order_type": "Shopping Cart",
+ "docstatus": 0,
+ },
+ order_by="modified desc",
+ limit_page_length=1,
+ )
if quotation:
qdoc = frappe.get_doc("Quotation", quotation[0].name)
else:
company = frappe.db.get_value("E Commerce Settings", None, ["company"])
- qdoc = frappe.get_doc({
- "doctype": "Quotation",
- "naming_series": get_shopping_cart_settings().quotation_series or "QTN-CART-",
- "quotation_to": party.doctype,
- "company": company,
- "order_type": "Shopping Cart",
- "status": "Draft",
- "docstatus": 0,
- "__islocal": 1,
- "party_name": party.name
- })
+ qdoc = frappe.get_doc(
+ {
+ "doctype": "Quotation",
+ "naming_series": get_shopping_cart_settings().quotation_series or "QTN-CART-",
+ "quotation_to": party.doctype,
+ "company": company,
+ "order_type": "Shopping Cart",
+ "status": "Draft",
+ "docstatus": 0,
+ "__islocal": 1,
+ "party_name": party.name,
+ }
+ )
qdoc.contact_person = frappe.db.get_value("Contact", {"email_id": frappe.session.user})
qdoc.contact_email = frappe.session.user
@@ -338,6 +366,7 @@ def _get_cart_quotation(party=None):
return qdoc
+
def update_party(fullname, company_name=None, mobile_no=None, phone=None):
party = get_party()
@@ -365,6 +394,7 @@ def update_party(fullname, company_name=None, mobile_no=None, phone=None):
qdoc.flags.ignore_permissions = True
qdoc.save()
+
def apply_cart_settings(party=None, quotation=None):
if not party:
party = get_party()
@@ -381,14 +411,16 @@ def apply_cart_settings(party=None, quotation=None):
_apply_shipping_rule(party, quotation, cart_settings)
+
def set_price_list_and_rate(quotation, cart_settings):
"""set price list based on billing territory"""
_set_price_list(cart_settings, quotation)
# reset values
- quotation.price_list_currency = quotation.currency = \
- quotation.plc_conversion_rate = quotation.conversion_rate = None
+ quotation.price_list_currency = (
+ quotation.currency
+ ) = quotation.plc_conversion_rate = quotation.conversion_rate = None
for item in quotation.get("items"):
item.price_list_rate = item.discount_percentage = item.rate = item.amount = None
@@ -399,9 +431,11 @@ def set_price_list_and_rate(quotation, cart_settings):
# set it in cookies for using in product page
frappe.local.cookie_manager.set_cookie("selling_price_list", quotation.selling_price_list)
+
def _set_price_list(cart_settings, quotation=None):
"""Set price list based on customer or shopping cart default"""
from erpnext.accounts.party import get_default_price_list
+
party_name = quotation.get("party_name") if quotation else get_party().get("name")
selling_price_list = None
@@ -418,23 +452,33 @@ def _set_price_list(cart_settings, quotation=None):
return selling_price_list
+
def set_taxes(quotation, cart_settings):
"""set taxes based on billing territory"""
from erpnext.accounts.party import set_taxes
customer_group = frappe.db.get_value("Customer", quotation.party_name, "customer_group")
- quotation.taxes_and_charges = set_taxes(quotation.party_name, "Customer",
- quotation.transaction_date, quotation.company, customer_group=customer_group, supplier_group=None,
- tax_category=quotation.tax_category, billing_address=quotation.customer_address,
- shipping_address=quotation.shipping_address_name, use_for_shopping_cart=1)
-#
-# # clear table
+ quotation.taxes_and_charges = set_taxes(
+ quotation.party_name,
+ "Customer",
+ quotation.transaction_date,
+ quotation.company,
+ customer_group=customer_group,
+ supplier_group=None,
+ tax_category=quotation.tax_category,
+ billing_address=quotation.customer_address,
+ shipping_address=quotation.shipping_address_name,
+ use_for_shopping_cart=1,
+ )
+ #
+ # # clear table
quotation.set("taxes", [])
-#
-# # append taxes
+ #
+ # # append taxes
quotation.append_taxes_from_master()
+
def get_party(user=None):
if not user:
user = frappe.session.user
@@ -443,14 +487,14 @@ def get_party(user=None):
party = None
if contact_name:
- contact = frappe.get_doc('Contact', contact_name)
+ contact = frappe.get_doc("Contact", contact_name)
if contact.links:
party_doctype = contact.links[0].link_doctype
party = contact.links[0].link_name
cart_settings = frappe.get_doc("E Commerce Settings")
- debtors_account = ''
+ debtors_account = ""
if cart_settings.enable_checkout:
debtors_account = get_debtors_account(cart_settings)
@@ -464,57 +508,62 @@ def get_party(user=None):
raise frappe.Redirect
customer = frappe.new_doc("Customer")
fullname = get_fullname(user)
- customer.update({
- "customer_name": fullname,
- "customer_type": "Individual",
- "customer_group": get_shopping_cart_settings().default_customer_group,
- "territory": get_root_of("Territory")
- })
+ customer.update(
+ {
+ "customer_name": fullname,
+ "customer_type": "Individual",
+ "customer_group": get_shopping_cart_settings().default_customer_group,
+ "territory": get_root_of("Territory"),
+ }
+ )
if debtors_account:
- customer.update({
- "accounts": [{
- "company": cart_settings.company,
- "account": debtors_account
- }]
- })
+ customer.update({"accounts": [{"company": cart_settings.company, "account": debtors_account}]})
customer.flags.ignore_mandatory = True
customer.insert(ignore_permissions=True)
contact = frappe.new_doc("Contact")
- contact.update({
- "first_name": fullname,
- "email_ids": [{"email_id": user, "is_primary": 1}]
- })
- contact.append('links', dict(link_doctype='Customer', link_name=customer.name))
+ contact.update({"first_name": fullname, "email_ids": [{"email_id": user, "is_primary": 1}]})
+ contact.append("links", dict(link_doctype="Customer", link_name=customer.name))
contact.flags.ignore_mandatory = True
contact.insert(ignore_permissions=True)
return customer
+
def get_debtors_account(cart_settings):
if not cart_settings.payment_gateway_account:
frappe.throw(_("Payment Gateway Account not set"), _("Mandatory"))
- payment_gateway_account_currency = \
- frappe.get_doc("Payment Gateway Account", cart_settings.payment_gateway_account).currency
+ payment_gateway_account_currency = frappe.get_doc(
+ "Payment Gateway Account", cart_settings.payment_gateway_account
+ ).currency
account_name = _("Debtors ({0})").format(payment_gateway_account_currency)
- debtors_account_name = get_account_name("Receivable", "Asset", is_group=0,\
- account_currency=payment_gateway_account_currency, company=cart_settings.company)
+ debtors_account_name = get_account_name(
+ "Receivable",
+ "Asset",
+ is_group=0,
+ account_currency=payment_gateway_account_currency,
+ company=cart_settings.company,
+ )
if not debtors_account_name:
- debtors_account = frappe.get_doc({
- "doctype": "Account",
- "account_type": "Receivable",
- "root_type": "Asset",
- "is_group": 0,
- "parent_account": get_account_name(root_type="Asset", is_group=1, company=cart_settings.company),
- "account_name": account_name,
- "currency": payment_gateway_account_currency
- }).insert(ignore_permissions=True)
+ debtors_account = frappe.get_doc(
+ {
+ "doctype": "Account",
+ "account_type": "Receivable",
+ "root_type": "Asset",
+ "is_group": 0,
+ "parent_account": get_account_name(
+ root_type="Asset", is_group=1, company=cart_settings.company
+ ),
+ "account_name": account_name,
+ "currency": payment_gateway_account_currency,
+ }
+ ).insert(ignore_permissions=True)
return debtors_account.name
@@ -522,26 +571,31 @@ def get_debtors_account(cart_settings):
return debtors_account_name
-def get_address_docs(doctype=None, txt=None, filters=None, limit_start=0, limit_page_length=20,
- party=None):
+def get_address_docs(
+ doctype=None, txt=None, filters=None, limit_start=0, limit_page_length=20, party=None
+):
if not party:
party = get_party()
if not party:
return []
- address_names = frappe.db.get_all('Dynamic Link', fields=('parent'),
- filters=dict(parenttype='Address', link_doctype=party.doctype, link_name=party.name))
+ address_names = frappe.db.get_all(
+ "Dynamic Link",
+ fields=("parent"),
+ filters=dict(parenttype="Address", link_doctype=party.doctype, link_name=party.name),
+ )
out = []
for a in address_names:
- address = frappe.get_doc('Address', a.parent)
+ address = frappe.get_doc("Address", a.parent)
address.display = get_address_display(address.as_dict())
out.append(address)
return out
+
@frappe.whitelist()
def apply_shipping_rule(shipping_rule):
quotation = _get_cart_quotation()
@@ -555,6 +609,7 @@ def apply_shipping_rule(shipping_rule):
return get_cart_quotation(quotation)
+
def _apply_shipping_rule(party=None, quotation=None, cart_settings=None):
if not quotation.shipping_rule:
shipping_rules = get_shipping_rules(quotation, cart_settings)
@@ -569,6 +624,7 @@ def _apply_shipping_rule(party=None, quotation=None, cart_settings=None):
quotation.run_method("apply_shipping_rule")
quotation.run_method("calculate_taxes_and_totals")
+
def get_applicable_shipping_rules(party=None, quotation=None):
shipping_rules = get_shipping_rules(quotation)
@@ -577,6 +633,7 @@ def get_applicable_shipping_rules(party=None, quotation=None):
# we need this in sorted order as per the position of the rule in the settings page
return [[rule, rule] for rule in shipping_rules]
+
def get_shipping_rules(quotation=None, cart_settings=None):
if not quotation:
quotation = _get_cart_quotation()
@@ -589,26 +646,24 @@ def get_shipping_rules(quotation=None, cart_settings=None):
sr = frappe.qb.DocType("Shipping Rule")
query = (
frappe.qb.from_(sr_country)
- .join(sr).on(sr.name == sr_country.parent)
+ .join(sr)
+ .on(sr.name == sr_country.parent)
.select(sr.name)
.distinct()
- .where(
- (sr_country.country == country)
- & (sr.disabled != 1)
- )
+ .where((sr_country.country == country) & (sr.disabled != 1))
)
result = query.run(as_list=True)
shipping_rules = [x[0] for x in result]
return shipping_rules
+
def get_address_territory(address_name):
"""Tries to match city, state and country of address to existing territory"""
territory = None
if address_name:
- address_fields = frappe.db.get_value("Address", address_name,
- ["city", "state", "country"])
+ address_fields = frappe.db.get_value("Address", address_name, ["city", "state", "country"])
for value in address_fields:
territory = frappe.db.get_value("Territory", value)
if territory:
@@ -616,9 +671,11 @@ def get_address_territory(address_name):
return territory
+
def show_terms(doc):
return doc.tc_name
+
@frappe.whitelist(allow_guest=True)
def apply_coupon_code(applied_code, applied_referral_sales_partner):
quotation = True
@@ -626,13 +683,14 @@ def apply_coupon_code(applied_code, applied_referral_sales_partner):
if not applied_code:
frappe.throw(_("Please enter a coupon code"))
- coupon_list = frappe.get_all('Coupon Code', filters={'coupon_code': applied_code})
+ coupon_list = frappe.get_all("Coupon Code", filters={"coupon_code": applied_code})
if not coupon_list:
frappe.throw(_("Please enter a valid coupon code"))
coupon_name = coupon_list[0].name
from erpnext.accounts.doctype.pricing_rule.utils import validate_coupon_code
+
validate_coupon_code(coupon_name)
quotation = _get_cart_quotation()
quotation.coupon_code = coupon_name
@@ -640,7 +698,9 @@ def apply_coupon_code(applied_code, applied_referral_sales_partner):
quotation.save()
if applied_referral_sales_partner:
- sales_partner_list = frappe.get_all('Sales Partner', filters={'referral_code': applied_referral_sales_partner})
+ sales_partner_list = frappe.get_all(
+ "Sales Partner", filters={"referral_code": applied_referral_sales_partner}
+ )
if sales_partner_list:
sales_partner_name = sales_partner_list[0].name
quotation.referral_sales_partner = sales_partner_name
diff --git a/erpnext/e_commerce/shopping_cart/product_info.py b/erpnext/e_commerce/shopping_cart/product_info.py
index 595fed01d25..0248ca73d7f 100644
--- a/erpnext/e_commerce/shopping_cart/product_info.py
+++ b/erpnext/e_commerce/shopping_cart/product_info.py
@@ -22,16 +22,17 @@ def get_product_info_for_website(item_code, skip_quotation_creation=False):
cart_settings = get_shopping_cart_settings()
if not cart_settings.enabled:
# return settings even if cart is disabled
- return frappe._dict({
- "product_info": {},
- "cart_settings": cart_settings
- })
+ return frappe._dict({"product_info": {}, "cart_settings": cart_settings})
cart_quotation = frappe._dict()
if not skip_quotation_creation:
cart_quotation = _get_cart_quotation()
- selling_price_list = cart_quotation.get("selling_price_list") if cart_quotation else _set_price_list(cart_settings, None)
+ selling_price_list = (
+ cart_quotation.get("selling_price_list")
+ if cart_quotation
+ else _set_price_list(cart_settings, None)
+ )
price = {}
if cart_settings.show_price:
@@ -40,10 +41,7 @@ def get_product_info_for_website(item_code, skip_quotation_creation=False):
# If not logged in, check if price is hidden for guest.
if not is_guest or not cart_settings.hide_price_for_guest:
price = get_price(
- item_code,
- selling_price_list,
- cart_settings.default_customer_group,
- cart_settings.company
+ item_code, selling_price_list, cart_settings.default_customer_group, cart_settings.company
)
stock_status = None
@@ -59,7 +57,7 @@ def get_product_info_for_website(item_code, skip_quotation_creation=False):
"price": price,
"qty": 0,
"uom": frappe.db.get_value("Item", item_code, "stock_uom"),
- "sales_uom": frappe.db.get_value("Item", item_code, "sales_uom")
+ "sales_uom": frappe.db.get_value("Item", item_code, "sales_uom"),
}
if stock_status:
@@ -67,7 +65,11 @@ def get_product_info_for_website(item_code, skip_quotation_creation=False):
product_info["on_backorder"] = True
else:
product_info["stock_qty"] = stock_status.stock_qty
- product_info["in_stock"] = stock_status.in_stock if stock_status.is_stock_item else get_non_stock_item_status(item_code, "website_warehouse")
+ product_info["in_stock"] = (
+ stock_status.in_stock
+ if stock_status.is_stock_item
+ else get_non_stock_item_status(item_code, "website_warehouse")
+ )
product_info["show_stock_qty"] = show_quantity_in_website()
if product_info["price"]:
@@ -76,14 +78,14 @@ def get_product_info_for_website(item_code, skip_quotation_creation=False):
if item:
product_info["qty"] = item[0].qty
- return frappe._dict({
- "product_info": product_info,
- "cart_settings": cart_settings
- })
+ return frappe._dict({"product_info": product_info, "cart_settings": cart_settings})
+
def set_product_info_for_website(item):
"""set product price uom for website"""
- product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get("product_info")
+ product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get(
+ "product_info"
+ )
if product_info:
item.update(product_info)
diff --git a/erpnext/e_commerce/shopping_cart/test_shopping_cart.py b/erpnext/e_commerce/shopping_cart/test_shopping_cart.py
index 8519e68d094..437ebeac069 100644
--- a/erpnext/e_commerce/shopping_cart/test_shopping_cart.py
+++ b/erpnext/e_commerce/shopping_cart/test_shopping_cart.py
@@ -5,7 +5,8 @@
import unittest
import frappe
-from frappe.utils import add_months, nowdate
+from frappe.tests.utils import change_settings
+from frappe.utils import add_months, cint, nowdate
from erpnext.accounts.doctype.tax_rule.tax_rule import ConflictingTaxRule
from erpnext.e_commerce.doctype.website_item.website_item import make_website_item
@@ -13,57 +14,67 @@ from erpnext.e_commerce.shopping_cart.cart import (
_get_cart_quotation,
get_cart_quotation,
get_party,
+ request_for_quotation,
update_cart,
)
-from erpnext.tests.utils import change_settings, create_test_contact_and_address
+from erpnext.tests.utils import create_test_contact_and_address
-# test_dependencies = ['Payment Terms Template']
class TestShoppingCart(unittest.TestCase):
"""
- Note:
- Shopping Cart == Quotation
+ Note:
+ Shopping Cart == Quotation
"""
- @classmethod
- def tearDownClass(cls):
- frappe.db.sql("delete from `tabTax Rule`")
-
def setUp(self):
frappe.set_user("Administrator")
create_test_contact_and_address()
self.enable_shopping_cart()
if not frappe.db.exists("Website Item", {"item_code": "_Test Item"}):
- make_website_item(frappe.get_cached_doc("Item", "_Test Item"))
+ make_website_item(frappe.get_cached_doc("Item", "_Test Item"))
if not frappe.db.exists("Website Item", {"item_code": "_Test Item 2"}):
- make_website_item(frappe.get_cached_doc("Item", "_Test Item 2"))
+ make_website_item(frappe.get_cached_doc("Item", "_Test Item 2"))
def tearDown(self):
frappe.db.rollback()
frappe.set_user("Administrator")
self.disable_shopping_cart()
+ @classmethod
+ def tearDownClass(cls):
+ frappe.db.sql("delete from `tabTax Rule`")
+
def test_get_cart_new_user(self):
self.login_as_new_user()
# test if lead is created and quotation with new lead is fetched
quotation = _get_cart_quotation()
self.assertEqual(quotation.quotation_to, "Customer")
- self.assertEqual(quotation.contact_person,
- frappe.db.get_value("Contact", dict(email_id="test_cart_user@example.com")))
+ self.assertEqual(
+ quotation.contact_person,
+ frappe.db.get_value("Contact", dict(email_id="test_cart_user@example.com")),
+ )
self.assertEqual(quotation.contact_email, frappe.session.user)
return quotation
def test_get_cart_customer(self):
- self.login_as_customer()
+ def validate_quotation():
+ # test if quotation with customer is fetched
+ quotation = _get_cart_quotation()
+ self.assertEqual(quotation.quotation_to, "Customer")
+ self.assertEqual(quotation.party_name, "_Test Customer")
+ self.assertEqual(quotation.contact_email, frappe.session.user)
+ return quotation
- # test if quotation with customer is fetched
- quotation = _get_cart_quotation()
- self.assertEqual(quotation.quotation_to, "Customer")
- self.assertEqual(quotation.party_name, "_Test Customer")
- self.assertEqual(quotation.contact_email, frappe.session.user)
+ self.login_as_customer(
+ "test_contact_two_customer@example.com", "_Test Contact 2 For _Test Customer"
+ )
+ validate_quotation()
+
+ self.login_as_customer()
+ quotation = validate_quotation()
return quotation
@@ -125,46 +136,55 @@ class TestShoppingCart(unittest.TestCase):
from erpnext.accounts.party import set_taxes
- tax_rule_master = set_taxes(quotation.party_name, "Customer",
- quotation.transaction_date, quotation.company, customer_group=None, supplier_group=None,
- tax_category=quotation.tax_category, billing_address=quotation.customer_address,
- shipping_address=quotation.shipping_address_name, use_for_shopping_cart=1)
+ tax_rule_master = set_taxes(
+ quotation.party_name,
+ "Customer",
+ quotation.transaction_date,
+ quotation.company,
+ customer_group=None,
+ supplier_group=None,
+ tax_category=quotation.tax_category,
+ billing_address=quotation.customer_address,
+ shipping_address=quotation.shipping_address_name,
+ use_for_shopping_cart=1,
+ )
self.assertEqual(quotation.taxes_and_charges, tax_rule_master)
self.assertEqual(quotation.total_taxes_and_charges, 1000.0)
self.remove_test_quotation(quotation)
- @change_settings("E Commerce Settings",{
- "company": "_Test Company",
- "enabled": 1,
- "default_customer_group": "_Test Customer Group",
- "price_list": "_Test Price List India",
- "show_price": 1
- })
+ @change_settings(
+ "E Commerce Settings",
+ {
+ "company": "_Test Company",
+ "enabled": 1,
+ "default_customer_group": "_Test Customer Group",
+ "price_list": "_Test Price List India",
+ "show_price": 1,
+ },
+ )
def test_add_item_variant_without_web_item_to_cart(self):
"Test adding Variants having no Website Items in cart via Template Web Item."
from erpnext.controllers.item_variant import create_variant
from erpnext.e_commerce.doctype.website_item.website_item import make_website_item
from erpnext.stock.doctype.item.test_item import make_item
- template_item = make_item("Test-Tshirt-Temp", {
- "has_variant": 1,
- "variant_based_on": "Item Attribute",
- "attributes": [
- {"attribute": "Test Size"},
- {"attribute": "Test Colour"}
- ]
- })
- variant = create_variant("Test-Tshirt-Temp", {
- "Test Size": "Small", "Test Colour": "Red"
- })
+ template_item = make_item(
+ "Test-Tshirt-Temp",
+ {
+ "has_variant": 1,
+ "variant_based_on": "Item Attribute",
+ "attributes": [{"attribute": "Test Size"}, {"attribute": "Test Colour"}],
+ },
+ )
+ variant = create_variant("Test-Tshirt-Temp", {"Test Size": "Small", "Test Colour": "Red"})
variant.save()
- make_website_item(template_item) # publish template not variant
+ make_website_item(template_item) # publish template not variant
update_cart("Test-Tshirt-Temp-S-R", 1)
- cart = get_cart_quotation() # test if cart page gets data without errors
+ cart = get_cart_quotation() # test if cart page gets data without errors
doc = cart.get("doc")
self.assertEqual(doc.get("items")[0].item_name, "Test-Tshirt-Temp-S-R")
@@ -172,10 +192,30 @@ class TestShoppingCart(unittest.TestCase):
# test if items are rendered without error
frappe.render_template("templates/includes/cart/cart_items.html", cart)
+ @change_settings("E Commerce Settings", {"save_quotations_as_draft": 1})
+ def test_cart_without_checkout_and_draft_quotation(self):
+ "Test impact of 'save_quotations_as_draft' checkbox."
+ frappe.local.shopping_cart_settings = None
+
+ # add item to cart
+ update_cart("_Test Item", 1)
+ quote_name = request_for_quotation() # Request for Quote
+ quote_doctstatus = cint(frappe.db.get_value("Quotation", quote_name, "docstatus"))
+
+ self.assertEqual(quote_doctstatus, 0)
+
+ frappe.db.set_value("E Commerce Settings", None, "save_quotations_as_draft", 0)
+ frappe.local.shopping_cart_settings = None
+ update_cart("_Test Item", 1)
+ quote_name = request_for_quotation() # Request for Quote
+ quote_doctstatus = cint(frappe.db.get_value("Quotation", quote_name, "docstatus"))
+
+ self.assertEqual(quote_doctstatus, 1)
+
def create_tax_rule(self):
tax_rule = frappe.get_test_records("Tax Rule")[0]
try:
- frappe.get_doc(tax_rule).insert()
+ frappe.get_doc(tax_rule).insert(ignore_if_duplicate=True)
except (frappe.DuplicateEntryError, ConflictingTaxRule):
pass
@@ -191,16 +231,13 @@ class TestShoppingCart(unittest.TestCase):
"contact_email": frappe.session.user,
"selling_price_list": "_Test Price List Rest of the World",
"currency": "USD",
- "taxes_and_charges" : "_Test Tax 1 - _TC",
- "conversion_rate":1,
- "transaction_date" : nowdate(),
- "valid_till" : add_months(nowdate(), 1),
- "items": [{
- "item_code": "_Test Item",
- "qty": 1
- }],
+ "taxes_and_charges": "_Test Tax 1 - _TC",
+ "conversion_rate": 1,
+ "transaction_date": nowdate(),
+ "valid_till": add_months(nowdate(), 1),
+ "items": [{"item_code": "_Test Item", "qty": 1}],
"taxes": frappe.get_doc("Sales Taxes and Charges Template", "_Test Tax 1 - _TC").taxes,
- "company": "_Test Company"
+ "company": "_Test Company",
}
quotation.update(values)
@@ -217,29 +254,36 @@ class TestShoppingCart(unittest.TestCase):
def enable_shopping_cart(self):
settings = frappe.get_doc("E Commerce Settings", "E Commerce Settings")
- settings.update({
- "enabled": 1,
- "company": "_Test Company",
- "default_customer_group": "_Test Customer Group",
- "quotation_series": "_T-Quotation-",
- "price_list": "_Test Price List India"
- })
+ settings.update(
+ {
+ "enabled": 1,
+ "company": "_Test Company",
+ "default_customer_group": "_Test Customer Group",
+ "quotation_series": "_T-Quotation-",
+ "price_list": "_Test Price List India",
+ }
+ )
# insert item price
- if not frappe.db.get_value("Item Price", {"price_list": "_Test Price List India",
- "item_code": "_Test Item"}):
- frappe.get_doc({
- "doctype": "Item Price",
- "price_list": "_Test Price List India",
- "item_code": "_Test Item",
- "price_list_rate": 10
- }).insert()
- frappe.get_doc({
- "doctype": "Item Price",
- "price_list": "_Test Price List India",
- "item_code": "_Test Item 2",
- "price_list_rate": 20
- }).insert()
+ if not frappe.db.get_value(
+ "Item Price", {"price_list": "_Test Price List India", "item_code": "_Test Item"}
+ ):
+ frappe.get_doc(
+ {
+ "doctype": "Item Price",
+ "price_list": "_Test Price List India",
+ "item_code": "_Test Item",
+ "price_list_rate": 10,
+ }
+ ).insert()
+ frappe.get_doc(
+ {
+ "doctype": "Item Price",
+ "price_list": "_Test Price List India",
+ "item_code": "_Test Item 2",
+ "price_list_rate": 20,
+ }
+ ).insert()
settings.save()
frappe.local.shopping_cart_settings = None
@@ -254,32 +298,49 @@ class TestShoppingCart(unittest.TestCase):
self.create_user_if_not_exists("test_cart_user@example.com")
frappe.set_user("test_cart_user@example.com")
- def login_as_customer(self):
- self.create_user_if_not_exists("test_contact_customer@example.com",
- "_Test Contact For _Test Customer")
- frappe.set_user("test_contact_customer@example.com")
+ def login_as_customer(
+ self, email="test_contact_customer@example.com", name="_Test Contact For _Test Customer"
+ ):
+ self.create_user_if_not_exists(email, name)
+ frappe.set_user(email)
def clear_existing_quotations(self):
- quotations = frappe.get_all("Quotation", filters={
- "party_name": get_party().name,
- "order_type": "Shopping Cart",
- "docstatus": 0
- }, order_by="modified desc", pluck="name")
+ quotations = frappe.get_all(
+ "Quotation",
+ filters={"party_name": get_party().name, "order_type": "Shopping Cart", "docstatus": 0},
+ order_by="modified desc",
+ pluck="name",
+ )
for quotation in quotations:
frappe.delete_doc("Quotation", quotation, ignore_permissions=True, force=True)
- def create_user_if_not_exists(self, email, first_name = None):
+ def create_user_if_not_exists(self, email, first_name=None):
if frappe.db.exists("User", email):
return
- frappe.get_doc({
- "doctype": "User",
- "user_type": "Website User",
- "email": email,
- "send_welcome_email": 0,
- "first_name": first_name or email.split("@")[0]
- }).insert(ignore_permissions=True)
+ frappe.get_doc(
+ {
+ "doctype": "User",
+ "user_type": "Website User",
+ "email": email,
+ "send_welcome_email": 0,
+ "first_name": first_name or email.split("@")[0],
+ }
+ ).insert(ignore_permissions=True)
-test_dependencies = ["Sales Taxes and Charges Template", "Price List", "Item Price", "Shipping Rule", "Currency Exchange",
- "Customer Group", "Lead", "Customer", "Contact", "Address", "Item", "Tax Rule"]
+
+test_dependencies = [
+ "Sales Taxes and Charges Template",
+ "Price List",
+ "Item Price",
+ "Shipping Rule",
+ "Currency Exchange",
+ "Customer Group",
+ "Lead",
+ "Customer",
+ "Contact",
+ "Address",
+ "Item",
+ "Tax Rule",
+]
diff --git a/erpnext/e_commerce/shopping_cart/utils.py b/erpnext/e_commerce/shopping_cart/utils.py
index e9745a44d72..3d48c28dd1c 100644
--- a/erpnext/e_commerce/shopping_cart/utils.py
+++ b/erpnext/e_commerce/shopping_cart/utils.py
@@ -6,12 +6,15 @@ from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import i
def show_cart_count():
- if (is_cart_enabled() and
- frappe.db.get_value("User", frappe.session.user, "user_type") == "Website User"):
+ if (
+ is_cart_enabled()
+ and frappe.db.get_value("User", frappe.session.user, "user_type") == "Website User"
+ ):
return True
return False
+
def set_cart_count(login_manager):
# since this is run only on hooks login event
# make sure user is already a customer
@@ -28,21 +31,24 @@ def set_cart_count(login_manager):
# cart count is calculated from this quotation's items
set_cart_count()
+
def clear_cart_count(login_manager):
if show_cart_count():
frappe.local.cookie_manager.delete_cookie("cart_count")
+
def update_website_context(context):
cart_enabled = is_cart_enabled()
context["shopping_cart_enabled"] = cart_enabled
+
def is_customer():
if frappe.session.user and frappe.session.user != "Guest":
contact_name = frappe.get_value("Contact", {"email_id": frappe.session.user})
if contact_name:
- contact = frappe.get_doc('Contact', contact_name)
+ contact = frappe.get_doc("Contact", contact_name)
for link in contact.links:
- if link.link_doctype == 'Customer':
+ if link.link_doctype == "Customer":
return True
return False
diff --git a/erpnext/e_commerce/variant_selector/item_variants_cache.py b/erpnext/e_commerce/variant_selector/item_variants_cache.py
index 3107c019e62..f8439d5d43d 100644
--- a/erpnext/e_commerce/variant_selector/item_variants_cache.py
+++ b/erpnext/e_commerce/variant_selector/item_variants_cache.py
@@ -6,63 +6,60 @@ class ItemVariantsCacheManager:
self.item_code = item_code
def get_item_variants_data(self):
- val = frappe.cache().hget('item_variants_data', self.item_code)
+ val = frappe.cache().hget("item_variants_data", self.item_code)
if not val:
self.build_cache()
- return frappe.cache().hget('item_variants_data', self.item_code)
-
+ return frappe.cache().hget("item_variants_data", self.item_code)
def get_attribute_value_item_map(self):
- val = frappe.cache().hget('attribute_value_item_map', self.item_code)
+ val = frappe.cache().hget("attribute_value_item_map", self.item_code)
if not val:
self.build_cache()
- return frappe.cache().hget('attribute_value_item_map', self.item_code)
-
+ return frappe.cache().hget("attribute_value_item_map", self.item_code)
def get_item_attribute_value_map(self):
- val = frappe.cache().hget('item_attribute_value_map', self.item_code)
+ val = frappe.cache().hget("item_attribute_value_map", self.item_code)
if not val:
self.build_cache()
- return frappe.cache().hget('item_attribute_value_map', self.item_code)
-
+ return frappe.cache().hget("item_attribute_value_map", self.item_code)
def get_optional_attributes(self):
- val = frappe.cache().hget('optional_attributes', self.item_code)
+ val = frappe.cache().hget("optional_attributes", self.item_code)
if not val:
self.build_cache()
- return frappe.cache().hget('optional_attributes', self.item_code)
+ return frappe.cache().hget("optional_attributes", self.item_code)
def get_ordered_attribute_values(self):
- val = frappe.cache().get_value('ordered_attribute_values_map')
- if val: return val
+ val = frappe.cache().get_value("ordered_attribute_values_map")
+ if val:
+ return val
- all_attribute_values = frappe.get_all('Item Attribute Value',
- ['attribute_value', 'idx', 'parent'], order_by='idx asc')
+ all_attribute_values = frappe.get_all(
+ "Item Attribute Value", ["attribute_value", "idx", "parent"], order_by="idx asc"
+ )
ordered_attribute_values_map = frappe._dict({})
for d in all_attribute_values:
ordered_attribute_values_map.setdefault(d.parent, []).append(d.attribute_value)
- frappe.cache().set_value('ordered_attribute_values_map', ordered_attribute_values_map)
+ frappe.cache().set_value("ordered_attribute_values_map", ordered_attribute_values_map)
return ordered_attribute_values_map
def build_cache(self):
parent_item_code = self.item_code
attributes = [
- a.attribute for a in frappe.get_all(
- 'Item Variant Attribute',
- {'parent': parent_item_code},
- ['attribute'],
- order_by='idx asc'
+ a.attribute
+ for a in frappe.get_all(
+ "Item Variant Attribute", {"parent": parent_item_code}, ["attribute"], order_by="idx asc"
)
]
@@ -71,13 +68,11 @@ class ItemVariantsCacheManager:
item = frappe.qb.DocType("Item")
query = (
frappe.qb.from_(iva)
- .join(item).on(item.name == iva.parent)
- .select(
- iva.parent, iva.attribute, iva.attribute_value
- ).where(
- (iva.variant_of == parent_item_code)
- & (item.disabled == 0)
- ).orderby(iva.name)
+ .join(item)
+ .on(item.name == iva.parent)
+ .select(iva.parent, iva.attribute, iva.attribute_value)
+ .where((iva.variant_of == parent_item_code) & (item.disabled == 0))
+ .orderby(iva.name)
)
item_variants_data = query.run()
@@ -97,13 +92,18 @@ class ItemVariantsCacheManager:
if attribute not in attr_dict:
optional_attributes.add(attribute)
- frappe.cache().hset('attribute_value_item_map', parent_item_code, attribute_value_item_map)
- frappe.cache().hset('item_attribute_value_map', parent_item_code, item_attribute_value_map)
- frappe.cache().hset('item_variants_data', parent_item_code, item_variants_data)
- frappe.cache().hset('optional_attributes', parent_item_code, optional_attributes)
+ frappe.cache().hset("attribute_value_item_map", parent_item_code, attribute_value_item_map)
+ frappe.cache().hset("item_attribute_value_map", parent_item_code, item_attribute_value_map)
+ frappe.cache().hset("item_variants_data", parent_item_code, item_variants_data)
+ frappe.cache().hset("optional_attributes", parent_item_code, optional_attributes)
def clear_cache(self):
- keys = ['attribute_value_item_map', 'item_attribute_value_map', 'item_variants_data', 'optional_attributes']
+ keys = [
+ "attribute_value_item_map",
+ "item_attribute_value_map",
+ "item_variants_data",
+ "optional_attributes",
+ ]
for key in keys:
frappe.cache().hdel(key, self.item_code)
@@ -114,15 +114,17 @@ class ItemVariantsCacheManager:
def build_cache(item_code):
- frappe.cache().hset('item_cache_build_in_progress', item_code, 1)
+ frappe.cache().hset("item_cache_build_in_progress", item_code, 1)
i = ItemVariantsCacheManager(item_code)
i.build_cache()
- frappe.cache().hset('item_cache_build_in_progress', item_code, 0)
+ frappe.cache().hset("item_cache_build_in_progress", item_code, 0)
+
def enqueue_build_cache(item_code):
- if frappe.cache().hget('item_cache_build_in_progress', item_code):
+ if frappe.cache().hget("item_cache_build_in_progress", item_code):
return
frappe.enqueue(
"erpnext.e_commerce.variant_selector.item_variants_cache.build_cache",
- item_code=item_code, queue='long'
+ item_code=item_code,
+ queue="long",
)
diff --git a/erpnext/e_commerce/variant_selector/test_variant_selector.py b/erpnext/e_commerce/variant_selector/test_variant_selector.py
index 4d907c62216..8eb497c1b53 100644
--- a/erpnext/e_commerce/variant_selector/test_variant_selector.py
+++ b/erpnext/e_commerce/variant_selector/test_variant_selector.py
@@ -1,4 +1,5 @@
import frappe
+from frappe.tests.utils import FrappeTestCase
from erpnext.controllers.item_variant import create_variant
from erpnext.e_commerce.doctype.e_commerce_settings.test_e_commerce_settings import (
@@ -7,44 +8,46 @@ from erpnext.e_commerce.doctype.e_commerce_settings.test_e_commerce_settings imp
from erpnext.e_commerce.doctype.website_item.website_item import make_website_item
from erpnext.e_commerce.variant_selector.utils import get_next_attribute_and_values
from erpnext.stock.doctype.item.test_item import make_item
-from erpnext.tests.utils import ERPNextTestCase
test_dependencies = ["Item"]
-class TestVariantSelector(ERPNextTestCase):
+class TestVariantSelector(FrappeTestCase):
@classmethod
def setUpClass(cls):
- template_item = make_item("Test-Tshirt-Temp", {
- "has_variant": 1,
- "variant_based_on": "Item Attribute",
- "attributes": [
- {"attribute": "Test Size"},
- {"attribute": "Test Colour"}
- ]
- })
+ super().setUpClass()
+ template_item = make_item(
+ "Test-Tshirt-Temp",
+ {
+ "has_variant": 1,
+ "variant_based_on": "Item Attribute",
+ "attributes": [{"attribute": "Test Size"}, {"attribute": "Test Colour"}],
+ },
+ )
# create L-R, L-G, M-R, M-G and S-R
- for size in ("Large", "Medium",):
- for colour in ("Red", "Green",):
- variant = create_variant("Test-Tshirt-Temp", {
- "Test Size": size, "Test Colour": colour
- })
+ for size in (
+ "Large",
+ "Medium",
+ ):
+ for colour in (
+ "Red",
+ "Green",
+ ):
+ variant = create_variant("Test-Tshirt-Temp", {"Test Size": size, "Test Colour": colour})
variant.save()
- variant = create_variant("Test-Tshirt-Temp", {
- "Test Size": "Small", "Test Colour": "Red"
- })
+ variant = create_variant("Test-Tshirt-Temp", {"Test Size": "Small", "Test Colour": "Red"})
variant.save()
- make_website_item(template_item) # publish template not variants
+ make_website_item(template_item) # publish template not variants
def test_item_attributes(self):
"""
- Test if the right attributes are fetched in the popup.
- (Attributes must only come from active items)
+ Test if the right attributes are fetched in the popup.
+ (Attributes must only come from active items)
- Attribute selection must not be linked to Website Items.
+ Attribute selection must not be linked to Website Items.
"""
from erpnext.e_commerce.variant_selector.utils import get_attributes_and_values
@@ -52,14 +55,14 @@ class TestVariantSelector(ERPNextTestCase):
self.assertEqual(attr_data[0]["attribute"], "Test Size")
self.assertEqual(attr_data[1]["attribute"], "Test Colour")
- self.assertEqual(len(attr_data[0]["values"]), 3) # ['Small', 'Medium', 'Large']
- self.assertEqual(len(attr_data[1]["values"]), 2) # ['Red', 'Green']
+ self.assertEqual(len(attr_data[0]["values"]), 3) # ['Small', 'Medium', 'Large']
+ self.assertEqual(len(attr_data[1]["values"]), 2) # ['Red', 'Green']
# disable small red tshirt, now there are no small tshirts.
# but there are some red tshirts
small_variant = frappe.get_doc("Item", "Test-Tshirt-Temp-S-R")
small_variant.disabled = 1
- small_variant.save() # trigger cache rebuild
+ small_variant.save() # trigger cache rebuild
attr_data = get_attributes_and_values("Test-Tshirt-Temp")
@@ -72,14 +75,16 @@ class TestVariantSelector(ERPNextTestCase):
def test_next_item_variant_values(self):
"""
- Test if on selecting an attribute value, the next possible values
- are filtered accordingly.
- Values that dont apply should not be fetched.
- E.g.
- There is a ** Small-Red ** Tshirt. No other colour in this size.
- On selecting ** Small **, only ** Red ** should be selectable next.
+ Test if on selecting an attribute value, the next possible values
+ are filtered accordingly.
+ Values that dont apply should not be fetched.
+ E.g.
+ There is a ** Small-Red ** Tshirt. No other colour in this size.
+ On selecting ** Small **, only ** Red ** should be selectable next.
"""
- next_values = get_next_attribute_and_values("Test-Tshirt-Temp", selected_attributes={"Test Size": "Small"})
+ next_values = get_next_attribute_and_values(
+ "Test-Tshirt-Temp", selected_attributes={"Test Size": "Small"}
+ )
next_colours = next_values["valid_options_for_attributes"]["Test Colour"]
filtered_items = next_values["filtered_items"]
@@ -90,30 +95,31 @@ class TestVariantSelector(ERPNextTestCase):
def test_exact_match_with_price(self):
"""
- Test price fetching and matching of variant without Website Item
+ Test price fetching and matching of variant without Website Item
"""
from erpnext.e_commerce.doctype.website_item.test_website_item import make_web_item_price
frappe.set_user("Administrator")
- setup_e_commerce_settings({
- "company": "_Test Company",
- "enabled": 1,
- "default_customer_group": "_Test Customer Group",
- "price_list": "_Test Price List India",
- "show_price": 1
- })
+ setup_e_commerce_settings(
+ {
+ "company": "_Test Company",
+ "enabled": 1,
+ "default_customer_group": "_Test Customer Group",
+ "price_list": "_Test Price List India",
+ "show_price": 1,
+ }
+ )
make_web_item_price(item_code="Test-Tshirt-Temp-S-R", price_list_rate=100)
- frappe.local.shopping_cart_settings = None # clear cached settings values
+ frappe.local.shopping_cart_settings = None # clear cached settings values
next_values = get_next_attribute_and_values(
- "Test-Tshirt-Temp",
- selected_attributes={"Test Size": "Small", "Test Colour": "Red"}
+ "Test-Tshirt-Temp", selected_attributes={"Test Size": "Small", "Test Colour": "Red"}
)
print(">>>>", next_values)
price_info = next_values["product_info"]["price"]
- self.assertEqual(next_values["exact_match"][0],"Test-Tshirt-Temp-S-R")
- self.assertEqual(next_values["exact_match"][0],"Test-Tshirt-Temp-S-R")
+ self.assertEqual(next_values["exact_match"][0], "Test-Tshirt-Temp-S-R")
+ self.assertEqual(next_values["exact_match"][0], "Test-Tshirt-Temp-S-R")
self.assertEqual(price_info["price_list_rate"], 100.0)
- self.assertEqual(price_info["formatted_price_sales_uom"], "₹ 100.00")
\ No newline at end of file
+ self.assertEqual(price_info["formatted_price_sales_uom"], "₹ 100.00")
diff --git a/erpnext/e_commerce/variant_selector/utils.py b/erpnext/e_commerce/variant_selector/utils.py
index 33802737efd..df62c23aa48 100644
--- a/erpnext/e_commerce/variant_selector/utils.py
+++ b/erpnext/e_commerce/variant_selector/utils.py
@@ -24,18 +24,18 @@ def get_item_codes_by_attributes(attribute_filters, template_item_code=None):
wheres = []
query_values = []
for attribute_value in attribute_values:
- wheres.append('( attribute = %s and attribute_value = %s )')
+ wheres.append("( attribute = %s and attribute_value = %s )")
query_values += [attribute, attribute_value]
- attribute_query = ' or '.join(wheres)
+ attribute_query = " or ".join(wheres)
if template_item_code:
- variant_of_query = 'AND t2.variant_of = %s'
+ variant_of_query = "AND t2.variant_of = %s"
query_values.append(template_item_code)
else:
- variant_of_query = ''
+ variant_of_query = ""
- query = '''
+ query = """
SELECT
t1.parent
FROM
@@ -58,20 +58,23 @@ def get_item_codes_by_attributes(attribute_filters, template_item_code=None):
t1.parent
ORDER BY
NULL
- '''.format(attribute_query=attribute_query, variant_of_query=variant_of_query)
+ """.format(
+ attribute_query=attribute_query, variant_of_query=variant_of_query
+ )
- item_codes = set([r[0] for r in frappe.db.sql(query, query_values)]) # nosemgrep
+ item_codes = set([r[0] for r in frappe.db.sql(query, query_values)]) # nosemgrep
items.append(item_codes)
res = list(set.intersection(*items))
return res
+
@frappe.whitelist(allow_guest=True)
def get_attributes_and_values(item_code):
- '''Build a list of attributes and their possible values.
+ """Build a list of attributes and their possible values.
This will ignore the values upon selection of which there cannot exist one item.
- '''
+ """
item_cache = ItemVariantsCacheManager(item_code)
item_variants_data = item_cache.get_item_variants_data()
@@ -83,8 +86,9 @@ def get_attributes_and_values(item_code):
if attribute in attribute_list:
valid_options.setdefault(attribute, set()).add(attribute_value)
- item_attribute_values = frappe.db.get_all('Item Attribute Value',
- ['parent', 'attribute_value', 'idx'], order_by='parent asc, idx asc')
+ item_attribute_values = frappe.db.get_all(
+ "Item Attribute Value", ["parent", "attribute_value", "idx"], order_by="parent asc, idx asc"
+ )
ordered_attribute_value_map = frappe._dict()
for iv in item_attribute_values:
ordered_attribute_value_map.setdefault(iv.parent, []).append(iv.attribute_value)
@@ -93,18 +97,18 @@ def get_attributes_and_values(item_code):
for attr in attributes:
valid_attribute_values = valid_options.get(attr.attribute, [])
ordered_values = ordered_attribute_value_map.get(attr.attribute, [])
- attr['values'] = [v for v in ordered_values if v in valid_attribute_values]
+ attr["values"] = [v for v in ordered_values if v in valid_attribute_values]
return attributes
@frappe.whitelist(allow_guest=True)
def get_next_attribute_and_values(item_code, selected_attributes):
- '''Find the count of Items that match the selected attributes.
+ """Find the count of Items that match the selected attributes.
Also, find the attribute values that are not applicable for further searching.
If less than equal to 10 items are found, return item_codes of those items.
If one item is matched exactly, return item_code of that item.
- '''
+ """
selected_attributes = frappe.parse_json(selected_attributes)
item_cache = ItemVariantsCacheManager(item_code)
@@ -133,7 +137,11 @@ def get_next_attribute_and_values(item_code, selected_attributes):
for row in item_variants_data:
item_code, attribute, attribute_value = row
- if item_code in filtered_items and attribute not in selected_attributes and attribute in attribute_list:
+ if (
+ item_code in filtered_items
+ and attribute not in selected_attributes
+ and attribute in attribute_list
+ ):
valid_options_for_attributes[attribute].add(attribute_value)
optional_attributes = item_cache.get_optional_attributes()
@@ -159,12 +167,12 @@ def get_next_attribute_and_values(item_code, selected_attributes):
product_info = None
return {
- 'next_attribute': next_attribute,
- 'valid_options_for_attributes': valid_options_for_attributes,
- 'filtered_items_count': filtered_items_count,
- 'filtered_items': filtered_items if filtered_items_count < 10 else [],
- 'exact_match': exact_match,
- 'product_info': product_info
+ "next_attribute": next_attribute,
+ "valid_options_for_attributes": valid_options_for_attributes,
+ "filtered_items_count": filtered_items_count,
+ "filtered_items": filtered_items if filtered_items_count < 10 else [],
+ "exact_match": exact_match,
+ "product_info": product_info,
}
@@ -179,16 +187,16 @@ def get_items_with_selected_attributes(item_code, selected_attributes):
return set.intersection(*items)
+
# utilities
+
def get_item_attributes(item_code):
- attributes = frappe.db.get_all('Item Variant Attribute',
- fields=['attribute'],
- filters={
- 'parenttype': 'Item',
- 'parent': item_code
- },
- order_by='idx asc'
+ attributes = frappe.db.get_all(
+ "Item Variant Attribute",
+ fields=["attribute"],
+ filters={"parenttype": "Item", "parent": item_code},
+ order_by="idx asc",
)
optional_attributes = ItemVariantsCacheManager(item_code).get_optional_attributes()
@@ -199,6 +207,7 @@ def get_item_attributes(item_code):
return attributes
+
def get_item_variant_price_dict(item_code, cart_settings):
if cart_settings.enabled and cart_settings.show_price:
is_guest = frappe.session.user == "Guest"
@@ -207,12 +216,8 @@ def get_item_variant_price_dict(item_code, cart_settings):
if not is_guest or not cart_settings.hide_price_for_guest:
price_list = _set_price_list(cart_settings, None)
price = get_price(
- item_code,
- price_list,
- cart_settings.default_customer_group,
- cart_settings.company
+ item_code, price_list, cart_settings.default_customer_group, cart_settings.company
)
return {"price": price}
return None
-
diff --git a/erpnext/education/__init__.py b/erpnext/education/__init__.py
index 56c2b29fde6..f6f84b37302 100644
--- a/erpnext/education/__init__.py
+++ b/erpnext/education/__init__.py
@@ -2,10 +2,16 @@ import frappe
from frappe import _
-class StudentNotInGroupError(frappe.ValidationError): pass
+class StudentNotInGroupError(frappe.ValidationError):
+ pass
+
def validate_student_belongs_to_group(student, student_group):
- groups = frappe.db.get_all('Student Group Student', ['parent'], dict(student = student, active=1))
+ groups = frappe.db.get_all("Student Group Student", ["parent"], dict(student=student, active=1))
if not student_group in [d.parent for d in groups]:
- frappe.throw(_('Student {0} does not belong to group {1}').format(frappe.bold(student), frappe.bold(student_group)),
- StudentNotInGroupError)
+ frappe.throw(
+ _("Student {0} does not belong to group {1}").format(
+ frappe.bold(student), frappe.bold(student_group)
+ ),
+ StudentNotInGroupError,
+ )
diff --git a/erpnext/education/api.py b/erpnext/education/api.py
index 636b948a1cc..8fd1725b2de 100644
--- a/erpnext/education/api.py
+++ b/erpnext/education/api.py
@@ -12,11 +12,12 @@ from frappe.utils import cstr, flt, getdate
def get_course(program):
- '''Return list of courses for a particular program
+ """Return list of courses for a particular program
:param program: Program
- '''
- courses = frappe.db.sql('''select course, course_name from `tabProgram Course` where parent=%s''',
- (program), as_dict=1)
+ """
+ courses = frappe.db.sql(
+ """select course, course_name from `tabProgram Course` where parent=%s""", (program), as_dict=1
+ )
return courses
@@ -26,24 +27,24 @@ def enroll_student(source_name):
:param source_name: Student Applicant.
"""
- frappe.publish_realtime('enroll_student_progress', {"progress": [1, 4]}, user=frappe.session.user)
- student = get_mapped_doc("Student Applicant", source_name,
- {"Student Applicant": {
- "doctype": "Student",
- "field_map": {
- "name": "student_applicant"
- }
- }}, ignore_permissions=True)
+ frappe.publish_realtime("enroll_student_progress", {"progress": [1, 4]}, user=frappe.session.user)
+ student = get_mapped_doc(
+ "Student Applicant",
+ source_name,
+ {"Student Applicant": {"doctype": "Student", "field_map": {"name": "student_applicant"}}},
+ ignore_permissions=True,
+ )
student.save()
- student_applicant = frappe.db.get_value("Student Applicant", source_name,
- ["student_category", "program"], as_dict=True)
+ student_applicant = frappe.db.get_value(
+ "Student Applicant", source_name, ["student_category", "program"], as_dict=True
+ )
program_enrollment = frappe.new_doc("Program Enrollment")
program_enrollment.student = student.name
program_enrollment.student_category = student_applicant.student_category
program_enrollment.student_name = student.title
program_enrollment.program = student_applicant.program
- frappe.publish_realtime('enroll_student_progress', {"progress": [2, 4]}, user=frappe.session.user)
+ frappe.publish_realtime("enroll_student_progress", {"progress": [2, 4]}, user=frappe.session.user)
return program_enrollment
@@ -58,11 +59,15 @@ def check_attendance_records_exist(course_schedule=None, student_group=None, dat
if course_schedule:
return frappe.get_list("Student Attendance", filters={"course_schedule": course_schedule})
else:
- return frappe.get_list("Student Attendance", filters={"student_group": student_group, "date": date})
+ return frappe.get_list(
+ "Student Attendance", filters={"student_group": student_group, "date": date}
+ )
@frappe.whitelist()
-def mark_attendance(students_present, students_absent, course_schedule=None, student_group=None, date=None):
+def mark_attendance(
+ students_present, students_absent, course_schedule=None, student_group=None, date=None
+):
"""Creates Multiple Attendance Records.
:param students_present: Students Present JSON.
@@ -73,26 +78,36 @@ def mark_attendance(students_present, students_absent, course_schedule=None, stu
"""
if student_group:
- academic_year = frappe.db.get_value('Student Group', student_group, 'academic_year')
+ academic_year = frappe.db.get_value("Student Group", student_group, "academic_year")
if academic_year:
- year_start_date, year_end_date = frappe.db.get_value('Academic Year', academic_year, ['year_start_date', 'year_end_date'])
+ year_start_date, year_end_date = frappe.db.get_value(
+ "Academic Year", academic_year, ["year_start_date", "year_end_date"]
+ )
if getdate(date) < getdate(year_start_date) or getdate(date) > getdate(year_end_date):
- frappe.throw(_('Attendance cannot be marked outside of Academic Year {0}').format(academic_year))
+ frappe.throw(
+ _("Attendance cannot be marked outside of Academic Year {0}").format(academic_year)
+ )
present = json.loads(students_present)
absent = json.loads(students_absent)
for d in present:
- make_attendance_records(d["student"], d["student_name"], "Present", course_schedule, student_group, date)
+ make_attendance_records(
+ d["student"], d["student_name"], "Present", course_schedule, student_group, date
+ )
for d in absent:
- make_attendance_records(d["student"], d["student_name"], "Absent", course_schedule, student_group, date)
+ make_attendance_records(
+ d["student"], d["student_name"], "Absent", course_schedule, student_group, date
+ )
frappe.db.commit()
frappe.msgprint(_("Attendance has been marked successfully."))
-def make_attendance_records(student, student_name, status, course_schedule=None, student_group=None, date=None):
+def make_attendance_records(
+ student, student_name, status, course_schedule=None, student_group=None, date=None
+):
"""Creates/Update Attendance Record.
:param student: Student.
@@ -100,13 +115,15 @@ def make_attendance_records(student, student_name, status, course_schedule=None,
:param course_schedule: Course Schedule.
:param status: Status (Present/Absent)
"""
- student_attendance = frappe.get_doc({
- "doctype": "Student Attendance",
- "student": student,
- "course_schedule": course_schedule,
- "student_group": student_group,
- "date": date
- })
+ student_attendance = frappe.get_doc(
+ {
+ "doctype": "Student Attendance",
+ "student": student,
+ "course_schedule": course_schedule,
+ "student_group": student_group,
+ "date": date,
+ }
+ )
if not student_attendance:
student_attendance = frappe.new_doc("Student Attendance")
student_attendance.student = student
@@ -125,8 +142,7 @@ def get_student_guardians(student):
:param student: Student.
"""
- guardians = frappe.get_all("Student Guardian", fields=["guardian"] ,
- filters={"parent": student})
+ guardians = frappe.get_all("Student Guardian", fields=["guardian"], filters={"parent": student})
return guardians
@@ -137,11 +153,19 @@ def get_student_group_students(student_group, include_inactive=0):
:param student_group: Student Group.
"""
if include_inactive:
- students = frappe.get_all("Student Group Student", fields=["student", "student_name"] ,
- filters={"parent": student_group}, order_by= "group_roll_number")
+ students = frappe.get_all(
+ "Student Group Student",
+ fields=["student", "student_name"],
+ filters={"parent": student_group},
+ order_by="group_roll_number",
+ )
else:
- students = frappe.get_all("Student Group Student", fields=["student", "student_name"] ,
- filters={"parent": student_group, "active": 1}, order_by= "group_roll_number")
+ students = frappe.get_all(
+ "Student Group Student",
+ fields=["student", "student_name"],
+ filters={"parent": student_group, "active": 1},
+ order_by="group_roll_number",
+ )
return students
@@ -152,8 +176,9 @@ def get_fee_structure(program, academic_term=None):
:param program: Program.
:param academic_term: Academic Term.
"""
- fee_structure = frappe.db.get_values("Fee Structure", {"program": program,
- "academic_term": academic_term}, 'name', as_dict=True)
+ fee_structure = frappe.db.get_values(
+ "Fee Structure", {"program": program, "academic_term": academic_term}, "name", as_dict=True
+ )
return fee_structure[0].name if fee_structure else None
@@ -164,7 +189,12 @@ def get_fee_components(fee_structure):
:param fee_structure: Fee Structure.
"""
if fee_structure:
- fs = frappe.get_all("Fee Component", fields=["fees_category", "description", "amount"] , filters={"parent": fee_structure}, order_by= "idx")
+ fs = frappe.get_all(
+ "Fee Component",
+ fields=["fees_category", "description", "amount"],
+ filters={"parent": fee_structure},
+ order_by="idx",
+ )
return fs
@@ -175,8 +205,12 @@ def get_fee_schedule(program, student_category=None):
:param program: Program.
:param student_category: Student Category
"""
- fs = frappe.get_all("Program Fee", fields=["academic_term", "fee_structure", "due_date", "amount"] ,
- filters={"parent": program, "student_category": student_category }, order_by= "idx")
+ fs = frappe.get_all(
+ "Program Fee",
+ fields=["academic_term", "fee_structure", "due_date", "amount"],
+ filters={"parent": program, "student_category": student_category},
+ order_by="idx",
+ )
return fs
@@ -198,18 +232,23 @@ def get_course_schedule_events(start, end, filters=None):
:param filters: Filters (JSON).
"""
from frappe.desk.calendar import get_event_conditions
+
conditions = get_event_conditions("Course Schedule", filters)
- data = frappe.db.sql("""select name, course, color,
+ data = frappe.db.sql(
+ """select name, course, color,
timestamp(schedule_date, from_time) as from_time,
timestamp(schedule_date, to_time) as to_time,
room, student_group, 0 as 'allDay'
from `tabCourse Schedule`
where ( schedule_date between %(start)s and %(end)s )
- {conditions}""".format(conditions=conditions), {
- "start": start,
- "end": end
- }, as_dict=True, update={"allDay": 0})
+ {conditions}""".format(
+ conditions=conditions
+ ),
+ {"start": start, "end": end},
+ as_dict=True,
+ update={"allDay": 0},
+ )
return data
@@ -220,8 +259,12 @@ def get_assessment_criteria(course):
:param Course: Course
"""
- return frappe.get_all("Course Assessment Criteria",
- fields=["assessment_criteria", "weightage"], filters={"parent": course}, order_by= "idx")
+ return frappe.get_all(
+ "Course Assessment Criteria",
+ fields=["assessment_criteria", "weightage"],
+ filters={"parent": course},
+ order_by="idx",
+ )
@frappe.whitelist()
@@ -233,17 +276,14 @@ def get_assessment_students(assessment_plan, student_group):
student_result = {}
for d in result.details:
student_result.update({d.assessment_criteria: [cstr(d.score), d.grade]})
- student_result.update({
- "total_score": [cstr(result.total_score), result.grade],
- "comment": result.comment
- })
- student.update({
- "assessment_details": student_result,
- "docstatus": result.docstatus,
- "name": result.name
- })
+ student_result.update(
+ {"total_score": [cstr(result.total_score), result.grade], "comment": result.comment}
+ )
+ student.update(
+ {"assessment_details": student_result, "docstatus": result.docstatus, "name": result.name}
+ )
else:
- student.update({'assessment_details': None})
+ student.update({"assessment_details": None})
return student_list
@@ -253,8 +293,12 @@ def get_assessment_details(assessment_plan):
:param Assessment Plan: Assessment Plan
"""
- return frappe.get_all("Assessment Plan Criteria",
- fields=["assessment_criteria", "maximum_score", "docstatus"], filters={"parent": assessment_plan}, order_by= "idx")
+ return frappe.get_all(
+ "Assessment Plan Criteria",
+ fields=["assessment_criteria", "maximum_score", "docstatus"],
+ filters={"parent": assessment_plan},
+ order_by="idx",
+ )
@frappe.whitelist()
@@ -264,8 +308,10 @@ def get_result(student, assessment_plan):
:param Student: Student
:param Assessment Plan: Assessment Plan
"""
- results = frappe.get_all("Assessment Result", filters={"student": student,
- "assessment_plan": assessment_plan, "docstatus": ("!=", 2)})
+ results = frappe.get_all(
+ "Assessment Result",
+ filters={"student": student, "assessment_plan": assessment_plan, "docstatus": ("!=", 2)},
+ )
if results:
return frappe.get_doc("Assessment Result", results[0])
else:
@@ -280,11 +326,13 @@ def get_grade(grading_scale, percentage):
:param Percentage: Score Percentage Percentage
"""
grading_scale_intervals = {}
- if not hasattr(frappe.local, 'grading_scale'):
- grading_scale = frappe.get_all("Grading Scale Interval", fields=["grade_code", "threshold"], filters={"parent": grading_scale})
+ if not hasattr(frappe.local, "grading_scale"):
+ grading_scale = frappe.get_all(
+ "Grading Scale Interval", fields=["grade_code", "threshold"], filters={"parent": grading_scale}
+ )
frappe.local.grading_scale = grading_scale
for d in frappe.local.grading_scale:
- grading_scale_intervals.update({d.threshold:d.grade_code})
+ grading_scale_intervals.update({d.threshold: d.grade_code})
intervals = sorted(grading_scale_intervals.keys(), key=float, reverse=True)
for interval in intervals:
if flt(percentage) >= interval:
@@ -297,21 +345,22 @@ def get_grade(grading_scale, percentage):
@frappe.whitelist()
def mark_assessment_result(assessment_plan, scores):
- student_score = json.loads(scores);
+ student_score = json.loads(scores)
assessment_details = []
for criteria in student_score.get("assessment_details"):
- assessment_details.append({
- "assessment_criteria": criteria,
- "score": flt(student_score["assessment_details"][criteria])
- })
+ assessment_details.append(
+ {"assessment_criteria": criteria, "score": flt(student_score["assessment_details"][criteria])}
+ )
assessment_result = get_assessment_result_doc(student_score["student"], assessment_plan)
- assessment_result.update({
- "student": student_score.get("student"),
- "assessment_plan": assessment_plan,
- "comment": student_score.get("comment"),
- "total_score":student_score.get("total_score"),
- "details": assessment_details
- })
+ assessment_result.update(
+ {
+ "student": student_score.get("student"),
+ "assessment_plan": assessment_plan,
+ "comment": student_score.get("comment"),
+ "total_score": student_score.get("total_score"),
+ "details": assessment_details,
+ }
+ )
assessment_result.save()
details = {}
for d in assessment_result.details:
@@ -321,7 +370,7 @@ def mark_assessment_result(assessment_plan, scores):
"student": assessment_result.student,
"total_score": assessment_result.total_score,
"grade": assessment_result.grade,
- "details": details
+ "details": details,
}
return assessment_result_dict
@@ -332,15 +381,17 @@ def submit_assessment_results(assessment_plan, student_group):
student_list = get_student_group_students(student_group)
for i, student in enumerate(student_list):
doc = get_result(student.student, assessment_plan)
- if doc and doc.docstatus==0:
+ if doc and doc.docstatus == 0:
total_result += 1
doc.submit()
return total_result
def get_assessment_result_doc(student, assessment_plan):
- assessment_result = frappe.get_all("Assessment Result", filters={"student": student,
- "assessment_plan": assessment_plan, "docstatus": ("!=", 2)})
+ assessment_result = frappe.get_all(
+ "Assessment Result",
+ filters={"student": student, "assessment_plan": assessment_plan, "docstatus": ("!=", 2)},
+ )
if assessment_result:
doc = frappe.get_doc("Assessment Result", assessment_result[0])
if doc.docstatus == 0:
@@ -369,10 +420,12 @@ def update_email_group(doctype, name):
email_list.append(email)
add_subscribers(name, email_list)
+
@frappe.whitelist()
def get_current_enrollment(student, academic_year=None):
current_academic_year = academic_year or frappe.defaults.get_defaults().academic_year
- program_enrollment_list = frappe.db.sql('''
+ program_enrollment_list = frappe.db.sql(
+ """
select
name as program_enrollment, student_name, program, student_batch_name as student_batch,
student_category, academic_term, academic_year
@@ -380,7 +433,10 @@ def get_current_enrollment(student, academic_year=None):
`tabProgram Enrollment`
where
student = %s and academic_year = %s
- order by creation''', (student, current_academic_year), as_dict=1)
+ order by creation""",
+ (student, current_academic_year),
+ as_dict=1,
+ )
if program_enrollment_list:
return program_enrollment_list[0]
diff --git a/erpnext/education/doctype/academic_term/academic_term.py b/erpnext/education/doctype/academic_term/academic_term.py
index 93861ca78af..b44fe99a30a 100644
--- a/erpnext/education/doctype/academic_term/academic_term.py
+++ b/erpnext/education/doctype/academic_term/academic_term.py
@@ -9,32 +9,62 @@ from frappe.utils import getdate
class AcademicTerm(Document):
- def autoname(self):
- self.name = self.academic_year + " ({})".format(self.term_name) if self.term_name else ""
+ def autoname(self):
+ self.name = self.academic_year + " ({})".format(self.term_name) if self.term_name else ""
- def validate(self):
- #Check if entry with same academic_year and the term_name already exists
- validate_duplication(self)
- self.title = self.academic_year + " ({})".format(self.term_name) if self.term_name else ""
+ def validate(self):
+ # Check if entry with same academic_year and the term_name already exists
+ validate_duplication(self)
+ self.title = self.academic_year + " ({})".format(self.term_name) if self.term_name else ""
- #Check that start of academic year is earlier than end of academic year
- if self.term_start_date and self.term_end_date \
- and getdate(self.term_start_date) > getdate(self.term_end_date):
- frappe.throw(_("The Term End Date cannot be earlier than the Term Start Date. Please correct the dates and try again."))
+ # Check that start of academic year is earlier than end of academic year
+ if (
+ self.term_start_date
+ and self.term_end_date
+ and getdate(self.term_start_date) > getdate(self.term_end_date)
+ ):
+ frappe.throw(
+ _(
+ "The Term End Date cannot be earlier than the Term Start Date. Please correct the dates and try again."
+ )
+ )
- # Check that the start of the term is not before the start of the academic year
+ # Check that the start of the term is not before the start of the academic year
# and end of term is not after the end of the academic year"""
- year = frappe.get_doc("Academic Year",self.academic_year)
- if self.term_start_date and getdate(year.year_start_date) and (getdate(self.term_start_date) < getdate(year.year_start_date)):
- frappe.throw(_("The Term Start Date cannot be earlier than the Year Start Date of the Academic Year to which the term is linked (Academic Year {}). Please correct the dates and try again.").format(self.academic_year))
+ year = frappe.get_doc("Academic Year", self.academic_year)
+ if (
+ self.term_start_date
+ and getdate(year.year_start_date)
+ and (getdate(self.term_start_date) < getdate(year.year_start_date))
+ ):
+ frappe.throw(
+ _(
+ "The Term Start Date cannot be earlier than the Year Start Date of the Academic Year to which the term is linked (Academic Year {}). Please correct the dates and try again."
+ ).format(self.academic_year)
+ )
- if self.term_end_date and getdate(year.year_end_date) and (getdate(self.term_end_date) > getdate(year.year_end_date)):
- frappe.throw(_("The Term End Date cannot be later than the Year End Date of the Academic Year to which the term is linked (Academic Year {}). Please correct the dates and try again.").format(self.academic_year))
+ if (
+ self.term_end_date
+ and getdate(year.year_end_date)
+ and (getdate(self.term_end_date) > getdate(year.year_end_date))
+ ):
+ frappe.throw(
+ _(
+ "The Term End Date cannot be later than the Year End Date of the Academic Year to which the term is linked (Academic Year {}). Please correct the dates and try again."
+ ).format(self.academic_year)
+ )
def validate_duplication(self):
- term = frappe.db.sql("""select name from `tabAcademic Term` where academic_year= %s and term_name= %s
- and docstatus<2 and name != %s""", (self.academic_year, self.term_name, self.name))
- if term:
- frappe.throw(_("An academic term with this 'Academic Year' {0} and 'Term Name' {1} already exists. Please modify these entries and try again.").format(self.academic_year,self.term_name))
+ term = frappe.db.sql(
+ """select name from `tabAcademic Term` where academic_year= %s and term_name= %s
+ and docstatus<2 and name != %s""",
+ (self.academic_year, self.term_name, self.name),
+ )
+ if term:
+ frappe.throw(
+ _(
+ "An academic term with this 'Academic Year' {0} and 'Term Name' {1} already exists. Please modify these entries and try again."
+ ).format(self.academic_year, self.term_name)
+ )
diff --git a/erpnext/education/doctype/academic_term/academic_term_dashboard.py b/erpnext/education/doctype/academic_term/academic_term_dashboard.py
index c686b09b9e7..348c04d3f6e 100644
--- a/erpnext/education/doctype/academic_term/academic_term_dashboard.py
+++ b/erpnext/education/doctype/academic_term/academic_term_dashboard.py
@@ -3,23 +3,11 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'academic_term',
- 'transactions': [
- {
- 'label': _('Student'),
- 'items': ['Student Applicant', 'Student Group', 'Student Log']
- },
- {
- 'label': _('Fee'),
- 'items': ['Fees', 'Fee Schedule', 'Fee Structure']
- },
- {
- 'label': _('Program'),
- 'items': ['Program Enrollment']
- },
- {
- 'label': _('Assessment'),
- 'items': ['Assessment Plan', 'Assessment Result']
- }
- ]
+ "fieldname": "academic_term",
+ "transactions": [
+ {"label": _("Student"), "items": ["Student Applicant", "Student Group", "Student Log"]},
+ {"label": _("Fee"), "items": ["Fees", "Fee Schedule", "Fee Structure"]},
+ {"label": _("Program"), "items": ["Program Enrollment"]},
+ {"label": _("Assessment"), "items": ["Assessment Plan", "Assessment Result"]},
+ ],
}
diff --git a/erpnext/education/doctype/academic_term/test_academic_term.py b/erpnext/education/doctype/academic_term/test_academic_term.py
index 0e39fb03d47..b4516b3a425 100644
--- a/erpnext/education/doctype/academic_term/test_academic_term.py
+++ b/erpnext/education/doctype/academic_term/test_academic_term.py
@@ -5,5 +5,6 @@ import unittest
# test_records = frappe.get_test_records('Academic Term')
+
class TestAcademicTerm(unittest.TestCase):
pass
diff --git a/erpnext/education/doctype/academic_year/academic_year.py b/erpnext/education/doctype/academic_year/academic_year.py
index e2010fb1b05..2a0438b7756 100644
--- a/erpnext/education/doctype/academic_year/academic_year.py
+++ b/erpnext/education/doctype/academic_year/academic_year.py
@@ -8,7 +8,11 @@ from frappe.model.document import Document
class AcademicYear(Document):
- def validate(self):
- #Check that start of academic year is earlier than end of academic year
- if self.year_start_date and self.year_end_date and self.year_start_date > self.year_end_date:
- frappe.throw(_("The Year End Date cannot be earlier than the Year Start Date. Please correct the dates and try again."))
+ def validate(self):
+ # Check that start of academic year is earlier than end of academic year
+ if self.year_start_date and self.year_end_date and self.year_start_date > self.year_end_date:
+ frappe.throw(
+ _(
+ "The Year End Date cannot be earlier than the Year Start Date. Please correct the dates and try again."
+ )
+ )
diff --git a/erpnext/education/doctype/academic_year/academic_year_dashboard.py b/erpnext/education/doctype/academic_year/academic_year_dashboard.py
index ede2411dc34..c69c97017a8 100644
--- a/erpnext/education/doctype/academic_year/academic_year_dashboard.py
+++ b/erpnext/education/doctype/academic_year/academic_year_dashboard.py
@@ -3,23 +3,14 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'academic_year',
- 'transactions': [
+ "fieldname": "academic_year",
+ "transactions": [
{
- 'label': _('Student'),
- 'items': ['Student Admission', 'Student Applicant', 'Student Group', 'Student Log']
+ "label": _("Student"),
+ "items": ["Student Admission", "Student Applicant", "Student Group", "Student Log"],
},
- {
- 'label': _('Fee'),
- 'items': ['Fees', 'Fee Schedule', 'Fee Structure']
- },
- {
- 'label': _('Academic Term and Program'),
- 'items': ['Academic Term', 'Program Enrollment']
- },
- {
- 'label': _('Assessment'),
- 'items': ['Assessment Plan', 'Assessment Result']
- }
- ]
+ {"label": _("Fee"), "items": ["Fees", "Fee Schedule", "Fee Structure"]},
+ {"label": _("Academic Term and Program"), "items": ["Academic Term", "Program Enrollment"]},
+ {"label": _("Assessment"), "items": ["Assessment Plan", "Assessment Result"]},
+ ],
}
diff --git a/erpnext/education/doctype/academic_year/test_academic_year.py b/erpnext/education/doctype/academic_year/test_academic_year.py
index 6d33fe6d7d5..e51cd0e2355 100644
--- a/erpnext/education/doctype/academic_year/test_academic_year.py
+++ b/erpnext/education/doctype/academic_year/test_academic_year.py
@@ -5,5 +5,6 @@ import unittest
# test_records = frappe.get_test_records('Academic Year')
+
class TestAcademicYear(unittest.TestCase):
pass
diff --git a/erpnext/education/doctype/article/article.py b/erpnext/education/doctype/article/article.py
index 8f1a2e33b34..12b6618e732 100644
--- a/erpnext/education/doctype/article/article.py
+++ b/erpnext/education/doctype/article/article.py
@@ -10,11 +10,12 @@ class Article(Document):
def get_article(self):
pass
+
@frappe.whitelist()
def get_topics_without_article(article):
data = []
- for entry in frappe.db.get_all('Topic'):
- topic = frappe.get_doc('Topic', entry.name)
+ for entry in frappe.db.get_all("Topic"):
+ topic = frappe.get_doc("Topic", entry.name)
topic_contents = [tc.content for tc in topic.topic_content]
if not topic_contents or article not in topic_contents:
data.append(topic.name)
diff --git a/erpnext/education/doctype/assessment_criteria/assessment_criteria.py b/erpnext/education/doctype/assessment_criteria/assessment_criteria.py
index 58448ea9418..ef9692a8eb0 100644
--- a/erpnext/education/doctype/assessment_criteria/assessment_criteria.py
+++ b/erpnext/education/doctype/assessment_criteria/assessment_criteria.py
@@ -8,6 +8,7 @@ from frappe.model.document import Document
STD_CRITERIA = ["total", "total score", "total grade", "maximum score", "score", "grade"]
+
class AssessmentCriteria(Document):
def validate(self):
if self.assessment_criteria.lower() in STD_CRITERIA:
diff --git a/erpnext/education/doctype/assessment_criteria/test_assessment_criteria.py b/erpnext/education/doctype/assessment_criteria/test_assessment_criteria.py
index 40ba0e7816a..90ff5754451 100644
--- a/erpnext/education/doctype/assessment_criteria/test_assessment_criteria.py
+++ b/erpnext/education/doctype/assessment_criteria/test_assessment_criteria.py
@@ -5,5 +5,6 @@ import unittest
# test_records = frappe.get_test_records('Assessment Criteria')
+
class TestAssessmentCriteria(unittest.TestCase):
pass
diff --git a/erpnext/education/doctype/assessment_criteria_group/test_assessment_criteria_group.py b/erpnext/education/doctype/assessment_criteria_group/test_assessment_criteria_group.py
index ccf82bad04a..b52aef83819 100644
--- a/erpnext/education/doctype/assessment_criteria_group/test_assessment_criteria_group.py
+++ b/erpnext/education/doctype/assessment_criteria_group/test_assessment_criteria_group.py
@@ -5,5 +5,6 @@ import unittest
# test_records = frappe.get_test_records('Assessment Criteria Group')
+
class TestAssessmentCriteriaGroup(unittest.TestCase):
pass
diff --git a/erpnext/education/doctype/assessment_group/assessment_group_dashboard.py b/erpnext/education/doctype/assessment_group/assessment_group_dashboard.py
index 956809179bc..7c75100b708 100644
--- a/erpnext/education/doctype/assessment_group/assessment_group_dashboard.py
+++ b/erpnext/education/doctype/assessment_group/assessment_group_dashboard.py
@@ -6,11 +6,6 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'assessment_group',
- 'transactions': [
- {
- 'label': _('Assessment'),
- 'items': ['Assessment Plan', 'Assessment Result']
- }
- ]
+ "fieldname": "assessment_group",
+ "transactions": [{"label": _("Assessment"), "items": ["Assessment Plan", "Assessment Result"]}],
}
diff --git a/erpnext/education/doctype/assessment_group/test_assessment_group.py b/erpnext/education/doctype/assessment_group/test_assessment_group.py
index 6e840aa2510..6f05caae946 100644
--- a/erpnext/education/doctype/assessment_group/test_assessment_group.py
+++ b/erpnext/education/doctype/assessment_group/test_assessment_group.py
@@ -5,5 +5,6 @@ import unittest
# test_records = frappe.get_test_records('Assessment Group')
+
class TestAssessmentGroup(unittest.TestCase):
pass
diff --git a/erpnext/education/doctype/assessment_plan/assessment_plan.py b/erpnext/education/doctype/assessment_plan/assessment_plan.py
index 82a28de1cb9..e1265b42087 100644
--- a/erpnext/education/doctype/assessment_plan/assessment_plan.py
+++ b/erpnext/education/doctype/assessment_plan/assessment_plan.py
@@ -18,14 +18,14 @@ class AssessmentPlan(Document):
from erpnext.education.utils import validate_overlap_for
- #Validate overlapping course schedules.
+ # Validate overlapping course schedules.
if self.student_group:
validate_overlap_for(self, "Course Schedule", "student_group")
validate_overlap_for(self, "Course Schedule", "instructor")
validate_overlap_for(self, "Course Schedule", "room")
- #validate overlapping assessment schedules.
+ # validate overlapping assessment schedules.
if self.student_group:
validate_overlap_for(self, "Assessment Plan", "student_group")
@@ -37,14 +37,24 @@ class AssessmentPlan(Document):
for d in self.assessment_criteria:
max_score += d.maximum_score
if self.maximum_assessment_score != max_score:
- frappe.throw(_("Sum of Scores of Assessment Criteria needs to be {0}.").format(self.maximum_assessment_score))
+ frappe.throw(
+ _("Sum of Scores of Assessment Criteria needs to be {0}.").format(
+ self.maximum_assessment_score
+ )
+ )
def validate_assessment_criteria(self):
- assessment_criteria_list = frappe.db.sql_list(''' select apc.assessment_criteria
+ assessment_criteria_list = frappe.db.sql_list(
+ """ select apc.assessment_criteria
from `tabAssessment Plan` ap , `tabAssessment Plan Criteria` apc
where ap.name = apc.parent and ap.course=%s and ap.student_group=%s and ap.assessment_group=%s
- and ap.name != %s and ap.docstatus=1''', (self.course, self.student_group, self.assessment_group, self.name))
+ and ap.name != %s and ap.docstatus=1""",
+ (self.course, self.student_group, self.assessment_group, self.name),
+ )
for d in self.assessment_criteria:
if d.assessment_criteria in assessment_criteria_list:
- frappe.throw(_("You have already assessed for the assessment criteria {}.")
- .format(frappe.bold(d.assessment_criteria)))
+ frappe.throw(
+ _("You have already assessed for the assessment criteria {}.").format(
+ frappe.bold(d.assessment_criteria)
+ )
+ )
diff --git a/erpnext/education/doctype/assessment_plan/assessment_plan_dashboard.py b/erpnext/education/doctype/assessment_plan/assessment_plan_dashboard.py
index 31b9509f191..f9c583a80fc 100644
--- a/erpnext/education/doctype/assessment_plan/assessment_plan_dashboard.py
+++ b/erpnext/education/doctype/assessment_plan/assessment_plan_dashboard.py
@@ -6,17 +6,7 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'assessment_plan',
- 'transactions': [
- {
- 'label': _('Assessment'),
- 'items': ['Assessment Result']
- }
- ],
- 'reports': [
- {
- 'label': _('Report'),
- 'items': ['Assessment Plan Status']
- }
- ]
+ "fieldname": "assessment_plan",
+ "transactions": [{"label": _("Assessment"), "items": ["Assessment Result"]}],
+ "reports": [{"label": _("Report"), "items": ["Assessment Plan Status"]}],
}
diff --git a/erpnext/education/doctype/assessment_plan/test_assessment_plan.py b/erpnext/education/doctype/assessment_plan/test_assessment_plan.py
index 9f55a78667b..e294c50ce48 100644
--- a/erpnext/education/doctype/assessment_plan/test_assessment_plan.py
+++ b/erpnext/education/doctype/assessment_plan/test_assessment_plan.py
@@ -5,5 +5,6 @@ import unittest
# test_records = frappe.get_test_records('Assessment Plan')
+
class TestAssessmentPlan(unittest.TestCase):
pass
diff --git a/erpnext/education/doctype/assessment_result/assessment_result.py b/erpnext/education/doctype/assessment_result/assessment_result.py
index 8278b9eebab..1d485c139e7 100644
--- a/erpnext/education/doctype/assessment_result/assessment_result.py
+++ b/erpnext/education/doctype/assessment_result/assessment_result.py
@@ -33,12 +33,23 @@ class AssessmentResult(Document):
def validate_grade(self):
self.total_score = 0.0
for d in self.details:
- d.grade = get_grade(self.grading_scale, (flt(d.score)/d.maximum_score)*100)
+ d.grade = get_grade(self.grading_scale, (flt(d.score) / d.maximum_score) * 100)
self.total_score += d.score
- self.grade = get_grade(self.grading_scale, (self.total_score/self.maximum_score)*100)
+ self.grade = get_grade(self.grading_scale, (self.total_score / self.maximum_score) * 100)
def validate_duplicate(self):
- assessment_result = frappe.get_list("Assessment Result", filters={"name": ("not in", [self.name]),
- "student":self.student, "assessment_plan":self.assessment_plan, "docstatus":("!=", 2)})
+ assessment_result = frappe.get_list(
+ "Assessment Result",
+ filters={
+ "name": ("not in", [self.name]),
+ "student": self.student,
+ "assessment_plan": self.assessment_plan,
+ "docstatus": ("!=", 2),
+ },
+ )
if assessment_result:
- frappe.throw(_("Assessment Result record {0} already exists.").format(getlink("Assessment Result",assessment_result[0].name)))
+ frappe.throw(
+ _("Assessment Result record {0} already exists.").format(
+ getlink("Assessment Result", assessment_result[0].name)
+ )
+ )
diff --git a/erpnext/education/doctype/assessment_result/assessment_result_dashboard.py b/erpnext/education/doctype/assessment_result/assessment_result_dashboard.py
index 3b07417b88d..5501f9221a2 100644
--- a/erpnext/education/doctype/assessment_result/assessment_result_dashboard.py
+++ b/erpnext/education/doctype/assessment_result/assessment_result_dashboard.py
@@ -6,10 +6,7 @@ from frappe import _
def get_data():
return {
- 'reports': [
- {
- 'label': _('Reports'),
- 'items': ['Final Assessment Grades', 'Course wise Assessment Report']
- }
+ "reports": [
+ {"label": _("Reports"), "items": ["Final Assessment Grades", "Course wise Assessment Report"]}
]
}
diff --git a/erpnext/education/doctype/assessment_result/test_assessment_result.py b/erpnext/education/doctype/assessment_result/test_assessment_result.py
index c0872dfb06d..4872f48eda0 100644
--- a/erpnext/education/doctype/assessment_result/test_assessment_result.py
+++ b/erpnext/education/doctype/assessment_result/test_assessment_result.py
@@ -7,6 +7,7 @@ from erpnext.education.api import get_grade
# test_records = frappe.get_test_records('Assessment Result')
+
class TestAssessmentResult(unittest.TestCase):
def test_grade(self):
grade = get_grade("_Test Grading Scale", 80)
diff --git a/erpnext/education/doctype/course/course.py b/erpnext/education/doctype/course/course.py
index 2d4f28226a6..baf72e8cb78 100644
--- a/erpnext/education/doctype/course/course.py
+++ b/erpnext/education/doctype/course/course.py
@@ -19,12 +19,12 @@ class Course(Document):
for criteria in self.assessment_criteria:
total_weightage += criteria.weightage or 0
if total_weightage != 100:
- frappe.throw(_('Total Weightage of all Assessment Criteria must be 100%'))
+ frappe.throw(_("Total Weightage of all Assessment Criteria must be 100%"))
def get_topics(self):
- topic_data= []
+ topic_data = []
for topic in self.topics:
- topic_doc = frappe.get_doc('Topic', topic.topic)
+ topic_doc = frappe.get_doc("Topic", topic.topic)
if topic_doc.topic_content:
topic_data.append(topic_doc)
return topic_data
@@ -34,23 +34,25 @@ class Course(Document):
def add_course_to_programs(course, programs, mandatory=False):
programs = json.loads(programs)
for entry in programs:
- program = frappe.get_doc('Program', entry)
- program.append('courses', {
- 'course': course,
- 'course_name': course,
- 'mandatory': mandatory
- })
+ program = frappe.get_doc("Program", entry)
+ program.append("courses", {"course": course, "course_name": course, "mandatory": mandatory})
program.flags.ignore_mandatory = True
program.save()
frappe.db.commit()
- frappe.msgprint(_('Course {0} has been added to all the selected programs successfully.').format(frappe.bold(course)),
- title=_('Programs updated'), indicator='green')
+ frappe.msgprint(
+ _("Course {0} has been added to all the selected programs successfully.").format(
+ frappe.bold(course)
+ ),
+ title=_("Programs updated"),
+ indicator="green",
+ )
+
@frappe.whitelist()
def get_programs_without_course(course):
data = []
- for entry in frappe.db.get_all('Program'):
- program = frappe.get_doc('Program', entry.name)
+ for entry in frappe.db.get_all("Program"):
+ program = frappe.get_doc("Program", entry.name)
courses = [c.course for c in program.courses]
if not courses or course not in courses:
data.append(program.name)
diff --git a/erpnext/education/doctype/course/course_dashboard.py b/erpnext/education/doctype/course/course_dashboard.py
index 276830f38ae..6ba44750797 100644
--- a/erpnext/education/doctype/course/course_dashboard.py
+++ b/erpnext/education/doctype/course/course_dashboard.py
@@ -6,19 +6,13 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'course',
- 'transactions': [
+ "fieldname": "course",
+ "transactions": [
{
- 'label': _('Program and Course'),
- 'items': ['Program', 'Course Enrollment', 'Course Schedule']
+ "label": _("Program and Course"),
+ "items": ["Program", "Course Enrollment", "Course Schedule"],
},
- {
- 'label': _('Student'),
- 'items': ['Student Group']
- },
- {
- 'label': _('Assessment'),
- 'items': ['Assessment Plan', 'Assessment Result']
- },
- ]
+ {"label": _("Student"), "items": ["Student Group"]},
+ {"label": _("Assessment"), "items": ["Assessment Plan", "Assessment Result"]},
+ ],
}
diff --git a/erpnext/education/doctype/course/test_course.py b/erpnext/education/doctype/course/test_course.py
index 6381cdb11b2..caddefef3e5 100644
--- a/erpnext/education/doctype/course/test_course.py
+++ b/erpnext/education/doctype/course/test_course.py
@@ -9,10 +9,11 @@ from erpnext.education.doctype.topic.test_topic import make_topic, make_topic_an
# test_records = frappe.get_test_records('Course')
+
class TestCourse(unittest.TestCase):
def setUp(self):
- make_topic_and_linked_content("_Test Topic 1", [{"type":"Article", "name": "_Test Article 1"}])
- make_topic_and_linked_content("_Test Topic 2", [{"type":"Article", "name": "_Test Article 2"}])
+ make_topic_and_linked_content("_Test Topic 1", [{"type": "Article", "name": "_Test Article 1"}])
+ make_topic_and_linked_content("_Test Topic 2", [{"type": "Article", "name": "_Test Article 2"}])
make_course_and_linked_topic("_Test Course 1", ["_Test Topic 1", "_Test Topic 2"])
def test_get_topics(self):
@@ -22,17 +23,15 @@ class TestCourse(unittest.TestCase):
self.assertEqual(topics[1].name, "_Test Topic 2")
frappe.db.rollback()
+
def make_course(name):
try:
course = frappe.get_doc("Course", name)
except frappe.DoesNotExistError:
- course = frappe.get_doc({
- "doctype": "Course",
- "course_name": name,
- "course_code": name
- }).insert()
+ course = frappe.get_doc({"doctype": "Course", "course_name": name, "course_code": name}).insert()
return course.name
+
def make_course_and_linked_topic(course_name, topic_name_list):
try:
course = frappe.get_doc("Course", course_name)
diff --git a/erpnext/education/doctype/course_activity/course_activity.py b/erpnext/education/doctype/course_activity/course_activity.py
index c1d82427dd5..784260d07c0 100644
--- a/erpnext/education/doctype/course_activity/course_activity.py
+++ b/erpnext/education/doctype/course_activity/course_activity.py
@@ -11,7 +11,6 @@ class CourseActivity(Document):
def validate(self):
self.check_if_enrolled()
-
def check_if_enrolled(self):
if frappe.db.exists("Course Enrollment", self.enrollment):
return True
diff --git a/erpnext/education/doctype/course_activity/test_course_activity.py b/erpnext/education/doctype/course_activity/test_course_activity.py
index 9514ff1bda9..677b60a8456 100644
--- a/erpnext/education/doctype/course_activity/test_course_activity.py
+++ b/erpnext/education/doctype/course_activity/test_course_activity.py
@@ -9,16 +9,22 @@ import frappe
class TestCourseActivity(unittest.TestCase):
pass
+
def make_course_activity(enrollment, content_type, content):
- activity = frappe.get_all("Course Activity", filters={'enrollment': enrollment, 'content_type': content_type, 'content': content})
+ activity = frappe.get_all(
+ "Course Activity",
+ filters={"enrollment": enrollment, "content_type": content_type, "content": content},
+ )
try:
- activity = frappe.get_doc("Course Activity", activity[0]['name'])
+ activity = frappe.get_doc("Course Activity", activity[0]["name"])
except (IndexError, frappe.DoesNotExistError):
- activity = frappe.get_doc({
- "doctype": "Course Activity",
- "enrollment": enrollment,
- "content_type": content_type,
- "content": content,
- "activity_date": frappe.utils.datetime.datetime.now()
- }).insert()
+ activity = frappe.get_doc(
+ {
+ "doctype": "Course Activity",
+ "enrollment": enrollment,
+ "content_type": content_type,
+ "content": content,
+ "activity_date": frappe.utils.datetime.datetime.now(),
+ }
+ ).insert()
return activity
diff --git a/erpnext/education/doctype/course_enrollment/course_enrollment.py b/erpnext/education/doctype/course_enrollment/course_enrollment.py
index 79212847533..18639b11785 100644
--- a/erpnext/education/doctype/course_enrollment/course_enrollment.py
+++ b/erpnext/education/doctype/course_enrollment/course_enrollment.py
@@ -18,77 +18,94 @@ class CourseEnrollment(Document):
"""
Returns Progress of given student for a particular course enrollment
- :param self: Course Enrollment Object
- :param student: Student Object
+ :param self: Course Enrollment Object
+ :param student: Student Object
"""
- course = frappe.get_doc('Course', self.course)
+ course = frappe.get_doc("Course", self.course)
topics = course.get_topics()
progress = []
for topic in topics:
progress.append(student.get_topic_progress(self.name, topic))
if progress:
- return reduce(lambda x,y: x+y, progress) # Flatten out the List
+ return reduce(lambda x, y: x + y, progress) # Flatten out the List
else:
return []
def validate_duplication(self):
- enrollment = frappe.db.exists("Course Enrollment", {
- "student": self.student,
- "course": self.course,
- "program_enrollment": self.program_enrollment,
- "name": ("!=", self.name)
- })
+ enrollment = frappe.db.exists(
+ "Course Enrollment",
+ {
+ "student": self.student,
+ "course": self.course,
+ "program_enrollment": self.program_enrollment,
+ "name": ("!=", self.name),
+ },
+ )
if enrollment:
- frappe.throw(_("Student is already enrolled via Course Enrollment {0}").format(
- get_link_to_form("Course Enrollment", enrollment)), title=_('Duplicate Entry'))
+ frappe.throw(
+ _("Student is already enrolled via Course Enrollment {0}").format(
+ get_link_to_form("Course Enrollment", enrollment)
+ ),
+ title=_("Duplicate Entry"),
+ )
def add_quiz_activity(self, quiz_name, quiz_response, answers, score, status, time_taken):
- result = {k: ('Correct' if v else 'Wrong') for k,v in answers.items()}
+ result = {k: ("Correct" if v else "Wrong") for k, v in answers.items()}
result_data = []
for key in answers:
item = {}
- item['question'] = key
- item['quiz_result'] = result[key]
+ item["question"] = key
+ item["quiz_result"] = result[key]
try:
if not quiz_response[key]:
- item['selected_option'] = "Unattempted"
+ item["selected_option"] = "Unattempted"
elif isinstance(quiz_response[key], list):
- item['selected_option'] = ', '.join(frappe.get_value('Options', res, 'option') for res in quiz_response[key])
+ item["selected_option"] = ", ".join(
+ frappe.get_value("Options", res, "option") for res in quiz_response[key]
+ )
else:
- item['selected_option'] = frappe.get_value('Options', quiz_response[key], 'option')
+ item["selected_option"] = frappe.get_value("Options", quiz_response[key], "option")
except KeyError:
- item['selected_option'] = "Unattempted"
+ item["selected_option"] = "Unattempted"
result_data.append(item)
- quiz_activity = frappe.get_doc({
- "doctype": "Quiz Activity",
- "enrollment": self.name,
- "quiz": quiz_name,
- "activity_date": frappe.utils.datetime.datetime.now(),
- "result": result_data,
- "score": score,
- "status": status,
- "time_taken": time_taken
- }).insert(ignore_permissions = True)
+ quiz_activity = frappe.get_doc(
+ {
+ "doctype": "Quiz Activity",
+ "enrollment": self.name,
+ "quiz": quiz_name,
+ "activity_date": frappe.utils.datetime.datetime.now(),
+ "result": result_data,
+ "score": score,
+ "status": status,
+ "time_taken": time_taken,
+ }
+ ).insert(ignore_permissions=True)
def add_activity(self, content_type, content):
activity = check_activity_exists(self.name, content_type, content)
if activity:
return activity
else:
- activity = frappe.get_doc({
- "doctype": "Course Activity",
- "enrollment": self.name,
- "content_type": content_type,
- "content": content,
- "activity_date": frappe.utils.datetime.datetime.now()
- })
+ activity = frappe.get_doc(
+ {
+ "doctype": "Course Activity",
+ "enrollment": self.name,
+ "content_type": content_type,
+ "content": content,
+ "activity_date": frappe.utils.datetime.datetime.now(),
+ }
+ )
activity.insert(ignore_permissions=True)
return activity.name
+
def check_activity_exists(enrollment, content_type, content):
- activity = frappe.get_all("Course Activity", filters={'enrollment': enrollment, 'content_type': content_type, 'content': content})
+ activity = frappe.get_all(
+ "Course Activity",
+ filters={"enrollment": enrollment, "content_type": content_type, "content": content},
+ )
if activity:
return activity[0].name
else:
diff --git a/erpnext/education/doctype/course_enrollment/course_enrollment_dashboard.py b/erpnext/education/doctype/course_enrollment/course_enrollment_dashboard.py
index 14a7a8fde12..31a90fd5adc 100644
--- a/erpnext/education/doctype/course_enrollment/course_enrollment_dashboard.py
+++ b/erpnext/education/doctype/course_enrollment/course_enrollment_dashboard.py
@@ -6,11 +6,6 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'enrollment',
- 'transactions': [
- {
- 'label': _('Activity'),
- 'items': ['Course Activity', 'Quiz Activity']
- }
- ]
+ "fieldname": "enrollment",
+ "transactions": [{"label": _("Activity"), "items": ["Course Activity", "Quiz Activity"]}],
}
diff --git a/erpnext/education/doctype/course_enrollment/test_course_enrollment.py b/erpnext/education/doctype/course_enrollment/test_course_enrollment.py
index e74d510e521..6862e058f0d 100644
--- a/erpnext/education/doctype/course_enrollment/test_course_enrollment.py
+++ b/erpnext/education/doctype/course_enrollment/test_course_enrollment.py
@@ -13,19 +13,37 @@ from erpnext.education.doctype.student.test_student import create_student, get_s
class TestCourseEnrollment(unittest.TestCase):
def setUp(self):
setup_program()
- student = create_student({"first_name": "_Test First", "last_name": "_Test Last", "email": "_test_student_1@example.com"})
+ student = create_student(
+ {"first_name": "_Test First", "last_name": "_Test Last", "email": "_test_student_1@example.com"}
+ )
program_enrollment = student.enroll_in_program("_Test Program")
- course_enrollment = frappe.db.get_value("Course Enrollment",
- {"course": "_Test Course 1", "student": student.name, "program_enrollment": program_enrollment.name}, 'name')
+ course_enrollment = frappe.db.get_value(
+ "Course Enrollment",
+ {
+ "course": "_Test Course 1",
+ "student": student.name,
+ "program_enrollment": program_enrollment.name,
+ },
+ "name",
+ )
make_course_activity(course_enrollment, "Article", "_Test Article 1-1")
def test_get_progress(self):
student = get_student("_test_student_1@example.com")
- program_enrollment_name = frappe.get_list("Program Enrollment", filters={"student": student.name, "Program": "_Test Program"})[0].name
- course_enrollment_name = frappe.get_list("Course Enrollment", filters={"student": student.name, "course": "_Test Course 1", "program_enrollment": program_enrollment_name})[0].name
+ program_enrollment_name = frappe.get_list(
+ "Program Enrollment", filters={"student": student.name, "Program": "_Test Program"}
+ )[0].name
+ course_enrollment_name = frappe.get_list(
+ "Course Enrollment",
+ filters={
+ "student": student.name,
+ "course": "_Test Course 1",
+ "program_enrollment": program_enrollment_name,
+ },
+ )[0].name
course_enrollment = frappe.get_doc("Course Enrollment", course_enrollment_name)
progress = course_enrollment.get_progress(student)
- finished = {'content': '_Test Article 1-1', 'content_type': 'Article', 'is_complete': True}
+ finished = {"content": "_Test Article 1-1", "content_type": "Article", "is_complete": True}
self.assertTrue(finished in progress)
frappe.db.rollback()
diff --git a/erpnext/education/doctype/course_schedule/course_schedule.py b/erpnext/education/doctype/course_schedule/course_schedule.py
index 615d2c47092..d2b31f4e054 100644
--- a/erpnext/education/doctype/course_schedule/course_schedule.py
+++ b/erpnext/education/doctype/course_schedule/course_schedule.py
@@ -20,10 +20,14 @@ class CourseSchedule(Document):
def set_title(self):
"""Set document Title"""
- self.title = self.course + " by " + (self.instructor_name if self.instructor_name else self.instructor)
+ self.title = (
+ self.course + " by " + (self.instructor_name if self.instructor_name else self.instructor)
+ )
def validate_course(self):
- group_based_on, course = frappe.db.get_value("Student Group", self.student_group, ["group_based_on", "course"])
+ group_based_on, course = frappe.db.get_value(
+ "Student Group", self.student_group, ["group_based_on", "course"]
+ )
if group_based_on == "Course":
self.course = course
@@ -35,7 +39,7 @@ class CourseSchedule(Document):
"""Handles specicfic case to update schedule date in calendar """
if isinstance(self.from_time, str):
try:
- datetime_obj = datetime.strptime(self.from_time, '%Y-%m-%d %H:%M:%S')
+ datetime_obj = datetime.strptime(self.from_time, "%Y-%m-%d %H:%M:%S")
self.schedule_date = datetime_obj
except ValueError:
pass
@@ -45,16 +49,16 @@ class CourseSchedule(Document):
from erpnext.education.utils import validate_overlap_for
- #Validate overlapping course schedules.
+ # Validate overlapping course schedules.
if self.student_group:
validate_overlap_for(self, "Course Schedule", "student_group")
validate_overlap_for(self, "Course Schedule", "instructor")
validate_overlap_for(self, "Course Schedule", "room")
- #validate overlapping assessment schedules.
+ # validate overlapping assessment schedules.
if self.student_group:
validate_overlap_for(self, "Assessment Plan", "student_group")
validate_overlap_for(self, "Assessment Plan", "room")
- validate_overlap_for(self, "Assessment Plan", "supervisor", self.instructor)
\ No newline at end of file
+ validate_overlap_for(self, "Assessment Plan", "supervisor", self.instructor)
diff --git a/erpnext/education/doctype/course_schedule/course_schedule_dashboard.py b/erpnext/education/doctype/course_schedule/course_schedule_dashboard.py
index 256e40b3b18..76a3f048058 100644
--- a/erpnext/education/doctype/course_schedule/course_schedule_dashboard.py
+++ b/erpnext/education/doctype/course_schedule/course_schedule_dashboard.py
@@ -6,11 +6,6 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'course_schedule',
- 'transactions': [
- {
- 'label': _('Attendance'),
- 'items': ['Student Attendance']
- }
- ]
+ "fieldname": "course_schedule",
+ "transactions": [{"label": _("Attendance"), "items": ["Student Attendance"]}],
}
diff --git a/erpnext/education/doctype/course_schedule/test_course_schedule.py b/erpnext/education/doctype/course_schedule/test_course_schedule.py
index 56149affcea..ac094645ba3 100644
--- a/erpnext/education/doctype/course_schedule/test_course_schedule.py
+++ b/erpnext/education/doctype/course_schedule/test_course_schedule.py
@@ -12,52 +12,76 @@ from erpnext.education.utils import OverlapError
# test_records = frappe.get_test_records('Course Schedule')
+
class TestCourseSchedule(unittest.TestCase):
def test_student_group_conflict(self):
- cs1 = make_course_schedule_test_record(simulate= True)
+ cs1 = make_course_schedule_test_record(simulate=True)
- cs2 = make_course_schedule_test_record(schedule_date=cs1.schedule_date, from_time= cs1.from_time,
- to_time= cs1.to_time, instructor="_Test Instructor 2", room=frappe.get_all("Room")[1].name, do_not_save= 1)
+ cs2 = make_course_schedule_test_record(
+ schedule_date=cs1.schedule_date,
+ from_time=cs1.from_time,
+ to_time=cs1.to_time,
+ instructor="_Test Instructor 2",
+ room=frappe.get_all("Room")[1].name,
+ do_not_save=1,
+ )
self.assertRaises(OverlapError, cs2.save)
def test_instructor_conflict(self):
- cs1 = make_course_schedule_test_record(simulate= True)
+ cs1 = make_course_schedule_test_record(simulate=True)
- cs2 = make_course_schedule_test_record(from_time= cs1.from_time, to_time= cs1.to_time,
- student_group="Course-TC101-2014-2015 (_Test Academic Term)", room=frappe.get_all("Room")[1].name, do_not_save= 1)
+ cs2 = make_course_schedule_test_record(
+ from_time=cs1.from_time,
+ to_time=cs1.to_time,
+ student_group="Course-TC101-2014-2015 (_Test Academic Term)",
+ room=frappe.get_all("Room")[1].name,
+ do_not_save=1,
+ )
self.assertRaises(OverlapError, cs2.save)
def test_room_conflict(self):
- cs1 = make_course_schedule_test_record(simulate= True)
+ cs1 = make_course_schedule_test_record(simulate=True)
- cs2 = make_course_schedule_test_record(from_time= cs1.from_time, to_time= cs1.to_time,
- student_group="Course-TC101-2014-2015 (_Test Academic Term)", instructor="_Test Instructor 2", do_not_save= 1)
+ cs2 = make_course_schedule_test_record(
+ from_time=cs1.from_time,
+ to_time=cs1.to_time,
+ student_group="Course-TC101-2014-2015 (_Test Academic Term)",
+ instructor="_Test Instructor 2",
+ do_not_save=1,
+ )
self.assertRaises(OverlapError, cs2.save)
def test_no_conflict(self):
- cs1 = make_course_schedule_test_record(simulate= True)
+ cs1 = make_course_schedule_test_record(simulate=True)
- make_course_schedule_test_record(from_time= cs1.from_time, to_time= cs1.to_time,
- student_group="Course-TC102-2014-2015 (_Test Academic Term)", instructor="_Test Instructor 2", room=frappe.get_all("Room")[1].name)
+ make_course_schedule_test_record(
+ from_time=cs1.from_time,
+ to_time=cs1.to_time,
+ student_group="Course-TC102-2014-2015 (_Test Academic Term)",
+ instructor="_Test Instructor 2",
+ room=frappe.get_all("Room")[1].name,
+ )
def test_update_schedule_date(self):
- doc = make_course_schedule_test_record(schedule_date= add_to_date(today(), days=1))
+ doc = make_course_schedule_test_record(schedule_date=add_to_date(today(), days=1))
doc.schedule_date = add_to_date(doc.schedule_date, days=1)
doc.save()
+
def make_course_schedule_test_record(**args):
args = frappe._dict(args)
course_schedule = frappe.new_doc("Course Schedule")
- course_schedule.student_group = args.student_group or "Course-TC101-2014-2015 (_Test Academic Term)"
+ course_schedule.student_group = (
+ args.student_group or "Course-TC101-2014-2015 (_Test Academic Term)"
+ )
course_schedule.course = args.course or "TC101"
course_schedule.instructor = args.instructor or "_Test Instructor"
course_schedule.room = args.room or frappe.get_all("Room")[0].name
course_schedule.schedule_date = args.schedule_date or today()
course_schedule.from_time = args.from_time or to_timedelta("01:00:00")
- course_schedule.to_time = args.to_time or course_schedule.from_time + datetime.timedelta(hours= 1)
-
+ course_schedule.to_time = args.to_time or course_schedule.from_time + datetime.timedelta(hours=1)
if not args.do_not_save:
if args.simulate:
@@ -67,7 +91,7 @@ def make_course_schedule_test_record(**args):
break
except OverlapError:
course_schedule.from_time = course_schedule.from_time + datetime.timedelta(minutes=10)
- course_schedule.to_time = course_schedule.from_time + datetime.timedelta(hours= 1)
+ course_schedule.to_time = course_schedule.from_time + datetime.timedelta(hours=1)
else:
course_schedule.save()
diff --git a/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py b/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py
index a309e4694c8..b3072c20b6c 100644
--- a/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py
+++ b/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py
@@ -13,7 +13,6 @@ from erpnext.education.utils import OverlapError
class CourseSchedulingTool(Document):
-
@frappe.whitelist()
def schedule_course(self):
"""Creates course schedules as per specified parameters"""
@@ -25,28 +24,25 @@ class CourseSchedulingTool(Document):
self.validate_mandatory()
self.validate_date()
- self.instructor_name = frappe.db.get_value(
- "Instructor", self.instructor, "instructor_name")
+ self.instructor_name = frappe.db.get_value("Instructor", self.instructor, "instructor_name")
group_based_on, course = frappe.db.get_value(
- "Student Group", self.student_group, ["group_based_on", "course"])
+ "Student Group", self.student_group, ["group_based_on", "course"]
+ )
if group_based_on == "Course":
self.course = course
if self.reschedule:
- rescheduled, reschedule_errors = self.delete_course_schedule(
- rescheduled, reschedule_errors)
+ rescheduled, reschedule_errors = self.delete_course_schedule(rescheduled, reschedule_errors)
date = self.course_start_date
while date < self.course_end_date:
if self.day == calendar.day_name[getdate(date).weekday()]:
course_schedule = self.make_course_schedule(date)
try:
- print('pass')
course_schedule.save()
except OverlapError:
- print('fail')
course_schedules_errors.append(date)
else:
course_schedules.append(course_schedule)
@@ -59,36 +55,43 @@ class CourseSchedulingTool(Document):
course_schedules=course_schedules,
course_schedules_errors=course_schedules_errors,
rescheduled=rescheduled,
- reschedule_errors=reschedule_errors
+ reschedule_errors=reschedule_errors,
)
def validate_mandatory(self):
"""Validates all mandatory fields"""
- fields = ['course', 'room', 'instructor', 'from_time',
- 'to_time', 'course_start_date', 'course_end_date', 'day']
+ fields = [
+ "course",
+ "room",
+ "instructor",
+ "from_time",
+ "to_time",
+ "course_start_date",
+ "course_end_date",
+ "day",
+ ]
for d in fields:
if not self.get(d):
- frappe.throw(_("{0} is mandatory").format(
- self.meta.get_label(d)))
+ frappe.throw(_("{0} is mandatory").format(self.meta.get_label(d)))
def validate_date(self):
"""Validates if Course Start Date is greater than Course End Date"""
if self.course_start_date > self.course_end_date:
- frappe.throw(
- _("Course Start Date cannot be greater than Course End Date."))
+ frappe.throw(_("Course Start Date cannot be greater than Course End Date."))
def delete_course_schedule(self, rescheduled, reschedule_errors):
"""Delete all course schedule within the Date range and specified filters"""
- schedules = frappe.get_list("Course Schedule",
+ schedules = frappe.get_list(
+ "Course Schedule",
fields=["name", "schedule_date"],
filters=[
["student_group", "=", self.student_group],
["course", "=", self.course],
["schedule_date", ">=", self.course_start_date],
- ["schedule_date", "<=", self.course_end_date]
- ]
+ ["schedule_date", "<=", self.course_end_date],
+ ],
)
for d in schedules:
diff --git a/erpnext/education/doctype/education_settings/education_settings.py b/erpnext/education/doctype/education_settings/education_settings.py
index 13123be78a3..295aa3a4cba 100644
--- a/erpnext/education/doctype/education_settings/education_settings.py
+++ b/erpnext/education/doctype/education_settings/education_settings.py
@@ -11,7 +11,7 @@ education_keydict = {
"academic_year": "current_academic_year",
"academic_term": "current_academic_term",
"validate_batch": "validate_batch",
- "validate_course": "validate_course"
+ "validate_course": "validate_course",
}
@@ -19,7 +19,7 @@ class EducationSettings(Document):
def on_update(self):
"""update defaults"""
for key in education_keydict:
- frappe.db.set_default(key, self.get(education_keydict[key], ''))
+ frappe.db.set_default(key, self.get(education_keydict[key], ""))
# clear cache
frappe.clear_cache()
@@ -29,10 +29,16 @@ class EducationSettings(Document):
def validate(self):
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
- if self.get('instructor_created_by')=='Naming Series':
- make_property_setter('Instructor', "naming_series", "hidden", 0, "Check", validate_fields_for_doctype=False)
+
+ if self.get("instructor_created_by") == "Naming Series":
+ make_property_setter(
+ "Instructor", "naming_series", "hidden", 0, "Check", validate_fields_for_doctype=False
+ )
else:
- make_property_setter('Instructor', "naming_series", "hidden", 1, "Check", validate_fields_for_doctype=False)
+ make_property_setter(
+ "Instructor", "naming_series", "hidden", 1, "Check", validate_fields_for_doctype=False
+ )
+
def update_website_context(context):
- context["lms_enabled"] = frappe.get_doc("Education Settings").enable_lms
+ context["lms_enabled"] = frappe.get_cached_doc("Education Settings").enable_lms
diff --git a/erpnext/education/doctype/fee_category/test_fee_category.py b/erpnext/education/doctype/fee_category/test_fee_category.py
index 9e74c7de11e..93565a9f926 100644
--- a/erpnext/education/doctype/fee_category/test_fee_category.py
+++ b/erpnext/education/doctype/fee_category/test_fee_category.py
@@ -5,5 +5,6 @@ import unittest
# test_records = frappe.get_test_records('Fee Category')
+
class TestFeeCategory(unittest.TestCase):
pass
diff --git a/erpnext/education/doctype/fee_schedule/fee_schedule.py b/erpnext/education/doctype/fee_schedule/fee_schedule.py
index a122fe88564..9ae5582f213 100644
--- a/erpnext/education/doctype/fee_schedule/fee_schedule.py
+++ b/erpnext/education/doctype/fee_schedule/fee_schedule.py
@@ -15,17 +15,20 @@ import erpnext
class FeeSchedule(Document):
def onload(self):
info = self.get_dashboard_info()
- self.set_onload('dashboard_info', info)
+ self.set_onload("dashboard_info", info)
def get_dashboard_info(self):
info = {
"total_paid": 0,
"total_unpaid": 0,
- "currency": erpnext.get_company_currency(self.company)
+ "currency": erpnext.get_company_currency(self.company),
}
- fees_amount = frappe.db.sql("""select sum(grand_total), sum(outstanding_amount) from tabFees
- where fee_schedule=%s and docstatus=1""", (self.name))
+ fees_amount = frappe.db.sql(
+ """select sum(grand_total), sum(outstanding_amount) from tabFees
+ where fee_schedule=%s and docstatus=1""",
+ (self.name),
+ )
if fees_amount:
info["total_paid"] = flt(fees_amount[0][0]) - flt(fees_amount[0][1])
@@ -40,33 +43,42 @@ class FeeSchedule(Document):
no_of_students = 0
for d in self.student_groups:
# if not d.total_students:
- d.total_students = get_total_students(d.student_group, self.academic_year,
- self.academic_term, self.student_category)
+ d.total_students = get_total_students(
+ d.student_group, self.academic_year, self.academic_term, self.student_category
+ )
no_of_students += cint(d.total_students)
# validate the program of fee structure and student groups
student_group_program = frappe.db.get_value("Student Group", d.student_group, "program")
if self.program and student_group_program and self.program != student_group_program:
- frappe.msgprint(_("Program in the Fee Structure and Student Group {0} are different.")
- .format(d.student_group))
- self.grand_total = no_of_students*self.total_amount
+ frappe.msgprint(
+ _("Program in the Fee Structure and Student Group {0} are different.").format(d.student_group)
+ )
+ self.grand_total = no_of_students * self.total_amount
self.grand_total_in_words = money_in_words(self.grand_total)
@frappe.whitelist()
def create_fees(self):
self.db_set("fee_creation_status", "In Process")
- frappe.publish_realtime("fee_schedule_progress",
- {"progress": "0", "reload": 1}, user=frappe.session.user)
+ frappe.publish_realtime(
+ "fee_schedule_progress", {"progress": "0", "reload": 1}, user=frappe.session.user
+ )
total_records = sum([int(d.total_students) for d in self.student_groups])
if total_records > 10:
- frappe.msgprint(_('''Fee records will be created in the background.
- In case of any error the error message will be updated in the Schedule.'''))
- enqueue(generate_fee, queue='default', timeout=6000, event='generate_fee',
- fee_schedule=self.name)
+ frappe.msgprint(
+ _(
+ """Fee records will be created in the background.
+ In case of any error the error message will be updated in the Schedule."""
+ )
+ )
+ enqueue(
+ generate_fee, queue="default", timeout=6000, event="generate_fee", fee_schedule=self.name
+ )
else:
generate_fee(self.name)
+
def generate_fee(fee_schedule):
doc = frappe.get_doc("Fee Schedule", fee_schedule)
error = False
@@ -77,17 +89,16 @@ def generate_fee(fee_schedule):
frappe.throw(_("Please setup Students under Student Groups"))
for d in doc.student_groups:
- students = get_students(d.student_group, doc.academic_year, doc.academic_term, doc.student_category)
+ students = get_students(
+ d.student_group, doc.academic_year, doc.academic_term, doc.student_category
+ )
for student in students:
try:
- fees_doc = get_mapped_doc("Fee Schedule", fee_schedule, {
- "Fee Schedule": {
- "doctype": "Fees",
- "field_map": {
- "name": "Fee Schedule"
- }
- }
- })
+ fees_doc = get_mapped_doc(
+ "Fee Schedule",
+ fee_schedule,
+ {"Fee Schedule": {"doctype": "Fees", "field_map": {"name": "Fee Schedule"}}},
+ )
fees_doc.posting_date = doc.posting_date
fees_doc.student = student.student
fees_doc.student_name = student.student_name
@@ -97,7 +108,11 @@ def generate_fee(fee_schedule):
fees_doc.save()
fees_doc.submit()
created_records += 1
- frappe.publish_realtime("fee_schedule_progress", {"progress": str(int(created_records * 100/total_records))}, user=frappe.session.user)
+ frappe.publish_realtime(
+ "fee_schedule_progress",
+ {"progress": str(int(created_records * 100 / total_records))},
+ user=frappe.session.user,
+ )
except Exception as e:
error = True
@@ -112,8 +127,9 @@ def generate_fee(fee_schedule):
frappe.db.set_value("Fee Schedule", fee_schedule, "fee_creation_status", "Successful")
frappe.db.set_value("Fee Schedule", fee_schedule, "error_log", None)
- frappe.publish_realtime("fee_schedule_progress",
- {"progress": "100", "reload": 1}, user=frappe.session.user)
+ frappe.publish_realtime(
+ "fee_schedule_progress", {"progress": "100", "reload": 1}, user=frappe.session.user
+ )
def get_students(student_group, academic_year, academic_term=None, student_category=None):
@@ -123,14 +139,20 @@ def get_students(student_group, academic_year, academic_term=None, student_categ
if academic_term:
conditions = " and pe.academic_term={}".format(frappe.db.escape(academic_term))
- students = frappe.db.sql("""
+ students = frappe.db.sql(
+ """
select pe.student, pe.student_name, pe.program, pe.student_batch_name
from `tabStudent Group Student` sgs, `tabProgram Enrollment` pe
where
pe.student = sgs.student and pe.academic_year = %s
and sgs.parent = %s and sgs.active = 1
{conditions}
- """.format(conditions=conditions), (academic_year, student_group), as_dict=1)
+ """.format(
+ conditions=conditions
+ ),
+ (academic_year, student_group),
+ as_dict=1,
+ )
return students
@@ -141,9 +163,11 @@ def get_total_students(student_group, academic_year, academic_term=None, student
@frappe.whitelist()
-def get_fee_structure(source_name,target_doc=None):
- fee_request = get_mapped_doc("Fee Structure", source_name,
- {"Fee Structure": {
- "doctype": "Fee Schedule"
- }}, ignore_permissions=True)
+def get_fee_structure(source_name, target_doc=None):
+ fee_request = get_mapped_doc(
+ "Fee Structure",
+ source_name,
+ {"Fee Structure": {"doctype": "Fee Schedule"}},
+ ignore_permissions=True,
+ )
return fee_request
diff --git a/erpnext/education/doctype/fee_schedule/fee_schedule_dashboard.py b/erpnext/education/doctype/fee_schedule/fee_schedule_dashboard.py
index f5d1dee98b4..4b99d27f299 100644
--- a/erpnext/education/doctype/fee_schedule/fee_schedule_dashboard.py
+++ b/erpnext/education/doctype/fee_schedule/fee_schedule_dashboard.py
@@ -3,11 +3,4 @@
def get_data():
- return {
- 'fieldname': 'fee_schedule',
- 'transactions': [
- {
- 'items': ['Fees']
- }
- ]
- }
+ return {"fieldname": "fee_schedule", "transactions": [{"items": ["Fees"]}]}
diff --git a/erpnext/education/doctype/fee_structure/fee_structure.py b/erpnext/education/doctype/fee_structure/fee_structure.py
index 9090a6b9d54..f1b7bb726e8 100644
--- a/erpnext/education/doctype/fee_structure/fee_structure.py
+++ b/erpnext/education/doctype/fee_structure/fee_structure.py
@@ -20,14 +20,17 @@ class FeeStructure(Document):
@frappe.whitelist()
def make_fee_schedule(source_name, target_doc=None):
- return get_mapped_doc("Fee Structure", source_name, {
- "Fee Structure": {
- "doctype": "Fee Schedule",
- "validation": {
- "docstatus": ["=", 1],
- }
+ return get_mapped_doc(
+ "Fee Structure",
+ source_name,
+ {
+ "Fee Structure": {
+ "doctype": "Fee Schedule",
+ "validation": {
+ "docstatus": ["=", 1],
+ },
+ },
+ "Fee Component": {"doctype": "Fee Component"},
},
- "Fee Component": {
- "doctype": "Fee Component"
- }
- }, target_doc)
+ target_doc,
+ )
diff --git a/erpnext/education/doctype/fee_structure/fee_structure_dashboard.py b/erpnext/education/doctype/fee_structure/fee_structure_dashboard.py
index 27ce06b29b3..0ae3032b0c2 100644
--- a/erpnext/education/doctype/fee_structure/fee_structure_dashboard.py
+++ b/erpnext/education/doctype/fee_structure/fee_structure_dashboard.py
@@ -6,11 +6,6 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'fee_structure',
- 'transactions': [
- {
- 'label': _('Fee'),
- 'items': ['Fees', 'Fee Schedule']
- }
- ]
+ "fieldname": "fee_structure",
+ "transactions": [{"label": _("Fee"), "items": ["Fees", "Fee Schedule"]}],
}
diff --git a/erpnext/education/doctype/fee_structure/test_fee_structure.py b/erpnext/education/doctype/fee_structure/test_fee_structure.py
index 61381a6289c..ebe0fea53bd 100644
--- a/erpnext/education/doctype/fee_structure/test_fee_structure.py
+++ b/erpnext/education/doctype/fee_structure/test_fee_structure.py
@@ -5,5 +5,6 @@ import unittest
# test_records = frappe.get_test_records('Fee Structure')
+
class TestFeeStructure(unittest.TestCase):
pass
diff --git a/erpnext/education/doctype/fees/fees.py b/erpnext/education/doctype/fees/fees.py
index 41d428d23de..be608bf731e 100644
--- a/erpnext/education/doctype/fees/fees.py
+++ b/erpnext/education/doctype/fees/fees.py
@@ -33,9 +33,11 @@ class Fees(AccountsController):
if not self.currency:
self.currency = erpnext.get_company_currency(self.company)
if not (self.receivable_account and self.income_account and self.cost_center):
- accounts_details = frappe.get_all("Company",
+ accounts_details = frappe.get_all(
+ "Company",
fields=["default_receivable_account", "default_income_account", "cost_center"],
- filters={"name": self.company})[0]
+ filters={"name": self.company},
+ )[0]
if not self.receivable_account:
self.receivable_account = accounts_details.default_receivable_account
if not self.income_account:
@@ -46,12 +48,15 @@ class Fees(AccountsController):
self.student_email = self.get_student_emails()
def get_student_emails(self):
- student_emails = frappe.db.sql_list("""
+ student_emails = frappe.db.sql_list(
+ """
select g.email_address
from `tabGuardian` g, `tabStudent Guardian` sg
where g.name = sg.guardian and sg.parent = %s and sg.parenttype = 'Student'
and ifnull(g.email_address, '')!=''
- """, self.student)
+ """,
+ self.student,
+ )
student_email_id = frappe.db.get_value("Student", self.student, "student_email_id")
if student_email_id:
@@ -61,7 +66,6 @@ class Fees(AccountsController):
else:
return None
-
def calculate_total(self):
"""Calculates total amount."""
self.grand_total = 0
@@ -75,61 +79,84 @@ class Fees(AccountsController):
self.make_gl_entries()
if self.send_payment_request and self.student_email:
- pr = make_payment_request(party_type="Student", party=self.student, dt="Fees",
- dn=self.name, recipient_id=self.student_email,
- submit_doc=True, use_dummy_message=True)
+ pr = make_payment_request(
+ party_type="Student",
+ party=self.student,
+ dt="Fees",
+ dn=self.name,
+ recipient_id=self.student_email,
+ submit_doc=True,
+ use_dummy_message=True,
+ )
frappe.msgprint(_("Payment request {0} created").format(getlink("Payment Request", pr.name)))
def on_cancel(self):
- 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)
# frappe.db.set(self, 'status', 'Cancelled')
-
def make_gl_entries(self):
if not self.grand_total:
return
- student_gl_entries = self.get_gl_dict({
- "account": self.receivable_account,
- "party_type": "Student",
- "party": self.student,
- "against": self.income_account,
- "debit": self.grand_total,
- "debit_in_account_currency": self.grand_total,
- "against_voucher": self.name,
- "against_voucher_type": self.doctype
- }, item=self)
+ student_gl_entries = self.get_gl_dict(
+ {
+ "account": self.receivable_account,
+ "party_type": "Student",
+ "party": self.student,
+ "against": self.income_account,
+ "debit": self.grand_total,
+ "debit_in_account_currency": self.grand_total,
+ "against_voucher": self.name,
+ "against_voucher_type": self.doctype,
+ },
+ item=self,
+ )
- fee_gl_entry = self.get_gl_dict({
- "account": self.income_account,
- "against": self.student,
- "credit": self.grand_total,
- "credit_in_account_currency": self.grand_total,
- "cost_center": self.cost_center
- }, item=self)
+ fee_gl_entry = self.get_gl_dict(
+ {
+ "account": self.income_account,
+ "against": self.student,
+ "credit": self.grand_total,
+ "credit_in_account_currency": self.grand_total,
+ "cost_center": self.cost_center,
+ },
+ item=self,
+ )
from erpnext.accounts.general_ledger import make_gl_entries
- make_gl_entries([student_gl_entries, fee_gl_entry], cancel=(self.docstatus == 2),
- update_outstanding="Yes", merge_entries=False)
+
+ make_gl_entries(
+ [student_gl_entries, fee_gl_entry],
+ cancel=(self.docstatus == 2),
+ update_outstanding="Yes",
+ merge_entries=False,
+ )
+
def get_fee_list(doctype, txt, filters, limit_start, limit_page_length=20, order_by="modified"):
user = frappe.session.user
student = frappe.db.sql("select name from `tabStudent` where student_email_id= %s", user)
if student:
- return frappe. db.sql('''
+ return frappe.db.sql(
+ """
select name, program, due_date, grand_total - outstanding_amount as paid_amount,
outstanding_amount, grand_total, currency
from `tabFees`
where student= %s and docstatus=1
- order by due_date asc limit {0} , {1}'''
- .format(limit_start, limit_page_length), student, as_dict = True)
+ order by due_date asc limit {0} , {1}""".format(
+ limit_start, limit_page_length
+ ),
+ student,
+ as_dict=True,
+ )
+
def get_list_context(context=None):
return {
"show_sidebar": True,
"show_search": True,
- 'no_breadcrumbs': True,
+ "no_breadcrumbs": True,
"title": _("Fees"),
"get_list": get_fee_list,
- "row_template": "templates/includes/fee/fee_row.html"
+ "row_template": "templates/includes/fee/fee_row.html",
}
diff --git a/erpnext/education/doctype/fees/test_fees.py b/erpnext/education/doctype/fees/test_fees.py
index 72f1d113962..f0f7e67d2bb 100644
--- a/erpnext/education/doctype/fees/test_fees.py
+++ b/erpnext/education/doctype/fees/test_fees.py
@@ -9,12 +9,15 @@ from frappe.utils.make_random import get_random
from erpnext.education.doctype.program.test_program import make_program_and_linked_courses
-test_dependencies = ['Company']
-class TestFees(unittest.TestCase):
+test_dependencies = ["Company"]
+
+class TestFees(unittest.TestCase):
def test_fees(self):
student = get_random("Student")
- program = make_program_and_linked_courses("_Test Program 1", ["_Test Course 1", "_Test Course 2"])
+ program = make_program_and_linked_courses(
+ "_Test Program 1", ["_Test Course 1", "_Test Course 2"]
+ )
fee = frappe.new_doc("Fees")
fee.posting_date = nowdate()
fee.due_date = nowdate()
@@ -25,22 +28,24 @@ class TestFees(unittest.TestCase):
fee.company = "_Test Company"
fee.program = program.name
- fee.extend("components", [
- {
- "fees_category": "Tuition Fee",
- "amount": 40000
- },
- {
- "fees_category": "Transportation Fee",
- "amount": 10000
- }])
+ fee.extend(
+ "components",
+ [
+ {"fees_category": "Tuition Fee", "amount": 40000},
+ {"fees_category": "Transportation Fee", "amount": 10000},
+ ],
+ )
fee.save()
fee.submit()
- gl_entries = frappe.db.sql("""
+ gl_entries = frappe.db.sql(
+ """
select account, posting_date, party_type, party, cost_center, fiscal_year, voucher_type,
voucher_no, against_voucher_type, against_voucher, cost_center, company, credit, debit
- from `tabGL Entry` where voucher_type=%s and voucher_no=%s""", ("Fees", fee.name), as_dict=True)
+ from `tabGL Entry` where voucher_type=%s and voucher_no=%s""",
+ ("Fees", fee.name),
+ as_dict=True,
+ )
if gl_entries[0].account == "_Test Receivable - _TC":
self.assertEqual(gl_entries[0].debit, 50000)
diff --git a/erpnext/education/doctype/grading_scale/grading_scale_dashboard.py b/erpnext/education/doctype/grading_scale/grading_scale_dashboard.py
index b8ae8b08a98..6995666d52c 100644
--- a/erpnext/education/doctype/grading_scale/grading_scale_dashboard.py
+++ b/erpnext/education/doctype/grading_scale/grading_scale_dashboard.py
@@ -3,18 +3,10 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'grading_scale',
- 'non_standard_fieldnames': {
- 'Course': 'default_grading_scale'
- },
- 'transactions': [
- {
- 'label': _('Course'),
- 'items': ['Course']
- },
- {
- 'label': _('Assessment'),
- 'items': ['Assessment Plan', 'Assessment Result']
- }
- ]
+ "fieldname": "grading_scale",
+ "non_standard_fieldnames": {"Course": "default_grading_scale"},
+ "transactions": [
+ {"label": _("Course"), "items": ["Course"]},
+ {"label": _("Assessment"), "items": ["Assessment Plan", "Assessment Result"]},
+ ],
}
diff --git a/erpnext/education/doctype/grading_scale/test_grading_scale.py b/erpnext/education/doctype/grading_scale/test_grading_scale.py
index 3ebefda22fc..09a092cc2a5 100644
--- a/erpnext/education/doctype/grading_scale/test_grading_scale.py
+++ b/erpnext/education/doctype/grading_scale/test_grading_scale.py
@@ -5,5 +5,6 @@ import unittest
# test_records = frappe.get_test_records('Grading Scale')
+
class TestGradingScale(unittest.TestCase):
pass
diff --git a/erpnext/education/doctype/guardian/guardian.py b/erpnext/education/doctype/guardian/guardian.py
index aae651b8550..a456e08d2f5 100644
--- a/erpnext/education/doctype/guardian/guardian.py
+++ b/erpnext/education/doctype/guardian/guardian.py
@@ -19,12 +19,15 @@ class Guardian(Document):
def load_students(self):
"""Load `students` from the database"""
self.students = []
- students = frappe.get_all("Student Guardian", filters={"guardian":self.name}, fields=["parent"])
+ students = frappe.get_all("Student Guardian", filters={"guardian": self.name}, fields=["parent"])
for student in students:
- self.append("students", {
- "student":student.parent,
- "student_name": frappe.db.get_value("Student", student.parent, "title")
- })
+ self.append(
+ "students",
+ {
+ "student": student.parent,
+ "student_name": frappe.db.get_value("Student", student.parent, "title"),
+ },
+ )
def validate(self):
self.students = []
@@ -36,17 +39,19 @@ def invite_guardian(guardian):
if not guardian_doc.email_address:
frappe.throw(_("Please set Email Address"))
else:
- guardian_as_user = frappe.get_value('User', dict(email=guardian_doc.email_address))
+ guardian_as_user = frappe.get_value("User", dict(email=guardian_doc.email_address))
if guardian_as_user:
frappe.msgprint(_("User {0} already exists").format(getlink("User", guardian_as_user)))
return guardian_as_user
else:
- user = frappe.get_doc({
- "doctype": "User",
- "first_name": guardian_doc.guardian_name,
- "email": guardian_doc.email_address,
- "user_type": "Website User",
- "send_welcome_email": 1
- }).insert(ignore_permissions = True)
+ user = frappe.get_doc(
+ {
+ "doctype": "User",
+ "first_name": guardian_doc.guardian_name,
+ "email": guardian_doc.email_address,
+ "user_type": "Website User",
+ "send_welcome_email": 1,
+ }
+ ).insert(ignore_permissions=True)
frappe.msgprint(_("User {0} created").format(getlink("User", user.name)))
return user.name
diff --git a/erpnext/education/doctype/guardian/test_guardian.py b/erpnext/education/doctype/guardian/test_guardian.py
index f474ed5e06e..de3638c3099 100644
--- a/erpnext/education/doctype/guardian/test_guardian.py
+++ b/erpnext/education/doctype/guardian/test_guardian.py
@@ -5,5 +5,6 @@ import unittest
# test_records = frappe.get_test_records('Guardian')
+
class TestGuardian(unittest.TestCase):
pass
diff --git a/erpnext/education/doctype/instructor/instructor.py b/erpnext/education/doctype/instructor/instructor.py
index 0076240f86f..24e0607e2d4 100644
--- a/erpnext/education/doctype/instructor/instructor.py
+++ b/erpnext/education/doctype/instructor/instructor.py
@@ -14,30 +14,37 @@ class Instructor(Document):
if not naming_method:
frappe.throw(_("Please setup Instructor Naming System in Education > Education 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":
if not self.employee:
frappe.throw(_("Please select Employee"))
self.name = self.employee
- elif naming_method == 'Full Name':
+ elif naming_method == "Full Name":
self.name = self.instructor_name
def validate(self):
self.validate_duplicate_employee()
def validate_duplicate_employee(self):
- if self.employee and frappe.db.get_value("Instructor", {'employee': self.employee, 'name': ['!=', self.name]}, 'name'):
+ if self.employee and frappe.db.get_value(
+ "Instructor", {"employee": self.employee, "name": ["!=", self.name]}, "name"
+ ):
frappe.throw(_("Employee ID is linked with another instructor"))
+
def get_timeline_data(doctype, name):
"""Return timeline for course schedule"""
- return dict(frappe.db.sql(
- """
+ return dict(
+ frappe.db.sql(
+ """
SELECT unix_timestamp(`schedule_date`), count(*)
FROM `tabCourse Schedule`
WHERE
instructor=%s and
`schedule_date` > date_sub(curdate(), interval 1 year)
GROUP BY schedule_date
- """, name))
+ """,
+ name,
+ )
+ )
diff --git a/erpnext/education/doctype/instructor/instructor_dashboard.py b/erpnext/education/doctype/instructor/instructor_dashboard.py
index eae67acabfd..ead10ca7f83 100644
--- a/erpnext/education/doctype/instructor/instructor_dashboard.py
+++ b/erpnext/education/doctype/instructor/instructor_dashboard.py
@@ -6,20 +6,12 @@ from frappe import _
def get_data():
return {
- 'heatmap': True,
- 'heatmap_message': _('This is based on the course schedules of this Instructor'),
- 'fieldname': 'instructor',
- 'non_standard_fieldnames': {
- 'Assessment Plan': 'supervisor'
- },
- 'transactions': [
- {
- 'label': _('Course and Assessment'),
- 'items': ['Course Schedule', 'Assessment Plan']
- },
- {
- 'label': _('Students'),
- 'items': ['Student Group']
- }
- ]
+ "heatmap": True,
+ "heatmap_message": _("This is based on the course schedules of this Instructor"),
+ "fieldname": "instructor",
+ "non_standard_fieldnames": {"Assessment Plan": "supervisor"},
+ "transactions": [
+ {"label": _("Course and Assessment"), "items": ["Course Schedule", "Assessment Plan"]},
+ {"label": _("Students"), "items": ["Student Group"]},
+ ],
}
diff --git a/erpnext/education/doctype/instructor/test_instructor.py b/erpnext/education/doctype/instructor/test_instructor.py
index 4eab37ae01f..ee1e81dd306 100644
--- a/erpnext/education/doctype/instructor/test_instructor.py
+++ b/erpnext/education/doctype/instructor/test_instructor.py
@@ -5,5 +5,6 @@ import unittest
# test_records = frappe.get_test_records('Instructor')
+
class TestInstructor(unittest.TestCase):
pass
diff --git a/erpnext/education/doctype/program/program.py b/erpnext/education/doctype/program/program.py
index a9ce64409ab..5250f8c05e6 100644
--- a/erpnext/education/doctype/program/program.py
+++ b/erpnext/education/doctype/program/program.py
@@ -7,8 +7,9 @@ from frappe.model.document import Document
class Program(Document):
-
def get_course_list(self):
program_course_list = self.courses
- course_list = [frappe.get_doc("Course", program_course.course) for program_course in program_course_list]
+ course_list = [
+ frappe.get_doc("Course", program_course.course) for program_course in program_course_list
+ ]
return course_list
diff --git a/erpnext/education/doctype/program/program_dashboard.py b/erpnext/education/doctype/program/program_dashboard.py
index 66960767f16..21820618bbe 100644
--- a/erpnext/education/doctype/program/program_dashboard.py
+++ b/erpnext/education/doctype/program/program_dashboard.py
@@ -3,23 +3,11 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'program',
- 'transactions': [
- {
- 'label': _('Admission and Enrollment'),
- 'items': ['Student Applicant', 'Program Enrollment']
- },
- {
- 'label': _('Student Activity'),
- 'items': ['Student Group', 'Student Log']
- },
- {
- 'label': _('Fee'),
- 'items': ['Fees','Fee Structure', 'Fee Schedule']
- },
- {
- 'label': _('Assessment'),
- 'items': ['Assessment Plan', 'Assessment Result']
- }
- ]
+ "fieldname": "program",
+ "transactions": [
+ {"label": _("Admission and Enrollment"), "items": ["Student Applicant", "Program Enrollment"]},
+ {"label": _("Student Activity"), "items": ["Student Group", "Student Log"]},
+ {"label": _("Fee"), "items": ["Fees", "Fee Structure", "Fee Schedule"]},
+ {"label": _("Assessment"), "items": ["Assessment Plan", "Assessment Result"]},
+ ],
}
diff --git a/erpnext/education/doctype/program/test_program.py b/erpnext/education/doctype/program/test_program.py
index cb8926bcf7e..c3dbad2d163 100644
--- a/erpnext/education/doctype/program/test_program.py
+++ b/erpnext/education/doctype/program/test_program.py
@@ -11,32 +11,30 @@ from erpnext.education.doctype.topic.test_topic import make_topic_and_linked_con
test_data = {
"program_name": "_Test Program",
"description": "_Test Program",
- "course": [{
- "course_name": "_Test Course 1",
- "topic": [{
- "topic_name": "_Test Topic 1-1",
- "content": [{
- "type": "Article",
- "name": "_Test Article 1-1"
- }, {
- "type": "Article",
- "name": "_Test Article 1-2"
- }]
- },
- {
- "topic_name": "_Test Topic 1-2",
- "content": [{
- "type": "Article",
- "name": "_Test Article 1-3"
- }, {
- "type": "Article",
- "name": "_Test Article 1-4"
- }]
- }
- ]
- }]
+ "course": [
+ {
+ "course_name": "_Test Course 1",
+ "topic": [
+ {
+ "topic_name": "_Test Topic 1-1",
+ "content": [
+ {"type": "Article", "name": "_Test Article 1-1"},
+ {"type": "Article", "name": "_Test Article 1-2"},
+ ],
+ },
+ {
+ "topic_name": "_Test Topic 1-2",
+ "content": [
+ {"type": "Article", "name": "_Test Article 1-3"},
+ {"type": "Article", "name": "_Test Article 1-4"},
+ ],
+ },
+ ],
+ }
+ ],
}
+
class TestProgram(unittest.TestCase):
def setUp(self):
make_program_and_linked_courses("_Test Program 1", ["_Test Course 1", "_Test Course 2"])
@@ -53,17 +51,21 @@ class TestProgram(unittest.TestCase):
for entry in frappe.get_all(dt):
frappe.delete_doc(dt, entry.program)
+
def make_program(name):
- program = frappe.get_doc({
- "doctype": "Program",
- "program_name": name,
- "program_code": name,
- "description": "_test description",
- "is_published": True,
- "is_featured": True,
- }).insert()
+ program = frappe.get_doc(
+ {
+ "doctype": "Program",
+ "program_name": name,
+ "program_code": name,
+ "description": "_test description",
+ "is_published": True,
+ "is_featured": True,
+ }
+ ).insert()
return program.name
+
def make_program_and_linked_courses(program_name, course_name_list):
try:
program = frappe.get_doc("Program", program_name)
@@ -76,15 +78,19 @@ def make_program_and_linked_courses(program_name, course_name_list):
program.save()
return program
+
def setup_program():
- topic_list = [course['topic'] for course in test_data['course']]
+ topic_list = [course["topic"] for course in test_data["course"]]
for topic in topic_list[0]:
- make_topic_and_linked_content(topic['topic_name'], topic['content'])
+ make_topic_and_linked_content(topic["topic_name"], topic["content"])
- all_courses_list = [{'course': course['course_name'], 'topic': [topic['topic_name'] for topic in course['topic']]} for course in test_data['course']] # returns [{'course': 'Applied Math', 'topic': ['Trignometry', 'Geometry']}]
+ all_courses_list = [
+ {"course": course["course_name"], "topic": [topic["topic_name"] for topic in course["topic"]]}
+ for course in test_data["course"]
+ ] # returns [{'course': 'Applied Math', 'topic': ['Trignometry', 'Geometry']}]
for course in all_courses_list:
- make_course_and_linked_topic(course['course'], course['topic'])
+ make_course_and_linked_topic(course["course"], course["topic"])
- course_list = [course['course_name'] for course in test_data['course']]
- program = make_program_and_linked_courses(test_data['program_name'], course_list)
+ course_list = [course["course_name"] for course in test_data["course"]]
+ program = make_program_and_linked_courses(test_data["program_name"], course_list)
return program
diff --git a/erpnext/education/doctype/program_enrollment/program_enrollment.py b/erpnext/education/doctype/program_enrollment/program_enrollment.py
index 4d0f3a98011..69d281b9386 100644
--- a/erpnext/education/doctype/program_enrollment/program_enrollment.py
+++ b/erpnext/education/doctype/program_enrollment/program_enrollment.py
@@ -27,45 +27,64 @@ class ProgramEnrollment(Document):
self.create_course_enrollments()
def validate_academic_year(self):
- start_date, end_date = frappe.db.get_value("Academic Year", self.academic_year, ["year_start_date", "year_end_date"])
+ start_date, end_date = frappe.db.get_value(
+ "Academic Year", self.academic_year, ["year_start_date", "year_end_date"]
+ )
if self.enrollment_date:
if start_date and getdate(self.enrollment_date) < getdate(start_date):
- frappe.throw(_("Enrollment Date cannot be before the Start Date of the Academic Year {0}").format(
- get_link_to_form("Academic Year", self.academic_year)))
+ frappe.throw(
+ _("Enrollment Date cannot be before the Start Date of the Academic Year {0}").format(
+ get_link_to_form("Academic Year", self.academic_year)
+ )
+ )
if end_date and getdate(self.enrollment_date) > getdate(end_date):
- frappe.throw(_("Enrollment Date cannot be after the End Date of the Academic Term {0}").format(
- get_link_to_form("Academic Year", self.academic_year)))
+ frappe.throw(
+ _("Enrollment Date cannot be after the End Date of the Academic Term {0}").format(
+ get_link_to_form("Academic Year", self.academic_year)
+ )
+ )
def validate_academic_term(self):
- start_date, end_date = frappe.db.get_value("Academic Term", self.academic_term, ["term_start_date", "term_end_date"])
+ start_date, end_date = frappe.db.get_value(
+ "Academic Term", self.academic_term, ["term_start_date", "term_end_date"]
+ )
if self.enrollment_date:
if start_date and getdate(self.enrollment_date) < getdate(start_date):
- frappe.throw(_("Enrollment Date cannot be before the Start Date of the Academic Term {0}").format(
- get_link_to_form("Academic Term", self.academic_term)))
+ frappe.throw(
+ _("Enrollment Date cannot be before the Start Date of the Academic Term {0}").format(
+ get_link_to_form("Academic Term", self.academic_term)
+ )
+ )
if end_date and getdate(self.enrollment_date) > getdate(end_date):
- frappe.throw(_("Enrollment Date cannot be after the End Date of the Academic Term {0}").format(
- get_link_to_form("Academic Term", self.academic_term)))
+ frappe.throw(
+ _("Enrollment Date cannot be after the End Date of the Academic Term {0}").format(
+ get_link_to_form("Academic Term", self.academic_term)
+ )
+ )
def validate_duplication(self):
- enrollment = frappe.get_all("Program Enrollment", filters={
- "student": self.student,
- "program": self.program,
- "academic_year": self.academic_year,
- "academic_term": self.academic_term,
- "docstatus": ("<", 2),
- "name": ("!=", self.name)
- })
+ enrollment = frappe.get_all(
+ "Program Enrollment",
+ filters={
+ "student": self.student,
+ "program": self.program,
+ "academic_year": self.academic_year,
+ "academic_term": self.academic_term,
+ "docstatus": ("<", 2),
+ "name": ("!=", self.name),
+ },
+ )
if enrollment:
frappe.throw(_("Student is already enrolled."))
def update_student_joining_date(self):
- table = frappe.qb.DocType('Program Enrollment')
+ table = frappe.qb.DocType("Program Enrollment")
date = (
frappe.qb.from_(table)
- .select(Min(table.enrollment_date).as_('enrollment_date'))
- .where(table.student == self.student)
+ .select(Min(table.enrollment_date).as_("enrollment_date"))
+ .where(table.student == self.student)
).run(as_dict=True)
if date:
@@ -73,45 +92,59 @@ class ProgramEnrollment(Document):
def make_fee_records(self):
from erpnext.education.api import get_fee_components
+
fee_list = []
for d in self.fees:
fee_components = get_fee_components(d.fee_structure)
if fee_components:
fees = frappe.new_doc("Fees")
- fees.update({
- "student": self.student,
- "academic_year": self.academic_year,
- "academic_term": d.academic_term,
- "fee_structure": d.fee_structure,
- "program": self.program,
- "due_date": d.due_date,
- "student_name": self.student_name,
- "program_enrollment": self.name,
- "components": fee_components
- })
+ fees.update(
+ {
+ "student": self.student,
+ "academic_year": self.academic_year,
+ "academic_term": d.academic_term,
+ "fee_structure": d.fee_structure,
+ "program": self.program,
+ "due_date": d.due_date,
+ "student_name": self.student_name,
+ "program_enrollment": self.name,
+ "components": fee_components,
+ }
+ )
fees.save()
fees.submit()
fee_list.append(fees.name)
if fee_list:
- fee_list = ["""%s""" % \
- (fee, fee) for fee in fee_list]
+ fee_list = [
+ """%s""" % (fee, fee) for fee in fee_list
+ ]
msgprint(_("Fee Records Created - {0}").format(comma_and(fee_list)))
-
@frappe.whitelist()
def get_courses(self):
- return frappe.db.sql('''select course from `tabProgram Course` where parent = %s and required = 1''', (self.program), as_dict=1)
+ return frappe.db.sql(
+ """select course from `tabProgram Course` where parent = %s and required = 1""",
+ (self.program),
+ as_dict=1,
+ )
def create_course_enrollments(self):
student = frappe.get_doc("Student", self.student)
course_list = [course.course for course in self.courses]
for course_name in course_list:
- student.enroll_in_course(course_name=course_name, program_enrollment=self.name, enrollment_date=self.enrollment_date)
+ student.enroll_in_course(
+ course_name=course_name, program_enrollment=self.name, enrollment_date=self.enrollment_date
+ )
def get_all_course_enrollments(self):
- course_enrollment_names = frappe.get_list("Course Enrollment", filters={'program_enrollment': self.name})
- return [frappe.get_doc('Course Enrollment', course_enrollment.name) for course_enrollment in course_enrollment_names]
+ course_enrollment_names = frappe.get_list(
+ "Course Enrollment", filters={"program_enrollment": self.name}
+ )
+ return [
+ frappe.get_doc("Course Enrollment", course_enrollment.name)
+ for course_enrollment in course_enrollment_names
+ ]
def get_quiz_progress(self):
student = frappe.get_doc("Student", self.student)
@@ -120,8 +153,8 @@ class ProgramEnrollment(Document):
for course_enrollment in self.get_all_course_enrollments():
course_progress = course_enrollment.get_progress(student)
for progress_item in course_progress:
- if progress_item['content_type'] == "Quiz":
- progress_item['course'] = course_enrollment.course
+ if progress_item["content_type"] == "Quiz":
+ progress_item["course"] = course_enrollment.course
progress_list.append(progress_item)
if not progress_list:
return None
@@ -130,27 +163,27 @@ class ProgramEnrollment(Document):
quiz_progress.program = self.program
return quiz_progress
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_program_courses(doctype, txt, searchfield, start, page_len, filters):
- if not filters.get('program'):
+ if not filters.get("program"):
frappe.msgprint(_("Please select a Program first."))
return []
- return frappe.db.sql("""select course, course_name from `tabProgram Course`
+ return frappe.db.sql(
+ """select course, course_name from `tabProgram Course`
where parent = %(program)s and course like %(txt)s {match_cond}
order by
if(locate(%(_txt)s, course), locate(%(_txt)s, course), 99999),
idx desc,
`tabProgram Course`.course asc
limit {start}, {page_len}""".format(
- match_cond=get_match_cond(doctype),
- start=start,
- page_len=page_len), {
- "txt": "%{0}%".format(txt),
- "_txt": txt.replace('%', ''),
- "program": filters['program']
- })
+ match_cond=get_match_cond(doctype), start=start, page_len=page_len
+ ),
+ {"txt": "%{0}%".format(txt), "_txt": txt.replace("%", ""), "program": filters["program"]},
+ )
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
@@ -161,14 +194,19 @@ def get_students(doctype, txt, searchfield, start, page_len, filters):
if not filters.get("academic_year"):
filters["academic_year"] = frappe.defaults.get_defaults().academic_year
- enrolled_students = frappe.get_list("Program Enrollment", filters={
- "academic_term": filters.get('academic_term'),
- "academic_year": filters.get('academic_year')
- }, fields=["student"])
+ enrolled_students = frappe.get_list(
+ "Program Enrollment",
+ filters={
+ "academic_term": filters.get("academic_term"),
+ "academic_year": filters.get("academic_year"),
+ },
+ fields=["student"],
+ )
students = [d.student for d in enrolled_students] if enrolled_students else [""]
- return frappe.db.sql("""select
+ return frappe.db.sql(
+ """select
name, title from tabStudent
where
name not in (%s)
@@ -176,8 +214,7 @@ def get_students(doctype, txt, searchfield, start, page_len, filters):
`%s` LIKE %s
order by
idx desc, name
- limit %s, %s"""%(
- ", ".join(['%s']*len(students)), searchfield, "%s", "%s", "%s"),
- tuple(students + ["%%%s%%" % txt, start, page_len]
- )
+ limit %s, %s"""
+ % (", ".join(["%s"] * len(students)), searchfield, "%s", "%s", "%s"),
+ tuple(students + ["%%%s%%" % txt, start, page_len]),
)
diff --git a/erpnext/education/doctype/program_enrollment/program_enrollment_dashboard.py b/erpnext/education/doctype/program_enrollment/program_enrollment_dashboard.py
index 14ed95d48cd..c308961d8cd 100644
--- a/erpnext/education/doctype/program_enrollment/program_enrollment_dashboard.py
+++ b/erpnext/education/doctype/program_enrollment/program_enrollment_dashboard.py
@@ -3,17 +3,7 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'program_enrollment',
- 'transactions': [
- {
- 'label': _('Course and Fee'),
- 'items': ['Course Enrollment', 'Fees']
- }
- ],
- 'reports': [
- {
- 'label': _('Report'),
- 'items': ['Student and Guardian Contact Details']
- }
- ]
+ "fieldname": "program_enrollment",
+ "transactions": [{"label": _("Course and Fee"), "items": ["Course Enrollment", "Fees"]}],
+ "reports": [{"label": _("Report"), "items": ["Student and Guardian Contact Details"]}],
}
diff --git a/erpnext/education/doctype/program_enrollment/test_program_enrollment.py b/erpnext/education/doctype/program_enrollment/test_program_enrollment.py
index dda2465eafd..4d43175fef0 100644
--- a/erpnext/education/doctype/program_enrollment/test_program_enrollment.py
+++ b/erpnext/education/doctype/program_enrollment/test_program_enrollment.py
@@ -10,9 +10,14 @@ from erpnext.education.doctype.student.test_student import create_student, get_s
class TestProgramEnrollment(unittest.TestCase):
-
def setUp(self):
- create_student({"first_name": "_Test Name", "last_name": "_Test Last Name", "email": "_test_student@example.com"})
+ create_student(
+ {
+ "first_name": "_Test Name",
+ "last_name": "_Test Last Name",
+ "email": "_test_student@example.com",
+ }
+ )
make_program_and_linked_courses("_Test Program 1", ["_Test Course 1", "_Test Course 2"])
def test_create_course_enrollments(self):
diff --git a/erpnext/education/doctype/program_enrollment_tool/program_enrollment_tool.py b/erpnext/education/doctype/program_enrollment_tool/program_enrollment_tool.py
index 7ffa077534a..9e72876f194 100644
--- a/erpnext/education/doctype/program_enrollment_tool/program_enrollment_tool.py
+++ b/erpnext/education/doctype/program_enrollment_tool/program_enrollment_tool.py
@@ -12,7 +12,7 @@ from erpnext.education.api import enroll_student
class ProgramEnrollmentTool(Document):
def onload(self):
- academic_term_reqd = cint(frappe.db.get_single_value('Education Settings', 'academic_term_reqd'))
+ academic_term_reqd = cint(frappe.db.get_single_value("Education Settings", "academic_term_reqd"))
self.set_onload("academic_term_reqd", academic_term_reqd)
@frappe.whitelist()
@@ -25,22 +25,36 @@ class ProgramEnrollmentTool(Document):
elif not self.academic_year:
frappe.throw(_("Mandatory field - Academic Year"))
else:
- condition = 'and academic_term=%(academic_term)s' if self.academic_term else " "
+ condition = "and academic_term=%(academic_term)s" if self.academic_term else " "
if self.get_students_from == "Student Applicant":
- students = frappe.db.sql('''select name as student_applicant, title as student_name from `tabStudent Applicant`
- where application_status="Approved" and program=%(program)s and academic_year=%(academic_year)s {0}'''
- .format(condition), self.as_dict(), as_dict=1)
+ students = frappe.db.sql(
+ """select name as student_applicant, title as student_name from `tabStudent Applicant`
+ where application_status="Approved" and program=%(program)s and academic_year=%(academic_year)s {0}""".format(
+ condition
+ ),
+ self.as_dict(),
+ as_dict=1,
+ )
elif self.get_students_from == "Program Enrollment":
- condition2 = 'and student_batch_name=%(student_batch)s' if self.student_batch else " "
- students = frappe.db.sql('''select student, student_name, student_batch_name, student_category from `tabProgram Enrollment`
- where program=%(program)s and academic_year=%(academic_year)s {0} {1} and docstatus != 2'''
- .format(condition, condition2), self.as_dict(), as_dict=1)
+ condition2 = "and student_batch_name=%(student_batch)s" if self.student_batch else " "
+ students = frappe.db.sql(
+ """select student, student_name, student_batch_name, student_category from `tabProgram Enrollment`
+ where program=%(program)s and academic_year=%(academic_year)s {0} {1} and docstatus != 2""".format(
+ condition, condition2
+ ),
+ self.as_dict(),
+ as_dict=1,
+ )
student_list = [d.student for d in students]
if student_list:
- inactive_students = frappe.db.sql('''
- select name as student, title as student_name from `tabStudent` where name in (%s) and enabled = 0''' %
- ', '.join(['%s']*len(student_list)), tuple(student_list), as_dict=1)
+ inactive_students = frappe.db.sql(
+ """
+ select name as student, title as student_name from `tabStudent` where name in (%s) and enabled = 0"""
+ % ", ".join(["%s"] * len(student_list)),
+ tuple(student_list),
+ as_dict=1,
+ )
for student in students:
if student.student in [d.student for d in inactive_students]:
@@ -55,7 +69,9 @@ class ProgramEnrollmentTool(Document):
def enroll_students(self):
total = len(self.students)
for i, stud in enumerate(self.students):
- frappe.publish_realtime("program_enrollment_tool", dict(progress=[i+1, total]), user=frappe.session.user)
+ frappe.publish_realtime(
+ "program_enrollment_tool", dict(progress=[i + 1, total]), user=frappe.session.user
+ )
if stud.student:
prog_enrollment = frappe.new_doc("Program Enrollment")
prog_enrollment.student = stud.student
@@ -64,12 +80,16 @@ class ProgramEnrollmentTool(Document):
prog_enrollment.program = self.new_program
prog_enrollment.academic_year = self.new_academic_year
prog_enrollment.academic_term = self.new_academic_term
- prog_enrollment.student_batch_name = stud.student_batch_name if stud.student_batch_name else self.new_student_batch
+ prog_enrollment.student_batch_name = (
+ stud.student_batch_name if stud.student_batch_name else self.new_student_batch
+ )
prog_enrollment.save()
elif stud.student_applicant:
prog_enrollment = enroll_student(stud.student_applicant)
prog_enrollment.academic_year = self.academic_year
prog_enrollment.academic_term = self.academic_term
- prog_enrollment.student_batch_name = stud.student_batch_name if stud.student_batch_name else self.new_student_batch
+ prog_enrollment.student_batch_name = (
+ stud.student_batch_name if stud.student_batch_name else self.new_student_batch
+ )
prog_enrollment.save()
frappe.msgprint(_("{0} Students have been enrolled").format(total))
diff --git a/erpnext/education/doctype/question/question.py b/erpnext/education/doctype/question/question.py
index aa6cf9f38d0..99910b3c90f 100644
--- a/erpnext/education/doctype/question/question.py
+++ b/erpnext/education/doctype/question/question.py
@@ -8,7 +8,6 @@ from frappe.model.document import Document
class Question(Document):
-
def validate(self):
self.check_at_least_one_option()
self.check_minimum_one_correct_answer()
diff --git a/erpnext/education/doctype/quiz/quiz.py b/erpnext/education/doctype/quiz/quiz.py
index 9ad7252db62..7a9cdf3d9ae 100644
--- a/erpnext/education/doctype/quiz/quiz.py
+++ b/erpnext/education/doctype/quiz/quiz.py
@@ -13,11 +13,14 @@ class Quiz(Document):
frappe.throw(_("Passing Score value should be between 0 and 100"))
def allowed_attempt(self, enrollment, quiz_name):
- if self.max_attempts == 0:
+ if self.max_attempts == 0:
return True
try:
- if len(frappe.get_all("Quiz Activity", {'enrollment': enrollment.name, 'quiz': quiz_name})) >= self.max_attempts:
+ if (
+ len(frappe.get_all("Quiz Activity", {"enrollment": enrollment.name, "quiz": quiz_name}))
+ >= self.max_attempts
+ ):
frappe.msgprint(_("Maximum attempts for this quiz reached!"))
return False
else:
@@ -25,30 +28,29 @@ class Quiz(Document):
except Exception as e:
return False
-
def evaluate(self, response_dict, quiz_name):
- questions = [frappe.get_doc('Question', question.question_link) for question in self.question]
- answers = {q.name:q.get_answer() for q in questions}
+ questions = [frappe.get_doc("Question", question.question_link) for question in self.question]
+ answers = {q.name: q.get_answer() for q in questions}
result = {}
for key in answers:
try:
if isinstance(response_dict[key], list):
is_correct = compare_list_elementwise(response_dict[key], answers[key])
else:
- is_correct = (response_dict[key] == answers[key])
+ is_correct = response_dict[key] == answers[key]
except Exception as e:
is_correct = False
result[key] = is_correct
- score = (sum(result.values()) * 100 ) / len(answers)
+ score = (sum(result.values()) * 100) / len(answers)
if score >= self.passing_score:
status = "Pass"
else:
status = "Fail"
return result, score, status
-
def get_questions(self):
- return [frappe.get_doc('Question', question.question_link) for question in self.question]
+ return [frappe.get_doc("Question", question.question_link) for question in self.question]
+
def compare_list_elementwise(*args):
try:
@@ -59,11 +61,12 @@ def compare_list_elementwise(*args):
except TypeError:
frappe.throw(_("Compare List function takes on list arguments"))
+
@frappe.whitelist()
def get_topics_without_quiz(quiz):
data = []
- for entry in frappe.db.get_all('Topic'):
- topic = frappe.get_doc('Topic', entry.name)
+ for entry in frappe.db.get_all("Topic"):
+ topic = frappe.get_doc("Topic", entry.name)
topic_contents = [tc.content for tc in topic.topic_content]
if not topic_contents or quiz not in topic_contents:
data.append(topic.name)
diff --git a/erpnext/education/doctype/room/room_dashboard.py b/erpnext/education/doctype/room/room_dashboard.py
index b710722f55d..dfc295cc44f 100644
--- a/erpnext/education/doctype/room/room_dashboard.py
+++ b/erpnext/education/doctype/room/room_dashboard.py
@@ -6,15 +6,9 @@ from frappe import _
def get_data():
return {
- 'fieldname': 'room',
- 'transactions': [
- {
- 'label': _('Course'),
- 'items': ['Course Schedule']
- },
- {
- 'label': _('Assessment'),
- 'items': ['Assessment Plan']
- }
- ]
+ "fieldname": "room",
+ "transactions": [
+ {"label": _("Course"), "items": ["Course Schedule"]},
+ {"label": _("Assessment"), "items": ["Assessment Plan"]},
+ ],
}
diff --git a/erpnext/education/doctype/room/test_room.py b/erpnext/education/doctype/room/test_room.py
index 68c97c7a008..ea0baf2137c 100644
--- a/erpnext/education/doctype/room/test_room.py
+++ b/erpnext/education/doctype/room/test_room.py
@@ -5,5 +5,6 @@ import unittest
# test_records = frappe.get_test_records('Room')
+
class TestRoom(unittest.TestCase):
pass
diff --git a/erpnext/education/doctype/student/student.py b/erpnext/education/doctype/student/student.py
index 44a327777bf..712d742dee9 100644
--- a/erpnext/education/doctype/student/student.py
+++ b/erpnext/education/doctype/student/student.py
@@ -26,7 +26,9 @@ class Student(Document):
def validate_dates(self):
for sibling in self.siblings:
if sibling.date_of_birth and getdate(sibling.date_of_birth) > getdate():
- frappe.throw(_("Row {0}:Sibling Date of Birth cannot be greater than today.").format(sibling.idx))
+ frappe.throw(
+ _("Row {0}:Sibling Date of Birth cannot be greater than today.").format(sibling.idx)
+ )
if self.date_of_birth and getdate(self.date_of_birth) >= getdate(today()):
frappe.throw(_("Date of Birth cannot be greater than today."))
@@ -34,7 +36,11 @@ class Student(Document):
if self.date_of_birth and getdate(self.date_of_birth) >= getdate(self.joining_date):
frappe.throw(_("Date of Birth cannot be greater than Joining Date."))
- if self.joining_date and self.date_of_leaving and getdate(self.joining_date) > getdate(self.date_of_leaving):
+ if (
+ self.joining_date
+ and self.date_of_leaving
+ and getdate(self.joining_date) > getdate(self.date_of_leaving)
+ ):
frappe.throw(_("Joining Date can not be greater than Leaving Date"))
def update_student_name_in_linked_doctype(self):
@@ -43,36 +49,54 @@ class Student(Document):
meta = frappe.get_meta(d)
if not meta.issingle:
if "student_name" in [f.fieldname for f in meta.fields]:
- frappe.db.sql("""UPDATE `tab{0}` set student_name = %s where {1} = %s"""
- .format(d, linked_doctypes[d]["fieldname"][0]),(self.title, self.name))
+ frappe.db.sql(
+ """UPDATE `tab{0}` set student_name = %s where {1} = %s""".format(
+ d, linked_doctypes[d]["fieldname"][0]
+ ),
+ (self.title, self.name),
+ )
- if "child_doctype" in linked_doctypes[d].keys() and "student_name" in \
- [f.fieldname for f in frappe.get_meta(linked_doctypes[d]["child_doctype"]).fields]:
- frappe.db.sql("""UPDATE `tab{0}` set student_name = %s where {1} = %s"""
- .format(linked_doctypes[d]["child_doctype"], linked_doctypes[d]["fieldname"][0]),(self.title, self.name))
+ if "child_doctype" in linked_doctypes[d].keys() and "student_name" in [
+ f.fieldname for f in frappe.get_meta(linked_doctypes[d]["child_doctype"]).fields
+ ]:
+ frappe.db.sql(
+ """UPDATE `tab{0}` set student_name = %s where {1} = %s""".format(
+ linked_doctypes[d]["child_doctype"], linked_doctypes[d]["fieldname"][0]
+ ),
+ (self.title, self.name),
+ )
def check_unique(self):
"""Validates if the Student Applicant is Unique"""
- student = frappe.db.sql("select name from `tabStudent` where student_applicant=%s and name!=%s", (self.student_applicant, self.name))
+ student = frappe.db.sql(
+ "select name from `tabStudent` where student_applicant=%s and name!=%s",
+ (self.student_applicant, self.name),
+ )
if student:
- frappe.throw(_("Student {0} exist against student applicant {1}").format(student[0][0], self.student_applicant))
+ frappe.throw(
+ _("Student {0} exist against student applicant {1}").format(
+ student[0][0], self.student_applicant
+ )
+ )
def after_insert(self):
- if not frappe.get_single('Education Settings').get('user_creation_skip'):
+ if not frappe.get_single("Education Settings").get("user_creation_skip"):
self.create_student_user()
def create_student_user(self):
"""Create a website user for student creation if not already exists"""
if not frappe.db.exists("User", self.student_email_id):
- student_user = frappe.get_doc({
- 'doctype':'User',
- 'first_name': self.first_name,
- 'last_name': self.last_name,
- 'email': self.student_email_id,
- 'gender': self.gender,
- 'send_welcome_email': 1,
- 'user_type': 'Website User'
- })
+ student_user = frappe.get_doc(
+ {
+ "doctype": "User",
+ "first_name": self.first_name,
+ "last_name": self.last_name,
+ "email": self.student_email_id,
+ "gender": self.gender,
+ "send_welcome_email": 1,
+ "user_type": "Website User",
+ }
+ )
student_user.flags.ignore_permissions = True
student_user.add_roles("Student")
student_user.save()
@@ -80,57 +104,77 @@ class Student(Document):
def update_applicant_status(self):
"""Updates Student Applicant status to Admitted"""
if self.student_applicant:
- frappe.db.set_value("Student Applicant", self.student_applicant, "application_status", "Admitted")
+ frappe.db.set_value(
+ "Student Applicant", self.student_applicant, "application_status", "Admitted"
+ )
def get_all_course_enrollments(self):
"""Returns a list of course enrollments linked with the current student"""
- course_enrollments = frappe.get_all("Course Enrollment", filters={"student": self.name}, fields=['course', 'name'])
+ course_enrollments = frappe.get_all(
+ "Course Enrollment", filters={"student": self.name}, fields=["course", "name"]
+ )
if not course_enrollments:
return None
else:
- enrollments = {item['course']:item['name'] for item in course_enrollments}
+ enrollments = {item["course"]: item["name"] for item in course_enrollments}
return enrollments
def get_program_enrollments(self):
"""Returns a list of course enrollments linked with the current student"""
- program_enrollments = frappe.get_all("Program Enrollment", filters={"student": self.name}, fields=['program'])
+ program_enrollments = frappe.get_all(
+ "Program Enrollment", filters={"student": self.name}, fields=["program"]
+ )
if not program_enrollments:
return None
else:
- enrollments = [item['program'] for item in program_enrollments]
+ enrollments = [item["program"] for item in program_enrollments]
return enrollments
def get_topic_progress(self, course_enrollment_name, topic):
"""
Get Progress Dictionary of a student for a particular topic
- :param self: Student Object
- :param course_enrollment_name: Name of the Course Enrollment
- :param topic: Topic DocType Object
+ :param self: Student Object
+ :param course_enrollment_name: Name of the Course Enrollment
+ :param topic: Topic DocType Object
"""
contents = topic.get_contents()
progress = []
if contents:
for content in contents:
- if content.doctype in ('Article', 'Video'):
+ if content.doctype in ("Article", "Video"):
status = check_content_completion(content.name, content.doctype, course_enrollment_name)
- progress.append({'content': content.name, 'content_type': content.doctype, 'is_complete': status})
- elif content.doctype == 'Quiz':
+ progress.append(
+ {"content": content.name, "content_type": content.doctype, "is_complete": status}
+ )
+ elif content.doctype == "Quiz":
status, score, result, time_taken = check_quiz_completion(content, course_enrollment_name)
- progress.append({'content': content.name, 'content_type': content.doctype, 'is_complete': status, 'score': score, 'result': result})
+ progress.append(
+ {
+ "content": content.name,
+ "content_type": content.doctype,
+ "is_complete": status,
+ "score": score,
+ "result": result,
+ }
+ )
return progress
def enroll_in_program(self, program_name):
try:
- enrollment = frappe.get_doc({
+ enrollment = frappe.get_doc(
+ {
"doctype": "Program Enrollment",
"student": self.name,
"academic_year": frappe.get_last_doc("Academic Year").name,
"program": program_name,
- "enrollment_date": frappe.utils.datetime.datetime.now()
- })
+ "enrollment_date": frappe.utils.datetime.datetime.now(),
+ }
+ )
enrollment.save(ignore_permissions=True)
except frappe.exceptions.ValidationError:
- enrollment_name = frappe.get_list("Program Enrollment", filters={"student": self.name, "Program": program_name})[0].name
+ enrollment_name = frappe.get_list(
+ "Program Enrollment", filters={"student": self.name, "Program": program_name}
+ )[0].name
return frappe.get_doc("Program Enrollment", enrollment_name)
else:
enrollment.submit()
@@ -140,25 +184,40 @@ class Student(Document):
if enrollment_date is None:
enrollment_date = frappe.utils.datetime.datetime.now()
try:
- enrollment = frappe.get_doc({
+ enrollment = frappe.get_doc(
+ {
"doctype": "Course Enrollment",
"student": self.name,
"course": course_name,
"program_enrollment": program_enrollment,
- "enrollment_date": enrollment_date
- })
+ "enrollment_date": enrollment_date,
+ }
+ )
enrollment.save(ignore_permissions=True)
except frappe.exceptions.ValidationError:
- enrollment_name = frappe.get_list("Course Enrollment", filters={"student": self.name, "course": course_name, "program_enrollment": program_enrollment})[0].name
+ enrollment_name = frappe.get_list(
+ "Course Enrollment",
+ filters={
+ "student": self.name,
+ "course": course_name,
+ "program_enrollment": program_enrollment,
+ },
+ )[0].name
return frappe.get_doc("Course Enrollment", enrollment_name)
else:
return enrollment
+
def get_timeline_data(doctype, name):
- '''Return timeline for attendance'''
- return dict(frappe.db.sql('''select unix_timestamp(`date`), count(*)
+ """Return timeline for attendance"""
+ return dict(
+ frappe.db.sql(
+ """select unix_timestamp(`date`), count(*)
from `tabStudent Attendance` where
student=%s
and `date` > date_sub(curdate(), interval 1 year)
and docstatus = 1 and status = 'Present'
- group by date''', name))
+ group by date""",
+ name,
+ )
+ )
diff --git a/erpnext/education/doctype/student/student_dashboard.py b/erpnext/education/doctype/student/student_dashboard.py
index 3ae772dd907..bcc85217380 100644
--- a/erpnext/education/doctype/student/student_dashboard.py
+++ b/erpnext/education/doctype/student/student_dashboard.py
@@ -3,36 +3,22 @@ from frappe import _
def get_data():
return {
- 'heatmap': True,
- 'heatmap_message': _('This is based on the attendance of this Student'),
- 'fieldname': 'student',
- 'non_standard_fieldnames': {
- 'Bank Account': 'party'
- },
- 'transactions': [
+ "heatmap": True,
+ "heatmap_message": _("This is based on the attendance of this Student"),
+ "fieldname": "student",
+ "non_standard_fieldnames": {"Bank Account": "party"},
+ "transactions": [
+ {"label": _("Admission"), "items": ["Program Enrollment", "Course Enrollment"]},
{
- 'label': _('Admission'),
- 'items': ['Program Enrollment', 'Course Enrollment']
+ "label": _("Student Activity"),
+ "items": [
+ "Student Log",
+ "Student Group",
+ ],
},
- {
- 'label': _('Student Activity'),
- 'items': ['Student Log', 'Student Group', ]
- },
- {
- 'label': _('Assessment'),
- 'items': ['Assessment Result']
- },
- {
- 'label': _('Student LMS Activity'),
- 'items': ['Course Activity', 'Quiz Activity' ]
- },
- {
- 'label': _('Attendance'),
- 'items': ['Student Attendance', 'Student Leave Application']
- },
- {
- 'label': _('Fee'),
- 'items': ['Fees', 'Bank Account']
- }
- ]
+ {"label": _("Assessment"), "items": ["Assessment Result"]},
+ {"label": _("Student LMS Activity"), "items": ["Course Activity", "Quiz Activity"]},
+ {"label": _("Attendance"), "items": ["Student Attendance", "Student Leave Application"]},
+ {"label": _("Fee"), "items": ["Fees", "Bank Account"]},
+ ],
}
diff --git a/erpnext/education/doctype/student/test_student.py b/erpnext/education/doctype/student/test_student.py
index 0a85708152c..89449b0299c 100644
--- a/erpnext/education/doctype/student/test_student.py
+++ b/erpnext/education/doctype/student/test_student.py
@@ -7,10 +7,18 @@ import frappe
from erpnext.education.doctype.program.test_program import make_program_and_linked_courses
-test_records = frappe.get_test_records('Student')
+test_records = frappe.get_test_records("Student")
+
+
class TestStudent(unittest.TestCase):
def setUp(self):
- create_student({"first_name": "_Test Name", "last_name": "_Test Last Name", "email": "_test_student@example.com"})
+ create_student(
+ {
+ "first_name": "_Test Name",
+ "last_name": "_Test Last Name",
+ "email": "_test_student@example.com",
+ }
+ )
make_program_and_linked_courses("_Test Program 1", ["_Test Course 1", "_Test Course 2"])
def test_create_student_user(self):
@@ -20,9 +28,11 @@ class TestStudent(unittest.TestCase):
def test_enroll_in_program(self):
student = get_student("_test_student@example.com")
enrollment = student.enroll_in_program("_Test Program 1")
- test_enrollment = frappe.get_all("Program Enrollment", filters={"student": student.name, "Program": "_Test Program 1"})
+ test_enrollment = frappe.get_all(
+ "Program Enrollment", filters={"student": student.name, "Program": "_Test Program 1"}
+ )
self.assertTrue(len(test_enrollment))
- self.assertEqual(test_enrollment[0]['name'], enrollment.name)
+ self.assertEqual(test_enrollment[0]["name"], enrollment.name)
frappe.db.rollback()
def test_get_program_enrollments(self):
@@ -51,16 +61,19 @@ class TestStudent(unittest.TestCase):
def create_student(student_dict):
- student = get_student(student_dict['email'])
+ student = get_student(student_dict["email"])
if not student:
- student = frappe.get_doc({
- "doctype": "Student",
- "first_name": student_dict['first_name'],
- "last_name": student_dict['last_name'],
- "student_email_id": student_dict['email']
- }).insert()
+ student = frappe.get_doc(
+ {
+ "doctype": "Student",
+ "first_name": student_dict["first_name"],
+ "last_name": student_dict["last_name"],
+ "student_email_id": student_dict["email"],
+ }
+ ).insert()
return student
+
def get_student(email):
try:
student_id = frappe.get_all("Student", {"student_email_id": email}, ["name"])[0].name
diff --git a/erpnext/education/doctype/student_admission/student_admission.py b/erpnext/education/doctype/student_admission/student_admission.py
index b1fd780d8cc..6c775f0dffc 100644
--- a/erpnext/education/doctype/student_admission/student_admission.py
+++ b/erpnext/education/doctype/student_admission/student_admission.py
@@ -15,7 +15,7 @@ class StudentAdmission(WebsiteGenerator):
self.name = self.title
def validate(self):
- if not self.route: #pylint: disable=E0203
+ if not self.route: # pylint: disable=E0203
self.route = "admissions/" + "-".join(self.title.split(" "))
if self.enable_admission_application and not self.program_details:
@@ -25,22 +25,35 @@ class StudentAdmission(WebsiteGenerator):
context.no_cache = 1
context.show_sidebar = True
context.title = self.title
- context.parents = [{'name': 'admissions', 'title': _('All Student Admissions'), 'route': 'admissions' }]
+ context.parents = [
+ {"name": "admissions", "title": _("All Student Admissions"), "route": "admissions"}
+ ]
def get_title(self):
return _("Admissions for {0}").format(self.academic_year)
def get_list_context(context=None):
- context.update({
- "show_sidebar": True,
- "title": _("Student Admissions"),
- "get_list": get_admission_list,
- "row_template": "education/doctype/student_admission/templates/student_admission_row.html",
- })
+ context.update(
+ {
+ "show_sidebar": True,
+ "title": _("Student Admissions"),
+ "get_list": get_admission_list,
+ "row_template": "education/doctype/student_admission/templates/student_admission_row.html",
+ }
+ )
-def get_admission_list(doctype, txt, filters, limit_start, limit_page_length=20, order_by="modified"):
- return frappe.db.sql('''select name, title, academic_year, modified, admission_start_date, route,
+
+def get_admission_list(
+ doctype, txt, filters, limit_start, limit_page_length=20, order_by="modified"
+):
+ return frappe.db.sql(
+ """select name, title, academic_year, modified, admission_start_date, route,
admission_end_date from `tabStudent Admission` where published=1 and admission_end_date >= %s
order by admission_end_date asc limit {0}, {1}
- '''.format(limit_start, limit_page_length), [nowdate()], as_dict=1)
+ """.format(
+ limit_start, limit_page_length
+ ),
+ [nowdate()],
+ as_dict=1,
+ )
diff --git a/erpnext/education/doctype/student_admission/templates/student_admission_row.html b/erpnext/education/doctype/student_admission/templates/student_admission_row.html
index 529d65184a8..dc4587bc940 100644
--- a/erpnext/education/doctype/student_admission/templates/student_admission_row.html
+++ b/erpnext/education/doctype/student_admission/templates/student_admission_row.html
@@ -1,6 +1,6 @@
|