Merge branch 'develop' into fix_print_bold

This commit is contained in:
Nihantra C. Patel
2024-05-16 14:11:31 +05:30
committed by GitHub
1727 changed files with 366956 additions and 102511 deletions

View File

@@ -9,6 +9,13 @@ trim_trailing_whitespace = true
charset = utf-8 charset = utf-8
# python, js indentation settings # python, js indentation settings
[{*.py,*.js}] [{*.py,*.js,*.vue,*.css,*.scss,*.html}]
indent_style = tab indent_style = tab
indent_size = 4 indent_size = 4
max_line_length = 110
# JSON files - mostly doctype schema files
[{*.json}]
insert_final_newline = false
indent_style = space
indent_size = 2

View File

@@ -124,6 +124,7 @@
"beforeEach": true, "beforeEach": true,
"onScan": true, "onScan": true,
"extend_cscript": true, "extend_cscript": true,
"localforage": true "localforage": true,
"Plaid": true
} }
} }

View File

@@ -32,3 +32,9 @@ baec607ff5905b1c67531096a9cf50ec7ff00a5d
# bulk refactor with sourcery # bulk refactor with sourcery
eb9ee3f79b94e594fc6dfa4f6514580e125eee8c eb9ee3f79b94e594fc6dfa4f6514580e125eee8c
# js formatting
ec74a5e56617bbd76ac402451468fd4668af543d
# ruff formatting
a308792ee7fda18a681e9181f4fd00b36385bc23

View File

@@ -1,7 +1,7 @@
import sys import sys
import requests
from urllib.parse import urlparse from urllib.parse import urlparse
import requests
WEBSITE_REPOS = [ WEBSITE_REPOS = [
"erpnext_com", "erpnext_com",
@@ -36,11 +36,7 @@ def is_documentation_link(word: str) -> bool:
def contains_documentation_link(body: str) -> bool: def contains_documentation_link(body: str) -> bool:
return any( return any(is_documentation_link(word) for line in body.splitlines() for word in line.split())
is_documentation_link(word)
for line in body.splitlines()
for word in line.split()
)
def check_pull_request(number: str) -> "tuple[int, str]": def check_pull_request(number: str) -> "tuple[int, str]":
@@ -53,12 +49,7 @@ def check_pull_request(number: str) -> "tuple[int, str]":
head_sha = (payload.get("head") or {}).get("sha") head_sha = (payload.get("head") or {}).get("sha")
body = (payload.get("body") or "").lower() body = (payload.get("body") or "").lower()
if ( if not title.startswith("feat") or not head_sha or "no-docs" in body or "backport" in body:
not title.startswith("feat")
or not head_sha
or "no-docs" in body
or "backport" in body
):
return 0, "Skipping documentation checks... 🏃" return 0, "Skipping documentation checks... 🏃"
if contains_documentation_link(body): if contains_documentation_link(body):

View File

@@ -2,7 +2,9 @@ import re
import sys import sys
errors_encounter = 0 errors_encounter = 0
pattern = re.compile(r"_\(([\"']{,3})(?P<message>((?!\1).)*)\1(\s*,\s*context\s*=\s*([\"'])(?P<py_context>((?!\5).)*)\5)*(\s*,(\s*?.*?\n*?)*(,\s*([\"'])(?P<js_context>((?!\11).)*)\11)*)*\)") pattern = re.compile(
r"_\(([\"']{,3})(?P<message>((?!\1).)*)\1(\s*,\s*context\s*=\s*([\"'])(?P<py_context>((?!\5).)*)\5)*(\s*,(\s*?.*?\n*?)*(,\s*([\"'])(?P<js_context>((?!\11).)*)\11)*)*\)"
)
words_pattern = re.compile(r"_{1,2}\([\"'`]{1,3}.*?[a-zA-Z]") words_pattern = re.compile(r"_{1,2}\([\"'`]{1,3}.*?[a-zA-Z]")
start_pattern = re.compile(r"_{1,2}\([f\"'`]{1,3}") start_pattern = re.compile(r"_{1,2}\([f\"'`]{1,3}")
f_string_pattern = re.compile(r"_\(f[\"']") f_string_pattern = re.compile(r"_\(f[\"']")
@@ -10,14 +12,14 @@ starts_with_f_pattern = re.compile(r"_\(f")
# skip first argument # skip first argument
files = sys.argv[1:] files = sys.argv[1:]
files_to_scan = [_file for _file in files if _file.endswith(('.py', '.js'))] files_to_scan = [_file for _file in files if _file.endswith((".py", ".js"))]
for _file in files_to_scan: for _file in files_to_scan:
with open(_file, 'r') as f: with open(_file) as f:
print(f'Checking: {_file}') print(f"Checking: {_file}")
file_lines = f.readlines() file_lines = f.readlines()
for line_number, line in enumerate(file_lines, 1): for line_number, line in enumerate(file_lines, 1):
if 'frappe-lint: disable-translate' in line: if "frappe-lint: disable-translate" in line:
continue continue
start_matches = start_pattern.search(line) start_matches = start_pattern.search(line)
@@ -28,7 +30,9 @@ for _file in files_to_scan:
has_f_string = f_string_pattern.search(line) has_f_string = f_string_pattern.search(line)
if has_f_string: if has_f_string:
errors_encounter += 1 errors_encounter += 1
print(f'\nF-strings are not supported for translations at line number {line_number}\n{line.strip()[:100]}') print(
f"\nF-strings are not supported for translations at line number {line_number}\n{line.strip()[:100]}"
)
continue continue
else: else:
continue continue
@@ -36,25 +40,29 @@ for _file in files_to_scan:
match = pattern.search(line) match = pattern.search(line)
error_found = False error_found = False
if not match and line.endswith((',\n', '[\n')): if not match and line.endswith((",\n", "[\n")):
# concat remaining text to validate multiline pattern # concat remaining text to validate multiline pattern
line = "".join(file_lines[line_number - 1:]) line = "".join(file_lines[line_number - 1 :])
line = line[start_matches.start() + 1:] line = line[start_matches.start() + 1 :]
match = pattern.match(line) match = pattern.match(line)
if not match: if not match:
error_found = True error_found = True
print(f'\nTranslation syntax error at line number {line_number}\n{line.strip()[:100]}') print(f"\nTranslation syntax error at line number {line_number}\n{line.strip()[:100]}")
if not error_found and not words_pattern.search(line): if not error_found and not words_pattern.search(line):
error_found = True error_found = True
print(f'\nTranslation is useless because it has no words at line number {line_number}\n{line.strip()[:100]}') print(
f"\nTranslation is useless because it has no words at line number {line_number}\n{line.strip()[:100]}"
)
if error_found: if error_found:
errors_encounter += 1 errors_encounter += 1
if errors_encounter > 0: if errors_encounter > 0:
print('\nVisit "https://frappeframework.com/docs/user/en/translations" to learn about valid translation strings.') print(
'\nVisit "https://frappeframework.com/docs/user/en/translations" to learn about valid translation strings.'
)
sys.exit(1) sys.exit(1)
else: else:
print('\nGood To Go!') print("\nGood To Go!")

40
.github/helper/update_pot_file.sh vendored Normal file
View File

@@ -0,0 +1,40 @@
#!/bin/bash
set -e
cd ~ || exit
echo "Setting Up Bench..."
pip install frappe-bench
bench -v init frappe-bench --skip-assets --skip-redis-config-generation --python "$(which python)"
cd ./frappe-bench || exit
echo "Get ERPNext..."
bench get-app --skip-assets erpnext "${GITHUB_WORKSPACE}"
echo "Generating POT file..."
bench generate-pot-file --app erpnext
cd ./apps/erpnext || exit
echo "Configuring git user..."
git config user.email "developers@erpnext.com"
git config user.name "frappe-pr-bot"
echo "Setting the correct git remote..."
# Here, the git remote is a local file path by default. Let's change it to the upstream repo.
git remote set-url upstream https://github.com/frappe/erpnext.git
echo "Creating a new branch..."
isodate=$(date -u +"%Y-%m-%d")
branch_name="pot_${BASE_BRANCH}_${isodate}"
git checkout -b "${branch_name}"
echo "Commiting changes..."
git add .
git commit -m "chore: update POT file"
gh auth setup-git
git push -u upstream "${branch_name}"
echo "Creating a PR..."
gh pr create --fill --base "${BASE_BRANCH}" --head "${branch_name}" -R frappe/erpnext

38
.github/workflows/generate-pot-file.yml vendored Normal file
View File

@@ -0,0 +1,38 @@
# This workflow is agnostic to branches. Only maintain on develop branch.
# To add/remove branches just modify the matrix.
name: Regenerate POT file (translatable strings)
on:
schedule:
# 9:30 UTC => 3 PM IST Sunday
- cron: "30 9 * * 0"
workflow_dispatch:
jobs:
regeneratee-pot-file:
name: Release
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
branch: ["develop"]
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ matrix.branch }}
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Run script to update POT file
run: |
bash ${GITHUB_WORKSPACE}/.github/helper/update_pot_file.sh
env:
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
BASE_BRANCH: ${{ matrix.branch }}

View File

@@ -32,7 +32,7 @@ jobs:
steps: steps:
- name: Clone - name: Clone
uses: actions/checkout@v2 uses: actions/checkout@v4
- name: Check for valid Python & Merge Conflicts - name: Check for valid Python & Merge Conflicts
run: | run: |
@@ -43,12 +43,12 @@ jobs:
fi fi
- name: Setup Python - name: Setup Python
uses: "actions/setup-python@v4" uses: actions/setup-python@v5
with: with:
python-version: '3.10' python-version: '3.11'
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v2 uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 18
check-latest: true check-latest: true
@@ -57,7 +57,7 @@ jobs:
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
- name: Cache pip - name: Cache pip
uses: actions/cache@v2 uses: actions/cache@v4
with: with:
path: ~/.cache/pip path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }} key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }}
@@ -66,7 +66,7 @@ jobs:
${{ runner.os }}- ${{ runner.os }}-
- name: Cache node modules - name: Cache node modules
uses: actions/cache@v2 uses: actions/cache@v4
env: env:
cache-name: cache-node-modules cache-name: cache-node-modules
with: with:
@@ -81,7 +81,7 @@ jobs:
id: yarn-cache-dir-path id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)" run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2 - uses: actions/cache@v4
id: yarn-cache id: yarn-cache
with: with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }} path: ${{ steps.yarn-cache-dir-path.outputs.dir }}

View File

@@ -29,7 +29,11 @@ jobs:
steps: steps:
- name: Update notes - name: Update notes
run: | run: |
NEW_NOTES=$(gh api --method POST -H "Accept: application/vnd.github+json" /repos/frappe/erpnext/releases/generate-notes -f tag_name=$RELEASE_TAG | jq -r '.body' | sed -E '/^\* (chore|ci|test|docs|style)/d' ) NEW_NOTES=$(gh api --method POST -H "Accept: application/vnd.github+json" /repos/frappe/erpnext/releases/generate-notes -f tag_name=$RELEASE_TAG \
| jq -r '.body' \
| sed -E '/^\* (chore|ci|test|docs|style)/d' \
| sed -E 's/by @mergify //'
)
RELEASE_ID=$(gh api -H "Accept: application/vnd.github+json" /repos/frappe/erpnext/releases/tags/$RELEASE_TAG | jq -r '.id') RELEASE_ID=$(gh api -H "Accept: application/vnd.github+json" /repos/frappe/erpnext/releases/tags/$RELEASE_TAG | jq -r '.id')
gh api --method PATCH -H "Accept: application/vnd.github+json" /repos/frappe/erpnext/releases/$RELEASE_ID -f body="$NEW_NOTES" gh api --method PATCH -H "Accept: application/vnd.github+json" /repos/frappe/erpnext/releases/$RELEASE_ID -f body="$NEW_NOTES"

View File

@@ -54,12 +54,12 @@ jobs:
steps: steps:
- name: Clone - name: Clone
uses: actions/checkout@v2 uses: actions/checkout@v4
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v2 uses: actions/setup-python@v5
with: with:
python-version: '3.11' python-version: '3.12'
- name: Check for valid Python & Merge Conflicts - name: Check for valid Python & Merge Conflicts
run: | run: |
@@ -70,7 +70,7 @@ jobs:
fi fi
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v2 uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 18
check-latest: true check-latest: true
@@ -79,7 +79,7 @@ jobs:
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
- name: Cache pip - name: Cache pip
uses: actions/cache@v2 uses: actions/cache@v4
with: with:
path: ~/.cache/pip path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }} key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }}
@@ -88,7 +88,7 @@ jobs:
${{ runner.os }}- ${{ runner.os }}-
- name: Cache node modules - name: Cache node modules
uses: actions/cache@v2 uses: actions/cache@v4
env: env:
cache-name: cache-node-modules cache-name: cache-node-modules
with: with:
@@ -103,7 +103,7 @@ jobs:
id: yarn-cache-dir-path id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)" run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2 - uses: actions/cache@v4
id: yarn-cache id: yarn-cache
with: with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }} path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
@@ -144,13 +144,13 @@ jobs:
if: ${{ github.event_name != 'pull_request' }} if: ${{ github.event_name != 'pull_request' }}
steps: steps:
- name: Clone - name: Clone
uses: actions/checkout@v2 uses: actions/checkout@v4
- name: Download artifacts - name: Download artifacts
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
- name: Upload coverage data - name: Upload coverage data
uses: codecov/codecov-action@v2 uses: codecov/codecov-action@v4
with: with:
name: MariaDB name: MariaDB
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -41,12 +41,12 @@ jobs:
steps: steps:
- name: Clone - name: Clone
uses: actions/checkout@v2 uses: actions/checkout@v4
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v2 uses: actions/setup-python@v5
with: with:
python-version: '3.10' python-version: '3.12'
- name: Check for valid Python & Merge Conflicts - name: Check for valid Python & Merge Conflicts
run: | run: |
@@ -57,7 +57,7 @@ jobs:
fi fi
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v2 uses: actions/setup-node@v4
with: with:
node-version: 18 node-version: 18
check-latest: true check-latest: true
@@ -66,7 +66,7 @@ jobs:
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
- name: Cache pip - name: Cache pip
uses: actions/cache@v2 uses: actions/cache@v4
with: with:
path: ~/.cache/pip path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }} key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }}
@@ -75,7 +75,7 @@ jobs:
${{ runner.os }}- ${{ runner.os }}-
- name: Cache node modules - name: Cache node modules
uses: actions/cache@v2 uses: actions/cache@v4
env: env:
cache-name: cache-node-modules cache-name: cache-node-modules
with: with:
@@ -90,7 +90,7 @@ jobs:
id: yarn-cache-dir-path id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)" run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2 - uses: actions/cache@v4
id: yarn-cache id: yarn-cache
with: with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }} path: ${{ steps.yarn-cache-dir-path.outputs.dir }}

1
.gitignore vendored
View File

@@ -2,7 +2,6 @@
*.py~ *.py~
.DS_Store .DS_Store
conf.py conf.py
locale
latest_updates.json latest_updates.json
.wnf-lang-status .wnf-lang-status
*.egg-info *.egg-info

View File

@@ -20,6 +20,23 @@ repos:
- id: check-yaml - id: check-yaml
- id: debug-statements - id: debug-statements
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v2.7.1
hooks:
- id: prettier
types_or: [javascript, vue, scss]
# Ignore any files that might contain jinja / bundles
exclude: |
(?x)^(
erpnext/public/dist/.*|
cypress/.*|
.*node_modules.*|
.*boilerplate.*|
erpnext/public/js/controllers/.*|
erpnext/templates/pages/order.js|
erpnext/templates/includes/.*
)$
- repo: https://github.com/pre-commit/mirrors-eslint - repo: https://github.com/pre-commit/mirrors-eslint
rev: v8.44.0 rev: v8.44.0
hooks: hooks:
@@ -38,28 +55,15 @@ repos:
erpnext/templates/includes/.* erpnext/templates/includes/.*
)$ )$
- repo: https://github.com/PyCQA/flake8 - repo: https://github.com/astral-sh/ruff-pre-commit
rev: 6.0.0 rev: v0.2.0
hooks: hooks:
- id: flake8 - id: ruff
additional_dependencies: [ name: "Run ruff linter and apply fixes"
'flake8-bugbear', args: ["--fix"]
'flake8-tuple',
]
args: ['--config', '.github/helper/.flake8_strict']
exclude: ".*setup.py$"
- repo: https://github.com/adityahase/black - id: ruff-format
rev: 9cb0a69f4d0030cdf687eddf314468b39ed54119 name: "Format Python code"
hooks:
- id: black
additional_dependencies: ['click==8.0.4']
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:
- id: isort
exclude: ".*setup.py$"
ci: ci:

1
babel_extractors.csv Normal file
View File

@@ -0,0 +1 @@
**/setup/setup_wizard/data/uom_data.json,erpnext.gettext.extractors.uom_data.extract
1 **/setup/setup_wizard/data/uom_data.json erpnext.gettext.extractors.uom_data.extract

View File

@@ -1,25 +1,13 @@
module.exports = { module.exports = {
parserPreset: 'conventional-changelog-conventionalcommits', parserPreset: "conventional-changelog-conventionalcommits",
rules: { rules: {
'subject-empty': [2, 'never'], "subject-empty": [2, "never"],
'type-case': [2, 'always', 'lower-case'], "type-case": [2, "always", "lower-case"],
'type-empty': [2, 'never'], "type-empty": [2, "never"],
'type-enum': [ "type-enum": [
2, 2,
'always', "always",
[ ["build", "chore", "ci", "docs", "feat", "fix", "perf", "refactor", "revert", "style", "test"],
'build',
'chore',
'ci',
'docs',
'feat',
'fix',
'perf',
'refactor',
'revert',
'style',
'test',
],
], ],
}, },
}; };

View File

@@ -1,3 +1,8 @@
files: files:
- source: /erpnext/locale/main.pot - source: /erpnext/locale/main.pot
translation: /erpnext/locale/%two_letters_code%.po translation: /erpnext/locale/%two_letters_code%.po
pull_request_title: "fix: sync translations from crowdin"
pull_request_labels:
- translation
commit_message: "fix: %language% translations"
append_commit_message: false

View File

@@ -13,7 +13,7 @@ def get_default_company(user=None):
if not user: if not user:
user = frappe.session.user user = frappe.session.user
companies = get_user_default_as_list(user, "company") companies = get_user_default_as_list("company", user)
if companies: if companies:
default_company = companies[0] default_company = companies[0]
else: else:
@@ -37,9 +37,7 @@ def get_default_cost_center(company):
if not frappe.flags.company_cost_center: if not frappe.flags.company_cost_center:
frappe.flags.company_cost_center = {} frappe.flags.company_cost_center = {}
if company not in frappe.flags.company_cost_center: if company not in frappe.flags.company_cost_center:
frappe.flags.company_cost_center[company] = frappe.get_cached_value( frappe.flags.company_cost_center[company] = frappe.get_cached_value("Company", company, "cost_center")
"Company", company, "cost_center"
)
return frappe.flags.company_cost_center[company] return frappe.flags.company_cost_center[company]

View File

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

View File

@@ -1,4 +1,4 @@
frappe.provide('frappe.dashboards.chart_sources'); frappe.provide("frappe.dashboards.chart_sources");
frappe.dashboards.chart_sources["Account Balance Timeline"] = { frappe.dashboards.chart_sources["Account Balance Timeline"] = {
method: "erpnext.accounts.dashboard_chart_source.account_balance_timeline.account_balance_timeline.get", method: "erpnext.accounts.dashboard_chart_source.account_balance_timeline.account_balance_timeline.get",
@@ -9,14 +9,14 @@ frappe.dashboards.chart_sources["Account Balance Timeline"] = {
fieldtype: "Link", fieldtype: "Link",
options: "Company", options: "Company",
default: frappe.defaults.get_user_default("Company"), default: frappe.defaults.get_user_default("Company"),
reqd: 1 reqd: 1,
}, },
{ {
fieldname: "account", fieldname: "account",
label: __("Account"), label: __("Account"),
fieldtype: "Link", fieldtype: "Link",
options: "Account", options: "Account",
reqd: 1 reqd: 1,
}, },
] ],
}; };

View File

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

View File

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

View File

@@ -26,19 +26,14 @@ frappe.ui.form.on("Account", {
frm.toggle_enable(["is_group", "company"], false); frm.toggle_enable(["is_group", "company"], false);
if (cint(frm.doc.is_group) == 0) { if (cint(frm.doc.is_group) == 0) {
frm.toggle_display( frm.toggle_display("freeze_account", frm.doc.__onload && frm.doc.__onload.can_freeze_account);
"freeze_account",
frm.doc.__onload && frm.doc.__onload.can_freeze_account
);
} }
// read-only for root accounts // read-only for root accounts
if (!frm.is_new()) { if (!frm.is_new()) {
if (!frm.doc.parent_account) { if (!frm.doc.parent_account) {
frm.set_read_only(); frm.set_read_only();
frm.set_intro( frm.set_intro(__("This is a root account and cannot be edited."));
__("This is a root account and cannot be edited.")
);
} else { } else {
// credit days and type if customer or supplier // credit days and type if customer or supplier
frm.set_intro(null); frm.set_intro(null);
@@ -80,27 +75,33 @@ frappe.ui.form.on("Account", {
); );
if (frm.doc.is_group == 1) { if (frm.doc.is_group == 1) {
frm.add_custom_button(__('Convert to Non-Group'), function () { frm.add_custom_button(
return frappe.call({ __("Convert to Non-Group"),
doc: frm.doc, function () {
method: 'convert_group_to_ledger', return frappe.call({
callback: function() { doc: frm.doc,
frm.refresh(); method: "convert_group_to_ledger",
} callback: function () {
}); frm.refresh();
}, __('Actions')); },
});
} else if (cint(frm.doc.is_group) == 0 },
&& frappe.boot.user.can_read.indexOf("GL Entry") !== -1) { __("Actions")
frm.add_custom_button(__('General Ledger'), function () { );
frappe.route_options = { } else if (cint(frm.doc.is_group) == 0 && frappe.boot.user.can_read.indexOf("GL Entry") !== -1) {
"account": frm.doc.name, frm.add_custom_button(
"from_date": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1], __("General Ledger"),
"to_date": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2], function () {
"company": frm.doc.company frappe.route_options = {
}; account: frm.doc.name,
frappe.set_route("query-report", "General Ledger"); from_date: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
}, __('View')); to_date: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
company: frm.doc.company,
};
frappe.set_route("query-report", "General Ledger");
},
__("View")
);
frm.add_custom_button( frm.add_custom_button(
__("Convert to Group"), __("Convert to Group"),
@@ -193,14 +194,8 @@ frappe.ui.form.on("Account", {
if (r.message) { if (r.message) {
frappe.set_route("Form", "Account", r.message); frappe.set_route("Form", "Account", r.message);
} else { } else {
frm.set_value( frm.set_value("account_number", data.account_number);
"account_number", frm.set_value("account_name", data.account_name);
data.account_number
);
frm.set_value(
"account_name",
data.account_name
);
} }
d.hide(); d.hide();
} }

View File

@@ -65,6 +65,8 @@
"label": "Is Group" "label": "Is Group"
}, },
{ {
"fetch_from": "parent_account.company",
"fetch_if_empty": 1,
"fieldname": "company", "fieldname": "company",
"fieldtype": "Link", "fieldtype": "Link",
"in_standard_filter": 1, "in_standard_filter": 1,
@@ -193,7 +195,7 @@
"idx": 1, "idx": 1,
"is_tree": 1, "is_tree": 1,
"links": [], "links": [],
"modified": "2024-01-10 04:57:33.681676", "modified": "2024-03-27 13:05:55.866034",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Account", "name": "Account",
@@ -251,8 +253,8 @@
"search_fields": "account_number", "search_fields": "account_number",
"show_name_in_global_search": 1, "show_name_in_global_search": 1,
"show_preview_popup": 1, "show_preview_popup": 1,
"sort_field": "modified", "sort_field": "creation",
"sort_order": "ASC", "sort_order": "ASC",
"states": [], "states": [],
"track_changes": 1 "track_changes": 1
} }

View File

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

View File

@@ -1,4 +1,4 @@
frappe.provide("frappe.treeview_settings") frappe.provide("frappe.treeview_settings");
frappe.treeview_settings["Account"] = { frappe.treeview_settings["Account"] = {
breadcrumb: "Accounts", breadcrumb: "Accounts",
@@ -7,12 +7,12 @@ frappe.treeview_settings["Account"] = {
filters: [ filters: [
{ {
fieldname: "company", fieldname: "company",
fieldtype:"Select", fieldtype: "Select",
options: erpnext.utils.get_tree_options("company"), options: erpnext.utils.get_tree_options("company"),
label: __("Company"), label: __("Company"),
default: erpnext.utils.get_tree_default("company"), default: erpnext.utils.get_tree_default("company"),
on_change: function() { on_change: function () {
var me = frappe.treeview_settings['Account'].treeview; var me = frappe.treeview_settings["Account"].treeview;
var company = me.page.fields_dict.company.get_value(); var company = me.page.fields_dict.company.get_value();
if (!company) { if (!company) {
frappe.throw(__("Please set a Company")); frappe.throw(__("Please set a Company"));
@@ -22,30 +22,36 @@ frappe.treeview_settings["Account"] = {
args: { args: {
company: company, company: company,
}, },
callback: function(r) { callback: function (r) {
if(r.message) { if (r.message) {
let root_company = r.message.length ? r.message[0] : ""; let root_company = r.message.length ? r.message[0] : "";
me.page.fields_dict.root_company.set_value(root_company); me.page.fields_dict.root_company.set_value(root_company);
frappe.db.get_value("Company", {"name": company}, "allow_account_creation_against_child_company", (r) => { frappe.db.get_value(
frappe.flags.ignore_root_company_validation = r.allow_account_creation_against_child_company; "Company",
}); { name: company },
"allow_account_creation_against_child_company",
(r) => {
frappe.flags.ignore_root_company_validation =
r.allow_account_creation_against_child_company;
}
);
} }
} },
}); });
} },
}, },
{ {
fieldname: "root_company", fieldname: "root_company",
fieldtype:"Data", fieldtype: "Data",
label: __("Root Company"), label: __("Root Company"),
hidden: true, hidden: true,
disable_onchange: true disable_onchange: true,
} },
], ],
root_label: "Accounts", root_label: "Accounts",
get_tree_nodes: 'erpnext.accounts.utils.get_children', get_tree_nodes: "erpnext.accounts.utils.get_children",
on_get_node: function(nodes, deep=false) { on_get_node: function (nodes, deep = false) {
if (frappe.boot.user.can_read.indexOf("GL Entry") == -1) return; if (frappe.boot.user.can_read.indexOf("GL Entry") == -1) return;
let accounts = []; let accounts = [];
@@ -57,151 +63,231 @@ frappe.treeview_settings["Account"] = {
} }
frappe.db.get_single_value("Accounts Settings", "show_balance_in_coa").then((value) => { frappe.db.get_single_value("Accounts Settings", "show_balance_in_coa").then((value) => {
if(value) { if (value) {
const get_balances = frappe.call({ const get_balances = frappe.call({
method: 'erpnext.accounts.utils.get_account_balances', method: "erpnext.accounts.utils.get_account_balances",
args: { args: {
accounts: accounts, accounts: accounts,
company: cur_tree.args.company company: cur_tree.args.company,
}, },
}); });
get_balances.then(r => { get_balances.then((r) => {
if (!r.message || r.message.length == 0) return; if (!r.message || r.message.length == 0) return;
for (let account of r.message) { for (let account of r.message) {
const node = cur_tree.nodes && cur_tree.nodes[account.value]; const node = cur_tree.nodes && cur_tree.nodes[account.value];
if (!node || node.is_root) continue; if (!node || node.is_root) continue;
// show Dr if positive since balance is calculated as debit - credit else show Cr // show Dr if positive since balance is calculated as debit - credit else show Cr
const balance = account.balance_in_account_currency || account.balance; const balance = account.balance_in_account_currency || account.balance;
const dr_or_cr = balance > 0 ? __("Dr"): __("Cr"); const dr_or_cr = balance > 0 ? __("Dr") : __("Cr");
const format = (value, currency) => format_currency(Math.abs(value), currency); const format = (value, currency) => format_currency(Math.abs(value), currency);
if (account.balance!==undefined) { if (account.balance !== undefined) {
node.parent && node.parent.find('.balance-area').remove(); node.parent && node.parent.find(".balance-area").remove();
$('<span class="balance-area pull-right">' $(
+ (account.balance_in_account_currency ? '<span class="balance-area pull-right">' +
(format(account.balance_in_account_currency, account.account_currency) + " / ") : "") (account.balance_in_account_currency
+ format(account.balance, account.company_currency) ? format(
+ " " + dr_or_cr account.balance_in_account_currency,
+ '</span>').insertBefore(node.$ul); account.account_currency
) + " / "
: "") +
format(account.balance, account.company_currency) +
" " +
dr_or_cr +
"</span>"
).insertBefore(node.$ul);
} }
} }
}); });
} }
}); });
}, },
add_tree_node: 'erpnext.accounts.utils.add_ac', add_tree_node: "erpnext.accounts.utils.add_ac",
menu_items:[ menu_items: [
{ {
label: __('New Company'), label: __("New Company"),
action: function() { frappe.new_doc("Company", true) }, action: function () {
condition: 'frappe.boot.user.can_create.indexOf("Company") !== -1' frappe.new_doc("Company", true);
} },
condition: 'frappe.boot.user.can_create.indexOf("Company") !== -1',
},
], ],
fields: [ fields: [
{fieldtype:'Data', fieldname:'account_name', label:__('New Account Name'), reqd:true, {
description: __("Name of new Account. Note: Please don't create accounts for Customers and Suppliers")}, fieldtype: "Data",
{fieldtype:'Data', fieldname:'account_number', label:__('Account Number'), fieldname: "account_name",
description: __("Number of new Account, it will be included in the account name as a prefix")}, label: __("New Account Name"),
{fieldtype:'Check', fieldname:'is_group', label:__('Is Group'), reqd: true,
description: __('Further accounts can be made under Groups, but entries can be made against non-Groups')}, description: __(
{fieldtype:'Select', fieldname:'root_type', label:__('Root Type'), "Name of new Account. Note: Please don't create accounts for Customers and Suppliers"
options: ['Asset', 'Liability', 'Equity', 'Income', 'Expense'].join('\n'), ),
depends_on: 'eval:doc.is_group && !doc.parent_account'}, },
{fieldtype:'Select', fieldname:'account_type', label:__('Account Type'), {
options: frappe.get_meta("Account").fields.filter(d => d.fieldname=='account_type')[0].options, fieldtype: "Data",
description: __("Optional. This setting will be used to filter in various transactions.") fieldname: "account_number",
label: __("Account Number"),
description: __("Number of new Account, it will be included in the account name as a prefix"),
},
{
fieldtype: "Check",
fieldname: "is_group",
label: __("Is Group"),
description: __(
"Further accounts can be made under Groups, but entries can be made against non-Groups"
),
},
{
fieldtype: "Select",
fieldname: "root_type",
label: __("Root Type"),
options: ["Asset", "Liability", "Equity", "Income", "Expense"].join("\n"),
depends_on: "eval:doc.is_group && !doc.parent_account",
},
{
fieldtype: "Select",
fieldname: "account_type",
label: __("Account Type"),
options: frappe.get_meta("Account").fields.filter((d) => d.fieldname == "account_type")[0]
.options,
description: __("Optional. This setting will be used to filter in various transactions."),
},
{
fieldtype: "Float",
fieldname: "tax_rate",
label: __("Tax Rate"),
depends_on: 'eval:doc.is_group==0&&doc.account_type=="Tax"',
},
{
fieldtype: "Link",
fieldname: "account_currency",
label: __("Currency"),
options: "Currency",
description: __("Optional. Sets company's default currency, if not specified."),
}, },
{fieldtype:'Float', fieldname:'tax_rate', label:__('Tax Rate'),
depends_on: 'eval:doc.is_group==0&&doc.account_type=="Tax"'},
{fieldtype:'Link', fieldname:'account_currency', label:__('Currency'), options:"Currency",
description: __("Optional. Sets company's default currency, if not specified.")}
], ],
ignore_fields:["parent_account"], ignore_fields: ["parent_account"],
onload: function(treeview) { onload: function (treeview) {
frappe.treeview_settings['Account'].treeview = {}; frappe.treeview_settings["Account"].treeview = {};
$.extend(frappe.treeview_settings['Account'].treeview, treeview); $.extend(frappe.treeview_settings["Account"].treeview, treeview);
function get_company() { function get_company() {
return treeview.page.fields_dict.company.get_value(); return treeview.page.fields_dict.company.get_value();
} }
// tools // tools
treeview.page.add_inner_button(__("Chart of Cost Centers"), function() { treeview.page.add_inner_button(
frappe.set_route('Tree', 'Cost Center', {company: get_company()}); __("Chart of Cost Centers"),
}, __('View')); function () {
frappe.set_route("Tree", "Cost Center", { company: get_company() });
},
__("View")
);
treeview.page.add_inner_button(__("Opening Invoice Creation Tool"), function() { treeview.page.add_inner_button(
frappe.set_route('Form', 'Opening Invoice Creation Tool', {company: get_company()}); __("Opening Invoice Creation Tool"),
}, __('View')); function () {
frappe.set_route("Form", "Opening Invoice Creation Tool", { company: get_company() });
},
__("View")
);
treeview.page.add_inner_button(__("Period Closing Voucher"), function() { treeview.page.add_inner_button(
frappe.set_route('List', 'Period Closing Voucher', {company: get_company()}); __("Period Closing Voucher"),
}, __('View')); function () {
frappe.set_route("List", "Period Closing Voucher", { company: get_company() });
},
__("View")
);
treeview.page.add_inner_button(
treeview.page.add_inner_button(__("Journal Entry"), function() { __("Journal Entry"),
frappe.new_doc('Journal Entry', {company: get_company()}); function () {
}, __('Create')); frappe.new_doc("Journal Entry", { company: get_company() });
treeview.page.add_inner_button(__("Company"), function() { },
frappe.new_doc('Company'); __("Create")
}, __('Create')); );
treeview.page.add_inner_button(
__("Company"),
function () {
frappe.new_doc("Company");
},
__("Create")
);
// financial statements // financial statements
for (let report of ['Trial Balance', 'General Ledger', 'Balance Sheet', for (let report of [
'Profit and Loss Statement', 'Cash Flow Statement', 'Accounts Payable', 'Accounts Receivable']) { "Trial Balance",
treeview.page.add_inner_button(__(report), function() { "General Ledger",
frappe.set_route('query-report', report, {company: get_company()}); "Balance Sheet",
}, __('Financial Statements')); "Profit and Loss Statement",
"Cash Flow Statement",
"Accounts Payable",
"Accounts Receivable",
]) {
treeview.page.add_inner_button(
__(report),
function () {
frappe.set_route("query-report", report, { company: get_company() });
},
__("Financial Statements")
);
} }
}, },
post_render: function(treeview) { post_render: function (treeview) {
frappe.treeview_settings['Account'].treeview["tree"] = treeview.tree; frappe.treeview_settings["Account"].treeview["tree"] = treeview.tree;
treeview.page.set_primary_action(__("New"), function() { treeview.page.set_primary_action(
let root_company = treeview.page.fields_dict.root_company.get_value(); __("New"),
function () {
let root_company = treeview.page.fields_dict.root_company.get_value();
if(root_company) { if (root_company) {
frappe.throw(__("Please add the account to root level Company - {0}"), [root_company]); frappe.throw(__("Please add the account to root level Company - {0}"), [root_company]);
} else { } else {
treeview.new_node(); treeview.new_node();
} }
}, "add"); },
"add"
);
}, },
toolbar: [ toolbar: [
{ {
label:__("Add Child"), label: __("Add Child"),
condition: function(node) { condition: function (node) {
return frappe.boot.user.can_create.indexOf("Account") !== -1 return (
&& (!frappe.treeview_settings['Account'].treeview.page.fields_dict.root_company.get_value() frappe.boot.user.can_create.indexOf("Account") !== -1 &&
|| frappe.flags.ignore_root_company_validation) (!frappe.treeview_settings[
&& node.expandable && !node.hide_add; "Account"
].treeview.page.fields_dict.root_company.get_value() ||
frappe.flags.ignore_root_company_validation) &&
node.expandable &&
!node.hide_add
);
}, },
click: function() { click: function () {
var me = frappe.views.trees['Account']; var me = frappe.views.trees["Account"];
me.new_node(); me.new_node();
}, },
btnClass: "hidden-xs" btnClass: "hidden-xs",
}, },
{ {
condition: function(node) { condition: function (node) {
return !node.root && frappe.boot.user.can_read.indexOf("GL Entry") !== -1 return !node.root && frappe.boot.user.can_read.indexOf("GL Entry") !== -1;
}, },
label: __("View Ledger"), label: __("View Ledger"),
click: function(node, btn) { click: function (node, btn) {
frappe.route_options = { frappe.route_options = {
"account": node.label, account: node.label,
"from_date": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1], from_date: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
"to_date": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2], to_date: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
"company": frappe.treeview_settings['Account'].treeview.page.fields_dict.company.get_value() company:
frappe.treeview_settings["Account"].treeview.page.fields_dict.company.get_value(),
}; };
frappe.set_route("query-report", "General Ledger"); frappe.set_route("query-report", "General Ledger");
}, },
btnClass: "hidden-xs" btnClass: "hidden-xs",
} },
], ],
extend_toolbar: true extend_toolbar: true,
} };

View File

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

View File

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

View File

@@ -129,7 +129,7 @@
"icon": "fa fa-list", "icon": "fa fa-list",
"in_create": 1, "in_create": 1,
"links": [], "links": [],
"modified": "2023-03-06 08:56:36.393237", "modified": "2024-03-27 13:05:56.710541",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Account Closing Balance", "name": "Account Closing Balance",
@@ -158,7 +158,7 @@
"role": "Auditor" "role": "Auditor"
} }
], ],
"sort_field": "modified", "sort_field": "creation",
"sort_order": "DESC", "sort_order": "DESC",
"states": [] "states": []
} }

View File

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

View File

@@ -1,74 +1,89 @@
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Accounting Dimension', { frappe.ui.form.on("Accounting Dimension", {
refresh: function(frm) { refresh: function (frm) {
frm.set_query('document_type', () => { frm.set_query("document_type", () => {
let invalid_doctypes = frappe.model.core_doctypes_list; let invalid_doctypes = frappe.model.core_doctypes_list;
invalid_doctypes.push('Accounting Dimension', 'Project', invalid_doctypes.push(
'Cost Center', 'Accounting Dimension Detail', 'Company'); "Accounting Dimension",
"Project",
"Cost Center",
"Accounting Dimension Detail",
"Company"
);
return { return {
filters: { filters: {
name: ['not in', invalid_doctypes] name: ["not in", invalid_doctypes],
} },
}; };
}); });
frm.set_query("offsetting_account", "dimension_defaults", function(doc, cdt, cdn) { frm.set_query("offsetting_account", "dimension_defaults", function (doc, cdt, cdn) {
let d = locals[cdt][cdn]; let d = locals[cdt][cdn];
return { return {
filters: { filters: {
company: d.company, company: d.company,
root_type: ["in", ["Asset", "Liability"]], root_type: ["in", ["Asset", "Liability"]],
is_group: 0 is_group: 0,
} },
} };
}); });
if (!frm.is_new()) { if (!frm.is_new()) {
frm.add_custom_button(__('Show {0}', [frm.doc.document_type]), function () { frm.add_custom_button(__("Show {0}", [frm.doc.document_type]), function () {
frappe.set_route("List", frm.doc.document_type); frappe.set_route("List", frm.doc.document_type);
}); });
let button = frm.doc.disabled ? "Enable" : "Disable"; let button = frm.doc.disabled ? "Enable" : "Disable";
frm.add_custom_button(__(button), function() { frm.add_custom_button(__(button), function () {
frm.set_value("disabled", 1 - frm.doc.disabled);
frm.set_value('disabled', 1 - frm.doc.disabled);
frappe.call({ frappe.call({
method: "erpnext.accounts.doctype.accounting_dimension.accounting_dimension.disable_dimension", method: "erpnext.accounts.doctype.accounting_dimension.accounting_dimension.disable_dimension",
args: { args: {
doc: frm.doc doc: frm.doc,
}, },
freeze: true, freeze: true,
callback: function(r) { callback: function (r) {
let message = frm.doc.disabled ? "Dimension Disabled" : "Dimension Enabled"; let message = frm.doc.disabled ? "Dimension Disabled" : "Dimension Enabled";
frm.save(); frm.save();
frappe.show_alert({message:__(message), indicator:'green'}); frappe.show_alert({ message: __(message), indicator: "green" });
} },
}); });
}); });
} }
}, },
document_type: function(frm) { label: function (frm) {
frm.set_value("fieldname", frappe.model.scrub(frm.doc.label));
},
frm.set_value('label', frm.doc.document_type); document_type: function (frm) {
frm.set_value('fieldname', frappe.model.scrub(frm.doc.document_type)); frm.set_value("label", frm.doc.document_type);
frappe.db.get_value('Accounting Dimension', {'document_type': frm.doc.document_type}, 'document_type', (r) => { frappe.db.get_value(
if (r && r.document_type) { "Accounting Dimension",
frm.set_df_property('document_type', 'description', "Document type is already set as dimension"); { document_type: frm.doc.document_type },
"document_type",
(r) => {
if (r && r.document_type) {
frm.set_df_property(
"document_type",
"description",
"Document type is already set as dimension"
);
}
} }
}); );
}, },
}); });
frappe.ui.form.on('Accounting Dimension Detail', { frappe.ui.form.on("Accounting Dimension Detail", {
dimension_defaults_add: function(frm, cdt, cdn) { dimension_defaults_add: function (frm, cdt, cdn) {
let row = locals[cdt][cdn]; let row = locals[cdt][cdn];
row.reference_document = frm.doc.document_type; row.reference_document = frm.doc.document_type;
} },
}); });

View File

@@ -49,7 +49,7 @@
} }
], ],
"links": [], "links": [],
"modified": "2021-02-08 16:37:53.936656", "modified": "2024-03-27 13:05:56.890002",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounting Dimension", "name": "Accounting Dimension",
@@ -80,7 +80,8 @@
"write": 1 "write": 1
} }
], ],
"sort_field": "modified", "sort_field": "creation",
"sort_order": "ASC", "sort_order": "ASC",
"states": [],
"track_changes": 1 "track_changes": 1
} }

View File

@@ -11,6 +11,10 @@ from frappe.model import core_doctypes_list
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import cstr from frappe.utils import cstr
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
get_allowed_types_from_settings,
)
class AccountingDimension(Document): class AccountingDimension(Document):
# begin: auto-generated types # begin: auto-generated types
@@ -36,7 +40,8 @@ class AccountingDimension(Document):
self.set_fieldname_and_label() self.set_fieldname_and_label()
def validate(self): def validate(self):
if self.document_type in core_doctypes_list + ( if self.document_type in (
*core_doctypes_list,
"Accounting Dimension", "Accounting Dimension",
"Project", "Project",
"Cost Center", "Cost Center",
@@ -44,13 +49,10 @@ class AccountingDimension(Document):
"Company", "Company",
"Account", "Account",
): ):
msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type) msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type)
frappe.throw(msg) frappe.throw(msg)
exists = frappe.db.get_value( exists = frappe.db.get_value("Accounting Dimension", {"document_type": self.document_type}, ["name"])
"Accounting Dimension", {"document_type": self.document_type}, ["name"]
)
if exists and self.is_new(): if exists and self.is_new():
frappe.throw(_("Document Type already used as a dimension")) frappe.throw(_("Document Type already used as a dimension"))
@@ -106,9 +108,9 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None):
doc_count = len(get_accounting_dimensions()) doc_count = len(get_accounting_dimensions())
count = 0 count = 0
repostable_doctypes = get_allowed_types_from_settings()
for doctype in doclist: for doctype in doclist:
if (doc_count + 1) % 2 == 0: if (doc_count + 1) % 2 == 0:
insert_after_field = "dimension_col_break" insert_after_field = "dimension_col_break"
else: else:
@@ -121,6 +123,7 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None):
"options": doc.document_type, "options": doc.document_type,
"insert_after": insert_after_field, "insert_after": insert_after_field,
"owner": "Administrator", "owner": "Administrator",
"allow_on_submit": 1 if doctype in repostable_doctypes else 0,
} }
meta = frappe.get_meta(doctype, cached=False) meta = frappe.get_meta(doctype, cached=False)
@@ -142,7 +145,7 @@ def add_dimension_to_budget_doctype(df, doc):
df.update( df.update(
{ {
"insert_after": "cost_center", "insert_after": "cost_center",
"depends_on": "eval:doc.budget_against == '{0}'".format(doc.document_type), "depends_on": f"eval:doc.budget_against == '{doc.document_type}'",
} }
) )
@@ -176,19 +179,17 @@ def delete_accounting_dimension(doc):
frappe.db.sql( frappe.db.sql(
""" """
DELETE FROM `tabCustom Field` DELETE FROM `tabCustom Field`
WHERE fieldname = %s WHERE fieldname = {}
AND dt IN (%s)""" AND dt IN ({})""".format("%s", ", ".join(["%s"] * len(doclist))), # nosec
% ("%s", ", ".join(["%s"] * len(doclist))), # nosec tuple([doc.fieldname, *doclist]),
tuple([doc.fieldname] + doclist),
) )
frappe.db.sql( frappe.db.sql(
""" """
DELETE FROM `tabProperty Setter` DELETE FROM `tabProperty Setter`
WHERE field_name = %s WHERE field_name = {}
AND doc_type IN (%s)""" AND doc_type IN ({})""".format("%s", ", ".join(["%s"] * len(doclist))), # nosec
% ("%s", ", ".join(["%s"] * len(doclist))), # nosec tuple([doc.fieldname, *doclist]),
tuple([doc.fieldname] + doclist),
) )
budget_against_property = frappe.get_doc("Property Setter", "Budget-budget_against-options") budget_against_property = frappe.get_doc("Property Setter", "Budget-budget_against-options")
@@ -237,7 +238,6 @@ def get_doctypes_with_dimensions():
def get_accounting_dimensions(as_list=True, filters=None): def get_accounting_dimensions(as_list=True, filters=None):
if not filters: if not filters:
filters = {"disabled": 0} filters = {"disabled": 0}
@@ -255,18 +255,19 @@ def get_accounting_dimensions(as_list=True, filters=None):
def get_checks_for_pl_and_bs_accounts(): def get_checks_for_pl_and_bs_accounts():
dimensions = frappe.db.sql( if frappe.flags.accounting_dimensions_details is None:
"""SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs # nosemgrep
FROM `tabAccounting Dimension`p ,`tabAccounting Dimension Detail` c frappe.flags.accounting_dimensions_details = frappe.db.sql(
WHERE p.name = c.parent""", """SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
as_dict=1, FROM `tabAccounting Dimension`p ,`tabAccounting Dimension Detail` c
) WHERE p.name = c.parent""",
as_dict=1,
)
return dimensions return frappe.flags.accounting_dimensions_details
def get_dimension_with_children(doctype, dimensions): def get_dimension_with_children(doctype, dimensions):
if isinstance(dimensions, str): if isinstance(dimensions, str):
dimensions = [dimensions] dimensions = [dimensions]
@@ -274,9 +275,7 @@ def get_dimension_with_children(doctype, dimensions):
for dimension in dimensions: for dimension in dimensions:
lft, rgt = frappe.db.get_value(doctype, dimension, ["lft", "rgt"]) lft, rgt = frappe.db.get_value(doctype, dimension, ["lft", "rgt"])
children = frappe.get_all( children = frappe.get_all(doctype, filters={"lft": [">=", lft], "rgt": ["<=", rgt]}, order_by="lft")
doctype, filters={"lft": [">=", lft], "rgt": ["<=", rgt]}, order_by="lft"
)
all_dimensions += [c.name for c in children] all_dimensions += [c.name for c in children]
return all_dimensions return all_dimensions
@@ -284,14 +283,10 @@ def get_dimension_with_children(doctype, dimensions):
@frappe.whitelist() @frappe.whitelist()
def get_dimensions(with_cost_center_and_project=False): def get_dimensions(with_cost_center_and_project=False):
c = frappe.qb.DocType("Accounting Dimension Detail") c = frappe.qb.DocType("Accounting Dimension Detail")
p = frappe.qb.DocType("Accounting Dimension") p = frappe.qb.DocType("Accounting Dimension")
dimension_filters = ( dimension_filters = (
frappe.qb.from_(p) frappe.qb.from_(p).select(p.label, p.fieldname, p.document_type).where(p.disabled == 0).run(as_dict=1)
.select(p.label, p.fieldname, p.document_type)
.where(p.disabled == 0)
.run(as_dict=1)
) )
default_dimensions = ( default_dimensions = (
frappe.qb.from_(c) frappe.qb.from_(c)

View File

@@ -78,6 +78,8 @@ class TestAccountingDimension(unittest.TestCase):
def tearDown(self): def tearDown(self):
disable_dimension() disable_dimension()
frappe.flags.accounting_dimensions_details = None
frappe.flags.dimension_filter_map = None
def create_dimension(): def create_dimension():

View File

@@ -1,4 +1,5 @@
{ {
"actions": [],
"creation": "2019-07-16 17:53:18.718831", "creation": "2019-07-16 17:53:18.718831",
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
@@ -73,13 +74,15 @@
} }
], ],
"istable": 1, "istable": 1,
"modified": "2019-08-15 11:59:09.389891", "links": [],
"modified": "2024-03-27 13:05:57.056874",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounting Dimension Detail", "name": "Accounting Dimension Detail",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],
"sort_field": "modified", "sort_field": "creation",
"sort_order": "DESC", "sort_order": "DESC",
"states": [],
"track_changes": 1 "track_changes": 1
} }

View File

@@ -1,10 +1,9 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Accounting Dimension Filter', { frappe.ui.form.on("Accounting Dimension Filter", {
refresh: function(frm, cdt, cdn) { refresh: function (frm, cdt, cdn) {
let help_content = let help_content = `<table class="table table-bordered" style="background-color: var(--scrollbar-track-color);">
`<table class="table table-bordered" style="background-color: var(--scrollbar-track-color);">
<tr><td> <tr><td>
<p> <p>
<i class="fa fa-hand-right"></i> <i class="fa fa-hand-right"></i>
@@ -13,77 +12,80 @@ frappe.ui.form.on('Accounting Dimension Filter', {
</td></tr> </td></tr>
</table>`; </table>`;
frm.set_df_property('dimension_filter_help', 'options', help_content); frm.set_df_property("dimension_filter_help", "options", help_content);
}, },
onload: function(frm) { onload: function (frm) {
frm.set_query('applicable_on_account', 'accounts', function() { frm.set_query("applicable_on_account", "accounts", function () {
return { return {
filters: { filters: {
'company': frm.doc.company company: frm.doc.company,
} },
}; };
}); });
frappe.db.get_list('Accounting Dimension', frappe.db.get_list("Accounting Dimension", { fields: ["document_type"] }).then((res) => {
{fields: ['document_type']}).then((res) => { let options = ["Cost Center", "Project"];
let options = ['Cost Center', 'Project'];
res.forEach((dimension) => { res.forEach((dimension) => {
options.push(dimension.document_type); options.push(dimension.document_type);
}); });
frm.set_df_property('accounting_dimension', 'options', options); frm.set_df_property("accounting_dimension", "options", options);
}); });
frm.trigger('setup_filters'); frm.trigger("setup_filters");
}, },
setup_filters: function(frm) { setup_filters: function (frm) {
let filters = {}; let filters = {};
if (frm.doc.accounting_dimension) { if (frm.doc.accounting_dimension) {
frappe.model.with_doctype(frm.doc.accounting_dimension, function() { frappe.model.with_doctype(frm.doc.accounting_dimension, function () {
if (frappe.model.is_tree(frm.doc.accounting_dimension)) { if (frappe.model.is_tree(frm.doc.accounting_dimension)) {
filters['is_group'] = 0; filters["is_group"] = 0;
} }
if (frappe.meta.has_field(frm.doc.accounting_dimension, 'company')) { if (frappe.meta.has_field(frm.doc.accounting_dimension, "company")) {
filters['company'] = frm.doc.company; filters["company"] = frm.doc.company;
} }
frm.set_query('dimension_value', 'dimensions', function() { frm.set_query("dimension_value", "dimensions", function () {
return { return {
filters: filters filters: filters,
}; };
}); });
}); });
} }
}, },
accounting_dimension: function(frm) { accounting_dimension: function (frm) {
frm.clear_table("dimensions"); frm.clear_table("dimensions");
let row = frm.add_child("dimensions"); let row = frm.add_child("dimensions");
row.accounting_dimension = frm.doc.accounting_dimension; row.accounting_dimension = frm.doc.accounting_dimension;
frm.fields_dict["dimensions"].grid.update_docfield_property("dimension_value", "label", frm.doc.accounting_dimension); frm.fields_dict["dimensions"].grid.update_docfield_property(
"dimension_value",
"label",
frm.doc.accounting_dimension
);
frm.refresh_field("dimensions"); frm.refresh_field("dimensions");
frm.trigger('setup_filters'); frm.trigger("setup_filters");
}, },
apply_restriction_on_values: function(frm) { apply_restriction_on_values: function (frm) {
/** If restriction on values is not applied, we should set "allow_or_restrict" to "Restrict" with an empty allowed dimension table. /** If restriction on values is not applied, we should set "allow_or_restrict" to "Restrict" with an empty allowed dimension table.
* Hence it's not "restricted" on any value. * Hence it's not "restricted" on any value.
*/ */
if (!frm.doc.apply_restriction_on_values) { if (!frm.doc.apply_restriction_on_values) {
frm.set_value("allow_or_restrict", "Restrict"); frm.set_value("allow_or_restrict", "Restrict");
frm.clear_table("dimensions"); frm.clear_table("dimensions");
frm.refresh_field("dimensions"); frm.refresh_field("dimensions");
} }
} },
}); });
frappe.ui.form.on('Allowed Dimension', { frappe.ui.form.on("Allowed Dimension", {
dimensions_add: function(frm, cdt, cdn) { dimensions_add: function (frm, cdt, cdn) {
let row = locals[cdt][cdn]; let row = locals[cdt][cdn];
row.accounting_dimension = frm.doc.accounting_dimension; row.accounting_dimension = frm.doc.accounting_dimension;
frm.refresh_field("dimensions"); frm.refresh_field("dimensions");
} },
}); });

View File

@@ -94,7 +94,7 @@
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2023-06-07 14:59:41.869117", "modified": "2024-03-27 13:05:57.199186",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounting Dimension Filter", "name": "Accounting Dimension Filter",
@@ -139,7 +139,7 @@
} }
], ],
"quick_entry": 1, "quick_entry": 1,
"sort_field": "modified", "sort_field": "creation",
"sort_order": "DESC", "sort_order": "DESC",
"states": [], "states": [],
"track_changes": 1 "track_changes": 1

View File

@@ -66,37 +66,39 @@ class AccountingDimensionFilter(Document):
def get_dimension_filter_map(): def get_dimension_filter_map():
filters = frappe.db.sql( if not frappe.flags.get("dimension_filter_map"):
""" filters = frappe.db.sql(
SELECT """
a.applicable_on_account, d.dimension_value, p.accounting_dimension, SELECT
p.allow_or_restrict, a.is_mandatory a.applicable_on_account, d.dimension_value, p.accounting_dimension,
FROM p.allow_or_restrict, a.is_mandatory
`tabApplicable On Account` a, FROM
`tabAccounting Dimension Filter` p `tabApplicable On Account` a,
LEFT JOIN `tabAllowed Dimension` d ON d.parent = p.name `tabAccounting Dimension Filter` p
WHERE LEFT JOIN `tabAllowed Dimension` d ON d.parent = p.name
p.name = a.parent WHERE
AND p.disabled = 0 p.name = a.parent
""", AND p.disabled = 0
as_dict=1, """,
) as_dict=1,
dimension_filter_map = {}
for f in filters:
f.fieldname = scrub(f.accounting_dimension)
build_map(
dimension_filter_map,
f.fieldname,
f.applicable_on_account,
f.dimension_value,
f.allow_or_restrict,
f.is_mandatory,
) )
return dimension_filter_map dimension_filter_map = {}
for f in filters:
f.fieldname = scrub(f.accounting_dimension)
build_map(
dimension_filter_map,
f.fieldname,
f.applicable_on_account,
f.dimension_value,
f.allow_or_restrict,
f.is_mandatory,
)
frappe.flags.dimension_filter_map = dimension_filter_map
return frappe.flags.dimension_filter_map
def build_map(map_object, dimension, account, filter_value, allow_or_restrict, is_mandatory): def build_map(map_object, dimension, account, filter_value, allow_or_restrict, is_mandatory):

View File

@@ -47,6 +47,8 @@ class TestAccountingDimensionFilter(unittest.TestCase):
def tearDown(self): def tearDown(self):
disable_dimension_filter() disable_dimension_filter()
disable_dimension() disable_dimension()
frappe.flags.accounting_dimensions_details = None
frappe.flags.dimension_filter_map = None
for si in self.invoice_list: for si in self.invoice_list:
si.load_from_db() si.load_from_db()
@@ -55,9 +57,7 @@ class TestAccountingDimensionFilter(unittest.TestCase):
def create_accounting_dimension_filter(): def create_accounting_dimension_filter():
if not frappe.db.get_value( if not frappe.db.get_value("Accounting Dimension Filter", {"accounting_dimension": "Cost Center"}):
"Accounting Dimension Filter", {"accounting_dimension": "Cost Center"}
):
frappe.get_doc( frappe.get_doc(
{ {
"doctype": "Accounting Dimension Filter", "doctype": "Accounting Dimension Filter",

View File

@@ -1,30 +1,33 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Accounting Period', { frappe.ui.form.on("Accounting Period", {
onload: function(frm) { onload: function (frm) {
if(frm.doc.closed_documents.length === 0 || (frm.doc.closed_documents.length === 1 && frm.doc.closed_documents[0].document_type == undefined)) { if (
frm.doc.closed_documents.length === 0 ||
(frm.doc.closed_documents.length === 1 && frm.doc.closed_documents[0].document_type == undefined)
) {
frappe.call({ frappe.call({
method: "get_doctypes_for_closing", method: "get_doctypes_for_closing",
doc:frm.doc, doc: frm.doc,
callback: function(r) { callback: function (r) {
if(r.message) { if (r.message) {
cur_frm.clear_table("closed_documents"); cur_frm.clear_table("closed_documents");
r.message.forEach(function(element) { r.message.forEach(function (element) {
var c = frm.add_child("closed_documents"); var c = frm.add_child("closed_documents");
c.document_type = element.document_type; c.document_type = element.document_type;
c.closed = element.closed; c.closed = element.closed;
}); });
refresh_field("closed_documents"); refresh_field("closed_documents");
} }
} },
}); });
} }
frm.set_query("document_type", "closed_documents", () => { frm.set_query("document_type", "closed_documents", () => {
return { return {
query: "erpnext.controllers.queries.get_doctypes_for_closing", query: "erpnext.controllers.queries.get_doctypes_for_closing",
} };
}); });
} },
}); });

View File

@@ -1,317 +1,112 @@
{ {
"allow_copy": 0, "actions": [],
"allow_guest_to_view": 0, "autoname": "field:period_name",
"allow_import": 0, "creation": "2018-04-13 18:50:14.672323",
"allow_rename": 0, "doctype": "DocType",
"autoname": "field:period_name", "editable_grid": 1,
"beta": 0, "engine": "InnoDB",
"creation": "2018-04-13 18:50:14.672323", "field_order": [
"custom": 0, "period_name",
"docstatus": 0, "start_date",
"doctype": "DocType", "end_date",
"document_type": "", "column_break_4",
"editable_grid": 1, "company",
"engine": "InnoDB", "section_break_7",
"closed_documents"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "fieldname": "period_name",
"allow_on_submit": 0, "fieldtype": "Data",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Period Name",
"columns": 0, "reqd": 1,
"fieldname": "period_name", "unique": 1
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Period Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "start_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Start Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "end_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "End Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_4",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Company",
"length": 0,
"no_copy": 0,
"options": "Company",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0, "fieldname": "start_date",
"allow_on_submit": 0, "fieldtype": "Date",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Start Date",
"columns": 0, "reqd": 1
"fieldname": "section_break_7", },
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "end_date",
"allow_on_submit": 0, "fieldtype": "Date",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "End Date",
"columns": 0, "reqd": 1
"fieldname": "closed_documents", },
"fieldtype": "Table", {
"hidden": 0, "fieldname": "column_break_4",
"ignore_user_permissions": 0, "fieldtype": "Column Break"
"ignore_xss_filter": 0, },
"in_filter": 0, {
"in_global_search": 0, "fieldname": "company",
"in_list_view": 0, "fieldtype": "Link",
"in_standard_filter": 0, "in_list_view": 1,
"label": "Closed Documents", "label": "Company",
"length": 0, "options": "Company",
"no_copy": 0, "reqd": 1
"options": "Closed Document", },
"permlevel": 0, {
"precision": "", "fieldname": "section_break_7",
"print_hide": 0, "fieldtype": "Section Break"
"print_hide_if_no_value": 0, },
"read_only": 0, {
"remember_last_selected_value": 0, "fieldname": "closed_documents",
"report_hide": 0, "fieldtype": "Table",
"reqd": 1, "label": "Closed Documents",
"search_index": 0, "options": "Closed Document",
"set_only_once": 0, "reqd": 1
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "links": [],
"hide_heading": 0, "modified": "2024-03-27 13:05:57.388109",
"hide_toolbar": 0, "modified_by": "Administrator",
"idx": 0, "module": "Accounts",
"image_view": 0, "name": "Accounting Period",
"in_create": 0, "owner": "Administrator",
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-08-01 19:14:47.593753",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounting Period",
"name_case": "",
"owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0, "create": 1,
"cancel": 0, "delete": 1,
"create": 1, "email": 1,
"delete": 1, "export": 1,
"email": 1, "print": 1,
"export": 1, "read": 1,
"if_owner": 0, "report": 1,
"import": 0, "role": "System Manager",
"permlevel": 0, "share": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0, "create": 1,
"cancel": 0, "delete": 1,
"create": 1, "email": 1,
"delete": 1, "export": 1,
"email": 1, "print": 1,
"export": 1, "read": 1,
"if_owner": 0, "report": 1,
"import": 0, "role": "Accounts Manager",
"permlevel": 0, "share": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0, "create": 1,
"cancel": 0, "delete": 1,
"create": 1, "email": 1,
"delete": 1, "export": 1,
"email": 1, "print": 1,
"export": 1, "read": 1,
"if_owner": 0, "report": 1,
"import": 0, "role": "Accounts User",
"permlevel": 0, "share": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1 "write": 1
} }
], ],
"quick_entry": 0, "sort_field": "creation",
"read_only": 0, "sort_order": "DESC",
"read_only_onload": 0, "states": [],
"show_name_in_global_search": 0, "track_changes": 1
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
} }

View File

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

View File

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

View File

@@ -1,8 +1,25 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Accounts Settings', { frappe.ui.form.on("Accounts Settings", {
refresh: function(frm) { refresh: function (frm) {},
enable_immutable_ledger: function (frm) {
if (!frm.doc.enable_immutable_ledger) {
return;
}
} let msg = __("Enabling this will change the way how cancelled transactions are handled.");
msg += " ";
msg += __("Please enable only if the understand the effects of enabling this.");
msg += "<br>";
msg += "Do you still want to enable immutable ledger?";
frappe.confirm(
msg,
() => {},
() => {
frm.set_value("enable_immutable_ledger", 0);
}
);
},
}); });

View File

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

View File

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

View File

@@ -1,8 +1,11 @@
frappe.ui.form.on("Accounts Settings", {
frappe.ui.form.on('Accounts Settings', { refresh: function (frm) {
refresh: function(frm) {
frm.set_df_property("acc_frozen_upto", "label", "Books Closed Through"); frm.set_df_property("acc_frozen_upto", "label", "Books Closed Through");
frm.set_df_property("frozen_accounts_modifier", "label", "Role Allowed to Close Books & Make Changes to Closed Periods"); frm.set_df_property(
"frozen_accounts_modifier",
"label",
"Role Allowed to Close Books & Make Changes to Closed Periods"
);
frm.set_df_property("credit_controller", "label", "Credit Manager"); frm.set_df_property("credit_controller", "label", "Credit Manager");
} },
}); });

View File

@@ -45,12 +45,13 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-11-25 10:27:51.712286", "modified": "2024-03-27 13:05:58.308002",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Advance Tax", "name": "Advance Tax",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],
"sort_field": "modified", "sort_field": "creation",
"sort_order": "DESC" "sort_order": "DESC",
"states": []
} }

View File

@@ -179,12 +179,13 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-11-25 11:10:10.945027", "modified": "2024-03-27 13:05:58.437605",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Advance Taxes and Charges", "name": "Advance Taxes and Charges",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],
"sort_field": "modified", "sort_field": "creation",
"sort_order": "ASC" "sort_order": "ASC",
"states": []
} }

View File

@@ -14,30 +14,27 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Accounting Dimension", "label": "Accounting Dimension",
"options": "DocType", "options": "DocType",
"read_only": 1, "read_only": 1
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "dimension_value", "fieldname": "dimension_value",
"fieldtype": "Dynamic Link", "fieldtype": "Dynamic Link",
"in_list_view": 1, "in_list_view": 1,
"options": "accounting_dimension", "options": "accounting_dimension"
"show_days": 1,
"show_seconds": 1
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-11-23 09:56:19.744200", "modified": "2024-03-27 13:05:58.587487",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Allowed Dimension", "name": "Allowed Dimension",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],
"quick_entry": 1, "quick_entry": 1,
"sort_field": "modified", "sort_field": "creation",
"sort_order": "DESC", "sort_order": "DESC",
"states": [],
"track_changes": 1 "track_changes": 1
} }

View File

@@ -20,14 +20,14 @@
], ],
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2024-01-03 11:13:02.669632", "modified": "2024-03-27 13:05:58.698893",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Allowed To Transact With", "name": "Allowed To Transact With",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],
"quick_entry": 1, "quick_entry": 1,
"sort_field": "modified", "sort_field": "creation",
"sort_order": "DESC", "sort_order": "DESC",
"states": [], "states": [],
"track_changes": 1 "track_changes": 1

View File

@@ -15,9 +15,7 @@
"in_list_view": 1, "in_list_view": 1,
"label": "Accounts", "label": "Accounts",
"options": "Account", "options": "Account",
"reqd": 1, "reqd": 1
"show_days": 1,
"show_seconds": 1
}, },
{ {
"columns": 2, "columns": 2,
@@ -25,22 +23,21 @@
"fieldname": "is_mandatory", "fieldname": "is_mandatory",
"fieldtype": "Check", "fieldtype": "Check",
"in_list_view": 1, "in_list_view": 1,
"label": "Is Mandatory", "label": "Is Mandatory"
"show_days": 1,
"show_seconds": 1
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-11-22 19:55:13.324136", "modified": "2024-03-27 13:05:59.168897",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Applicable On Account", "name": "Applicable On Account",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],
"quick_entry": 1, "quick_entry": 1,
"sort_field": "modified", "sort_field": "creation",
"sort_order": "DESC", "sort_order": "DESC",
"states": [],
"track_changes": 1 "track_changes": 1
} }

View File

@@ -1,38 +1,36 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.provide('erpnext.integrations'); frappe.provide("erpnext.integrations");
frappe.ui.form.on('Bank', { frappe.ui.form.on("Bank", {
onload: function(frm) { onload: function (frm) {
add_fields_to_mapping_table(frm); add_fields_to_mapping_table(frm);
}, },
refresh: function(frm) { refresh: function (frm) {
add_fields_to_mapping_table(frm); add_fields_to_mapping_table(frm);
frm.toggle_display(['address_html','contact_html'], !frm.doc.__islocal); frm.toggle_display(["address_html", "contact_html"], !frm.doc.__islocal);
if (frm.doc.__islocal) { if (frm.doc.__islocal) {
frm.set_df_property('address_and_contact', 'hidden', 1); frm.set_df_property("address_and_contact", "hidden", 1);
frappe.contacts.clear_address_and_contact(frm); frappe.contacts.clear_address_and_contact(frm);
} } else {
else { frm.set_df_property("address_and_contact", "hidden", 0);
frm.set_df_property('address_and_contact', 'hidden', 0);
frappe.contacts.render_address_and_contact(frm); frappe.contacts.render_address_and_contact(frm);
} }
if (frm.doc.plaid_access_token) { if (frm.doc.plaid_access_token) {
frm.add_custom_button(__('Refresh Plaid Link'), () => { frm.add_custom_button(__("Refresh Plaid Link"), () => {
new erpnext.integrations.refreshPlaidLink(frm.doc.plaid_access_token); new erpnext.integrations.refreshPlaidLink(frm.doc.plaid_access_token);
}); });
} }
} },
}); });
let add_fields_to_mapping_table = function (frm) { let add_fields_to_mapping_table = function (frm) {
let options = []; let options = [];
frappe.model.with_doctype("Bank Transaction", function() { frappe.model.with_doctype("Bank Transaction", function () {
let meta = frappe.get_meta("Bank Transaction"); let meta = frappe.get_meta("Bank Transaction");
meta.fields.forEach(value => { meta.fields.forEach((value) => {
if (!["Section Break", "Column Break"].includes(value.fieldtype)) { if (!["Section Break", "Column Break"].includes(value.fieldtype)) {
options.push(value.fieldname); options.push(value.fieldname);
} }
@@ -40,30 +38,32 @@ let add_fields_to_mapping_table = function (frm) {
}); });
frm.fields_dict.bank_transaction_mapping.grid.update_docfield_property( frm.fields_dict.bank_transaction_mapping.grid.update_docfield_property(
'bank_transaction_field', 'options', options "bank_transaction_field",
"options",
options
); );
}; };
erpnext.integrations.refreshPlaidLink = class refreshPlaidLink { erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
constructor(access_token) { constructor(access_token) {
this.access_token = access_token; this.access_token = access_token;
this.plaidUrl = 'https://cdn.plaid.com/link/v2/stable/link-initialize.js'; this.plaidUrl = "https://cdn.plaid.com/link/v2/stable/link-initialize.js";
this.init_config(); this.init_config();
} }
async init_config() { async init_config() {
this.plaid_env = await frappe.db.get_single_value('Plaid Settings', 'plaid_env'); this.plaid_env = await frappe.db.get_single_value("Plaid Settings", "plaid_env");
this.token = await this.get_link_token_for_update(); this.token = await this.get_link_token_for_update();
this.init_plaid(); this.init_plaid();
} }
async get_link_token_for_update() { async get_link_token_for_update() {
const token = frappe.xcall( const token = frappe.xcall(
'erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.get_link_token_for_update', "erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.get_link_token_for_update",
{ access_token: this.access_token } { access_token: this.access_token }
) );
if (!token) { if (!token) {
frappe.throw(__('Cannot retrieve link token for update. Check Error Log for more information')); frappe.throw(__("Cannot retrieve link token for update. Check Error Log for more information"));
} }
return token; return token;
} }
@@ -90,35 +90,45 @@ erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
resolve(); resolve();
return; return;
} }
const el = document.createElement('script'); const el = document.createElement("script");
el.type = 'text/javascript'; el.type = "text/javascript";
el.async = true; el.async = true;
el.src = src; el.src = src;
el.addEventListener('load', resolve); el.addEventListener("load", resolve);
el.addEventListener('error', reject); el.addEventListener("error", reject);
el.addEventListener('abort', reject); el.addEventListener("abort", reject);
document.head.appendChild(el); document.head.appendChild(el);
}); });
} }
onScriptLoaded(me) { onScriptLoaded(me) {
me.linkHandler = Plaid.create({ // eslint-disable-line no-undef me.linkHandler = Plaid.create({
// eslint-disable-line no-undef
env: me.plaid_env, env: me.plaid_env,
token: me.token, token: me.token,
onSuccess: me.plaid_success onSuccess: me.plaid_success,
}); });
} }
onScriptError(error) { onScriptError(error) {
frappe.msgprint(__("There was an issue connecting to Plaid's authentication server. Check browser console for more information")); frappe.msgprint(
__(
"There was an issue connecting to Plaid's authentication server. Check browser console for more information"
)
);
console.log(error); console.log(error);
} }
plaid_success(token, response) { plaid_success(token, response) {
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.update_bank_account_ids', { frappe
response: response, .xcall(
}).then(() => { "erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.update_bank_account_ids",
frappe.show_alert({ message: __('Plaid Link Updated'), indicator: 'green' }); {
}); response: response,
}
)
.then(() => {
frappe.show_alert({ message: __("Plaid Link Updated"), indicator: "green" });
});
} }
}; };

View File

@@ -101,7 +101,7 @@
} }
], ],
"links": [], "links": [],
"modified": "2020-07-17 14:00:13.105433", "modified": "2024-03-27 13:06:36.896195",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Bank", "name": "Bank",
@@ -121,7 +121,8 @@
} }
], ],
"quick_entry": 1, "quick_entry": 1,
"sort_field": "modified", "sort_field": "creation",
"sort_order": "DESC", "sort_order": "DESC",
"states": [],
"track_changes": 1 "track_changes": 1
} }

View File

@@ -1,45 +1,49 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Bank Account', { frappe.ui.form.on("Bank Account", {
setup: function(frm) { setup: function (frm) {
frm.set_query("account", function() { frm.set_query("account", function () {
return { return {
filters: { filters: {
'account_type': 'Bank', account_type: "Bank",
'company': frm.doc.company, company: frm.doc.company,
'is_group': 0 is_group: 0,
} },
}; };
}); });
frm.set_query("party_type", function() { frm.set_query("party_type", function () {
return { return {
query: "erpnext.setup.doctype.party_type.party_type.get_party_type", query: "erpnext.setup.doctype.party_type.party_type.get_party_type",
}; };
}); });
}, },
refresh: function(frm) { refresh: function (frm) {
frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Bank Account' } frappe.dynamic_link = { doc: frm.doc, fieldname: "name", doctype: "Bank Account" };
frm.toggle_display(['address_html','contact_html'], !frm.doc.__islocal); frm.toggle_display(["address_html", "contact_html"], !frm.doc.__islocal);
if (frm.doc.__islocal) { if (frm.doc.__islocal) {
frappe.contacts.clear_address_and_contact(frm); frappe.contacts.clear_address_and_contact(frm);
} } else {
else {
frappe.contacts.render_address_and_contact(frm); frappe.contacts.render_address_and_contact(frm);
} }
if (frm.doc.integration_id) { if (frm.doc.integration_id) {
frm.add_custom_button(__("Unlink external integrations"), function() { frm.add_custom_button(__("Unlink external integrations"), function () {
frappe.confirm(__("This action will unlink this account from any external service integrating ERPNext with your bank accounts. It cannot be undone. Are you certain ?"), function() { frappe.confirm(
frm.set_value("integration_id", ""); __(
}); "This action will unlink this account from any external service integrating ERPNext with your bank accounts. It cannot be undone. Are you certain ?"
),
function () {
frm.set_value("integration_id", "");
}
);
}); });
} }
}, },
is_company_account: function(frm) { is_company_account: function (frm) {
frm.set_df_property('account', 'reqd', frm.doc.is_company_account); frm.set_df_property("account", "reqd", frm.doc.is_company_account);
} },
}); });

View File

@@ -209,7 +209,7 @@
} }
], ],
"links": [], "links": [],
"modified": "2023-09-22 21:31:34.763977", "modified": "2024-03-27 13:06:37.049542",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Bank Account", "name": "Bank Account",
@@ -242,7 +242,7 @@
} }
], ],
"search_fields": "bank,account", "search_fields": "bank,account",
"sort_field": "modified", "sort_field": "creation",
"sort_order": "DESC", "sort_order": "DESC",
"states": [], "states": [],
"track_changes": 1 "track_changes": 1

View File

@@ -54,10 +54,13 @@ class BankAccount(Document):
self.validate_company() self.validate_company()
self.validate_iban() self.validate_iban()
self.validate_account() self.validate_account()
self.update_default_bank_account()
def validate_account(self): def validate_account(self):
if self.account: if self.account:
if accounts := frappe.db.get_all("Bank Account", filters={"account": self.account}, as_list=1): if accounts := frappe.db.get_all(
"Bank Account", filters={"account": self.account, "name": ["!=", self.name]}, as_list=1
):
frappe.throw( frappe.throw(
_("'{0}' account is already used by {1}. Use another account.").format( _("'{0}' account is already used by {1}. Use another account.").format(
frappe.bold(self.account), frappe.bold(self.account),
@@ -98,19 +101,51 @@ class BankAccount(Document):
if to_check % 97 != 1: if to_check % 97 != 1:
frappe.throw(_("IBAN is not valid")) frappe.throw(_("IBAN is not valid"))
def update_default_bank_account(self):
if self.is_default and not self.disabled:
frappe.db.set_value(
"Bank Account",
{
"party_type": self.party_type,
"party": self.party,
"is_company_account": self.is_company_account,
"is_default": 1,
"disabled": 0,
},
"is_default",
0,
)
@frappe.whitelist() @frappe.whitelist()
def make_bank_account(doctype, docname): def make_bank_account(doctype, docname):
doc = frappe.new_doc("Bank Account") doc = frappe.new_doc("Bank Account")
doc.party_type = doctype doc.party_type = doctype
doc.party = docname doc.party = docname
doc.is_default = 1
return doc return doc
def get_party_bank_account(party_type, party): def get_party_bank_account(party_type, party):
return frappe.db.get_value(party_type, party, "default_bank_account") return frappe.db.get_value(
"Bank Account",
{"party_type": party_type, "party": party, "is_default": 1, "disabled": 0},
"name",
)
def get_default_company_bank_account(company, party_type, party):
default_company_bank_account = frappe.db.get_value(party_type, party, "default_bank_account")
if default_company_bank_account:
if company != frappe.get_cached_value("Bank Account", default_company_bank_account, "company"):
default_company_bank_account = None
if not default_company_bank_account:
default_company_bank_account = frappe.db.get_value(
"Bank Account", {"company": company, "is_company_account": 1, "is_default": 1}
)
return default_company_bank_account
@frappe.whitelist() @frappe.whitelist()

View File

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

View File

@@ -1,8 +1,6 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Bank Account Subtype', { frappe.ui.form.on("Bank Account Subtype", {
refresh: function() { refresh: function () {},
}
}); });

View File

@@ -1,134 +1,69 @@
{ {
"allow_copy": 0, "actions": [],
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 1, "allow_import": 1,
"allow_rename": 1, "allow_rename": 1,
"autoname": "field:account_subtype", "autoname": "field:account_subtype",
"beta": 0,
"creation": "2018-10-25 15:46:08.054586", "creation": "2018-10-25 15:46:08.054586",
"custom": 0,
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [
"account_subtype"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "account_subtype", "fieldname": "account_subtype",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Account Subtype", "label": "Account Subtype",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 1 "unique": 1
} }
], ],
"has_web_view": 0, "links": [],
"hide_heading": 0, "modified": "2024-03-27 13:06:37.221876",
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-10-25 15:47:03.841390",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Bank Account Subtype", "name": "Bank Account Subtype",
"name_case": "",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "System Manager", "role": "System Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Accounts Manager", "role": "Accounts Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Accounts User", "role": "Accounts User",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
} }
], ],
"quick_entry": 1, "quick_entry": 1,
"read_only": 0, "sort_field": "creation",
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"track_changes": 0, "states": []
"track_seen": 0,
"track_views": 0
} }

View File

@@ -1,8 +1,7 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on('Bank Account Type', { frappe.ui.form.on("Bank Account Type", {
// refresh: function(frm) { // refresh: function(frm) {
// } // }
}); });

View File

@@ -19,7 +19,7 @@
} }
], ],
"links": [], "links": [],
"modified": "2020-04-10 21:13:09.137898", "modified": "2024-03-27 13:06:37.347035",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Bank Account Type", "name": "Bank Account Type",
@@ -63,6 +63,7 @@
} }
], ],
"quick_entry": 1, "quick_entry": 1,
"sort_field": "modified", "sort_field": "creation",
"sort_order": "DESC" "sort_order": "DESC",
"states": []
} }

View File

@@ -2,80 +2,76 @@
// License: GNU General Public License v3. See license.txt // License: GNU General Public License v3. See license.txt
frappe.ui.form.on("Bank Clearance", { frappe.ui.form.on("Bank Clearance", {
setup: function(frm) { setup: function (frm) {
frm.add_fetch("account", "account_currency", "account_currency"); frm.add_fetch("account", "account_currency", "account_currency");
frm.set_query("account", function() { frm.set_query("account", function () {
return { return {
"filters": { filters: {
"account_type": ["in",["Bank","Cash"]], account_type: ["in", ["Bank", "Cash"]],
"is_group": 0, is_group: 0,
} },
}; };
}); });
frm.set_query("bank_account", function () { frm.set_query("bank_account", function () {
return { return {
filters: { filters: {
'is_company_account': 1 is_company_account: 1,
}, },
}; };
}); });
}, },
onload: function(frm) { onload: function (frm) {
let default_bank_account = frappe.defaults.get_user_default("Company")
let default_bank_account = frappe.defaults.get_user_default("Company")? ? locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]
locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]: ""; : "";
frm.set_value("account", default_bank_account); frm.set_value("account", default_bank_account);
frm.set_value("from_date", frappe.datetime.month_start()); frm.set_value("from_date", frappe.datetime.month_start());
frm.set_value("to_date", frappe.datetime.month_end()); frm.set_value("to_date", frappe.datetime.month_end());
}, },
refresh: function(frm) { refresh: function (frm) {
frm.disable_save(); frm.disable_save();
frm.add_custom_button(__('Get Payment Entries'), () => frm.add_custom_button(__("Get Payment Entries"), () => frm.trigger("get_payment_entries"));
frm.trigger("get_payment_entries")
);
frm.change_custom_button_type(__('Get Payment Entries'), null, 'primary'); frm.change_custom_button_type(__("Get Payment Entries"), null, "primary");
}, },
update_clearance_date: function(frm) { update_clearance_date: function (frm) {
return frappe.call({ return frappe.call({
method: "update_clearance_date", method: "update_clearance_date",
doc: frm.doc, doc: frm.doc,
callback: function(r, rt) { callback: function (r, rt) {
frm.refresh_field("payment_entries"); frm.refresh_field("payment_entries");
frm.refresh_fields(); frm.refresh_fields();
if (!frm.doc.payment_entries.length) { if (!frm.doc.payment_entries.length) {
frm.change_custom_button_type(__('Get Payment Entries'), null, 'primary'); frm.change_custom_button_type(__("Get Payment Entries"), null, "primary");
frm.change_custom_button_type(__('Update Clearance Date'), null, 'default'); frm.change_custom_button_type(__("Update Clearance Date"), null, "default");
} }
} },
}); });
}, },
get_payment_entries: function(frm) { get_payment_entries: function (frm) {
return frappe.call({ return frappe.call({
method: "get_payment_entries", method: "get_payment_entries",
doc: frm.doc, doc: frm.doc,
callback: function(r, rt) { callback: function (r, rt) {
frm.refresh_field("payment_entries"); frm.refresh_field("payment_entries");
if (frm.doc.payment_entries.length) { if (frm.doc.payment_entries.length) {
frm.add_custom_button(__('Update Clearance Date'), () => frm.add_custom_button(__("Update Clearance Date"), () =>
frm.trigger("update_clearance_date") frm.trigger("update_clearance_date")
); );
frm.change_custom_button_type(__('Get Payment Entries'), null, 'default'); frm.change_custom_button_type(__("Get Payment Entries"), null, "default");
frm.change_custom_button_type(__('Update Clearance Date'), null, 'primary'); frm.change_custom_button_type(__("Update Clearance Date"), null, "primary");
} }
} },
}); });
} },
}); });

View File

@@ -91,7 +91,7 @@
"idx": 1, "idx": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2022-11-28 17:24:13.008692", "modified": "2024-03-27 13:06:37.477927",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Bank Clearance", "name": "Bank Clearance",
@@ -107,7 +107,7 @@
], ],
"quick_entry": 1, "quick_entry": 1,
"read_only": 1, "read_only": 1,
"sort_field": "modified", "sort_field": "creation",
"sort_order": "ASC", "sort_order": "ASC",
"states": [] "states": []
} }

View File

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

View File

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

View File

@@ -1,340 +1,112 @@
{ {
"allow_copy": 0, "actions": [],
"allow_events_in_timeline": 0, "creation": "2013-02-22 01:27:37",
"allow_guest_to_view": 0, "doctype": "DocType",
"allow_import": 0, "editable_grid": 1,
"allow_rename": 0, "engine": "InnoDB",
"beta": 0, "field_order": [
"creation": "2013-02-22 01:27:37", "payment_document",
"custom": 0, "payment_entry",
"docstatus": 0, "against_account",
"doctype": "DocType", "amount",
"editable_grid": 1, "column_break_5",
"posting_date",
"cheque_number",
"cheque_date",
"clearance_date"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "fieldname": "payment_document",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "label": "Payment Document",
"bold": 0, "options": "DocType"
"collapsible": 0, },
"columns": 0,
"fieldname": "payment_document",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Payment Document",
"length": 0,
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "columns": 2,
"allow_in_quick_entry": 0, "fieldname": "payment_entry",
"allow_on_submit": 0, "fieldtype": "Dynamic Link",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Payment Entry",
"columns": 2, "oldfieldname": "voucher_id",
"fieldname": "payment_entry", "oldfieldtype": "Link",
"fieldtype": "Dynamic Link", "options": "payment_document",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Payment Entry",
"length": 0,
"no_copy": 0,
"oldfieldname": "voucher_id",
"oldfieldtype": "Link",
"options": "payment_document",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": "50" "width": "50"
}, },
{ {
"allow_bulk_edit": 0, "columns": 2,
"allow_in_quick_entry": 0, "fieldname": "against_account",
"allow_on_submit": 0, "fieldtype": "Data",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Against Account",
"columns": 2, "oldfieldname": "against_account",
"fieldname": "against_account", "oldfieldtype": "Data",
"fieldtype": "Data", "read_only": 1,
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Against Account",
"length": 0,
"no_copy": 0,
"oldfieldname": "against_account",
"oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": "15" "width": "15"
}, },
{ {
"allow_bulk_edit": 0, "columns": 2,
"allow_in_quick_entry": 0, "fieldname": "amount",
"allow_on_submit": 0, "fieldtype": "Data",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Amount",
"columns": 2, "oldfieldname": "debit",
"fieldname": "amount", "oldfieldtype": "Currency",
"fieldtype": "Data", "read_only": 1
"hidden": 0, },
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Amount",
"length": 0,
"no_copy": 0,
"oldfieldname": "debit",
"oldfieldtype": "Currency",
"options": "",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "column_break_5",
"allow_in_quick_entry": 0, "fieldtype": "Column Break",
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_5",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": "50%" "width": "50%"
}, },
{ {
"allow_bulk_edit": 0, "columns": 2,
"allow_in_quick_entry": 0, "fieldname": "posting_date",
"allow_on_submit": 0, "fieldtype": "Date",
"bold": 0, "label": "Posting Date",
"collapsible": 0, "oldfieldname": "posting_date",
"columns": 2, "oldfieldtype": "Date",
"fieldname": "posting_date", "read_only": 1
"fieldtype": "Date", },
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Posting Date",
"length": 0,
"no_copy": 0,
"oldfieldname": "posting_date",
"oldfieldtype": "Date",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "columns": 2,
"allow_in_quick_entry": 0, "fieldname": "cheque_number",
"allow_on_submit": 0, "fieldtype": "Data",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Cheque Number",
"columns": 2, "oldfieldname": "cheque_number",
"fieldname": "cheque_number", "oldfieldtype": "Data",
"fieldtype": "Data", "read_only": 1
"hidden": 0, },
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Cheque Number",
"length": 0,
"no_copy": 0,
"oldfieldname": "cheque_number",
"oldfieldtype": "Data",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "cheque_date",
"allow_in_quick_entry": 0, "fieldtype": "Date",
"allow_on_submit": 0, "label": "Cheque Date",
"bold": 0, "oldfieldname": "cheque_date",
"collapsible": 0, "oldfieldtype": "Date",
"columns": 0, "read_only": 1
"fieldname": "cheque_date", },
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Cheque Date",
"length": 0,
"no_copy": 0,
"oldfieldname": "cheque_date",
"oldfieldtype": "Date",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "columns": 2,
"allow_in_quick_entry": 0, "fieldname": "clearance_date",
"allow_on_submit": 0, "fieldtype": "Date",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Clearance Date",
"columns": 2, "oldfieldname": "clearance_date",
"fieldname": "clearance_date", "oldfieldtype": "Date"
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Clearance Date",
"length": 0,
"no_copy": 0,
"oldfieldname": "clearance_date",
"oldfieldtype": "Date",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "idx": 1,
"hide_heading": 0, "istable": 1,
"hide_toolbar": 0, "links": [],
"idx": 1, "modified": "2024-03-27 13:06:37.609319",
"image_view": 0, "modified_by": "Administrator",
"in_create": 0, "module": "Accounts",
"is_submittable": 0, "name": "Bank Clearance Detail",
"issingle": 0, "owner": "Administrator",
"istable": 1, "permissions": [],
"max_attachments": 0, "quick_entry": 1,
"menu_index": 0, "sort_field": "creation",
"modified": "2019-01-07 16:52:07.174687", "sort_order": "ASC",
"modified_by": "Administrator", "states": []
"module": "Accounts",
"name": "Bank Clearance Detail",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_order": "ASC",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
} }

View File

@@ -1,39 +1,39 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
cur_frm.add_fetch('bank_account','account','account'); cur_frm.add_fetch("bank_account", "account", "account");
cur_frm.add_fetch('bank_account','bank_account_no','bank_account_no'); cur_frm.add_fetch("bank_account", "bank_account_no", "bank_account_no");
cur_frm.add_fetch('bank_account','iban','iban'); cur_frm.add_fetch("bank_account", "iban", "iban");
cur_frm.add_fetch('bank_account','branch_code','branch_code'); cur_frm.add_fetch("bank_account", "branch_code", "branch_code");
cur_frm.add_fetch('bank','swift_number','swift_number'); cur_frm.add_fetch("bank", "swift_number", "swift_number");
frappe.ui.form.on('Bank Guarantee', { frappe.ui.form.on("Bank Guarantee", {
setup: function(frm) { setup: function (frm) {
frm.set_query("bank", function() { frm.set_query("bank", function () {
return {
filters: {
company: frm.doc.company
}
};
});
frm.set_query("bank_account", function() {
return { return {
filters: { filters: {
company: frm.doc.company, company: frm.doc.company,
bank: frm.doc.bank },
} };
}
}); });
frm.set_query("project", function() { frm.set_query("bank_account", function () {
return { return {
filters: { filters: {
customer: frm.doc.customer company: frm.doc.company,
} bank: frm.doc.bank,
},
};
});
frm.set_query("project", function () {
return {
filters: {
customer: frm.doc.customer,
},
}; };
}); });
}, },
bg_type: function(frm) { bg_type: function (frm) {
if (frm.doc.bg_type == "Receiving") { if (frm.doc.bg_type == "Receiving") {
frm.set_value("reference_doctype", "Sales Order"); frm.set_value("reference_doctype", "Sales Order");
} else if (frm.doc.bg_type == "Providing") { } else if (frm.doc.bg_type == "Providing") {
@@ -41,34 +41,33 @@ frappe.ui.form.on('Bank Guarantee', {
} }
}, },
reference_docname: function(frm) { reference_docname: function (frm) {
if (frm.doc.reference_docname && frm.doc.reference_doctype) { if (frm.doc.reference_docname && frm.doc.reference_doctype) {
let party_field = frm.doc.reference_doctype == "Sales Order" ? "customer" : "supplier"; let party_field = frm.doc.reference_doctype == "Sales Order" ? "customer" : "supplier";
frappe.call({ frappe.call({
method: "erpnext.accounts.doctype.bank_guarantee.bank_guarantee.get_voucher_details", method: "erpnext.accounts.doctype.bank_guarantee.bank_guarantee.get_voucher_details",
args: { args: {
"bank_guarantee_type": frm.doc.bg_type, bank_guarantee_type: frm.doc.bg_type,
"reference_name": frm.doc.reference_docname reference_name: frm.doc.reference_docname,
}, },
callback: function(r) { callback: function (r) {
if (r.message) { if (r.message) {
if (r.message[party_field]) frm.set_value(party_field, r.message[party_field]); if (r.message[party_field]) frm.set_value(party_field, r.message[party_field]);
if (r.message.project) frm.set_value("project", r.message.project); if (r.message.project) frm.set_value("project", r.message.project);
if (r.message.grand_total) frm.set_value("amount", r.message.grand_total); if (r.message.grand_total) frm.set_value("amount", r.message.grand_total);
} }
} },
}); });
} }
}, },
start_date: function(frm) { start_date: function (frm) {
var end_date = frappe.datetime.add_days(cur_frm.doc.start_date, cur_frm.doc.validity - 1); var end_date = frappe.datetime.add_days(cur_frm.doc.start_date, cur_frm.doc.validity - 1);
cur_frm.set_value("end_date", end_date); cur_frm.set_value("end_date", end_date);
}, },
validity: function(frm) { validity: function (frm) {
var end_date = frappe.datetime.add_days(cur_frm.doc.start_date, cur_frm.doc.validity - 1); var end_date = frappe.datetime.add_days(cur_frm.doc.start_date, cur_frm.doc.validity - 1);
cur_frm.set_value("end_date", end_date); cur_frm.set_value("end_date", end_date);
} },
}); });

File diff suppressed because it is too large Load Diff

View File

@@ -8,21 +8,22 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
return { return {
filters: { filters: {
company: frm.doc.company, company: frm.doc.company,
'is_company_account': 1 is_company_account: 1,
}, },
}; };
}); });
let no_bank_transactions_text = let no_bank_transactions_text = `<div class="text-muted text-center">${__(
`<div class="text-muted text-center">${__("No Matching Bank Transactions Found")}</div>` "No Matching Bank Transactions Found"
)}</div>`;
set_field_options("no_bank_transactions", no_bank_transactions_text); set_field_options("no_bank_transactions", no_bank_transactions_text);
}, },
onload: function (frm) { onload: function (frm) {
// Set default filter dates // Set default filter dates
let today = frappe.datetime.get_today() let today = frappe.datetime.get_today();
frm.doc.bank_statement_from_date = frappe.datetime.add_months(today, -1); frm.doc.bank_statement_from_date = frappe.datetime.add_months(today, -1);
frm.doc.bank_statement_to_date = today; frm.doc.bank_statement_to_date = today;
frm.trigger('bank_account'); frm.trigger("bank_account");
}, },
filter_by_reference_date: function (frm) { filter_by_reference_date: function (frm) {
@@ -37,34 +38,27 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
refresh: function (frm) { refresh: function (frm) {
frm.disable_save(); frm.disable_save();
frappe.require("bank-reconciliation-tool.bundle.js", () => frappe.require("bank-reconciliation-tool.bundle.js", () => frm.trigger("make_reconciliation_tool"));
frm.trigger("make_reconciliation_tool")
);
frm.add_custom_button(__("Upload Bank Statement"), () => frm.add_custom_button(__("Upload Bank Statement"), () =>
frappe.call({ frappe.call({
method: method: "erpnext.accounts.doctype.bank_statement_import.bank_statement_import.upload_bank_statement",
"erpnext.accounts.doctype.bank_statement_import.bank_statement_import.upload_bank_statement", args: {
args: { dt: frm.doc.doctype,
dt: frm.doc.doctype, dn: frm.doc.name,
dn: frm.doc.name, company: frm.doc.company,
company: frm.doc.company, bank_account: frm.doc.bank_account,
bank_account: frm.doc.bank_account, },
}, callback: function (r) {
callback: function (r) { if (!r.exc) {
if (!r.exc) { var doc = frappe.model.sync(r.message);
var doc = frappe.model.sync(r.message); frappe.set_route("Form", doc[0].doctype, doc[0].name);
frappe.set_route( }
"Form", },
doc[0].doctype, })
doc[0].name
);
}
},
})
); );
frm.add_custom_button(__('Auto Reconcile'), function() { frm.add_custom_button(__("Auto Reconcile"), function () {
frappe.call({ frappe.call({
method: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.auto_reconcile_vouchers", method: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.auto_reconcile_vouchers",
args: { args: {
@@ -75,33 +69,22 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
from_reference_date: frm.doc.from_reference_date, from_reference_date: frm.doc.from_reference_date,
to_reference_date: frm.doc.to_reference_date, to_reference_date: frm.doc.to_reference_date,
}, },
}) });
}); });
frm.add_custom_button(__('Get Unreconciled Entries'), function() { frm.add_custom_button(__("Get Unreconciled Entries"), function () {
frm.trigger("make_reconciliation_tool"); frm.trigger("make_reconciliation_tool");
}); });
frm.change_custom_button_type(__('Get Unreconciled Entries'), null, 'primary'); frm.change_custom_button_type(__("Get Unreconciled Entries"), null, "primary");
}, },
bank_account: function (frm) { bank_account: function (frm) {
frappe.db.get_value( frappe.db.get_value("Bank Account", frm.doc.bank_account, "account", (r) => {
"Bank Account", frappe.db.get_value("Account", r.account, "account_currency", (r) => {
frm.doc.bank_account, frm.doc.account_currency = r.account_currency;
"account", frm.trigger("render_chart");
(r) => { });
frappe.db.get_value( });
"Account",
r.account,
"account_currency",
(r) => {
frm.doc.account_currency = r.account_currency;
frm.trigger("render_chart");
}
);
}
);
frm.trigger("get_account_opening_balance"); frm.trigger("get_account_opening_balance");
}, },
@@ -120,11 +103,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
) { ) {
frm.trigger("render_chart"); frm.trigger("render_chart");
frm.trigger("render"); frm.trigger("render");
frappe.utils.scroll_to( frappe.utils.scroll_to(frm.get_field("reconciliation_tool_cards").$wrapper, true, 30);
frm.get_field("reconciliation_tool_cards").$wrapper,
true,
30
);
} }
}); });
} }
@@ -133,11 +112,10 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
get_account_opening_balance(frm) { get_account_opening_balance(frm) {
if (frm.doc.bank_account && frm.doc.bank_statement_from_date) { if (frm.doc.bank_account && frm.doc.bank_statement_from_date) {
frappe.call({ frappe.call({
method: method: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance",
"erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance",
args: { args: {
bank_account: frm.doc.bank_account, bank_account: frm.doc.bank_account,
till_date: frappe.datetime.add_days(frm.doc.bank_statement_from_date, -1) till_date: frappe.datetime.add_days(frm.doc.bank_statement_from_date, -1),
}, },
callback: (response) => { callback: (response) => {
frm.set_value("account_opening_balance", response.message); frm.set_value("account_opening_balance", response.message);
@@ -149,8 +127,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
get_cleared_balance(frm) { get_cleared_balance(frm) {
if (frm.doc.bank_account && frm.doc.bank_statement_to_date) { if (frm.doc.bank_account && frm.doc.bank_statement_to_date) {
return frappe.call({ return frappe.call({
method: method: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance",
"erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance",
args: { args: {
bank_account: frm.doc.bank_account, bank_account: frm.doc.bank_account,
till_date: frm.doc.bank_statement_to_date, till_date: frm.doc.bank_statement_to_date,
@@ -163,41 +140,30 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
}, },
render_chart(frm) { render_chart(frm) {
frm.cards_manager = new erpnext.accounts.bank_reconciliation.NumberCardManager( frm.cards_manager = new erpnext.accounts.bank_reconciliation.NumberCardManager({
{ $reconciliation_tool_cards: frm.get_field("reconciliation_tool_cards").$wrapper,
$reconciliation_tool_cards: frm.get_field( bank_statement_closing_balance: frm.doc.bank_statement_closing_balance,
"reconciliation_tool_cards" cleared_balance: frm.cleared_balance,
).$wrapper, currency: frm.doc.account_currency,
bank_statement_closing_balance: });
frm.doc.bank_statement_closing_balance,
cleared_balance: frm.cleared_balance,
currency: frm.doc.account_currency,
}
);
}, },
render(frm) { render(frm) {
if (frm.doc.bank_account) { if (frm.doc.bank_account) {
frm.bank_reconciliation_data_table_manager = new erpnext.accounts.bank_reconciliation.DataTableManager( frm.bank_reconciliation_data_table_manager =
{ new erpnext.accounts.bank_reconciliation.DataTableManager({
company: frm.doc.company, company: frm.doc.company,
bank_account: frm.doc.bank_account, bank_account: frm.doc.bank_account,
$reconciliation_tool_dt: frm.get_field( $reconciliation_tool_dt: frm.get_field("reconciliation_tool_dt").$wrapper,
"reconciliation_tool_dt" $no_bank_transactions: frm.get_field("no_bank_transactions").$wrapper,
).$wrapper,
$no_bank_transactions: frm.get_field(
"no_bank_transactions"
).$wrapper,
bank_statement_from_date: frm.doc.bank_statement_from_date, bank_statement_from_date: frm.doc.bank_statement_from_date,
bank_statement_to_date: frm.doc.bank_statement_to_date, bank_statement_to_date: frm.doc.bank_statement_to_date,
filter_by_reference_date: frm.doc.filter_by_reference_date, filter_by_reference_date: frm.doc.filter_by_reference_date,
from_reference_date: frm.doc.from_reference_date, from_reference_date: frm.doc.from_reference_date,
to_reference_date: frm.doc.to_reference_date, to_reference_date: frm.doc.to_reference_date,
bank_statement_closing_balance: bank_statement_closing_balance: frm.doc.bank_statement_closing_balance,
frm.doc.bank_statement_closing_balance,
cards_manager: frm.cards_manager, cards_manager: frm.cards_manager,
} });
);
} }
}, },
}); });

View File

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

View File

@@ -9,7 +9,6 @@ from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
from frappe.query_builder.custom import ConstantColumn from frappe.query_builder.custom import ConstantColumn
from frappe.utils import cint, flt from frappe.utils import cint, flt
from pypika.terms import Parameter
from erpnext import get_default_cost_center from erpnext import get_default_cost_center
from erpnext.accounts.doctype.bank_transaction.bank_transaction import get_total_allocated_amount from erpnext.accounts.doctype.bank_transaction.bank_transaction import get_total_allocated_amount
@@ -82,9 +81,7 @@ def get_bank_transactions(bank_account, from_date=None, to_date=None):
def get_account_balance(bank_account, till_date): def get_account_balance(bank_account, till_date):
# returns account balance till the specified date # returns account balance till the specified date
account = frappe.db.get_value("Bank Account", bank_account, "account") account = frappe.db.get_value("Bank Account", bank_account, "account")
filters = frappe._dict( filters = frappe._dict({"account": account, "report_date": till_date, "include_pos_transactions": 1})
{"account": account, "report_date": till_date, "include_pos_transactions": 1}
)
data = get_entries(filters) data = get_entries(filters)
balance_as_per_system = get_balance_on(filters["account"], filters["report_date"]) balance_as_per_system = get_balance_on(filters["account"], filters["report_date"])
@@ -97,10 +94,7 @@ def get_account_balance(bank_account, till_date):
amounts_not_reflected_in_system = get_amounts_not_reflected_in_system(filters) amounts_not_reflected_in_system = get_amounts_not_reflected_in_system(filters)
bank_bal = ( bank_bal = (
flt(balance_as_per_system) flt(balance_as_per_system) - flt(total_debit) + flt(total_credit) + amounts_not_reflected_in_system
- flt(total_debit)
+ flt(total_credit)
+ amounts_not_reflected_in_system
) )
return bank_bal return bank_bal
@@ -509,6 +503,18 @@ def check_matching(
to_reference_date, to_reference_date,
): ):
exact_match = True if "exact_match" in document_types else False exact_match = True if "exact_match" in document_types else False
common_filters = frappe._dict(
{
"amount": transaction.unallocated_amount,
"payment_type": "Receive" if transaction.deposit > 0.0 else "Pay",
"reference_no": transaction.reference_number,
"party_type": transaction.party_type,
"party": transaction.party,
"bank_account": bank_account,
}
)
queries = get_queries( queries = get_queries(
bank_account, bank_account,
company, company,
@@ -520,24 +526,14 @@ def check_matching(
from_reference_date, from_reference_date,
to_reference_date, to_reference_date,
exact_match, exact_match,
common_filters,
) )
filters = {
"amount": transaction.unallocated_amount,
"payment_type": "Receive" if transaction.deposit > 0.0 else "Pay",
"reference_no": transaction.reference_number,
"party_type": transaction.party_type,
"party": transaction.party,
"bank_account": bank_account,
}
matching_vouchers = [] matching_vouchers = []
for query in queries: for query in queries:
matching_vouchers.extend(frappe.db.sql(query, filters, as_dict=True)) matching_vouchers.extend(query.run(as_dict=True))
return ( return sorted(matching_vouchers, key=lambda x: x["rank"], reverse=True) if matching_vouchers else []
sorted(matching_vouchers, key=lambda x: x["rank"], reverse=True) if matching_vouchers else []
)
def get_queries( def get_queries(
@@ -551,6 +547,7 @@ def get_queries(
from_reference_date, from_reference_date,
to_reference_date, to_reference_date,
exact_match, exact_match,
common_filters,
): ):
# get queries to get matching vouchers # get queries to get matching vouchers
account_from_to = "paid_to" if transaction.deposit > 0.0 else "paid_from" account_from_to = "paid_to" if transaction.deposit > 0.0 else "paid_from"
@@ -571,6 +568,7 @@ def get_queries(
filter_by_reference_date, filter_by_reference_date,
from_reference_date, from_reference_date,
to_reference_date, to_reference_date,
common_filters,
) )
or [] or []
) )
@@ -590,6 +588,7 @@ def get_matching_queries(
filter_by_reference_date, filter_by_reference_date,
from_reference_date, from_reference_date,
to_reference_date, to_reference_date,
common_filters,
): ):
queries = [] queries = []
currency = get_account_currency(bank_account) currency = get_account_currency(bank_account)
@@ -604,6 +603,7 @@ def get_matching_queries(
filter_by_reference_date, filter_by_reference_date,
from_reference_date, from_reference_date,
to_reference_date, to_reference_date,
common_filters,
) )
queries.append(query) queries.append(query)
@@ -616,16 +616,17 @@ def get_matching_queries(
filter_by_reference_date, filter_by_reference_date,
from_reference_date, from_reference_date,
to_reference_date, to_reference_date,
common_filters,
) )
queries.append(query) queries.append(query)
if transaction.deposit > 0.0 and "sales_invoice" in document_types: if transaction.deposit > 0.0 and "sales_invoice" in document_types:
query = get_si_matching_query(exact_match, currency) query = get_si_matching_query(exact_match, currency, common_filters)
queries.append(query) queries.append(query)
if transaction.withdrawal > 0.0: if transaction.withdrawal > 0.0:
if "purchase_invoice" in document_types: if "purchase_invoice" in document_types:
query = get_pi_matching_query(exact_match, currency) query = get_pi_matching_query(exact_match, currency, common_filters)
queries.append(query) queries.append(query)
if "bank_transaction" in document_types: if "bank_transaction" in document_types:
@@ -646,17 +647,13 @@ def get_bt_matching_query(exact_match, transaction):
amount_rank = frappe.qb.terms.Case().when(amount_equality, 1).else_(0) amount_rank = frappe.qb.terms.Case().when(amount_equality, 1).else_(0)
amount_condition = amount_equality if exact_match else getattr(bt, field) > 0.0 amount_condition = amount_equality if exact_match else getattr(bt, field) > 0.0
ref_rank = ( ref_rank = frappe.qb.terms.Case().when(bt.reference_number == transaction.reference_number, 1).else_(0)
frappe.qb.terms.Case().when(bt.reference_number == transaction.reference_number, 1).else_(0)
)
unallocated_rank = ( unallocated_rank = (
frappe.qb.terms.Case().when(bt.unallocated_amount == transaction.unallocated_amount, 1).else_(0) frappe.qb.terms.Case().when(bt.unallocated_amount == transaction.unallocated_amount, 1).else_(0)
) )
party_condition = ( party_condition = (
(bt.party_type == transaction.party_type) (bt.party_type == transaction.party_type) & (bt.party == transaction.party) & bt.party.isnotnull()
& (bt.party == transaction.party)
& bt.party.isnotnull()
) )
party_rank = frappe.qb.terms.Case().when(party_condition, 1).else_(0) party_rank = frappe.qb.terms.Case().when(party_condition, 1).else_(0)
@@ -680,7 +677,7 @@ def get_bt_matching_query(exact_match, transaction):
.where(amount_condition) .where(amount_condition)
.where(bt.docstatus == 1) .where(bt.docstatus == 1)
) )
return str(query) return query
def get_pe_matching_query( def get_pe_matching_query(
@@ -692,6 +689,7 @@ def get_pe_matching_query(
filter_by_reference_date, filter_by_reference_date,
from_reference_date, from_reference_date,
to_reference_date, to_reference_date,
common_filters,
): ):
# get matching payment entries query # get matching payment entries query
to_from = "to" if transaction.deposit > 0.0 else "from" to_from = "to" if transaction.deposit > 0.0 else "from"
@@ -707,9 +705,7 @@ def get_pe_matching_query(
amount_condition = amount_equality if exact_match else pe.paid_amount > 0.0 amount_condition = amount_equality if exact_match else pe.paid_amount > 0.0
party_condition = ( party_condition = (
(pe.party_type == transaction.party_type) (pe.party_type == transaction.party_type) & (pe.party == transaction.party) & pe.party.isnotnull()
& (pe.party == transaction.party)
& pe.party.isnotnull()
) )
party_rank = frappe.qb.terms.Case().when(party_condition, 1).else_(0) party_rank = frappe.qb.terms.Case().when(party_condition, 1).else_(0)
@@ -723,7 +719,7 @@ def get_pe_matching_query(
(ref_rank + amount_rank + party_rank + 1).as_("rank"), (ref_rank + amount_rank + party_rank + 1).as_("rank"),
ConstantColumn("Payment Entry").as_("doctype"), ConstantColumn("Payment Entry").as_("doctype"),
pe.name, pe.name,
pe.paid_amount, pe.paid_amount_after_tax.as_("paid_amount"),
pe.reference_no, pe.reference_no,
pe.reference_date, pe.reference_date,
pe.party, pe.party,
@@ -734,16 +730,16 @@ def get_pe_matching_query(
.where(pe.docstatus == 1) .where(pe.docstatus == 1)
.where(pe.payment_type.isin([payment_type, "Internal Transfer"])) .where(pe.payment_type.isin([payment_type, "Internal Transfer"]))
.where(pe.clearance_date.isnull()) .where(pe.clearance_date.isnull())
.where(getattr(pe, account_from_to) == Parameter("%(bank_account)s")) .where(getattr(pe, account_from_to) == common_filters.bank_account)
.where(amount_condition) .where(amount_condition)
.where(filter_by_date) .where(filter_by_date)
.orderby(pe.reference_date if cint(filter_by_reference_date) else pe.posting_date) .orderby(pe.reference_date if cint(filter_by_reference_date) else pe.posting_date)
) )
if frappe.flags.auto_reconcile_vouchers == True: if frappe.flags.auto_reconcile_vouchers is True:
query = query.where(ref_condition) query = query.where(ref_condition)
return str(query) return query
def get_je_matching_query( def get_je_matching_query(
@@ -754,6 +750,7 @@ def get_je_matching_query(
filter_by_reference_date, filter_by_reference_date,
from_reference_date, from_reference_date,
to_reference_date, to_reference_date,
common_filters,
): ):
# get matching journal entry query # get matching journal entry query
# We have mapping at the bank level # We have mapping at the bank level
@@ -793,29 +790,29 @@ def get_je_matching_query(
.where(je.docstatus == 1) .where(je.docstatus == 1)
.where(je.voucher_type != "Opening Entry") .where(je.voucher_type != "Opening Entry")
.where(je.clearance_date.isnull()) .where(je.clearance_date.isnull())
.where(jea.account == Parameter("%(bank_account)s")) .where(jea.account == common_filters.bank_account)
.where(amount_equality if exact_match else getattr(jea, amount_field) > 0.0) .where(amount_equality if exact_match else getattr(jea, amount_field) > 0.0)
.where(je.docstatus == 1) .where(je.docstatus == 1)
.where(filter_by_date) .where(filter_by_date)
.orderby(je.cheque_date if cint(filter_by_reference_date) else je.posting_date) .orderby(je.cheque_date if cint(filter_by_reference_date) else je.posting_date)
) )
if frappe.flags.auto_reconcile_vouchers == True: if frappe.flags.auto_reconcile_vouchers is True:
query = query.where(ref_condition) query = query.where(ref_condition)
return str(query) return query
def get_si_matching_query(exact_match, currency): def get_si_matching_query(exact_match, currency, common_filters):
# get matching sales invoice query # get matching sales invoice query
si = frappe.qb.DocType("Sales Invoice") si = frappe.qb.DocType("Sales Invoice")
sip = frappe.qb.DocType("Sales Invoice Payment") sip = frappe.qb.DocType("Sales Invoice Payment")
amount_equality = sip.amount == Parameter("%(amount)s") amount_equality = sip.amount == common_filters.amount
amount_rank = frappe.qb.terms.Case().when(amount_equality, 1).else_(0) amount_rank = frappe.qb.terms.Case().when(amount_equality, 1).else_(0)
amount_condition = amount_equality if exact_match else sip.amount > 0.0 amount_condition = amount_equality if exact_match else sip.amount > 0.0
party_condition = si.customer == Parameter("%(party)s") party_condition = si.customer == common_filters.party
party_rank = frappe.qb.terms.Case().when(party_condition, 1).else_(0) party_rank = frappe.qb.terms.Case().when(party_condition, 1).else_(0)
query = ( query = (
@@ -836,23 +833,23 @@ def get_si_matching_query(exact_match, currency):
) )
.where(si.docstatus == 1) .where(si.docstatus == 1)
.where(sip.clearance_date.isnull()) .where(sip.clearance_date.isnull())
.where(sip.account == Parameter("%(bank_account)s")) .where(sip.account == common_filters.bank_account)
.where(amount_condition) .where(amount_condition)
.where(si.currency == currency) .where(si.currency == currency)
) )
return str(query) return query
def get_pi_matching_query(exact_match, currency): def get_pi_matching_query(exact_match, currency, common_filters):
# get matching purchase invoice query when they are also used as payment entries (is_paid) # get matching purchase invoice query when they are also used as payment entries (is_paid)
purchase_invoice = frappe.qb.DocType("Purchase Invoice") purchase_invoice = frappe.qb.DocType("Purchase Invoice")
amount_equality = purchase_invoice.paid_amount == Parameter("%(amount)s") amount_equality = purchase_invoice.paid_amount == common_filters.amount
amount_rank = frappe.qb.terms.Case().when(amount_equality, 1).else_(0) amount_rank = frappe.qb.terms.Case().when(amount_equality, 1).else_(0)
amount_condition = amount_equality if exact_match else purchase_invoice.paid_amount > 0.0 amount_condition = amount_equality if exact_match else purchase_invoice.paid_amount > 0.0
party_condition = purchase_invoice.supplier == Parameter("%(party)s") party_condition = purchase_invoice.supplier == common_filters.party
party_rank = frappe.qb.terms.Case().when(party_condition, 1).else_(0) party_rank = frappe.qb.terms.Case().when(party_condition, 1).else_(0)
query = ( query = (
@@ -872,9 +869,9 @@ def get_pi_matching_query(exact_match, currency):
.where(purchase_invoice.docstatus == 1) .where(purchase_invoice.docstatus == 1)
.where(purchase_invoice.is_paid == 1) .where(purchase_invoice.is_paid == 1)
.where(purchase_invoice.clearance_date.isnull()) .where(purchase_invoice.clearance_date.isnull())
.where(purchase_invoice.cash_bank_account == Parameter("%(bank_account)s")) .where(purchase_invoice.cash_bank_account == common_filters.bank_account)
.where(amount_condition) .where(amount_condition)
.where(purchase_invoice.currency == currency) .where(purchase_invoice.currency == currency)
) )
return str(query) return query

View File

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

View File

@@ -17,11 +17,9 @@ frappe.ui.form.on("Bank Statement Import", {
frm.import_in_progress = false; frm.import_in_progress = false;
if (data_import !== frm.doc.name) return; if (data_import !== frm.doc.name) return;
frappe.model.clear_doc("Bank Statement Import", frm.doc.name); frappe.model.clear_doc("Bank Statement Import", frm.doc.name);
frappe.model frappe.model.with_doc("Bank Statement Import", frm.doc.name).then(() => {
.with_doc("Bank Statement Import", frm.doc.name) frm.refresh();
.then(() => { });
frm.refresh();
});
}); });
frappe.realtime.on("data_import_progress", (data) => { frappe.realtime.on("data_import_progress", (data) => {
frm.import_in_progress = true; frm.import_in_progress = true;
@@ -48,20 +46,9 @@ frappe.ui.form.on("Bank Statement Import", {
: __("Updating {0} of {1}, {2}", message_args); : __("Updating {0} of {1}, {2}", message_args);
} }
if (data.skipping) { if (data.skipping) {
message = __( message = __("Skipping {0} of {1}, {2}", [data.current, data.total, eta_message]);
"Skipping {0} of {1}, {2}",
[
data.current,
data.total,
eta_message,
]
);
} }
frm.dashboard.show_progress( frm.dashboard.show_progress(__("Import Progress"), percent, message);
__("Import Progress"),
percent,
message
);
frm.page.set_indicator(__("In Progress"), "orange"); frm.page.set_indicator(__("In Progress"), "orange");
// hide progress when complete // hide progress when complete
@@ -103,15 +90,12 @@ frappe.ui.form.on("Bank Statement Import", {
frm.trigger("show_report_error_button"); frm.trigger("show_report_error_button");
if (frm.doc.status === "Partial Success") { if (frm.doc.status === "Partial Success") {
frm.add_custom_button(__("Export Errored Rows"), () => frm.add_custom_button(__("Export Errored Rows"), () => frm.trigger("export_errored_rows"));
frm.trigger("export_errored_rows")
);
} }
if (frm.doc.status.includes("Success")) { if (frm.doc.status.includes("Success")) {
frm.add_custom_button( frm.add_custom_button(__("Go to {0} List", [__(frm.doc.reference_doctype)]), () =>
__("Go to {0} List", [__(frm.doc.reference_doctype)]), frappe.set_route("List", frm.doc.reference_doctype)
() => frappe.set_route("List", frm.doc.reference_doctype)
); );
} }
}, },
@@ -128,13 +112,8 @@ frappe.ui.form.on("Bank Statement Import", {
frm.disable_save(); frm.disable_save();
if (frm.doc.status !== "Success") { if (frm.doc.status !== "Success") {
if (!frm.is_new() && frm.has_import_file()) { if (!frm.is_new() && frm.has_import_file()) {
let label = let label = frm.doc.status === "Pending" ? __("Start Import") : __("Retry");
frm.doc.status === "Pending" frm.page.set_primary_action(label, () => frm.events.start_import(frm));
? __("Start Import")
: __("Retry");
frm.page.set_primary_action(label, () =>
frm.events.start_import(frm)
);
} else { } else {
frm.page.set_primary_action(__("Save"), () => frm.save()); frm.page.set_primary_action(__("Save"), () => frm.save());
} }
@@ -176,24 +155,24 @@ frappe.ui.form.on("Bank Statement Import", {
message = message =
successful_records.length > 1 successful_records.length > 1
? __( ? __(
"Successfully imported {0} records out of {1}. Click on Export Errored Rows, fix the errors and import again.", "Successfully imported {0} records out of {1}. Click on Export Errored Rows, fix the errors and import again.",
message_args message_args
) )
: __( : __(
"Successfully imported {0} record out of {1}. Click on Export Errored Rows, fix the errors and import again.", "Successfully imported {0} record out of {1}. Click on Export Errored Rows, fix the errors and import again.",
message_args message_args
); );
} else { } else {
message = message =
successful_records.length > 1 successful_records.length > 1
? __( ? __(
"Successfully updated {0} records out of {1}. Click on Export Errored Rows, fix the errors and import again.", "Successfully updated {0} records out of {1}. Click on Export Errored Rows, fix the errors and import again.",
message_args message_args
) )
: __( : __(
"Successfully updated {0} record out of {1}. Click on Export Errored Rows, fix the errors and import again.", "Successfully updated {0} record out of {1}. Click on Export Errored Rows, fix the errors and import again.",
message_args message_args
); );
} }
} }
frm.dashboard.set_headline(message); frm.dashboard.set_headline(message);
@@ -236,8 +215,7 @@ frappe.ui.form.on("Bank Statement Import", {
}, },
download_template() { download_template() {
let method = let method = "/api/method/frappe.core.doctype.data_import.data_import.download_template";
"/api/method/frappe.core.doctype.data_import.data_import.download_template";
open_url_post(method, { open_url_post(method, {
doctype: "Bank Transaction", doctype: "Bank Transaction",
@@ -250,7 +228,7 @@ frappe.ui.form.on("Bank Statement Import", {
"description", "description",
"reference_number", "reference_number",
"bank_account", "bank_account",
"currency" "currency",
], ],
}, },
}); });
@@ -321,10 +299,7 @@ frappe.ui.form.on("Bank Statement Import", {
show_import_preview(frm, preview_data) { show_import_preview(frm, preview_data) {
let import_log = JSON.parse(frm.doc.statement_import_log || "[]"); let import_log = JSON.parse(frm.doc.statement_import_log || "[]");
if ( if (frm.import_preview && frm.import_preview.doctype === frm.doc.reference_doctype) {
frm.import_preview &&
frm.import_preview.doctype === frm.doc.reference_doctype
) {
frm.import_preview.preview_data = preview_data; frm.import_preview.preview_data = preview_data;
frm.import_preview.import_log = import_log; frm.import_preview.import_log = import_log;
frm.import_preview.refresh(); frm.import_preview.refresh();
@@ -340,19 +315,10 @@ frappe.ui.form.on("Bank Statement Import", {
frm, frm,
events: { events: {
remap_column(changed_map) { remap_column(changed_map) {
let template_options = JSON.parse( let template_options = JSON.parse(frm.doc.template_options || "{}");
frm.doc.template_options || "{}" template_options.column_to_field_map = template_options.column_to_field_map || {};
); Object.assign(template_options.column_to_field_map, changed_map);
template_options.column_to_field_map = frm.set_value("template_options", JSON.stringify(template_options));
template_options.column_to_field_map || {};
Object.assign(
template_options.column_to_field_map,
changed_map
);
frm.set_value(
"template_options",
JSON.stringify(template_options)
);
frm.save().then(() => frm.trigger("import_file")); frm.save().then(() => frm.trigger("import_file"));
}, },
}, },
@@ -386,8 +352,7 @@ frappe.ui.form.on("Bank Statement Import", {
let other_warnings = []; let other_warnings = [];
for (let warning of warnings) { for (let warning of warnings) {
if (warning.row) { if (warning.row) {
warnings_by_row[warning.row] = warnings_by_row[warning.row] = warnings_by_row[warning.row] || [];
warnings_by_row[warning.row] || [];
warnings_by_row[warning.row].push(warning); warnings_by_row[warning.row].push(warning);
} else { } else {
other_warnings.push(warning); other_warnings.push(warning);
@@ -402,9 +367,7 @@ frappe.ui.form.on("Bank Statement Import", {
if (w.field) { if (w.field) {
let label = let label =
w.field.label + w.field.label +
(w.field.parent !== frm.doc.reference_doctype (w.field.parent !== frm.doc.reference_doctype ? ` (${w.field.parent})` : "");
? ` (${w.field.parent})`
: "");
return `<li>${label}: ${w.message}</li>`; return `<li>${label}: ${w.message}</li>`;
} }
return `<li>${w.message}</li>`; return `<li>${w.message}</li>`;
@@ -423,10 +386,9 @@ frappe.ui.form.on("Bank Statement Import", {
.map((warning) => { .map((warning) => {
let header = ""; let header = "";
if (warning.col) { if (warning.col) {
let column_number = `<span class="text-uppercase">${__( let column_number = `<span class="text-uppercase">${__("Column {0}", [
"Column {0}", warning.col,
[warning.col] ])}</span>`;
)}</span>`;
let column_header = columns[warning.col].header_title; let column_header = columns[warning.col].header_title;
header = `${column_number} (${column_header})`; header = `${column_number} (${column_header})`;
} }
@@ -465,36 +427,28 @@ frappe.ui.form.on("Bank Statement Import", {
let html = ""; let html = "";
if (log.success) { if (log.success) {
if (frm.doc.import_type === "Insert New Records") { if (frm.doc.import_type === "Insert New Records") {
html = __( html = __("Successfully imported {0}", [
"Successfully imported {0}", [ `<span class="underline">${frappe.utils.get_form_link(
`<span class="underline">${frappe.utils.get_form_link( frm.doc.reference_doctype,
frm.doc.reference_doctype, log.docname,
log.docname, true
true )}<span>`,
)}<span>`, ]);
]
);
} else { } else {
html = __( html = __("Successfully updated {0}", [
"Successfully updated {0}", [ `<span class="underline">${frappe.utils.get_form_link(
`<span class="underline">${frappe.utils.get_form_link( frm.doc.reference_doctype,
frm.doc.reference_doctype, log.docname,
log.docname, true
true )}<span>`,
)}<span>`, ]);
]
);
} }
} else { } else {
let messages = log.messages let messages = log.messages
.map(JSON.parse) .map(JSON.parse)
.map((m) => { .map((m) => {
let title = m.title let title = m.title ? `<strong>${m.title}</strong>` : "";
? `<strong>${m.title}</strong>` let message = m.message ? `<div>${m.message}</div>` : "";
: "";
let message = m.message
? `<div>${m.message}</div>`
: "";
return title + message; return title + message;
}) })
.join(""); .join("");

View File

@@ -202,7 +202,7 @@
], ],
"hide_toolbar": 1, "hide_toolbar": 1,
"links": [], "links": [],
"modified": "2022-09-07 11:11:40.293317", "modified": "2024-03-27 13:06:38.098765",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Bank Statement Import", "name": "Bank Statement Import",
@@ -221,7 +221,8 @@
"write": 1 "write": 1
} }
], ],
"sort_field": "modified", "sort_field": "creation",
"sort_order": "DESC", "sort_order": "DESC",
"states": [],
"track_changes": 1 "track_changes": 1
} }

View File

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

View File

@@ -1,36 +1,34 @@
let imports_in_progress = []; let imports_in_progress = [];
frappe.listview_settings['Bank Statement Import'] = { frappe.listview_settings["Bank Statement Import"] = {
onload(listview) { onload(listview) {
frappe.realtime.on('data_import_progress', data => { frappe.realtime.on("data_import_progress", (data) => {
if (!imports_in_progress.includes(data.data_import)) { if (!imports_in_progress.includes(data.data_import)) {
imports_in_progress.push(data.data_import); imports_in_progress.push(data.data_import);
} }
}); });
frappe.realtime.on('data_import_refresh', data => { frappe.realtime.on("data_import_refresh", (data) => {
imports_in_progress = imports_in_progress.filter( imports_in_progress = imports_in_progress.filter((d) => d !== data.data_import);
d => d !== data.data_import
);
listview.refresh(); listview.refresh();
}); });
}, },
get_indicator: function(doc) { get_indicator: function (doc) {
var colors = { var colors = {
'Pending': 'orange', Pending: "orange",
'Not Started': 'orange', "Not Started": "orange",
'Partial Success': 'orange', "Partial Success": "orange",
'Success': 'green', Success: "green",
'In Progress': 'orange', "In Progress": "orange",
'Error': 'red' Error: "red",
}; };
let status = doc.status; let status = doc.status;
if (imports_in_progress.includes(doc.name)) { if (imports_in_progress.includes(doc.name)) {
status = 'In Progress'; status = "In Progress";
} }
if (status == 'Pending') { if (status == "Pending") {
status = 'Not Started'; status = "Not Started";
} }
return [__(status), colors[status], 'status,=,' + doc.status]; return [__(status), colors[status], "status,=," + doc.status];
}, },
hide_name_column: true hide_name_column: true,
}; };

View File

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

View File

@@ -3,7 +3,7 @@
frappe.ui.form.on("Bank Transaction", { frappe.ui.form.on("Bank Transaction", {
onload(frm) { onload(frm) {
frm.set_query("payment_document", "payment_entries", function() { frm.set_query("payment_document", "payment_entries", function () {
const payment_doctypes = frm.events.get_payment_doctypes(frm); const payment_doctypes = frm.events.get_payment_doctypes(frm);
return { return {
filters: { filters: {
@@ -23,7 +23,7 @@ frappe.ui.form.on("Bank Transaction", {
set_bank_statement_filter(frm); set_bank_statement_filter(frm);
}, },
setup: function(frm) { setup: function (frm) {
frm.set_query("party_type", function () { frm.set_query("party_type", function () {
return { return {
filters: { filters: {
@@ -33,16 +33,10 @@ frappe.ui.form.on("Bank Transaction", {
}); });
}, },
get_payment_doctypes: function() { get_payment_doctypes: function () {
// get payment doctypes from all the apps // get payment doctypes from all the apps
return [ return ["Payment Entry", "Journal Entry", "Sales Invoice", "Purchase Invoice", "Bank Transaction"];
"Payment Entry", },
"Journal Entry",
"Sales Invoice",
"Purchase Invoice",
"Bank Transaction",
];
}
}); });
frappe.ui.form.on("Bank Transaction Payments", { frappe.ui.form.on("Bank Transaction Payments", {
@@ -54,10 +48,11 @@ frappe.ui.form.on("Bank Transaction Payments", {
const update_clearance_date = (frm, cdt, cdn) => { const update_clearance_date = (frm, cdt, cdn) => {
if (frm.doc.docstatus === 1) { if (frm.doc.docstatus === 1) {
frappe frappe
.xcall( .xcall("erpnext.accounts.doctype.bank_transaction.bank_transaction.unclear_reference_payment", {
"erpnext.accounts.doctype.bank_transaction.bank_transaction.unclear_reference_payment", doctype: cdt,
{ doctype: cdt, docname: cdn, bt_name: frm.doc.name } docname: cdn,
) bt_name: frm.doc.name,
})
.then((e) => { .then((e) => {
if (e == "success") { if (e == "success") {
frappe.show_alert({ frappe.show_alert({

View File

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

View File

@@ -1,15 +1,15 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt // License: GNU General Public License v3. See license.txt
frappe.listview_settings['Bank Transaction'] = { frappe.listview_settings["Bank Transaction"] = {
add_fields: ["unallocated_amount"], add_fields: ["unallocated_amount"],
get_indicator: function(doc) { get_indicator: function (doc) {
if(doc.docstatus == 2) { if (doc.docstatus == 2) {
return [__("Cancelled"), "red", "docstatus,=,2"]; return [__("Cancelled"), "red", "docstatus,=,2"];
} else if(flt(doc.unallocated_amount)<=0) { } else if (flt(doc.unallocated_amount) <= 0) {
return [__("Reconciled"), "green", "unallocated_amount,=,0"]; return [__("Reconciled"), "green", "unallocated_amount,=,0"];
} else if(flt(doc.unallocated_amount)>0) { } else if (flt(doc.unallocated_amount) > 0) {
return [__("Unreconciled"), "orange", "unallocated_amount,>,0"]; return [__("Unreconciled"), "orange", "unallocated_amount,>,0"];
} }
} },
}; };

View File

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

View File

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

View File

@@ -1,107 +1,38 @@
{ {
"allow_copy": 0, "actions": [],
"allow_events_in_timeline": 0, "creation": "2018-10-24 15:24:56.713277",
"allow_guest_to_view": 0, "doctype": "DocType",
"allow_import": 0, "editable_grid": 1,
"allow_rename": 0, "engine": "InnoDB",
"beta": 0, "field_order": [
"creation": "2018-10-24 15:24:56.713277", "bank_transaction_field",
"custom": 0, "file_field"
"docstatus": 0, ],
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "fieldname": "bank_transaction_field",
"allow_in_quick_entry": 0, "fieldtype": "Select",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "Field in Bank Transaction",
"collapsible": 0, "reqd": 1
"columns": 0, },
"fieldname": "bank_transaction_field",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Field in Bank Transaction",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "file_field",
"allow_in_quick_entry": 0, "fieldtype": "Data",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "Column in Bank File",
"collapsible": 0, "reqd": 1
"columns": 0,
"fieldname": "file_field",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Column in Bank File",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "istable": 1,
"hide_heading": 0, "links": [],
"hide_toolbar": 0, "modified": "2024-03-27 13:06:38.436517",
"idx": 0, "modified_by": "Administrator",
"image_view": 0, "module": "Accounts",
"in_create": 0, "name": "Bank Transaction Mapping",
"is_submittable": 0, "owner": "Administrator",
"issingle": 0, "permissions": [],
"istable": 1, "sort_field": "creation",
"max_attachments": 0, "sort_order": "DESC",
"modified": "2018-10-24 15:24:56.713277", "states": []
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Transaction Mapping",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
} }

View File

@@ -1,116 +1,39 @@
{ {
"allow_copy": 0, "actions": [],
"allow_events_in_timeline": 0, "creation": "2018-11-28 08:55:40.815355",
"allow_guest_to_view": 0, "doctype": "DocType",
"allow_import": 0, "editable_grid": 1,
"allow_rename": 0, "engine": "InnoDB",
"beta": 0, "field_order": [
"creation": "2018-11-28 08:55:40.815355", "payment_document",
"custom": 0, "payment_entry",
"docstatus": 0, "allocated_amount",
"doctype": "DocType", "clearance_date"
"document_type": "", ],
"editable_grid": 1,
"engine": "InnoDB",
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "fieldname": "payment_document",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "Payment Document",
"collapsible": 0, "options": "DocType",
"columns": 0, "reqd": 1
"fieldname": "payment_document", },
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Payment Document",
"length": 0,
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "payment_entry",
"allow_in_quick_entry": 0, "fieldtype": "Dynamic Link",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "Payment Entry",
"collapsible": 0, "options": "payment_document",
"columns": 0, "reqd": 1
"fieldname": "payment_entry", },
"fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Payment Entry",
"length": 0,
"no_copy": 0,
"options": "payment_document",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "allocated_amount",
"allow_in_quick_entry": 0, "fieldtype": "Currency",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "Allocated Amount",
"collapsible": 0, "reqd": 1
"columns": 0, },
"fieldname": "allocated_amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Allocated Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"depends_on": "eval:doc.docstatus==1", "depends_on": "eval:doc.docstatus==1",
"fieldname": "clearance_date", "fieldname": "clearance_date",
@@ -120,31 +43,18 @@
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
} }
], ],
"has_web_view": 0, "istable": 1,
"hide_heading": 0, "links": [],
"hide_toolbar": 0, "modified": "2024-03-27 13:06:38.549438",
"idx": 0, "modified_by": "Administrator",
"image_view": 0, "module": "Accounts",
"in_create": 0, "name": "Bank Transaction Payments",
"is_submittable": 0, "owner": "Administrator",
"issingle": 0, "permissions": [],
"istable": 1, "quick_entry": 1,
"max_attachments": 0, "sort_field": "creation",
"modified": "2020-01-22 00:00:00.000000", "sort_order": "DESC",
"modified_by": "Administrator", "states": [],
"module": "Accounts", "track_changes": 1
"name": "Bank Transaction Payments", }
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
}

View File

@@ -6,18 +6,18 @@ frappe.ui.form.on("Bisect Accounting Statements", {
frm.trigger("render_heatmap"); frm.trigger("render_heatmap");
}, },
refresh(frm) { refresh(frm) {
frm.add_custom_button(__('Bisect Left'), () => { frm.add_custom_button(__("Bisect Left"), () => {
frm.trigger("bisect_left"); frm.trigger("bisect_left");
}); });
frm.add_custom_button(__('Bisect Right'), () => { frm.add_custom_button(__("Bisect Right"), () => {
frm.trigger("bisect_right"); frm.trigger("bisect_right");
}); });
frm.add_custom_button(__('Up'), () => { frm.add_custom_button(__("Up"), () => {
frm.trigger("move_up"); frm.trigger("move_up");
}); });
frm.add_custom_button(__('Build Tree'), () => { frm.add_custom_button(__("Build Tree"), () => {
frm.trigger("build_tree"); frm.trigger("build_tree");
}); });
}, },
@@ -26,16 +26,16 @@ frappe.ui.form.on("Bisect Accounting Statements", {
bisect_heatmap.addClass("bisect_heatmap_location"); bisect_heatmap.addClass("bisect_heatmap_location");
// milliseconds in a day // milliseconds in a day
let msiad=24*60*60*1000; let msiad = 24 * 60 * 60 * 1000;
let datapoints = {}; let datapoints = {};
let fr_dt = new Date(frm.doc.from_date).getTime(); let fr_dt = new Date(frm.doc.from_date).getTime();
let to_dt = new Date(frm.doc.to_date).getTime(); let to_dt = new Date(frm.doc.to_date).getTime();
let bisect_start = new Date(frm.doc.current_from_date).getTime(); let bisect_start = new Date(frm.doc.current_from_date).getTime();
let bisect_end = new Date(frm.doc.current_to_date).getTime(); let bisect_end = new Date(frm.doc.current_to_date).getTime();
for(let x=fr_dt; x <= to_dt; x+=msiad){ for (let x = fr_dt; x <= to_dt; x += msiad) {
let epoch_in_seconds = x/1000; let epoch_in_seconds = x / 1000;
if ((bisect_start <= x) && (x <= bisect_end )) { if (bisect_start <= x && x <= bisect_end) {
datapoints[epoch_in_seconds] = 1.0; datapoints[epoch_in_seconds] = 1.0;
} else { } else {
datapoints[epoch_in_seconds] = 0.0; datapoints[epoch_in_seconds] = 0.0;
@@ -49,19 +49,19 @@ frappe.ui.form.on("Bisect Accounting Statements", {
start: new Date(frm.doc.from_date), start: new Date(frm.doc.from_date),
end: new Date(frm.doc.to_date), end: new Date(frm.doc.to_date),
}, },
countLabel: 'Bisecting', countLabel: "Bisecting",
discreteDomains: 1, discreteDomains: 1,
}); });
}, },
bisect_left(frm) { bisect_left(frm) {
frm.call({ frm.call({
doc: frm.doc, doc: frm.doc,
method: 'bisect_left', method: "bisect_left",
freeze: true, freeze: true,
freeze_message: __("Bisecting Left ..."), freeze_message: __("Bisecting Left ..."),
callback: (r) => { callback: (r) => {
frm.trigger("render_heatmap"); frm.trigger("render_heatmap");
} },
}); });
}, },
bisect_right(frm) { bisect_right(frm) {
@@ -69,10 +69,10 @@ frappe.ui.form.on("Bisect Accounting Statements", {
doc: frm.doc, doc: frm.doc,
freeze: true, freeze: true,
freeze_message: __("Bisecting Right ..."), freeze_message: __("Bisecting Right ..."),
method: 'bisect_right', method: "bisect_right",
callback: (r) => { callback: (r) => {
frm.trigger("render_heatmap"); frm.trigger("render_heatmap");
} },
}); });
}, },
move_up(frm) { move_up(frm) {
@@ -80,10 +80,10 @@ frappe.ui.form.on("Bisect Accounting Statements", {
doc: frm.doc, doc: frm.doc,
freeze: true, freeze: true,
freeze_message: __("Moving up in tree ..."), freeze_message: __("Moving up in tree ..."),
method: 'move_up', method: "move_up",
callback: (r) => { callback: (r) => {
frm.trigger("render_heatmap"); frm.trigger("render_heatmap");
} },
}); });
}, },
build_tree(frm) { build_tree(frm) {
@@ -91,10 +91,10 @@ frappe.ui.form.on("Bisect Accounting Statements", {
doc: frm.doc, doc: frm.doc,
freeze: true, freeze: true,
freeze_message: __("Rebuilding BTree for period ..."), freeze_message: __("Rebuilding BTree for period ..."),
method: 'build_tree', method: "build_tree",
callback: (r) => { callback: (r) => {
frm.trigger("render_heatmap"); frm.trigger("render_heatmap");
} },
}); });
}, },
}); });

View File

@@ -170,7 +170,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2023-12-01 16:49:54.073890", "modified": "2024-03-27 13:06:39.619458",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Bisect Accounting Statements", "name": "Bisect Accounting Statements",
@@ -188,7 +188,7 @@
} }
], ],
"read_only": 1, "read_only": 1,
"sort_field": "modified", "sort_field": "creation",
"sort_order": "DESC", "sort_order": "DESC",
"states": [] "states": []
} }

View File

@@ -138,10 +138,11 @@ class BisectAccountingStatements(Document):
# set root as current node # set root as current node
root = frappe.db.get_all("Bisect Nodes", filters={"root": ["is", "not set"]})[0] root = frappe.db.get_all("Bisect Nodes", filters={"root": ["is", "not set"]})[0]
self.get_report_summary()
self.current_node = root.name self.current_node = root.name
self.current_from_date = self.from_date self.current_from_date = self.from_date
self.current_to_date = self.to_date self.current_to_date = self.to_date
self.get_report_summary()
self.save() self.save()
def get_report_summary(self): def get_report_summary(self):

View File

@@ -70,7 +70,7 @@
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2023-12-01 17:46:12.437996", "modified": "2024-03-27 13:06:39.766063",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Bisect Nodes", "name": "Bisect Nodes",
@@ -91,7 +91,7 @@
} }
], ],
"read_only": 1, "read_only": 1,
"sort_field": "modified", "sort_field": "creation",
"sort_order": "DESC", "sort_order": "DESC",
"states": [] "states": []
} }

View File

@@ -2,48 +2,48 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.provide("erpnext.accounts.dimensions"); frappe.provide("erpnext.accounts.dimensions");
frappe.ui.form.on('Budget', { frappe.ui.form.on("Budget", {
onload: function(frm) { onload: function (frm) {
frm.set_query("account", "accounts", function() { frm.set_query("account", "accounts", function () {
return { return {
filters: { filters: {
company: frm.doc.company, company: frm.doc.company,
report_type: "Profit and Loss", report_type: "Profit and Loss",
is_group: 0 is_group: 0,
} },
}; };
}); });
frm.set_query("monthly_distribution", function() { frm.set_query("monthly_distribution", function () {
return { return {
filters: { filters: {
fiscal_year: frm.doc.fiscal_year fiscal_year: frm.doc.fiscal_year,
} },
}; };
}); });
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype); erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
}, },
refresh: function(frm) { refresh: function (frm) {
frm.trigger("toggle_reqd_fields") frm.trigger("toggle_reqd_fields");
}, },
budget_against: function(frm) { budget_against: function (frm) {
frm.trigger("set_null_value") frm.trigger("set_null_value");
frm.trigger("toggle_reqd_fields") frm.trigger("toggle_reqd_fields");
}, },
set_null_value: function(frm) { set_null_value: function (frm) {
if(frm.doc.budget_against == 'Cost Center') { if (frm.doc.budget_against == "Cost Center") {
frm.set_value('project', null) frm.set_value("project", null);
} else { } else {
frm.set_value('cost_center', null) frm.set_value("cost_center", null);
} }
}, },
toggle_reqd_fields: function(frm) { toggle_reqd_fields: function (frm) {
frm.toggle_reqd("cost_center", frm.doc.budget_against=="Cost Center"); frm.toggle_reqd("cost_center", frm.doc.budget_against == "Cost Center");
frm.toggle_reqd("project", frm.doc.budget_against=="Project"); frm.toggle_reqd("project", frm.doc.budget_against == "Project");
} },
}); });

View File

@@ -207,7 +207,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2022-10-10 22:14:36.361509", "modified": "2024-03-27 13:06:42.675933",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Budget", "name": "Budget",
@@ -231,7 +231,7 @@
"write": 1 "write": 1
} }
], ],
"sort_field": "modified", "sort_field": "creation",
"sort_order": "DESC", "sort_order": "DESC",
"states": [], "states": [],
"track_changes": 1 "track_changes": 1

View File

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

View File

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

View File

@@ -1,94 +1,42 @@
{ {
"allow_copy": 0, "actions": [],
"allow_import": 0, "creation": "2016-05-16 11:54:09.286135",
"allow_rename": 0, "doctype": "DocType",
"beta": 0, "editable_grid": 1,
"creation": "2016-05-16 11:54:09.286135", "engine": "InnoDB",
"custom": 0, "field_order": [
"docstatus": 0, "account",
"doctype": "DocType", "budget_amount"
"document_type": "", ],
"editable_grid": 1,
"engine": "InnoDB",
"fields": [ "fields": [
{ {
"allow_on_submit": 0, "fieldname": "account",
"bold": 0, "fieldtype": "Link",
"collapsible": 0, "in_list_view": 1,
"columns": 0, "label": "Account",
"fieldname": "account", "options": "Account",
"fieldtype": "Link", "reqd": 1,
"hidden": 0, "search_index": 1
"ignore_user_permissions": 0, },
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Account",
"length": 0,
"no_copy": 0,
"options": "Account",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_on_submit": 0, "fieldname": "budget_amount",
"bold": 0, "fieldtype": "Currency",
"collapsible": 0, "in_list_view": 1,
"columns": 0, "label": "Budget Amount",
"fieldname": "budget_amount", "options": "Company:company:default_currency",
"fieldtype": "Currency", "reqd": 1
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Budget Amount",
"length": 0,
"no_copy": 0,
"options": "Company:company:default_currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
} }
], ],
"hide_heading": 0, "istable": 1,
"hide_toolbar": 0, "links": [],
"idx": 0, "modified": "2024-03-27 13:06:42.854458",
"image_view": 0, "modified_by": "Administrator",
"in_create": 0, "module": "Accounts",
"name": "Budget Account",
"is_submittable": 0, "owner": "Administrator",
"issingle": 0, "permissions": [],
"istable": 1, "quick_entry": 1,
"max_attachments": 0, "sort_field": "creation",
"modified": "2017-01-02 17:02:53.339420", "sort_order": "DESC",
"modified_by": "Administrator", "states": []
"module": "Accounts",
"name": "Budget Account",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_seen": 0
} }

View File

@@ -19,13 +19,14 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-05-07 10:43:49.717633", "modified": "2024-03-27 13:06:44.142625",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Campaign Item", "name": "Campaign Item",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],
"sort_field": "modified", "sort_field": "creation",
"sort_order": "DESC", "sort_order": "DESC",
"states": [],
"track_changes": 1 "track_changes": 1
} }

View File

@@ -1,11 +1,10 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt // License: GNU General Public License v3. See license.txt
frappe.ui.form.on('Cashier Closing', { frappe.ui.form.on("Cashier Closing", {
setup: function (frm) {
setup: function(frm){
if (frm.doc.user == "" || frm.doc.user == null) { if (frm.doc.user == "" || frm.doc.user == null) {
frm.doc.user = frappe.session.user; frm.doc.user = frappe.session.user;
} }
} },
}); });

View File

@@ -124,7 +124,7 @@
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2023-12-28 13:15:46.858427", "modified": "2024-03-27 13:06:44.260440",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Cashier Closing", "name": "Cashier Closing",
@@ -145,7 +145,7 @@
"write": 1 "write": 1
} }
], ],
"sort_field": "modified", "sort_field": "creation",
"sort_order": "DESC", "sort_order": "DESC",
"states": [], "states": [],
"track_changes": 1 "track_changes": 1

View File

@@ -1,109 +1,43 @@
{ {
"allow_copy": 0, "actions": [],
"allow_events_in_timeline": 0, "creation": "2018-09-02 14:45:36.303520",
"allow_guest_to_view": 0, "doctype": "DocType",
"allow_import": 0, "editable_grid": 1,
"allow_rename": 0, "engine": "InnoDB",
"beta": 0, "field_order": [
"creation": "2018-09-02 14:45:36.303520", "mode_of_payment",
"custom": 0, "amount"
"docstatus": 0, ],
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "fieldname": "mode_of_payment",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "in_filter": 1,
"bold": 0, "in_list_view": 1,
"collapsible": 0, "in_standard_filter": 1,
"columns": 0, "label": "Mode of Payment",
"fieldname": "mode_of_payment", "options": "Mode of Payment",
"fieldtype": "Link", "reqd": 1
"hidden": 0, },
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 1,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Mode of Payment",
"length": 0,
"no_copy": 0,
"options": "Mode of Payment",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "default": "0.00",
"allow_in_quick_entry": 0, "fieldname": "amount",
"allow_on_submit": 0, "fieldtype": "Float",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Amount"
"columns": 0,
"default": "0.00",
"fieldname": "amount",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "istable": 1,
"hide_heading": 0, "links": [],
"hide_toolbar": 0, "modified": "2024-03-27 13:06:44.414987",
"idx": 0, "modified_by": "Administrator",
"image_view": 0, "module": "Accounts",
"in_create": 0, "name": "Cashier Closing Payments",
"is_submittable": 0, "owner": "Administrator",
"issingle": 0, "permissions": [],
"istable": 1, "quick_entry": 1,
"max_attachments": 0, "sort_field": "creation",
"modified": "2019-02-19 08:34:20.268037", "sort_order": "DESC",
"modified_by": "Administrator", "states": [],
"module": "Accounts", "track_changes": 1
"name": "Cashier Closing Payments",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
} }

View File

@@ -1,4 +1,4 @@
frappe.ui.form.on('Chart of Accounts Importer', { frappe.ui.form.on("Chart of Accounts Importer", {
onload: function (frm) { onload: function (frm) {
frm.set_value("company", ""); frm.set_value("company", "");
frm.set_value("import_file", ""); frm.set_value("import_file", "");
@@ -8,31 +8,34 @@ frappe.ui.form.on('Chart of Accounts Importer', {
frm.disable_save(); frm.disable_save();
// make company mandatory // make company mandatory
frm.set_df_property('company', 'reqd', frm.doc.company ? 0 : 1); frm.set_df_property("company", "reqd", frm.doc.company ? 0 : 1);
frm.set_df_property('import_file_section', 'hidden', frm.doc.company ? 0 : 1); frm.set_df_property("import_file_section", "hidden", frm.doc.company ? 0 : 1);
if (frm.doc.import_file) { if (frm.doc.import_file) {
frappe.run_serially([ frappe.run_serially([
() => generate_tree_preview(frm), () => generate_tree_preview(frm),
() => create_import_button(frm), () => create_import_button(frm),
() => frm.set_df_property('chart_preview', 'hidden', 0) () => frm.set_df_property("chart_preview", "hidden", 0),
]); ]);
} }
frm.set_df_property('chart_preview', 'hidden', frm.set_df_property(
$(frm.fields_dict['chart_tree'].wrapper).html()!="" ? 0 : 1); "chart_preview",
"hidden",
$(frm.fields_dict["chart_tree"].wrapper).html() != "" ? 0 : 1
);
}, },
download_template: function(frm) { download_template: function (frm) {
var d = new frappe.ui.Dialog({ var d = new frappe.ui.Dialog({
title: __("Download Template"), title: __("Download Template"),
fields: [ fields: [
{ {
label : "File Type", label: "File Type",
fieldname: "file_type", fieldname: "file_type",
fieldtype: "Select", fieldtype: "Select",
reqd: 1, reqd: 1,
options: ["Excel", "CSV"] options: ["Excel", "CSV"],
}, },
{ {
label: "Template Type", label: "Template Type",
@@ -41,21 +44,27 @@ frappe.ui.form.on('Chart of Accounts Importer', {
reqd: 1, reqd: 1,
options: ["Sample Template", "Blank Template"], options: ["Sample Template", "Blank Template"],
change: () => { change: () => {
let template_type = d.get_value('template_type'); let template_type = d.get_value("template_type");
if (template_type === "Sample Template") { if (template_type === "Sample Template") {
d.set_df_property('template_type', 'description', d.set_df_property(
"template_type",
"description",
`The Sample Template contains all the required accounts pre filled in the template. `The Sample Template contains all the required accounts pre filled in the template.
You can add more accounts or change existing accounts in the template as per your choice.`); You can add more accounts or change existing accounts in the template as per your choice.`
);
} else { } else {
d.set_df_property('template_type', 'description', d.set_df_property(
"template_type",
"description",
`The Blank Template contains just the account type and root type required to build the Chart `The Blank Template contains just the account type and root type required to build the Chart
of Accounts. Please enter the account names and add more rows as per your requirement.`); of Accounts. Please enter the account names and add more rows as per your requirement.`
);
} }
} },
}, },
{ {
label : "Company", label: "Company",
fieldname: "company", fieldname: "company",
fieldtype: "Link", fieldtype: "Link",
reqd: 1, reqd: 1,
@@ -63,25 +72,25 @@ frappe.ui.form.on('Chart of Accounts Importer', {
default: frm.doc.company, default: frm.doc.company,
}, },
], ],
primary_action: function() { primary_action: function () {
let data = d.get_values(); let data = d.get_values();
if (!data.template_type) { if (!data.template_type) {
frappe.throw(__('Please select <b>Template Type</b> to download template')); frappe.throw(__("Please select <b>Template Type</b> to download template"));
} }
open_url_post( open_url_post(
'/api/method/erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.download_template', "/api/method/erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.download_template",
{ {
file_type: data.file_type, file_type: data.file_type,
template_type: data.template_type, template_type: data.template_type,
company: data.company company: data.company,
} }
); );
d.hide(); d.hide();
}, },
primary_action_label: __('Download') primary_action_label: __("Download"),
}); });
d.show(); d.show();
}, },
@@ -89,7 +98,7 @@ frappe.ui.form.on('Chart of Accounts Importer', {
import_file: function (frm) { import_file: function (frm) {
if (!frm.doc.import_file) { if (!frm.doc.import_file) {
frm.page.set_indicator(""); frm.page.set_indicator("");
$(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper on removing file $(frm.fields_dict["chart_tree"].wrapper).empty(); // empty wrapper on removing file
} }
}, },
@@ -99,89 +108,97 @@ frappe.ui.form.on('Chart of Accounts Importer', {
frappe.call({ frappe.call({
method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.validate_company", method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.validate_company",
args: { args: {
company: frm.doc.company company: frm.doc.company,
}, },
callback: function(r) { callback: function (r) {
if(r.message===false) { if (r.message === false) {
frm.set_value("company", ""); frm.set_value("company", "");
frappe.throw(__("Transactions against the Company already exist! Chart of Accounts can only be imported for a Company with no transactions.")); frappe.throw(
__(
"Transactions against the Company already exist! Chart of Accounts can only be imported for a Company with no transactions."
)
);
} else { } else {
frm.trigger("refresh"); frm.trigger("refresh");
} }
} },
}); });
} }
} },
}); });
var create_import_button = function(frm) { var create_import_button = function (frm) {
frm.page.set_primary_action(__("Import"), function () { frm.page
.set_primary_action(__("Import"), function () {
return frappe.call({
method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.import_coa",
args: {
file_name: frm.doc.import_file,
company: frm.doc.company,
},
freeze: true,
freeze_message: __("Creating Accounts..."),
callback: function (r) {
if (!r.exc) {
clearInterval(frm.page["interval"]);
frm.page.set_indicator(__("Import Successful"), "blue");
create_reset_button(frm);
}
},
});
})
.addClass("btn btn-primary");
};
var create_reset_button = function (frm) {
frm.page
.set_primary_action(__("Reset"), function () {
frm.page.clear_primary_action();
delete frm.page["show_import_button"];
frm.reload_doc();
})
.addClass("btn btn-primary");
};
var validate_coa = function (frm) {
if (frm.doc.import_file) {
let parent = __("All Accounts");
return frappe.call({ return frappe.call({
method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.import_coa", method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.get_coa",
args: { args: {
file_name: frm.doc.import_file,
company: frm.doc.company
},
freeze: true,
freeze_message: __("Creating Accounts..."),
callback: function(r) {
if (!r.exc) {
clearInterval(frm.page["interval"]);
frm.page.set_indicator(__('Import Successful'), 'blue');
create_reset_button(frm);
}
}
});
}).addClass('btn btn-primary');
};
var create_reset_button = function(frm) {
frm.page.set_primary_action(__("Reset"), function () {
frm.page.clear_primary_action();
delete frm.page["show_import_button"];
frm.reload_doc();
}).addClass('btn btn-primary');
};
var validate_coa = function(frm) {
if (frm.doc.import_file) {
let parent = __('All Accounts');
return frappe.call({
'method': 'erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.get_coa',
'args': {
file_name: frm.doc.import_file, file_name: frm.doc.import_file,
parent: parent, parent: parent,
doctype: 'Chart of Accounts Importer', doctype: "Chart of Accounts Importer",
file_type: frm.doc.file_type, file_type: frm.doc.file_type,
for_validate: 1 for_validate: 1,
}, },
callback: function(r) { callback: function (r) {
if (r.message['show_import_button']) { if (r.message["show_import_button"]) {
frm.page['show_import_button'] = Boolean(r.message['show_import_button']); frm.page["show_import_button"] = Boolean(r.message["show_import_button"]);
} }
} },
}); });
} }
}; };
var generate_tree_preview = function(frm) { var generate_tree_preview = function (frm) {
let parent = __('All Accounts'); let parent = __("All Accounts");
$(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper to load new data $(frm.fields_dict["chart_tree"].wrapper).empty(); // empty wrapper to load new data
// generate tree structure based on the csv data // generate tree structure based on the csv data
return new frappe.ui.Tree({ return new frappe.ui.Tree({
parent: $(frm.fields_dict['chart_tree'].wrapper), parent: $(frm.fields_dict["chart_tree"].wrapper),
label: parent, label: parent,
expandable: true, expandable: true,
method: 'erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.get_coa', method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.get_coa",
args: { args: {
file_name: frm.doc.import_file, file_name: frm.doc.import_file,
parent: parent, parent: parent,
doctype: 'Chart of Accounts Importer', doctype: "Chart of Accounts Importer",
file_type: frm.doc.file_type file_type: frm.doc.file_type,
}, },
onclick: function(node) { onclick: function (node) {
parent = node.value; parent = node.value;
} },
}); });
}; };

View File

@@ -50,7 +50,7 @@
"in_create": 1, "in_create": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2020-02-28 08:49:11.422846", "modified": "2024-03-27 13:06:44.535780",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Chart of Accounts Importer", "name": "Chart of Accounts Importer",
@@ -66,6 +66,7 @@
], ],
"quick_entry": 1, "quick_entry": 1,
"read_only": 1, "read_only": 1,
"sort_field": "modified", "sort_field": "creation",
"sort_order": "DESC" "sort_order": "DESC",
"states": []
} }

View File

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

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