Compare commits

..

2 Commits

Author SHA1 Message Date
Ankush Menat
e8571bac00 fix: Correctly extract last message
frappe.message_log now contains plain dictionary and not JSON strings,
so no need to load them.
2023-10-20 15:18:04 +05:30
Ankush Menat
40cdde8820 ci: seutp v15 config
https://github.com/frappe/frappe/issues/22817
2023-10-19 15:55:02 +05:30
1983 changed files with 43071 additions and 116763 deletions

View File

@@ -9,13 +9,6 @@ trim_trailing_whitespace = true
charset = utf-8 charset = utf-8
# python, js indentation settings # python, js indentation settings
[{*.py,*.js,*.vue,*.css,*.scss,*.html}] [{*.py,*.js}]
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,7 +124,6 @@
"beforeEach": true, "beforeEach": true,
"onScan": true, "onScan": true,
"extend_cscript": true, "extend_cscript": true,
"localforage": true, "localforage": true
"Plaid": true
} }
} }

View File

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

View File

@@ -10,7 +10,6 @@ WEBSITE_REPOS = [
DOCUMENTATION_DOMAINS = [ DOCUMENTATION_DOMAINS = [
"docs.erpnext.com", "docs.erpnext.com",
"docs.frappe.io",
"frappeframework.com", "frappeframework.com",
] ]

View File

@@ -6,7 +6,7 @@ cd ~ || exit
sudo apt update sudo apt update
sudo apt remove mysql-server mysql-client sudo apt remove mysql-server mysql-client
sudo apt install libcups2-dev redis-server mariadb-client sudo apt install libcups2-dev redis-server mariadb-client-10.6
pip install frappe-bench pip install frappe-bench
@@ -44,9 +44,13 @@ fi
install_whktml() { install_whktml() {
if [ "$(lsb_release -rs)" = "22.04" ]; then
wget -O /tmp/wkhtmltox.deb https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-2/wkhtmltox_0.12.6.1-2.jammy_amd64.deb wget -O /tmp/wkhtmltox.deb https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-2/wkhtmltox_0.12.6.1-2.jammy_amd64.deb
sudo apt install /tmp/wkhtmltox.deb sudo apt install /tmp/wkhtmltox.deb
else
echo "Please update this script to support wkhtmltopdf for $(lsb_release -ds)"
exit 1
fi
} }
install_whktml & install_whktml &
wkpid=$! wkpid=$!

60
.github/helper/translation.py vendored Normal file
View File

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

26
.github/workflows/backport.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: Backport
on:
pull_request_target:
types:
- closed
- labeled
jobs:
main:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout Actions
uses: actions/checkout@v2
with:
repository: "frappe/backport"
path: ./actions
ref: develop
- name: Install Actions
run: npm install --production --prefix ./actions
- name: Run backport
uses: ./actions/backport
with:
token: ${{secrets.BACKPORT_BOT_TOKEN}}
labelsToAdd: "backport"
title: "{{originalTitle}}"

32
.github/workflows/initiate_release.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
# This workflow is agnostic to branches. Only maintain on develop branch.
# To add/remove versions just modify the matrix.
name: Create weekly release pull requests
on:
schedule:
# 9:30 UTC => 3 PM IST Tuesday
- cron: "30 9 * * 2"
workflow_dispatch:
jobs:
stable-release:
name: Release
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
version: ["13", "14"]
steps:
- uses: octokit/request-action@v2.x
with:
route: POST /repos/{owner}/{repo}/pulls
owner: frappe
repo: erpnext
title: |-
"chore: release v${{ matrix.version }}"
body: "Automated weekly release."
base: version-${{ matrix.version }}
head: version-${{ matrix.version }}-hotfix
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}

View File

@@ -134,6 +134,7 @@ jobs:
} }
update_to_version 14 update_to_version 14
update_to_version 15
echo "Updating to latest version" echo "Updating to latest version"
git -C "apps/frappe" checkout -q -f "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}" git -C "apps/frappe" checkout -q -f "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}"

View File

@@ -2,23 +2,21 @@ name: Generate Semantic Release
on: on:
push: push:
branches: branches:
- version-15 - version-13
jobs: jobs:
release: release:
name: Release name: Release
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout Entire Repository - name: Checkout Entire Repository
uses: actions/checkout@v4 uses: actions/checkout@v2
with: with:
fetch-depth: 0 fetch-depth: 0
persist-credentials: false persist-credentials: false
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v2 uses: actions/setup-node@v2
with: with:
node-version: 20 node-version: 18
- name: Setup dependencies - name: Setup dependencies
run: | run: |
npm install @semantic-release/git @semantic-release/exec --no-save npm install @semantic-release/git @semantic-release/exec --no-save

View File

@@ -117,7 +117,7 @@ jobs:
FRAPPE_BRANCH: ${{ github.event.inputs.branch }} FRAPPE_BRANCH: ${{ github.event.inputs.branch }}
- name: Run Tests - name: Run Tests
run: 'cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --total-builds 4 --build-number ${{ matrix.container }}' run: 'cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --with-coverage --total-builds 4 --build-number ${{ matrix.container }}'
env: env:
TYPE: server TYPE: server
CI_BUILD_ID: ${{ github.run_id }} CI_BUILD_ID: ${{ github.run_id }}
@@ -126,3 +126,27 @@ jobs:
- name: Show bench output - name: Show bench output
if: ${{ always() }} if: ${{ always() }}
run: cat ~/frappe-bench/bench_start.log || true run: cat ~/frappe-bench/bench_start.log || true
- name: Upload coverage data
uses: actions/upload-artifact@v3
with:
name: coverage-${{ matrix.container }}
path: /home/runner/frappe-bench/sites/coverage.xml
coverage:
name: Coverage Wrap Up
needs: test
runs-on: ubuntu-latest
steps:
- name: Clone
uses: actions/checkout@v2
- name: Download artifacts
uses: actions/download-artifact@v3
- name: Upload coverage data
uses: codecov/codecov-action@v2
with:
name: MariaDB
fail_ci_if_error: true
verbose: true

View File

@@ -17,6 +17,7 @@ pull_request_rules:
- base=version-12 - base=version-12
- base=version-14 - base=version-14
- base=version-15 - base=version-15
- base=version-16
actions: actions:
close: close:
comment: comment:
@@ -24,16 +25,6 @@ pull_request_rules:
@{{author}}, thanks for the contribution, but we do not accept pull requests on a stable branch. Please raise PR on an appropriate hotfix branch. @{{author}}, thanks for the contribution, but we do not accept pull requests on a stable branch. Please raise PR on an appropriate hotfix branch.
https://github.com/frappe/erpnext/wiki/Pull-Request-Checklist#which-branch https://github.com/frappe/erpnext/wiki/Pull-Request-Checklist#which-branch
- name: Auto-close PRs on pre-release branch
conditions:
- base=version-13-pre-release
actions:
close:
comment:
message: |
@{{author}}, pre-release branch is not maintained anymore. Releases are directly done by merging hotfix branch to stable branches.
- name: backport to develop - name: backport to develop
conditions: conditions:
- label="backport develop" - label="backport develop"
@@ -54,13 +45,13 @@ pull_request_rules:
assignees: assignees:
- "{{ author }}" - "{{ author }}"
- name: backport to version-14-pre-release - name: backport to version-15-hotfix
conditions: conditions:
- label="backport version-14-pre-release" - label="backport version-15-hotfix"
actions: actions:
backport: backport:
branches: branches:
- version-14-pre-release - version-15-hotfix
assignees: assignees:
- "{{ author }}" - "{{ author }}"
@@ -74,35 +65,6 @@ pull_request_rules:
assignees: assignees:
- "{{ author }}" - "{{ author }}"
- name: backport to version-13-pre-release
conditions:
- label="backport version-13-pre-release"
actions:
backport:
branches:
- version-13-pre-release
assignees:
- "{{ author }}"
- name: backport to version-12-hotfix
conditions:
- label="backport version-12-hotfix"
actions:
backport:
branches:
- version-12-hotfix
assignees:
- "{{ author }}"
- name: backport to version-12-pre-release
conditions:
- label="backport version-12-pre-release"
actions:
backport:
branches:
- version-12-pre-release
assignees:
- "{{ author }}"
- name: Automatic merge on CI success and review - name: Automatic merge on CI success and review
conditions: conditions:

View File

@@ -5,7 +5,7 @@ fail_fast: false
repos: repos:
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0 rev: v4.0.1
hooks: hooks:
- id: trailing-whitespace - id: trailing-whitespace
files: "erpnext.*" files: "erpnext.*"
@@ -15,27 +15,6 @@ repos:
args: ['--branch', 'develop'] args: ['--branch', 'develop']
- id: check-merge-conflict - id: check-merge-conflict
- id: check-ast - id: check-ast
- id: check-json
- id: check-toml
- id: check-yaml
- 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
@@ -55,18 +34,29 @@ repos:
erpnext/templates/includes/.* erpnext/templates/includes/.*
)$ )$
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/PyCQA/flake8
rev: v0.2.0 rev: 6.0.0
hooks: hooks:
- id: ruff - id: flake8
name: "Run ruff import sorter" additional_dependencies: [
args: ["--select=I", "--fix"] 'flake8-bugbear',
'flake8-tuple',
]
args: ['--config', '.github/helper/.flake8_strict']
exclude: ".*setup.py$"
- id: ruff - repo: https://github.com/adityahase/black
name: "Run ruff linter" rev: 9cb0a69f4d0030cdf687eddf314468b39ed54119
hooks:
- id: black
additional_dependencies: ['click==8.0.4']
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:
- id: isort
exclude: ".*setup.py$"
- id: ruff-format
name: "Run ruff formatter"
ci: ci:
autoupdate_schedule: weekly autoupdate_schedule: weekly

View File

@@ -1,5 +1,5 @@
{ {
"branches": ["version-15"], "branches": ["version-13"],
"plugins": [ "plugins": [
"@semantic-release/commit-analyzer", { "@semantic-release/commit-analyzer", {
"preset": "angular", "preset": "angular",

View File

@@ -4,21 +4,21 @@
# the repo. Unless a later match takes precedence, # the repo. Unless a later match takes precedence,
erpnext/accounts/ @deepeshgarg007 @ruthra-kumar erpnext/accounts/ @deepeshgarg007 @ruthra-kumar
erpnext/assets/ @khushi8112 @deepeshgarg007 erpnext/assets/ @anandbaburajan @deepeshgarg007
erpnext/regional @deepeshgarg007 @ruthra-kumar erpnext/regional @deepeshgarg007 @ruthra-kumar
erpnext/selling @deepeshgarg007 @ruthra-kumar erpnext/selling @deepeshgarg007 @ruthra-kumar
erpnext/support/ @deepeshgarg007 erpnext/support/ @deepeshgarg007
pos* pos*
erpnext/buying/ @rohitwaghchaure erpnext/buying/ @rohitwaghchaure @s-aga-r
erpnext/maintenance/ @rohitwaghchaure erpnext/maintenance/ @rohitwaghchaure @s-aga-r
erpnext/manufacturing/ @rohitwaghchaure erpnext/manufacturing/ @rohitwaghchaure @s-aga-r
erpnext/quality_management/ @rohitwaghchaure erpnext/quality_management/ @rohitwaghchaure @s-aga-r
erpnext/stock/ @rohitwaghchaure erpnext/stock/ @rohitwaghchaure @s-aga-r
erpnext/subcontracting @rohitwaghchaure erpnext/subcontracting @rohitwaghchaure @s-aga-r
erpnext/controllers/ @deepeshgarg007 @rohitwaghchaure erpnext/controllers/ @deepeshgarg007 @rohitwaghchaure
erpnext/patches/ @deepeshgarg007 erpnext/patches/ @deepeshgarg007
.github/ @deepeshgarg007 .github/ @deepeshgarg007
pyproject.toml @akhilnarang pyproject.toml @phot0n

View File

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

@@ -2,9 +2,8 @@ import functools
import inspect import inspect
import frappe import frappe
from frappe.utils.user import is_website_user
__version__ = "15.45.4" __version__ = "15.0.0-dev"
def get_default_company(user=None): def get_default_company(user=None):
@@ -14,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("company", user) companies = get_user_default_as_list(user, "company")
if companies: if companies:
default_company = companies[0] default_company = companies[0]
else: else:
@@ -37,8 +36,10 @@ 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 not company in frappe.flags.company_cost_center:
frappe.flags.company_cost_center[company] = frappe.get_cached_value("Company", company, "cost_center") frappe.flags.company_cost_center[company] = frappe.get_cached_value(
"Company", company, "cost_center"
)
return frappe.flags.company_cost_center[company] return frappe.flags.company_cost_center[company]
@@ -46,7 +47,7 @@ def get_company_currency(company):
"""Returns the default company currency""" """Returns the default company currency"""
if not frappe.flags.company_currency: if not frappe.flags.company_currency:
frappe.flags.company_currency = {} frappe.flags.company_currency = {}
if company not in frappe.flags.company_currency: if not company in frappe.flags.company_currency:
frappe.flags.company_currency[company] = frappe.db.get_value( frappe.flags.company_currency[company] = frappe.db.get_value(
"Company", company, "default_currency", cache=True "Company", company, "default_currency", cache=True
) )
@@ -80,7 +81,7 @@ def is_perpetual_inventory_enabled(company):
if not hasattr(frappe.local, "enable_perpetual_inventory"): if not hasattr(frappe.local, "enable_perpetual_inventory"):
frappe.local.enable_perpetual_inventory = {} frappe.local.enable_perpetual_inventory = {}
if company not in frappe.local.enable_perpetual_inventory: if not company in frappe.local.enable_perpetual_inventory:
frappe.local.enable_perpetual_inventory[company] = ( frappe.local.enable_perpetual_inventory[company] = (
frappe.get_cached_value("Company", company, "enable_perpetual_inventory") or 0 frappe.get_cached_value("Company", company, "enable_perpetual_inventory") or 0
) )
@@ -95,7 +96,7 @@ def get_default_finance_book(company=None):
if not hasattr(frappe.local, "default_finance_book"): if not hasattr(frappe.local, "default_finance_book"):
frappe.local.default_finance_book = {} frappe.local.default_finance_book = {}
if company not in frappe.local.default_finance_book: if not company in frappe.local.default_finance_book:
frappe.local.default_finance_book[company] = frappe.get_cached_value( frappe.local.default_finance_book[company] = frappe.get_cached_value(
"Company", company, "default_finance_book" "Company", company, "default_finance_book"
) )
@@ -107,7 +108,7 @@ def get_party_account_type(party_type):
if not hasattr(frappe.local, "party_account_types"): if not hasattr(frappe.local, "party_account_types"):
frappe.local.party_account_types = {} frappe.local.party_account_types = {}
if party_type not in frappe.local.party_account_types: if not party_type in frappe.local.party_account_types:
frappe.local.party_account_types[party_type] = ( frappe.local.party_account_types[party_type] = (
frappe.db.get_value("Party Type", party_type, "account_type") or "" frappe.db.get_value("Party Type", party_type, "account_type") or ""
) )
@@ -150,13 +151,3 @@ def allow_regional(fn):
return frappe.get_attr(overrides[function_path][-1])(*args, **kwargs) return frappe.get_attr(overrides[function_path][-1])(*args, **kwargs)
return caller return caller
def check_app_permission():
if frappe.session.user == "Administrator":
return True
if is_website_user():
return False
return True

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().validate() super(ERPNextAddress, self).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().link_address() return super(ERPNextAddress, self).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 [row for row in self.links if row.link_doctype == "Company"]: if self.is_your_company_address and not [
row for row in self.links if row.link_doctype == "Company"
]:
frappe.throw( 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")
filters.get("company") company = filters.get("company")
if not account and chart_name: if not account and chart_name:
frappe.throw( frappe.throw(
@@ -83,6 +83,7 @@ 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
@@ -132,6 +133,8 @@ 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(add_to_date(dates[-1], years=years, months=months, days=days), timegrain) date = get_period_ending(
add_to_date(dates[-1], years=years, months=months, days=days), timegrain
)
dates.append(date) dates.append(date)
return dates return dates

View File

@@ -24,10 +24,14 @@ 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_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense" enable_check = (
"enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense"
)
old_stop_dates = {} old_stop_dates = {}
old_doc = frappe.db.get_all(f"{doc.doctype} Item", {"parent": doc.name}, ["name", "service_stop_date"]) old_doc = frappe.db.get_all(
"{0} Item".format(doc.doctype), {"parent": doc.name}, ["name", "service_stop_date"]
)
for d in old_doc: 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 ""
@@ -58,14 +62,16 @@ def build_conditions(process_type, account, company):
) )
if account: if account:
conditions += f"AND {deferred_account}={frappe.db.escape(account)}" conditions += "AND %s='%s'" % (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(deferred_process, start_date=None, end_date=None, conditions=""): def convert_deferred_expense_to_expense(
deferred_process, start_date=None, end_date=None, conditions=""
):
# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM # 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:
@@ -75,14 +81,16 @@ def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_d
# check for the purchase invoice for which GL entries has to be done # 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
{conditions} {0}
""", """.format(
conditions
),
(end_date, start_date), (end_date, start_date),
) # nosec ) # nosec
@@ -95,7 +103,9 @@ def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_d
send_mail(deferred_process) send_mail(deferred_process)
def convert_deferred_revenue_to_income(deferred_process, start_date=None, end_date=None, conditions=""): def convert_deferred_revenue_to_income(
deferred_process, start_date=None, end_date=None, conditions=""
):
# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM # 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:
@@ -105,14 +115,16 @@ def convert_deferred_revenue_to_income(deferred_process, start_date=None, end_da
# check for the sales invoice for which GL entries has to be done # 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
{conditions} {0}
""", """.format(
conditions
),
(end_date, start_date), (end_date, start_date),
) # nosec ) # nosec
@@ -231,7 +243,9 @@ 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(item.base_net_amount - already_booked_amount, item.precision("base_net_amount")) base_amount = flt(
item.base_net_amount - already_booked_amount, item.precision("base_net_amount")
)
if account_currency == doc.company_currency: if account_currency == doc.company_currency:
amount = base_amount amount = base_amount
else: else:
@@ -251,13 +265,17 @@ def calculate_amount(doc, item, last_gl_entry, total_days, total_booking_days, a
if account_currency == doc.company_currency: if account_currency == doc.company_currency:
amount = base_amount amount = base_amount
else: else:
amount = flt(item.net_amount * total_booking_days / flt(total_days), item.precision("net_amount")) amount = flt(
item.net_amount * total_booking_days / flt(total_days), item.precision("net_amount")
)
else: 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(item.base_net_amount - already_booked_amount, item.precision("base_net_amount")) base_amount = flt(
item.base_net_amount - already_booked_amount, item.precision("base_net_amount")
)
if account_currency == doc.company_currency: if account_currency == doc.company_currency:
amount = base_amount amount = base_amount
else: else:
@@ -278,22 +296,26 @@ def get_already_booked_amount(doc, item):
gl_entries_details = frappe.db.sql( gl_entries_details = frappe.db.sql(
""" """
select sum({}) as total_credit, sum({}) as total_credit_in_account_currency, voucher_detail_no select sum({0}) as total_credit, sum({1}) as total_credit_in_account_currency, voucher_detail_no
from `tabGL Entry` where company=%s and account=%s and voucher_type=%s and voucher_no=%s and voucher_detail_no=%s 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(total_credit_debit, total_credit_debit_currency), """.format(
total_credit_debit, total_credit_debit_currency
),
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), (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.{}) as total_credit, sum(c.{}) as total_credit_in_account_currency, reference_detail_no SELECT sum(c.{0}) as total_credit, sum(c.{1}) as total_credit_in_account_currency, reference_detail_no
FROM `tabJournal Entry` p , `tabJournal Entry Account` c WHERE p.name = c.parent and 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(total_credit_debit, total_credit_debit_currency), """.format(
total_credit_debit, total_credit_debit_currency
),
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
as_dict=True, as_dict=True,
) )
@@ -315,7 +337,9 @@ def get_already_booked_amount(doc, item):
def book_deferred_income_or_expense(doc, deferred_process, posting_date=None): def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
enable_check = "enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense" enable_check = (
"enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense"
)
accounts_frozen_upto = frappe.db.get_single_value("Accounts Settings", "acc_frozen_upto") accounts_frozen_upto = frappe.db.get_single_value("Accounts Settings", "acc_frozen_upto")
@@ -360,8 +384,8 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
) )
if not amount: if not amount:
prev_posting_date = end_date return
else:
gl_posting_date = end_date gl_posting_date = end_date
prev_posting_date = None prev_posting_date = None
# check if books nor frozen till endate: # check if books nor frozen till endate:
@@ -416,7 +440,9 @@ 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(frappe.db.get_singles_value("Accounts Settings", "submit_journal_entries")) submit_journal_entry = cint(
frappe.db.get_singles_value("Accounts Settings", "submit_journal_entries")
)
book_deferred_entries_based_on = frappe.db.get_singles_value( book_deferred_entries_based_on = frappe.db.get_singles_value(
"Accounts Settings", "book_deferred_entries_based_on" "Accounts Settings", "book_deferred_entries_based_on"
) )
@@ -436,7 +462,9 @@ def process_deferred_accounting(posting_date=None):
posting_date = today() posting_date = today()
if not cint( if not cint(
frappe.db.get_singles_value("Accounts Settings", "automatically_process_deferred_accounting_entry") frappe.db.get_singles_value(
"Accounts Settings", "automatically_process_deferred_accounting_entry"
)
): ):
return return
@@ -559,13 +587,16 @@ 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 = "Deferred Revenue" if doc.doctype == "Sales Invoice" else "Deferred Expense" journal_entry.voucher_type = (
"Deferred Revenue" if doc.doctype == "Sales Invoice" else "Deferred Expense"
)
journal_entry.process_deferred_accounting = deferred_process journal_entry.process_deferred_accounting = deferred_process
debit_entry = { debit_entry = {
@@ -614,6 +645,7 @@ 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

@@ -22,17 +22,23 @@ frappe.ui.form.on("Account", {
// hide fields if group // hide fields if group
frm.toggle_display(["tax_rate"], cint(frm.doc.is_group) == 0); frm.toggle_display(["tax_rate"], cint(frm.doc.is_group) == 0);
frm.toggle_enable(["is_group", "company", "account_number"], frm.is_new()); // disable fields
frm.toggle_enable(["is_group", "company"], false);
if (cint(frm.doc.is_group) == 0) { if (cint(frm.doc.is_group) == 0) {
frm.toggle_display("freeze_account", frm.doc.__onload && frm.doc.__onload.can_freeze_account); frm.toggle_display(
"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(__("This is a root account and cannot be edited.")); frm.set_intro(
__("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);
@@ -74,33 +80,27 @@ frappe.ui.form.on("Account", {
); );
if (frm.doc.is_group == 1) { if (frm.doc.is_group == 1) {
frm.add_custom_button( frm.add_custom_button(__('Convert to Non-Group'), function () {
__("Convert to Non-Group"),
function () {
return frappe.call({ return frappe.call({
doc: frm.doc, doc: frm.doc,
method: "convert_group_to_ledger", method: 'convert_group_to_ledger',
callback: function () { callback: function() {
frm.refresh(); frm.refresh();
}, }
}); });
}, }, __('Actions'));
__("Actions")
); } else if (cint(frm.doc.is_group) == 0
} else if (cint(frm.doc.is_group) == 0 && frappe.boot.user.can_read.indexOf("GL Entry") !== -1) { && frappe.boot.user.can_read.indexOf("GL Entry") !== -1) {
frm.add_custom_button( frm.add_custom_button(__('General Ledger'), function () {
__("General Ledger"),
function () {
frappe.route_options = { frappe.route_options = {
account: frm.doc.name, "account": frm.doc.name,
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: frm.doc.company, "company": frm.doc.company
}; };
frappe.set_route("query-report", "General Ledger"); frappe.set_route("query-report", "General Ledger");
}, }, __('View'));
__("View")
);
frm.add_custom_button( frm.add_custom_button(
__("Convert to Group"), __("Convert to Group"),
@@ -193,8 +193,14 @@ 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("account_number", data.account_number); frm.set_value(
frm.set_value("account_name", data.account_name); "account_number",
data.account_number
);
frm.set_value(
"account_name",
data.account_name
);
} }
d.hide(); d.hide();
} }

View File

@@ -55,7 +55,8 @@
"fieldtype": "Data", "fieldtype": "Data",
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Account Number" "label": "Account Number",
"read_only": 1
}, },
{ {
"default": "0", "default": "0",
@@ -71,6 +72,7 @@
"oldfieldname": "company", "oldfieldname": "company",
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Company", "options": "Company",
"read_only": 1,
"remember_last_selected_value": 1, "remember_last_selected_value": 1,
"reqd": 1 "reqd": 1
}, },
@@ -121,14 +123,13 @@
"label": "Account Type", "label": "Account Type",
"oldfieldname": "account_type", "oldfieldname": "account_type",
"oldfieldtype": "Select", "oldfieldtype": "Select",
"options": "\nAccumulated Depreciation\nAsset Received But Not Billed\nBank\nCash\nChargeable\nCapital Work in Progress\nCost of Goods Sold\nCurrent Asset\nCurrent Liability\nDepreciation\nDirect Expense\nDirect Income\nEquity\nExpense Account\nExpenses Included In Asset Valuation\nExpenses Included In Valuation\nFixed Asset\nIncome Account\nIndirect Expense\nIndirect Income\nLiability\nPayable\nReceivable\nRound Off\nRound Off for Opening\nStock\nStock Adjustment\nStock Received But Not Billed\nService Received But Not Billed\nTax\nTemporary", "options": "\nAccumulated Depreciation\nAsset Received But Not Billed\nBank\nCash\nChargeable\nCapital Work in Progress\nCost of Goods Sold\nCurrent Asset\nCurrent Liability\nDepreciation\nDirect Expense\nDirect Income\nEquity\nExpense Account\nExpenses Included In Asset Valuation\nExpenses Included In Valuation\nFixed Asset\nIncome Account\nIndirect Expense\nIndirect Income\nLiability\nPayable\nReceivable\nRound Off\nStock\nStock Adjustment\nStock Received But Not Billed\nService Received But Not Billed\nTax\nTemporary"
"search_index": 1
}, },
{ {
"description": "Rate at which this tax is applied", "description": "Rate at which this tax is applied",
"fieldname": "tax_rate", "fieldname": "tax_rate",
"fieldtype": "Float", "fieldtype": "Float",
"label": "Tax Rate", "label": "Rate",
"oldfieldname": "tax_rate", "oldfieldname": "tax_rate",
"oldfieldtype": "Currency" "oldfieldtype": "Currency"
}, },
@@ -191,7 +192,7 @@
"idx": 1, "idx": 1,
"is_tree": 1, "is_tree": 1,
"links": [], "links": [],
"modified": "2024-08-19 15:19:11.095045", "modified": "2023-07-20 18:18:44.405723",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Account", "name": "Account",

View File

@@ -23,73 +23,13 @@ class InvalidAccountMergeError(frappe.ValidationError):
class Account(NestedSet): class Account(NestedSet):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
account_currency: DF.Link | None
account_name: DF.Data
account_number: DF.Data | None
account_type: DF.Literal[
"",
"Accumulated Depreciation",
"Asset Received But Not Billed",
"Bank",
"Cash",
"Chargeable",
"Capital Work in Progress",
"Cost of Goods Sold",
"Current Asset",
"Current Liability",
"Depreciation",
"Direct Expense",
"Direct Income",
"Equity",
"Expense Account",
"Expenses Included In Asset Valuation",
"Expenses Included In Valuation",
"Fixed Asset",
"Income Account",
"Indirect Expense",
"Indirect Income",
"Liability",
"Payable",
"Receivable",
"Round Off",
"Round Off for Opening",
"Stock",
"Stock Adjustment",
"Stock Received But Not Billed",
"Service Received But Not Billed",
"Tax",
"Temporary",
]
balance_must_be: DF.Literal["", "Debit", "Credit"]
company: DF.Link
disabled: DF.Check
freeze_account: DF.Literal["No", "Yes"]
include_in_gross: DF.Check
is_group: DF.Check
lft: DF.Int
old_parent: DF.Data | None
parent_account: DF.Link
report_type: DF.Literal["", "Balance Sheet", "Profit and Loss"]
rgt: DF.Int
root_type: DF.Literal["", "Asset", "Liability", "Income", "Expense", "Equity"]
tax_rate: DF.Float
# end: auto-generated types
nsm_parent_field = "parent_account" nsm_parent_field = "parent_account"
def on_update(self): def on_update(self):
if frappe.local.flags.ignore_update_nsm: if frappe.local.flags.ignore_update_nsm:
return return
else: else:
super().on_update() super(Account, self).on_update()
def onload(self): def onload(self):
frozen_accounts_modifier = frappe.db.get_value( frozen_accounts_modifier = frappe.db.get_value(
@@ -104,12 +44,14 @@ class Account(NestedSet):
self.name = get_autoname_with_number(self.account_number, self.account_name, self.company) self.name = get_autoname_with_number(self.account_number, self.account_name, self.company)
def validate(self): def validate(self):
from erpnext.accounts.utils import validate_field_number
if frappe.local.flags.allow_unverified_charts: if frappe.local.flags.allow_unverified_charts:
return return
self.validate_parent() self.validate_parent()
self.validate_parent_child_account_type() self.validate_parent_child_account_type()
self.validate_root_details() self.validate_root_details()
self.validate_account_number() validate_field_number("Account", self.name, self.account_number, self.company, "account_number")
self.validate_group_or_ledger() self.validate_group_or_ledger()
self.set_root_and_report_type() self.set_root_and_report_type()
self.validate_mandatory() self.validate_mandatory()
@@ -117,7 +59,6 @@ class Account(NestedSet):
self.validate_balance_must_be_debit_or_credit() self.validate_balance_must_be_debit_or_credit()
self.validate_account_currency() self.validate_account_currency()
self.validate_root_company_and_sync_account_to_children() self.validate_root_company_and_sync_account_to_children()
self.validate_receivable_payable_account_type()
def validate_parent_child_account_type(self): def validate_parent_child_account_type(self):
if self.parent_account: if self.parent_account:
@@ -188,24 +129,6 @@ class Account(NestedSet):
"Balance Sheet" if self.root_type in ("Asset", "Liability", "Equity") else "Profit and Loss" "Balance Sheet" if self.root_type in ("Asset", "Liability", "Equity") else "Profit and Loss"
) )
def validate_receivable_payable_account_type(self):
doc_before_save = self.get_doc_before_save()
receivable_payable_types = ["Receivable", "Payable"]
if (
doc_before_save
and doc_before_save.account_type in receivable_payable_types
and doc_before_save.account_type != self.account_type
):
# check for ledger entries
if frappe.db.get_all("GL Entry", filters={"account": self.name, "is_cancelled": 0}, limit=1):
msg = _(
"There are ledger entries against this account. Changing {0} to non-{1} in live system will cause incorrect output in 'Accounts {2}' report"
).format(
frappe.bold(_("Account Type")), doc_before_save.account_type, doc_before_save.account_type
)
frappe.msgprint(msg)
self.add_comment("Comment", msg)
def validate_root_details(self): def validate_root_details(self):
doc_before_save = self.get_doc_before_save() doc_before_save = self.get_doc_before_save()
@@ -217,7 +140,9 @@ 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 frappe.local.flags.ignore_root_company_validation or self.flags.ignore_root_company_validation: if (
frappe.local.flags.ignore_root_company_validation or self.flags.ignore_root_company_validation
):
return return
ancestors = get_root_company(self.company) ancestors = get_root_company(self.company)
if ancestors: if ancestors:
@@ -310,22 +235,6 @@ class Account(NestedSet):
if frappe.db.get_value("GL Entry", {"account": self.name}): if frappe.db.get_value("GL Entry", {"account": self.name}):
frappe.throw(_("Currency can not be changed after making entries using some other currency")) frappe.throw(_("Currency can not be changed after making entries using some other currency"))
def validate_account_number(self, account_number=None):
if not account_number:
account_number = self.account_number
if account_number:
account_with_same_number = frappe.db.get_value(
"Account",
{"account_number": account_number, "company": self.company, "name": ["!=", self.name]},
)
if account_with_same_number:
frappe.throw(
_("Account Number {0} already used in account {1}").format(
account_number, account_with_same_number
)
)
def create_account_for_child_company(self, parent_acc_name_map, descendants, parent_acc_name): def create_account_for_child_company(self, parent_acc_name_map, descendants, parent_acc_name):
for company in descendants: for company in descendants:
company_bold = frappe.bold(company) company_bold = frappe.bold(company)
@@ -431,7 +340,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().on_trash(True) super(Account, self).on_trash(True)
@frappe.whitelist() @frappe.whitelist()
@@ -439,8 +348,9 @@ 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 = {} where is_group = 1 and docstatus != 2 and company = %s
and {} like {} order by name limit {} offset {}""".format("%s", searchfield, "%s", "%s", "%s"), and %s like %s order by name limit %s offset %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,
) )
@@ -479,6 +389,19 @@ def get_account_autoname(account_number, account_name, company):
return " - ".join(parts) return " - ".join(parts)
def validate_account_number(name, account_number, company):
if account_number:
account_with_same_number = frappe.db.get_value(
"Account", {"account_number": account_number, "company": company, "name": ["!=", name]}
)
if account_with_same_number:
frappe.throw(
_("Account Number {0} already used in account {1}").format(
account_number, account_with_same_number
)
)
@frappe.whitelist() @frappe.whitelist()
def update_account_number(name, account_name, account_number=None, from_descendant=False): def update_account_number(name, account_name, account_number=None, from_descendant=False):
account = frappe.get_cached_doc("Account", name) account = frappe.get_cached_doc("Account", name)
@@ -519,7 +442,7 @@ def update_account_number(name, account_name, account_number=None, from_descenda
frappe.throw(message, title=_("Rename Not Allowed")) frappe.throw(message, title=_("Rename Not Allowed"))
account.validate_account_number(account_number) validate_account_number(name, account_number, account.company)
if account_number: if account_number:
frappe.db.set_value("Account", name, "account_number", account_number.strip()) frappe.db.set_value("Account", name, "account_number", account_number.strip())
else: else:
@@ -593,5 +516,7 @@ 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("Account", filters=filters, fieldname=["company", "name"], as_dict=True): for d in frappe.db.get_values(
"Account", filters=filters, fieldname=["company", "name"], as_dict=True
):
update_account_number(d["name"], account_name, account_number, from_descendant=True) 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,36 +22,30 @@ 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( frappe.db.get_value("Company", {"name": company}, "allow_account_creation_against_child_company", (r) => {
"Company", frappe.flags.ignore_root_company_validation = r.allow_account_creation_against_child_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 = [];
@@ -63,234 +57,151 @@ 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">'
'<span class="balance-area pull-right">' + + (account.balance_in_account_currency ?
(account.balance_in_account_currency (format(account.balance_in_account_currency, account.account_currency) + " / ") : "")
? format( + format(account.balance, account.company_currency)
account.balance_in_account_currency, + " " + dr_or_cr
account.account_currency + '</span>').insertBefore(node.$ul);
) + " / "
: "") +
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 () { action: function() { frappe.new_doc("Company", true) },
frappe.new_doc("Company", true); condition: 'frappe.boot.user.can_create.indexOf("Company") !== -1'
}, }
condition: 'frappe.boot.user.can_create.indexOf("Company") !== -1',
},
], ],
fields: [ fields: [
{ {fieldtype:'Data', fieldname:'account_name', label:__('New Account Name'), reqd:true,
fieldtype: "Data", description: __("Name of new Account. Note: Please don't create accounts for Customers and Suppliers")},
fieldname: "account_name", {fieldtype:'Data', fieldname:'account_number', label:__('Account Number'),
label: __("New Account Name"), description: __("Number of new Account, it will be included in the account name as a prefix")},
reqd: true, {fieldtype:'Check', fieldname:'is_group', label:__('Is Group'),
description: __( description: __('Further accounts can be made under Groups, but entries can be made against non-Groups')},
"Name of new Account. Note: Please don't create accounts for Customers and Suppliers" {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'),
fieldtype: "Data", options: frappe.get_meta("Account").fields.filter(d => d.fieldname=='account_type')[0].options,
fieldname: "account_number", description: __("Optional. This setting will be used to filter in various transactions.")
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( treeview.page.add_inner_button(__("Chart of Cost Centers"), function() {
__("Chart of Cost Centers"), frappe.set_route('Tree', 'Cost Center', {company: get_company()});
function () { }, __('View'));
frappe.set_route("Tree", "Cost Center", { company: get_company() });
},
__("View")
);
treeview.page.add_inner_button( treeview.page.add_inner_button(__("Opening Invoice Creation Tool"), function() {
__("Opening Invoice Creation Tool"), frappe.set_route('Form', 'Opening Invoice Creation Tool', {company: get_company()});
function () { }, __('View'));
frappe.set_route("Form", "Opening Invoice Creation Tool", { company: get_company() });
},
__("View")
);
treeview.page.add_inner_button( treeview.page.add_inner_button(__("Period Closing Voucher"), function() {
__("Period Closing Voucher"), frappe.set_route('List', 'Period Closing Voucher', {company: get_company()});
function () { }, __('View'));
frappe.set_route("List", "Period Closing Voucher", { company: get_company() });
},
__("View")
);
treeview.page.add_inner_button(
__("Journal Entry"), treeview.page.add_inner_button(__("Journal Entry"), function() {
function () { frappe.new_doc('Journal Entry', {company: get_company()});
frappe.new_doc("Journal Entry", { company: get_company() }); }, __('Create'));
}, treeview.page.add_inner_button(__("Company"), function() {
__("Create") frappe.new_doc('Company');
); }, __('Create'));
treeview.page.add_inner_button(
__("Company"),
function () {
frappe.new_doc("Company");
},
__("Create")
);
// financial statements // financial statements
for (let report of [ for (let report of ['Trial Balance', 'General Ledger', 'Balance Sheet',
"Trial Balance", 'Profit and Loss Statement', 'Cash Flow Statement', 'Accounts Payable', 'Accounts Receivable']) {
"General Ledger", treeview.page.add_inner_button(__(report), function() {
"Balance Sheet", frappe.set_route('query-report', report, {company: get_company()});
"Profit and Loss Statement", }, __('Financial Statements'));
"Cash Flow",
"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;
if (treeview.can_create) { treeview.page.set_primary_action(__("New"), function() {
treeview.page.set_primary_action(
__("New"),
function () {
let root_company = treeview.page.fields_dict.root_company.get_value(); let root_company = treeview.page.fields_dict.root_company.get_value();
if (root_company) {
frappe.throw(__("Please add the account to root level Company - {0}"), [ if(root_company) {
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 ( return frappe.boot.user.can_create.indexOf("Account") !== -1
frappe.boot.user.can_create.indexOf("Account") !== -1 && && (!frappe.treeview_settings['Account'].treeview.page.fields_dict.root_company.get_value()
(!frappe.treeview_settings[ || frappe.flags.ignore_root_company_validation)
"Account" && node.expandable && !node.hide_add;
].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: "company": frappe.treeview_settings['Account'].treeview.page.fields_dict.company.get_value()
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,6 +31,7 @@ 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
@@ -38,9 +39,7 @@ def create_charts(
is_group = identify_is_group(child) is_group = identify_is_group(child)
report_type = ( report_type = (
"Balance Sheet" "Balance Sheet" if root_type in ["Asset", "Liability", "Equity"] else "Profit and Loss"
if root_type in ["Asset", "Liability", "Equity"]
else "Profit and Loss"
) )
account = frappe.get_doc( account = frappe.get_doc(
@@ -142,7 +141,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)) as f: with open(os.path.join(path, fname), "r") 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")
@@ -174,7 +173,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)) as f: with open(os.path.join(path, fname), "r") 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
@@ -248,13 +247,7 @@ 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 [ if account_name not in ["account_number", "account_type", "root_type", "is_group", "tax_rate"]:
"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

@@ -77,6 +77,7 @@
"1500 Fertige Erzeugnisse": {"account_type": "Stock"}, "1500 Fertige Erzeugnisse": {"account_type": "Stock"},
"1600 Handelswarenvorrat": {"account_type": "Stock Received But Not Billed"}, "1600 Handelswarenvorrat": {"account_type": "Stock Received But Not Billed"},
"1700 Noch nicht abrechenbare Leistungen": {"account_type": "Stock"}, "1700 Noch nicht abrechenbare Leistungen": {"account_type": "Stock"},
"1900 Wertberichtigungen": {"account_type": "Stock"},
"1800 Geleistete Anzahlungen": {"account_type": "Stock"}, "1800 Geleistete Anzahlungen": {"account_type": "Stock"},
"1900 Wertberichtigungen": {"account_type": "Stock"}, "1900 Wertberichtigungen": {"account_type": "Stock"},
"root_type": "Asset" "root_type": "Asset"

View File

@@ -0,0 +1,531 @@
{
"country_code": "tr",
"name": "Turkey - Tek D\u00fczen Hesap Plan\u0131",
"tree": {
"Duran Varl\u0131klar": {
"Di\u011fer Alacaklar": {
"Ba\u011fl\u0131 Ortakl\u0131klardan Alacaklar": {},
"Di\u011fer Alacak Senetleri Reeskontu(-)": {},
"Di\u011fer \u00c7e\u015fitli Alacaklar": {},
"Ortaklardan Alacaklar": {},
"Personelden Alacaklar": {},
"\u0130\u015ftiraklerden Alacaklar": {},
"\u015e\u00fcpheli Di\u011fer Alacaklar Kar\u015f\u0131l\u0131\u011f\u0131(-)": {}
},
"Di\u011fer Duran Varl\u0131klar": {
"Birikmi\u015f Amortismanlar(-)": {},
"Di\u011fer KDV": {},
"Di\u011fer \u00c7e\u015fitli Duran Varl\u0131klar": {},
"Elden \u00c7\u0131kar\u0131lacak Stoklar Ve Maddi Duran Varl\u0131klar": {},
"Gelecek Y\u0131llar \u0130htiyac\u0131 Stoklar": {},
"Gelecek Y\u0131llarda \u0130ndirilecek KDV": {},
"Pe\u015fin \u00d6denen Vergi Ve Fonlar": {},
"Stok De\u011fer D\u00fc\u015f\u00fckl\u00fc\u011f\u00fc Kar\u015f\u0131l\u0131\u011f\u0131(-)": {}
},
"Gelecek Y\u0131llara Ait Giderler ve Gelir Tahakkuklar\u0131": {
"Gelecek Y\u0131llara Ait Giderler": {},
"Gelir Tahakkuklar\u0131": {}
},
"Maddi Duran Varl\u0131klar": {
"Arazi Ve Arsalar": {},
"Binalar": {},
"Birikmi\u015f Amortismanlar(-)": {},
"Demirba\u015flar": {},
"Di\u011fer Maddi Duran Varl\u0131klar": {},
"Ta\u015f\u0131tlar": {},
"Tesis, Makine Ve Cihazlar": {},
"Verilen Avanslar": {},
"Yap\u0131lmakta Olan Yat\u0131r\u0131mlar": {},
"Yer Alt\u0131 Ve Yer \u00dcst\u00fc D\u00fczenleri": {}
},
"Maddi Olmayan Duran Varl\u0131klar": {
"Ara\u015ft\u0131rma Ve Geli\u015ftirme Giderleri": {},
"Birikmi\u015f Amortismanlar(-)": {},
"Di\u011fer Maddi Olmayan Duran Varl\u0131klar": {},
"Haklar": {},
"Kurulu\u015f Ve \u00d6rg\u00fctlenme Giderleri": {},
"Verilen Avanslar": {},
"\u00d6zel Maliyetler": {},
"\u015eerefiye": {}
},
"Mali Duran Varl\u0131klar": {
"Ba\u011fl\u0131 Menkul K\u0131ymetler": {},
"Ba\u011fl\u0131 Menkul K\u0131ymetler De\u011fer D\u00fc\u015f\u00fckl\u00fc\u011f\u00fc Kar\u015f\u0131l\u0131\u011f\u0131(-)": {},
"Ba\u011fl\u0131 Ortakl\u0131klar": {},
"Ba\u011fl\u0131 Ortakl\u0131klar Sermaye Paylar\u0131 De\u011fer D\u00fc\u015f\u00fckl\u00fc\u011f\u00fc Kar\u015f\u0131l\u0131\u011f\u0131(-)": {},
"Ba\u011fl\u0131 Ortakl\u0131klara Sermaye Taahh\u00fctleri(-)": {},
"Di\u011fer Mali Duran Varl\u0131klar": {},
"Di\u011fer Mali Duran Varl\u0131klar Kar\u015f\u0131l\u0131\u011f\u0131(-)": {},
"\u0130\u015ftirakler": {},
"\u0130\u015ftirakler Sermaye Paylar\u0131 De\u011fer D\u00fc\u015f\u00fckl\u00fc\u011f\u00fc Kar\u015f\u0131l\u0131\u011f\u0131(-)": {},
"\u0130\u015ftiraklere Sermaye Taahh\u00fctleri(-)": {}
},
"Ticari Alacaklar": {
"Alacak Senetleri": {},
"Alacak Senetleri Reeskontu(-)": {},
"Al\u0131c\u0131lar": {},
"Kazaqn\u0131lmam\u0131\u015f Finansal Kiralama Faiz Gelirleri(-)": {},
"Verilen Depozito Ve Teminatlar": {},
"\u015e\u00fcpheli Ticari Alacaklar Kar\u015f\u0131l\u0131\u011f\u0131(-)": {}
},
"root_type": "",
"\u00d6zel T\u00fckenmeye Tabi Varl\u0131klar": {
"Arama Giderleri": {},
"Birikmi\u015f T\u00fckenme Paylar\u0131(-)": {},
"Di\u011fer \u00d6zel T\u00fckenmeye Tabi Varl\u0131klar": {},
"Haz\u0131rl\u0131k Ve Geli\u015ftirme Giderleri": {},
"Verilen Avanslar": {}
}
},
"D\u00f6nen Varl\u0131klar": {
"Di\u011fer Alacaklar": {
"Ba\u011fl\u0131 Ortakl\u0131klardan Alacaklar": {},
"Di\u011fer Alacak Senetleri Reeskontu(-)": {},
"Di\u011fer \u00c7e\u015fitli Alacaklar": {},
"Ortaklardan Alacaklar": {},
"Personelden Alacaklar": {},
"\u0130\u015ftiraklerden Alacaklar": {},
"\u015e\u00fcpheli Di\u011fer Alacaklar": {},
"\u015e\u00fcpheli Di\u011fer Alacaklar Kar\u015f\u0131l\u0131\u011f\u0131(-)": {}
},
"Di\u011fer D\u00f6nen Varl\u0131klar": {
"Devreden KDV": {},
"Di\u011fer D\u00f6nen Varl\u0131klar Kar\u015f\u0131l\u0131\u011f\u0131(-)": {},
"Di\u011fer KDV": {},
"Di\u011fer \u00c7e\u015fitli D\u00f6nen Varl\u0131klar": {},
"Personel Avanslar\u0131": {},
"Pe\u015fin \u00d6denen Vergiler Ve Fonlar": {},
"Say\u0131m Ve Tesell\u00fcm Noksanlar\u0131": {},
"\u0130ndirilecek KDV": {},
"\u0130\u015f Avanslar\u0131": {}
},
"Gelecek Aylara Ait Giderler ve Gelir Tahakkuklar\u0131": {
"Gelecek Aylara Ait Giderler": {},
"Gelir Tahakkuklar\u0131": {}
},
"Haz\u0131r De\u011ferler": {
"Al\u0131nan \u00c7ekler": {},
"Bankalar": {
"account_type": "Bank"
},
"Di\u011fer Haz\u0131r De\u011ferler": {},
"Kasa": {
"account_type": "Cash"
},
"Verilen \u00c7ekler ve \u00d6deme Emirleri(-)": {}
},
"Menkul K\u0131ymetler": {
"Di\u011fer Menkul K\u0131ymetler": {},
"Hisse Senetleri": {},
"Kamu Kesimi Tahvil, Senet ve Bonolar\u0131": {},
"Menkul K\u0131ymetler De\u011fer D\u00fc\u015f\u00fckl\u00fc\u011f\u00fc Kar\u015f\u0131l\u0131\u011f\u0131(-)": {},
"\u00d6zel Kesim Tahvil Senet Ve Bonolar\u0131": {}
},
"Stoklar": {
"Mamuller": {},
"Stok De\u011fer D\u00fc\u015f\u00fckl\u00fc\u011f\u00fc Kar\u015f\u0131l\u0131\u011f\u0131(-)": {},
"Ticari Mallar": {},
"Verilen Sipari\u015f Avanslar\u0131": {},
"Yar\u0131 Mamuller": {},
"\u0130lk Madde Malzeme": {}
},
"Ticari Alacaklar": {
"Alacak Senetleri": {},
"Alacak Senetleri Reeskontu(-)": {},
"Al\u0131c\u0131lar": {},
"Di\u011fer Ticari Alacaklar": {},
"Kazan\u0131lmam\u0131\u015f Finansal Kiralama Faiz Gelirleri(-)": {},
"Verilen Depozito ve Teminatlar": {},
"\u015e\u00fcpheli Ticari Alacaklar": {},
"\u015e\u00fcpheli Ticari Alacaklar Kar\u015f\u0131l\u0131\u011f\u0131": {}
},
"Y\u0131llara Yayg\u0131n \u0130n\u015faat ve Onar\u0131m Maliyetleri": {
"Ta\u015feronlara Verilen Avanslar": {},
"Y\u0131llara Yayg\u0131n \u0130n\u015faat Ve Onar\u0131m Maliyetleri": {}
},
"root_type": ""
},
"Gelir Tablosu Hesaplar\u0131": {
"Br\u00fct Sat\u0131\u015flar": {
"Di\u011fer Gelirler": {},
"Yurt D\u0131\u015f\u0131 Sat\u0131\u015flar": {},
"Yurt \u0130\u00e7i Sat\u0131\u015flar": {}
},
"Di\u011fer Faaliyetlerden Olu\u015fan Gelir ve K\u00e2rlar": {
"Ba\u011fl\u0131 Ortakl\u0131klardan Temett\u00fc Gelirleri": {},
"Di\u011fer Ola\u011fan Gelir Ve K\u00e2rlar": {},
"Enflasyon D\u00fczeltme K\u00e2rlar\u0131": {},
"Faiz Gelirleri": {},
"Kambiyo K\u00e2rlar\u0131": {},
"Komisyon Gelirleri": {},
"Konusu Kalmayan Kar\u015f\u0131l\u0131klar": {},
"Menkul K\u0131ymet Sat\u0131\u015f K\u00e2rlar\u0131": {},
"Reeskont Faiz Gelirleri": {},
"\u0130\u015ftiraklerden Temett\u00fc Gelirleri": {}
},
"Di\u011fer Faaliyetlerden Olu\u015fan Gider ve Zararlar (-)": {
"Di\u011fer Ola\u011fan Gider Ve Zararlar(-)": {},
"Enflasyon D\u00fczeltmesi Zararlar\u0131(-)": {},
"Kambiyo Zararlar\u0131(-)": {},
"Kar\u015f\u0131l\u0131k Giderleri(-)": {},
"Komisyon Giderleri(-)": {},
"Menkul K\u0131ymet Sat\u0131\u015f Zararlar\u0131(-)": {},
"Reeskont Faiz Giderleri(-)": {}
},
"D\u00f6nem Net K\u00e2r\u0131 Ve Zarar\u0131": {
"D\u00f6nem K\u00e2r\u0131 Vergi Ve Di\u011fer Yasal Y\u00fck\u00fcml\u00fcl\u00fck Kar\u015f\u0131l\u0131klar\u0131(-)": {},
"D\u00f6nem K\u00e2r\u0131 Veya Zarar\u0131": {},
"D\u00f6nem Net K\u00e2r\u0131 Veya Zarar\u0131": {},
"Enflasyon D\u00fczeltme Hesab\u0131": {},
"Y\u0131llara Yayg\u0131n \u0130n\u015faat Ve Enflasyon D\u00fczeltme Hesab\u0131": {}
},
"Faaliyet Giderleri(-)": {
"Ara\u015ft\u0131rma Ve Geli\u015ftirme Giderleri(-)": {},
"Genel Y\u00f6netim Giderleri(-)": {},
"Pazarlama Sat\u0131\u015f Ve Da\u011f\u0131t\u0131m Giderleri(-)": {}
},
"Finansman Giderleri": {
"K\u0131sa Vadeli Bor\u00e7lanma Giderleri(-)": {},
"Uzun Vadeli Bor\u00e7lanma Giderleri(-)": {}
},
"Ola\u011fan D\u0131\u015f\u0131 Gelir Ve K\u00e2rlar": {
"Di\u011fer Ola\u011fan D\u0131\u015f\u0131 Gelir Ve K\u00e2rlar": {},
"\u00d6nceki D\u00f6nem Gelir Ve K\u00e2rlar\u0131": {}
},
"Ola\u011fan D\u0131\u015f\u0131 Gider Ve Zaralar(-)": {
"Di\u011fer Ola\u011fan D\u0131\u015f\u0131 Gider Ve Zararlar(-)": {},
"\u00c7al\u0131\u015fmayan K\u0131s\u0131m Gider Ve Zararlar\u0131(-)": {},
"\u00d6nceki D\u00f6nem Gider Ve Zararlar\u0131(-)": {}
},
"Sat\u0131\u015f \u0130ndirimleri (-)": {
"Di\u011fer \u0130ndirimler": {},
"Sat\u0131\u015f \u0130ndirimleri(-)": {},
"Sat\u0131\u015ftan \u0130adeler(-)": {}
},
"Sat\u0131\u015flar\u0131n Maliyeti(-)": {
"Di\u011fer Sat\u0131\u015flar\u0131n Maliyeti(-)": {},
"Sat\u0131lan Hizmet Maliyeti(-)": {},
"Sat\u0131lan Mamuller Maliyeti(-)": {},
"Sat\u0131lan Ticari Mallar Maliyeti(-)": {}
},
"root_type": ""
},
"K\u0131sa Vadeli Yabanc\u0131 Kaynaklar": {
"Al\u0131nan Avanslar": {
"Al\u0131nan Di\u011fer Avanslar": {
"account_type": "Payable"
},
"Al\u0131nan Sipari\u015f Avanslar\u0131": {
"account_type": "Payable"
},
"account_type": "Payable"
},
"Bor\u00e7 ve Gider Kar\u015f\u0131l\u0131klar\u0131": {
"Di\u011fer Bor\u00e7 Ve Gider Kar\u015f\u0131l\u0131klar\u0131": {
"account_type": "Payable"
},
"D\u00f6nem K\u00e2r\u0131 Vergi Ve Di\u011fer Yasal Y\u00fck\u00fcml\u00fcl\u00fck Kar\u015f\u0131l\u0131klar\u0131": {
"account_type": "Tax"
},
"D\u00f6nem K\u00e2r\u0131n\u0131n Pe\u015fin \u00d6denen Vergi Ve Di\u011fer Y\u00fck\u00fcml\u00fcl\u00fckler(-)": {
"account_type": "Tax"
},
"K\u0131dem Tazminat\u0131 Kar\u015f\u0131l\u0131\u011f\u0131": {},
"Maliyet Giderleri Kar\u015f\u0131l\u0131\u011f\u0131": {},
"account_type": "Payable"
},
"Di\u011fer Bor\u00e7lar": {
"Ba\u011fl\u0131 Ortakl\u0131klara Bor\u00e7lar": {
"account_type": "Payable"
},
"Di\u011fer Bor\u00e7 Senetleri Reeskontu(-)": {
"account_type": "Payable"
},
"Di\u011fer \u00c7e\u015fitli Bor\u00e7lar": {
"account_type": "Payable"
},
"Ortaklara Bor\u00e7lar": {
"account_type": "Payable"
},
"Personele Bor\u00e7lar": {
"account_type": "Payable"
},
"account_type": "Payable",
"\u0130\u015ftiraklere Bor\u00e7lar": {
"account_type": "Payable"
}
},
"Di\u011fer K\u0131sa Vadeli Yabanc\u0131 Kaynaklar": {
"Di\u011fer KDV": {
"account_type": "Tax"
},
"Di\u011fer \u00c7e\u015fitli Yabanc\u0131 Kaynaklar": {},
"Hesaplanan KDV": {
"account_type": "Tax"
},
"Merkez Ve \u015eubeler Cari Hesab\u0131": {},
"Say\u0131m Ve Tesell\u00fcm Fazlalar\u0131": {},
"account_type": "Payable"
},
"Gelecek Aylara Ait Gelirler Ve Gider Tahakkuklar\u0131": {
"Gelecek Aylara Ait Gelirler": {},
"Gider Tahakkuklar\u0131": {}
},
"Mali Bor\u00e7lar": {
"Banka Kredileri": {
"account_type": "Payable"
},
"Di\u011fer Mali Bor\u00e7lar": {
"account_type": "Payable"
},
"Ertelenmi\u015f Finansal Kiralama Bor\u00e7lanma Maliyetleri(-)": {
"account_type": "Payable"
},
"Finansal Kiralama \u0130\u015flemlerinden Bor\u00e7lar": {
"account_type": "Payable"
},
"Menkul K\u0131ymetler \u0130hra\u00e7 Fark\u0131(-)": {
"account_type": "Payable"
},
"Tahvil Anapara Bor\u00e7, Taksit Ve Faizleri": {
"account_type": "Payable"
},
"Uzun Vadeli Kredilerin Anapara Taksitleri Ve Faizleri": {
"account_type": "Payable"
},
"account_type": "Payable",
"\u00c7\u0131kar\u0131lan Bonolar Ve Senetler": {
"account_type": "Payable"
},
"\u00c7\u0131kar\u0131lm\u0131\u015f Di\u011fer Menkul K\u0131ymetler": {
"account_type": "Payable"
}
},
"Ticari Bor\u00e7lar": {
"Al\u0131nan Depozito Ve Teminatlar": {
"account_type": "Payable"
},
"Bor\u00e7 Senetleri": {
"account_type": "Payable"
},
"Bor\u00e7 Senetleri Reeskontu(-)": {
"account_type": "Payable"
},
"Di\u011fer Ticari Bor\u00e7lar": {
"account_type": "Payable"
},
"Sat\u0131c\u0131lar": {
"account_type": "Payable"
},
"account_type": "Payable"
},
"Y\u0131llara Yayg\u0131n \u0130n\u015faat Ve Onar\u0131m Hakedi\u015fleri": {
"350 Y\u0131llara Yayg\u0131n \u0130n\u015faat Ve Onar\u0131m Hakedi\u015fleri Bedelleri": {
"account_type": "Payable"
},
"account_type": "Payable"
},
"root_type": "",
"\u00d6denecek Vergi ve Di\u011fer Y\u00fck\u00fcml\u00fcl\u00fckler": {
"Vadesi Ge\u00e7mi\u015f, Ertelenmi\u015f Veya Taksitlendirilmi\u015f Vergi Ve Di\u011fer Y\u00fck\u00fcml\u00fcl\u00fckler": {
"account_type": "Tax"
},
"account_type": "Tax",
"\u00d6denecek Di\u011fer Y\u00fck\u00fcml\u00fcl\u00fckler": {
"account_type": "Tax"
},
"\u00d6denecek Sosyal G\u00fcvenl\u00fck Kesintileri": {
"account_type": "Tax"
},
"\u00d6denecek Vergi Ve Fonlar": {
"account_type": "Tax"
}
}
},
"Maliyet Hesaplar\u0131": {
"Ara\u015ft\u0131rma Ve Geli\u015ftirme Giderleri": {},
"Direkt \u0130lk Madde Ve Malzeme Giderleri": {
"Direk \u0130lk Madde Ve Malzeme Giderleri Hesab\u0131": {},
"Direkt \u0130lk Madde Ve Malzeme Fiyat Fark\u0131": {},
"Direkt \u0130lk Madde Ve Malzeme Miktar Fark\u0131": {},
"Direkt \u0130lk Madde Ve Malzeme Yans\u0131tma Hesab\u0131": {}
},
"Direkt \u0130\u015f\u00e7ilik Giderleri": {
"Direkt \u0130\u015f\u00e7ilik Giderleri": {},
"Direkt \u0130\u015f\u00e7ilik Giderleri Yans\u0131tma Hesab\u0131": {},
"Direkt \u0130\u015f\u00e7ilik S\u00fcre Farklar\u0131": {},
"Direkt \u0130\u015f\u00e7ilik \u00dccret Farklar\u0131": {}
},
"Finansman Giderleri": {
"Finansman Giderleri": {},
"Finansman Giderleri Fark Hesab\u0131": {},
"Finansman Giderleri Yans\u0131tma Hesab\u0131": {}
},
"Genel Y\u00f6netim Giderleri": {
"Genel Y\u00f6netim Gider Farklar\u0131 Hesab\u0131": {},
"Genel Y\u00f6netim Giderleri": {},
"Genel Y\u00f6netim Giderleri Yans\u0131tma Hesab\u0131": {}
},
"Genel \u00dcretim Giderleri": {
"Genel \u00dcretim Giderleri": {},
"Genel \u00dcretim Giderleri B\u00fct\u00e7e Farklar\u0131": {},
"Genel \u00dcretim Giderleri Kapasite Farklar\u0131": {},
"Genel \u00dcretim Giderleri Verimlilik Giderleri": {},
"Genel \u00dcretim Giderleri Yans\u0131tma Hesab\u0131": {}
},
"Hizmet \u00dcretim Maliyeti": {
"Hizmet \u00dcretim Maliyeti": {},
"Hizmet \u00dcretim Maliyeti Fark Hesaplar\u0131": {},
"Hizmet \u00dcretim Maliyeti Yans\u0131tma Hesab\u0131": {}
},
"Maliyet Muhasebesi Ba\u011flant\u0131 Hesaplar\u0131": {
"Maliyet Muhasebesi Ba\u011flant\u0131 Hesab\u0131": {},
"Maliyet Muhasebesi Yans\u0131tma Hesab\u0131": {}
},
"Pazarlama, Sat\u0131\u015f Ve Da\u011f\u0131t\u0131m Giderleri": {
"Atra\u015ft\u0131rma Ve Geli\u015ftirme Giderleri": {},
"Pazarlama Sat\u0131\u015f Ve Dag\u0131t\u0131m Giderleri Yans\u0131tma Hesab\u0131": {},
"Pazarlama Sat\u0131\u015f Ve Da\u011f\u0131t\u0131m Giderleri Fark Hesab\u0131": {}
},
"root_type": ""
},
"Naz\u0131m Hesaplar": {
"root_type": ""
},
"Serbest Hesaplar": {
"root_type": ""
},
"Uzun Vadeli Yabanc\u0131 Kaynaklar": {
"Al\u0131nan Avanslar": {
"Al\u0131nan Di\u011fer Avanslar": {
"account_type": "Payable"
},
"Al\u0131nan Sipari\u015f Avanslar\u0131": {
"account_type": "Payable"
},
"account_type": "Payable"
},
"Bor\u00e7 Ve Gider Kar\u015f\u0131l\u0131klar\u0131": {
"Di\u011fer Bor\u00e7 Ve Gider Kar\u015f\u0131l\u0131klar\u0131": {
"account_type": "Payable"
},
"K\u0131dem Tazminat\u0131 Kar\u015f\u0131l\u0131\u011f\u0131": {},
"account_type": "Payable"
},
"Di\u011fer Bor\u00e7lar": {
"Ba\u011fl\u0131 Ortakl\u0131klara Bor\u00e7lar": {
"account_type": "Payable"
},
"Di\u011fer Bor\u00e7 Senetleri Reeskontu(-)": {
"account_type": "Payable"
},
"Di\u011fer \u00c7e\u015fitli Bor\u00e7lar": {
"account_type": "Payable"
},
"Kamuya Olan Ertelenmi\u015f Veya Taksitlendirilmi\u015f Bor\u00e7lar": {
"account_type": "Payable"
},
"Ortaklara Bor\u00e7lar": {
"account_type": "Payable"
},
"account_type": "Payable",
"\u0130\u015ftiraklere Bor\u00e7lar": {
"account_type": "Payable"
}
},
"Di\u011fer Uzun Vadeli Yabanc\u0131 Kaynaklar": {
"Di\u011fer \u00c7e\u015fitli Uzun Vadeli Yabanc\u0131 Kaynaklar": {
"account_type": "Payable"
},
"Gelecek Y\u0131llara Ertelenmi\u015f Veya Terkin Edilecek KDV": {
"account_type": "Payable"
},
"Tesise Kat\u0131lma Paylar\u0131": {
"account_type": "Payable"
},
"account_type": "Payable"
},
"Gelecek Y\u0131llara Ait Gelirler Ve Gider Tahakkuklar\u0131": {
"Gelecek Y\u0131llara Ait Gelirler": {},
"Gider Tahakkuklar\u0131": {}
},
"Mali Bor\u00e7lar": {
"Banka Kredileri": {
"account_type": "Payable"
},
"Di\u011fer Mali Bor\u00e7lar": {
"account_type": "Payable"
},
"Ertelenmi\u015f Finansal Kiralama Bor\u00e7lanma Maliyetleri(-)": {
"account_type": "Payable"
},
"Finansal Kiralama \u0130\u015flemlerinden Bor\u00e7lar": {
"account_type": "Payable"
},
"Menkul K\u0131ymetler \u0130hra\u00e7 Fark\u0131(-)": {
"account_type": "Payable"
},
"account_type": "Payable",
"\u00c7\u0131kar\u0131lm\u0131\u015f Di\u011fer Menkul K\u0131ymetler": {
"account_type": "Payable"
},
"\u00c7\u0131kar\u0131lm\u0131\u015f Tahviller": {
"account_type": "Payable"
}
},
"Ticari Bor\u00e7lar": {
"Al\u0131nan Depozito Ve Teminatlar": {
"account_type": "Payable"
},
"Bor\u00e7 Senetleri": {
"account_type": "Payable"
},
"Bor\u00e7 Senetleri Reeskontu(-)": {
"account_type": "Payable"
},
"Di\u011fer Ticari Bor\u00e7lar": {
"account_type": "Payable"
},
"Sat\u0131c\u0131lar": {
"account_type": "Payable"
},
"account_type": "Payable"
},
"root_type": ""
},
"\u00d6z Kaynaklar": {
"D\u00f6nem Net K\u00e2r\u0131 (Zarar\u0131)": {
"D\u00f6nem Net K\u00e2r\u0131": {},
"D\u00f6nem Net Zarar\u0131(-)": {}
},
"Ge\u00e7mi\u015f Y\u0131llar K\u00e2rlar\u0131": {
"Ge\u00e7mi\u015f Y\u0131llar K\u00e2rlar\u0131": {}
},
"Ge\u00e7mi\u015f Y\u0131llar Zararlar\u0131(-)": {
"Ge\u00e7mi\u015f Y\u0131llar Zararlar\u0131(-)": {}
},
"K\u00e2r Yedekleri": {
"Di\u011fer K\u00e2r Yedekleri": {},
"Ola\u011fan\u00fcst\u00fc Yedekler": {},
"Stat\u00fc Yedekleri": {},
"Yasal Yedekler": {},
"\u00d6zel Fonlar": {}
},
"Sermaye Yedekleri": {
"Di\u011fer Sermaye Yedekleri": {},
"Hisse Senedi \u0130ptal K\u00e2rlar\u0131": {},
"Hisse Senetleri \u0130hra\u00e7 Primleri": {},
"Maddi Duran Varl\u0131k Yeniden De\u011ferlenme Art\u0131\u015flar\u0131": {},
"Maliyet Art\u0131\u015flar\u0131 Fonu": {},
"\u0130\u015ftirakler Yeniden De\u011ferleme Art\u0131\u015flar\u0131": {}
},
"root_type": "",
"\u00d6denmi\u015f Sermaye": {
"Sermaye": {},
"\u00d6denmi\u015f Sermaye(-)": {
"account_type": "Payable"
}
}
}
}
}

View File

@@ -56,9 +56,7 @@
"Constru\u00e7\u00f5es em Andamento de Im\u00f3veis Destinados \u00e0 Venda": {}, "Constru\u00e7\u00f5es em Andamento de Im\u00f3veis Destinados \u00e0 Venda": {},
"Estoques Destinados \u00e0 Doa\u00e7\u00e3o": {}, "Estoques Destinados \u00e0 Doa\u00e7\u00e3o": {},
"Im\u00f3veis Destinados \u00e0 Venda": {}, "Im\u00f3veis Destinados \u00e0 Venda": {},
"Insumos (materiais diretos)": { "Insumos (materiais diretos)": {},
"account_type": "Stock"
},
"Insumos Agropecu\u00e1rios": {}, "Insumos Agropecu\u00e1rios": {},
"Mercadorias para Revenda": {}, "Mercadorias para Revenda": {},
"Outras 11": {}, "Outras 11": {},
@@ -148,65 +146,6 @@
"root_type": "Asset" "root_type": "Asset"
}, },
"CUSTOS DE PRODU\u00c7\u00c3O": { "CUSTOS DE PRODU\u00c7\u00c3O": {
"CUSTO DOS PRODUTOS E SERVI\u00c7OS VENDIDOS": {
"CUSTO DOS PRODUTOS VENDIDOS": {
"CUSTO DOS PRODUTOS VENDIDOS PARA AS DEMAIS ATIVIDADES": {
"Custos dos Produtos Vendidos em Geral": {
"account_type": "Cost of Goods Sold"
},
"Outros Custos 4": {},
"account_type": "Cost of Goods Sold"
},
"CUSTO DOS PRODUTOS VENDIDOS PARA ASSIST\u00caNCIA SOCIAL": {
"Custos dos Produtos para Assist\u00eancia Social - Gratuidades": {},
"Custos dos Produtos para Assist\u00eancia Social - Vendidos": {},
"Outras": {}
},
"CUSTO DOS PRODUTOS VENDIDOS PARA EDUCA\u00c7\u00c3O": {
"Custos dos Produtos para Educa\u00e7\u00e3o - Gratuidades": {},
"Custos dos Produtos para Educa\u00e7\u00e3o - Vendidos": {},
"Outros Custos 6": {}
},
"CUSTO DOS PRODUTOS VENDIDOS PARA SA\u00daDE": {
"Custos dos Produtos para Sa\u00fade - Gratuidades": {},
"Custos dos Produtos para Sa\u00fade \u2013 Vendidos": {},
"Outros Custos 5": {}
},
"account_type": "Cost of Goods Sold"
},
"CUSTO DOS SERVI\u00c7OS PRESTADOS": {
"CUSTO DOS SERVI\u00c7OS PRESTADOS PARA AS DEMAIS ATIVIDADES": {
"Custo dos Servi\u00e7os Prestados em Geral": {},
"Outros Custos": {}
},
"CUSTO DOS SERVI\u00c7OS PRESTADOS PARA ASSIST\u00caNCIA SOCIAL": {
"Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es 1": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas 1": {},
"Custo dos Servi\u00e7os Prestados a Gratuidade 1": {},
"Custo dos Servi\u00e7os Prestados a Pacientes Particulares": {},
"Outros Custos 2": {}
},
"CUSTO DOS SERVI\u00c7OS PRESTADOS PARA EDUCA\u00c7\u00c3O": {
"Custo dos Servi\u00e7os Prestados a Alunos N\u00e3o Bolsistas": {},
"Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias (Exceto PROUNI)": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas": {},
"Custo dos Servi\u00e7os Prestados a Gratuidade": {},
"Custo dos Servi\u00e7os Prestados ao PROUNI": {},
"Outros Custos 1": {}
},
"CUSTO DOS SERVI\u00c7OS PRESTADOS PARA SA\u00daDE": {
"Custo dos Servi\u00e7os Prestados a Conv\u00eanios SUS": {},
"Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias 1": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es 2": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas 2": {},
"Custo dos Servi\u00e7os Prestados a Gratuidade 2": {},
"Custo dos Servi\u00e7os Prestados a Pacientes Particulares 1": {},
"Outros Custos 3": {}
}
}
},
"CUSTO DOS BENS E SERVI\u00c7OS PRODUZIDOS": { "CUSTO DOS BENS E SERVI\u00c7OS PRODUZIDOS": {
"CUSTO DOS PRODUTOS DE FABRICA\u00c7\u00c3O PR\u00d3PRIA PRODUZIDOS": { "CUSTO DOS PRODUTOS DE FABRICA\u00c7\u00c3O PR\u00d3PRIA PRODUZIDOS": {
"Alimenta\u00e7\u00e3o do Trabalhador": {}, "Alimenta\u00e7\u00e3o do Trabalhador": {},
@@ -682,9 +621,7 @@
"Receita das Unidades Imobili\u00e1rias Vendidas": {}, "Receita das Unidades Imobili\u00e1rias Vendidas": {},
"Receita de Exporta\u00e7\u00e3o Direta de Mercadorias e Produtos": {}, "Receita de Exporta\u00e7\u00e3o Direta de Mercadorias e Produtos": {},
"Receita de Exporta\u00e7\u00e3o de Servi\u00e7os": {}, "Receita de Exporta\u00e7\u00e3o de Servi\u00e7os": {},
"Receita de Loca\u00e7\u00e3o de Bens M\u00f3veis e Im\u00f3veis": { "Receita de Loca\u00e7\u00e3o de Bens M\u00f3veis e Im\u00f3veis": {},
"account_type": "Income Account"
},
"Receita de Vendas de Mercadorias e Produtos a Comercial Exportadora com Fim Espec\u00edfico de Exporta\u00e7\u00e3o": {} "Receita de Vendas de Mercadorias e Produtos a Comercial Exportadora com Fim Espec\u00edfico de Exporta\u00e7\u00e3o": {}
} }
} }
@@ -708,6 +645,65 @@
} }
}, },
"RESULTADO OPERACIONAL": { "RESULTADO OPERACIONAL": {
"CUSTO DOS PRODUTOS E SERVI\u00c7OS VENDIDOS": {
"CUSTO DOS PRODUTOS VENDIDOS": {
"CUSTO DOS PRODUTOS VENDIDOS PARA AS DEMAIS ATIVIDADES": {
"Custos dos Produtos Vendidos em Geral": {
"account_type": "Cost of Goods Sold"
},
"Outros Custos 4": {},
"account_type": "Cost of Goods Sold"
},
"CUSTO DOS PRODUTOS VENDIDOS PARA ASSIST\u00caNCIA SOCIAL": {
"Custos dos Produtos para Assist\u00eancia Social - Gratuidades": {},
"Custos dos Produtos para Assist\u00eancia Social - Vendidos": {},
"Outras": {}
},
"CUSTO DOS PRODUTOS VENDIDOS PARA EDUCA\u00c7\u00c3O": {
"Custos dos Produtos para Educa\u00e7\u00e3o - Gratuidades": {},
"Custos dos Produtos para Educa\u00e7\u00e3o - Vendidos": {},
"Outros Custos 6": {}
},
"CUSTO DOS PRODUTOS VENDIDOS PARA SA\u00daDE": {
"Custos dos Produtos para Sa\u00fade - Gratuidades": {},
"Custos dos Produtos para Sa\u00fade \u2013 Vendidos": {},
"Outros Custos 5": {}
},
"account_type": "Cost of Goods Sold"
},
"CUSTO DOS SERVI\u00c7OS PRESTADOS": {
"CUSTO DOS SERVI\u00c7OS PRESTADOS PARA AS DEMAIS ATIVIDADES": {
"Custo dos Servi\u00e7os Prestados em Geral": {},
"Outros Custos": {}
},
"CUSTO DOS SERVI\u00c7OS PRESTADOS PARA ASSIST\u00caNCIA SOCIAL": {
"Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es 1": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas 1": {},
"Custo dos Servi\u00e7os Prestados a Gratuidade 1": {},
"Custo dos Servi\u00e7os Prestados a Pacientes Particulares": {},
"Outros Custos 2": {}
},
"CUSTO DOS SERVI\u00c7OS PRESTADOS PARA EDUCA\u00c7\u00c3O": {
"Custo dos Servi\u00e7os Prestados a Alunos N\u00e3o Bolsistas": {},
"Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias (Exceto PROUNI)": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas": {},
"Custo dos Servi\u00e7os Prestados a Gratuidade": {},
"Custo dos Servi\u00e7os Prestados ao PROUNI": {},
"Outros Custos 1": {}
},
"CUSTO DOS SERVI\u00c7OS PRESTADOS PARA SA\u00daDE": {
"Custo dos Servi\u00e7os Prestados a Conv\u00eanios SUS": {},
"Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias 1": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es 2": {},
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas 2": {},
"Custo dos Servi\u00e7os Prestados a Gratuidade 2": {},
"Custo dos Servi\u00e7os Prestados a Pacientes Particulares 1": {},
"Outros Custos 3": {}
}
}
},
"DESPESAS OPERACIONAIS": { "DESPESAS OPERACIONAIS": {
"DESPESAS OPERACIONAIS 1": { "DESPESAS OPERACIONAIS 1": {
"DESPESAS OPERACIONAIS 2": { "DESPESAS OPERACIONAIS 2": {

View File

@@ -33,9 +33,7 @@
}, },
"Stocks": { "Stocks": {
"Mati\u00e8res premi\u00e8res": {}, "Mati\u00e8res premi\u00e8res": {},
"Stock de produits fini": { "Stock de produits fini": {},
"account_type": "Stock"
},
"Stock exp\u00e9di\u00e9 non-factur\u00e9": {}, "Stock exp\u00e9di\u00e9 non-factur\u00e9": {},
"Travaux en cours": {}, "Travaux en cours": {},
"account_type": "Stock" "account_type": "Stock"
@@ -397,11 +395,9 @@
}, },
"Produits": { "Produits": {
"Revenus de ventes": { "Revenus de ventes": {
"Escomptes de volume sur ventes": {}, " Escomptes de volume sur ventes": {},
"Autres produits d'exploitation": {}, "Autres produits d'exploitation": {},
"Ventes": { "Ventes": {},
"account_type": "Income Account"
},
"Ventes avec des provinces harmonis\u00e9es": {}, "Ventes avec des provinces harmonis\u00e9es": {},
"Ventes avec des provinces non-harmonis\u00e9es": {}, "Ventes avec des provinces non-harmonis\u00e9es": {},
"Ventes \u00e0 l'\u00e9tranger": {} "Ventes \u00e0 l'\u00e9tranger": {}

View File

@@ -53,13 +53,8 @@
}, },
"II. Forderungen und sonstige Vermögensgegenstände": { "II. Forderungen und sonstige Vermögensgegenstände": {
"is_group": 1, "is_group": 1,
"Forderungen aus Lieferungen und Leistungen mit Kontokorrent": { "Ford. a. Lieferungen und Leistungen": {
"account_number": "1400", "account_number": "1400",
"account_type": "Receivable",
"is_group": 1
},
"Forderungen aus Lieferungen und Leistungen ohne Kontokorrent": {
"account_number": "1410",
"account_type": "Receivable" "account_type": "Receivable"
}, },
"Durchlaufende Posten": { "Durchlaufende Posten": {
@@ -185,13 +180,8 @@
}, },
"IV. Verbindlichkeiten aus Lieferungen und Leistungen": { "IV. Verbindlichkeiten aus Lieferungen und Leistungen": {
"is_group": 1, "is_group": 1,
"Verbindlichkeiten aus Lieferungen und Leistungen mit Kontokorrent": { "Verbindlichkeiten aus Lieferungen u. Leistungen": {
"account_number": "1600", "account_number": "1600",
"account_type": "Payable",
"is_group": 1
},
"Verbindlichkeiten aus Lieferungen und Leistungen ohne Kontokorrent": {
"account_number": "1610",
"account_type": "Payable" "account_type": "Payable"
} }
}, },

View File

@@ -407,10 +407,13 @@
"Bewertungskorrektur zu Forderungen aus Lieferungen und Leistungen": { "Bewertungskorrektur zu Forderungen aus Lieferungen und Leistungen": {
"account_number": "9960" "account_number": "9960"
}, },
"Forderungen aus Lieferungen und Leistungen mit Kontokorrent": { "Debitoren": {
"is_group": 1,
"account_number": "10000"
},
"Forderungen aus Lieferungen und Leistungen": {
"account_number": "1200", "account_number": "1200",
"account_type": "Receivable", "account_type": "Receivable"
"is_group": 1
}, },
"Forderungen aus Lieferungen und Leistungen ohne Kontokorrent": { "Forderungen aus Lieferungen und Leistungen ohne Kontokorrent": {
"account_number": "1210" "account_number": "1210"
@@ -1135,15 +1138,18 @@
"Bewertungskorrektur zu Verb. aus Lieferungen und Leistungen": { "Bewertungskorrektur zu Verb. aus Lieferungen und Leistungen": {
"account_number": "9964" "account_number": "9964"
}, },
"Verb. aus Lieferungen und Leistungen mit Kontokorrent": { "Kreditoren": {
"account_number": "3300", "account_number": "70000",
"account_type": "Payable",
"is_group": 1, "is_group": 1,
"Wareneingangs-Verrechnungskonto" : { "Wareneingangs-­Verrechnungskonto" : {
"account_number": "70001", "account_number": "70001",
"account_type": "Stock Received But Not Billed" "account_type": "Stock Received But Not Billed"
} }
}, },
"Verb. aus Lieferungen und Leistungen": {
"account_number": "3300",
"account_type": "Payable"
},
"Verb. aus Lieferungen und Leistungen ohne Kontokorrent": { "Verb. aus Lieferungen und Leistungen ohne Kontokorrent": {
"account_number": "3310" "account_number": "3310"
}, },

View File

@@ -1525,8 +1525,7 @@
"41-Clients et comptes rattach\u00e9s (PASSIF)": { "41-Clients et comptes rattach\u00e9s (PASSIF)": {
"Clients cr\u00e9diteurs": { "Clients cr\u00e9diteurs": {
"Clients - Avances et acomptes re\u00e7us sur commandes": { "Clients - Avances et acomptes re\u00e7us sur commandes": {
"account_number": "4191", "account_number": "4191"
"account_type": "Income Account"
}, },
"Clients - Dettes pour emballages et mat\u00e9riels consign\u00e9s": { "Clients - Dettes pour emballages et mat\u00e9riels consign\u00e9s": {
"account_number": "4196" "account_number": "4196"

View File

@@ -1,6 +1,4 @@
{ {
"country_code": "hu",
"name": "Hungary - Chart of Accounts for Microenterprises",
"tree": { "tree": {
"SZ\u00c1MLAOSZT\u00c1LY BEFEKTETETT ESZK\u00d6Z\u00d6K": { "SZ\u00c1MLAOSZT\u00c1LY BEFEKTETETT ESZK\u00d6Z\u00d6K": {
"account_number": 1, "account_number": 1,

View File

@@ -109,8 +109,7 @@
"Utility Expenses": {}, "Utility Expenses": {},
"Write Off": {}, "Write Off": {},
"Exchange Gain/Loss": {}, "Exchange Gain/Loss": {},
"Gain/Loss on Asset Disposal": {}, "Gain/Loss on Asset Disposal": {}
"Impairment": {}
}, },
"root_type": "Expense" "root_type": "Expense"
}, },
@@ -133,8 +132,7 @@
"Source of Funds (Liabilities)": { "Source of Funds (Liabilities)": {
"Capital Account": { "Capital Account": {
"Reserves and Surplus": {}, "Reserves and Surplus": {},
"Shareholders Funds": {}, "Shareholders Funds": {}
"Revaluation Surplus": {}
}, },
"Current Liabilities": { "Current Liabilities": {
"Accounts Payable": { "Accounts Payable": {

View File

@@ -72,7 +72,6 @@ def get():
_("Write Off"): {}, _("Write Off"): {},
_("Exchange Gain/Loss"): {}, _("Exchange Gain/Loss"): {},
_("Gain/Loss on Asset Disposal"): {}, _("Gain/Loss on Asset Disposal"): {},
_("Impairment"): {},
}, },
"root_type": "Expense", "root_type": "Expense",
}, },
@@ -105,7 +104,6 @@ def get():
_("Dividends Paid"): {"account_type": "Equity"}, _("Dividends Paid"): {"account_type": "Equity"},
_("Opening Balance Equity"): {"account_type": "Equity"}, _("Opening Balance Equity"): {"account_type": "Equity"},
_("Retained Earnings"): {"account_type": "Equity"}, _("Retained Earnings"): {"account_type": "Equity"},
_("Revaluation Surplus"): {"account_type": "Equity"},
"root_type": "Equity", "root_type": "Equity",
}, },
} }

View File

@@ -6,7 +6,6 @@ import unittest
import frappe import frappe
from frappe.test_runner import make_test_records from frappe.test_runner import make_test_records
from frappe.utils import nowdate
from erpnext.accounts.doctype.account.account import ( from erpnext.accounts.doctype.account.account import (
InvalidAccountMergeError, InvalidAccountMergeError,
@@ -261,20 +260,28 @@ class TestAccount(unittest.TestCase):
acc.insert() acc.insert()
self.assertTrue( self.assertTrue(
frappe.db.exists("Account", {"account_name": "Test Group Account", "company": "_Test Company 4"}) frappe.db.exists(
"Account", {"account_name": "Test Group Account", "company": "_Test Company 4"}
)
) )
self.assertTrue( self.assertTrue(
frappe.db.exists("Account", {"account_name": "Test Group Account", "company": "_Test Company 5"}) frappe.db.exists(
"Account", {"account_name": "Test Group Account", "company": "_Test Company 5"}
)
) )
# Try renaming child company account # 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(frappe.ValidationError, update_account_number, acc_tc_5, "Test Modified Account") self.assertRaises(
frappe.ValidationError, update_account_number, acc_tc_5, "Test Modified Account"
)
# Rename child company account with allow_account_creation_against_child_company enabled # Rename child company account with allow_account_creation_against_child_company enabled
frappe.db.set_value("Company", "_Test Company 5", "allow_account_creation_against_child_company", 1) frappe.db.set_value(
"Company", "_Test Company 5", "allow_account_creation_against_child_company", 1
)
update_account_number(acc_tc_5, "Test Modified Account") update_account_number(acc_tc_5, "Test Modified Account")
self.assertTrue( self.assertTrue(
@@ -283,7 +290,9 @@ class TestAccount(unittest.TestCase):
) )
) )
frappe.db.set_value("Company", "_Test Company 5", "allow_account_creation_against_child_company", 0) frappe.db.set_value(
"Company", "_Test Company 5", "allow_account_creation_against_child_company", 0
)
to_delete = [ to_delete = [
"Test Group Account - _TC3", "Test Group Account - _TC3",
@@ -308,24 +317,13 @@ 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("Test Currency Account - _TC", "Miscellaneous Expenses - _TC", 100, submit=True) make_journal_entry(
"Test Currency Account - _TC", "Miscellaneous Expenses - _TC", 100, submit=True
)
acc.account_currency = "USD" acc.account_currency = "USD"
self.assertRaises(frappe.ValidationError, acc.save) self.assertRaises(frappe.ValidationError, acc.save)
def test_account_balance(self):
from erpnext.accounts.utils import get_balance_on
if not frappe.db.exists("Account", "Test Percent Account %5 - _TC"):
acc = frappe.new_doc("Account")
acc.account_name = "Test Percent Account %5"
acc.parent_account = "Tax Assets - _TC"
acc.company = "_Test Company"
acc.insert()
balance = get_balance_on(account="Test Percent Account %5 - _TC", date=nowdate())
self.assertEqual(balance, 0)
def _make_test_records(verbose=None): def _make_test_records(verbose=None):
from frappe.test_runner import make_test_objects from frappe.test_runner import make_test_objects

View File

@@ -11,41 +11,22 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
class AccountClosingBalance(Document): class AccountClosingBalance(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
account: DF.Link | None
account_currency: DF.Link | None
closing_date: DF.Date | None
company: DF.Link | None
cost_center: DF.Link | None
credit: DF.Currency
credit_in_account_currency: DF.Currency
debit: DF.Currency
debit_in_account_currency: DF.Currency
finance_book: DF.Link | None
is_period_closing_voucher_entry: DF.Check
period_closing_voucher: DF.Link | None
project: DF.Link | None
# end: auto-generated types
pass pass
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(company, closing_date, accounting_dimensions) previous_closing_entries = get_previous_closing_entries(
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(combined_entries, accounting_dimensions) merged_entries = aggregate_with_last_account_closing_balance(
combined_entries, accounting_dimensions
)
for _key, value in merged_entries.items(): for key, value in merged_entries.items():
cle = frappe.new_doc("Account Closing Balance") cle = frappe.new_doc("Account Closing Balance")
cle.update(value) cle.update(value)
cle.update(value["dimensions"]) cle.update(value["dimensions"])
@@ -113,9 +94,9 @@ def get_previous_closing_entries(company, closing_date, accounting_dimensions):
entries = [] entries = []
last_period_closing_voucher = frappe.db.get_all( last_period_closing_voucher = frappe.db.get_all(
"Period Closing Voucher", "Period Closing Voucher",
filters={"docstatus": 1, "company": company, "period_end_date": ("<", closing_date)}, filters={"docstatus": 1, "company": company, "posting_date": ("<", closing_date)},
fields=["name"], fields=["name"],
order_by="period_end_date desc", order_by="posting_date desc",
limit=1, limit=1,
) )

View File

@@ -1,89 +1,74 @@
// 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( invalid_doctypes.push('Accounting Dimension', 'Project',
"Accounting Dimension", 'Cost Center', 'Accounting Dimension Detail', 'Company');
"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'});
}, }
}); });
}); });
} }
}, },
label: function (frm) { document_type: function(frm) {
frm.set_value("fieldname", frm.doc.label.replace(/ /g, "_").replace(/-/g, "_").toLowerCase());
},
document_type: function (frm) { frm.set_value('label', frm.doc.document_type);
frm.set_value("label", frm.doc.document_type); frm.set_value('fieldname', frappe.model.scrub(frm.doc.document_type));
frappe.db.get_value( frappe.db.get_value('Accounting Dimension', {'document_type': frm.doc.document_type}, 'document_type', (r) => {
"Accounting Dimension",
{ document_type: frm.doc.document_type },
"document_type",
(r) => {
if (r && r.document_type) { if (r && r.document_type) {
frm.set_df_property( frm.set_df_property('document_type', 'description', "Document type is already set as dimension");
"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

@@ -7,42 +7,17 @@ import json
import frappe import frappe
from frappe import _, scrub from frappe import _, scrub
from frappe.custom.doctype.custom_field.custom_field import create_custom_field from frappe.custom.doctype.custom_field.custom_field import create_custom_field
from frappe.database.schema import validate_column_name
from frappe.model import core_doctypes_list 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
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
from erpnext.accounts.doctype.accounting_dimension_detail.accounting_dimension_detail import (
AccountingDimensionDetail,
)
dimension_defaults: DF.Table[AccountingDimensionDetail]
disabled: DF.Check
document_type: DF.Link
fieldname: DF.Data | None
label: DF.Data | None
# end: auto-generated types
def before_insert(self): def before_insert(self):
self.set_fieldname_and_label() self.set_fieldname_and_label()
def validate(self): def validate(self):
if self.document_type in ( if self.document_type in core_doctypes_list + (
*core_doctypes_list,
"Accounting Dimension", "Accounting Dimension",
"Project", "Project",
"Cost Center", "Cost Center",
@@ -50,10 +25,13 @@ 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("Accounting Dimension", {"document_type": self.document_type}, ["name"]) exists = frappe.db.get_value(
"Accounting Dimension", {"document_type": self.document_type}, ["name"]
)
if exists and self.is_new(): if exists and self.is_new():
frappe.throw(_("Document Type already used as a dimension")) frappe.throw(_("Document Type already used as a dimension"))
@@ -61,7 +39,6 @@ class AccountingDimension(Document):
if not self.is_new(): if not self.is_new():
self.validate_document_type_change() self.validate_document_type_change()
validate_column_name(self.fieldname)
self.validate_dimension_defaults() self.validate_dimension_defaults()
def validate_document_type_change(self): def validate_document_type_change(self):
@@ -110,9 +87,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:
@@ -125,7 +102,6 @@ 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)
@@ -147,7 +123,7 @@ def add_dimension_to_budget_doctype(df, doc):
df.update( df.update(
{ {
"insert_after": "cost_center", "insert_after": "cost_center",
"depends_on": f"eval:doc.budget_against == '{doc.document_type}'", "depends_on": "eval:doc.budget_against == '{0}'".format(doc.document_type),
} }
) )
@@ -181,17 +157,19 @@ def delete_accounting_dimension(doc):
frappe.db.sql( frappe.db.sql(
""" """
DELETE FROM `tabCustom Field` DELETE FROM `tabCustom Field`
WHERE fieldname = {} WHERE fieldname = %s
AND dt IN ({})""".format("%s", ", ".join(["%s"] * len(doclist))), # nosec AND dt IN (%s)"""
tuple([doc.fieldname, *doclist]), % ("%s", ", ".join(["%s"] * len(doclist))), # nosec
tuple([doc.fieldname] + doclist),
) )
frappe.db.sql( frappe.db.sql(
""" """
DELETE FROM `tabProperty Setter` DELETE FROM `tabProperty Setter`
WHERE field_name = {} WHERE field_name = %s
AND doc_type IN ({})""".format("%s", ", ".join(["%s"] * len(doclist))), # nosec AND doc_type IN (%s)"""
tuple([doc.fieldname, *doclist]), % ("%s", ", ".join(["%s"] * len(doclist))), # nosec
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")
@@ -240,6 +218,7 @@ 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}
@@ -257,19 +236,18 @@ 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():
if frappe.flags.accounting_dimensions_details is None: dimensions = frappe.db.sql(
# nosemgrep
frappe.flags.accounting_dimensions_details = frappe.db.sql(
"""SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs """SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
FROM `tabAccounting Dimension`p ,`tabAccounting Dimension Detail` c FROM `tabAccounting Dimension`p ,`tabAccounting Dimension Detail` c
WHERE p.name = c.parent""", WHERE p.name = c.parent""",
as_dict=1, as_dict=1,
) )
return frappe.flags.accounting_dimensions_details return dimensions
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]
@@ -277,7 +255,9 @@ 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(doctype, filters={"lft": [">=", lft], "rgt": ["<=", rgt]}, order_by="lft") children = frappe.get_all(
doctype, filters={"lft": [">=", lft], "rgt": ["<=", rgt]}, order_by="lft"
)
all_dimensions += [c.name for c in children] all_dimensions += [c.name for c in children]
return all_dimensions return all_dimensions
@@ -285,10 +265,14 @@ 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).select(p.label, p.fieldname, p.document_type).where(p.disabled == 0).run(as_dict=1) frappe.qb.from_(p)
.select(p.label, p.fieldname, p.document_type)
.where(p.disabled == 0)
.run(as_dict=1)
) )
default_dimensions = ( default_dimensions = (
frappe.qb.from_(c) frappe.qb.from_(c)

View File

@@ -78,8 +78,6 @@ 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

@@ -7,24 +7,4 @@ from frappe.model.document import Document
class AccountingDimensionDetail(Document): class AccountingDimensionDetail(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
automatically_post_balancing_accounting_entry: DF.Check
company: DF.Link | None
default_dimension: DF.DynamicLink | None
mandatory_for_bs: DF.Check
mandatory_for_pl: DF.Check
offsetting_account: DF.Link | None
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
reference_document: DF.Link | None
# end: auto-generated types
pass pass

View File

@@ -1,9 +1,10 @@
// 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 = `<table class="table table-bordered" style="background-color: var(--scrollbar-track-color);"> let help_content =
`<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>
@@ -12,65 +13,62 @@ 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", { fields: ["document_type"] }).then((res) => { frappe.db.get_list('Accounting Dimension',
let options = ["Cost Center", "Project"]; {fields: ['document_type']}).then((res) => {
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( frm.fields_dict["dimensions"].grid.update_docfield_property("dimension_value", "label", frm.doc.accounting_dimension);
"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.
*/ */
@@ -79,13 +77,13 @@ frappe.ui.form.on("Accounting Dimension Filter", {
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

@@ -8,28 +8,6 @@ from frappe.model.document import Document
class AccountingDimensionFilter(Document): class AccountingDimensionFilter(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
from erpnext.accounts.doctype.allowed_dimension.allowed_dimension import AllowedDimension
from erpnext.accounts.doctype.applicable_on_account.applicable_on_account import (
ApplicableOnAccount,
)
accounting_dimension: DF.Literal
accounts: DF.Table[ApplicableOnAccount]
allow_or_restrict: DF.Literal["Allow", "Restrict"]
apply_restriction_on_values: DF.Check
company: DF.Link
dimensions: DF.Table[AllowedDimension]
disabled: DF.Check
# end: auto-generated types
def before_save(self): def before_save(self):
# If restriction is not applied on values, then remove all the dimensions and set allow_or_restrict to Restrict # If restriction is not applied on values, then remove all the dimensions and set allow_or_restrict to Restrict
if not self.apply_restriction_on_values: if not self.apply_restriction_on_values:
@@ -66,8 +44,6 @@ class AccountingDimensionFilter(Document):
def get_dimension_filter_map(): def get_dimension_filter_map():
if not frappe.flags.get("dimension_filter_map"):
# nosemgrep
filters = frappe.db.sql( filters = frappe.db.sql(
""" """
SELECT SELECT
@@ -97,9 +73,8 @@ def get_dimension_filter_map():
f.allow_or_restrict, f.allow_or_restrict,
f.is_mandatory, f.is_mandatory,
) )
frappe.flags.dimension_filter_map = dimension_filter_map
return frappe.flags.dimension_filter_map return 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,8 +47,6 @@ 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()
@@ -57,7 +55,9 @@ class TestAccountingDimensionFilter(unittest.TestCase):
def create_accounting_dimension_filter(): def create_accounting_dimension_filter():
if not frappe.db.get_value("Accounting Dimension Filter", {"accounting_dimension": "Cost Center"}): if not frappe.db.get_value(
"Accounting Dimension Filter", {"accounting_dimension": "Cost Center"}
):
frappe.get_doc( frappe.get_doc(
{ {
"doctype": "Accounting Dimension Filter", "doctype": "Accounting Dimension Filter",

View File

@@ -1,33 +1,30 @@
// 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 ( if(frm.doc.closed_documents.length === 0 || (frm.doc.closed_documents.length === 1 && frm.doc.closed_documents[0].document_type == undefined)) {
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

@@ -16,23 +16,6 @@ class ClosedAccountingPeriod(frappe.ValidationError):
class AccountingPeriod(Document): class AccountingPeriod(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
from erpnext.accounts.doctype.closed_document.closed_document import ClosedDocument
closed_documents: DF.Table[ClosedDocument]
company: DF.Link
end_date: DF.Date
period_name: DF.Data
start_date: DF.Date
# end: auto-generated types
def validate(self): def validate(self):
self.validate_overlap() self.validate_overlap()
@@ -84,10 +67,7 @@ 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,
},
) )
@@ -101,8 +81,6 @@ def validate_accounting_period_on_doc_save(doc, method=None):
date = doc.available_for_use_date date = doc.available_for_use_date
elif doc.doctype == "Asset Repair": elif doc.doctype == "Asset Repair":
date = doc.completion_date date = doc.completion_date
elif doc.doctype == "Period Closing Voucher":
date = doc.period_end_date
else: else:
date = doc.posting_date date = doc.posting_date

View File

@@ -34,7 +34,9 @@ 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(do_not_save=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC") doc = create_sales_invoice(
do_not_save=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC"
)
self.assertRaises(ClosedAccountingPeriod, doc.save) self.assertRaises(ClosedAccountingPeriod, doc.save)
def tearDown(self): def tearDown(self):

View File

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

@@ -1,6 +1,7 @@
{ {
"actions": [], "actions": [],
"creation": "2013-06-24 15:49:57", "creation": "2013-06-24 15:49:57",
"description": "Settings for Accounts",
"doctype": "DocType", "doctype": "DocType",
"document_type": "Other", "document_type": "Other",
"editable_grid": 1, "editable_grid": 1,
@@ -12,7 +13,6 @@
"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",
@@ -55,8 +55,6 @@
"post_change_gl_entries", "post_change_gl_entries",
"assets_tab", "assets_tab",
"asset_settings_section", "asset_settings_section",
"calculate_depr_using_total_days",
"column_break_gjcc",
"book_asset_depreciation_entry_automatically", "book_asset_depreciation_entry_automatically",
"closing_settings_tab", "closing_settings_tab",
"period_closing_settings_section", "period_closing_settings_section",
@@ -68,14 +66,7 @@
"show_balance_in_coa", "show_balance_in_coa",
"banking_tab", "banking_tab",
"enable_party_matching", "enable_party_matching",
"enable_fuzzy_matching", "enable_fuzzy_matching"
"reports_tab",
"remarks_section",
"general_ledger_remarks_length",
"column_break_lvjk",
"receivable_payable_remarks_length",
"payment_request_settings",
"create_pr_in_draft_status"
], ],
"fields": [ "fields": [
{ {
@@ -110,7 +101,7 @@
}, },
{ {
"default": "0", "default": "0",
"description": "Enabling this ensures each Purchase Invoice has a unique value in Supplier Invoice No. field within a particular fiscal year", "description": "Enabling ensure each Purchase Invoice has a unique value in Supplier Invoice No. field",
"fieldname": "check_supplier_invoice_uniqueness", "fieldname": "check_supplier_invoice_uniqueness",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Check Supplier Invoice Number Uniqueness" "label": "Check Supplier Invoice Number Uniqueness"
@@ -431,64 +422,6 @@
"fieldname": "round_row_wise_tax", "fieldname": "round_row_wise_tax",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Round Tax Amount Row-wise" "label": "Round Tax Amount Row-wise"
},
{
"fieldname": "reports_tab",
"fieldtype": "Tab Break",
"label": "Reports"
},
{
"default": "0",
"description": "Truncates 'Remarks' column to set character length",
"fieldname": "general_ledger_remarks_length",
"fieldtype": "Int",
"label": "General Ledger"
},
{
"default": "0",
"description": "Truncates 'Remarks' column to set character length",
"fieldname": "receivable_payable_remarks_length",
"fieldtype": "Int",
"label": "Accounts Receivable/Payable"
},
{
"fieldname": "column_break_lvjk",
"fieldtype": "Column Break"
},
{
"fieldname": "remarks_section",
"fieldtype": "Section Break",
"label": "Remarks Column Length"
},
{
"default": "0",
"description": "On enabling this cancellation entries will be posted on the actual cancellation date and reports will consider cancelled entries as well",
"fieldname": "enable_immutable_ledger",
"fieldtype": "Check",
"label": "Enable Immutable Ledger"
},
{
"fieldname": "column_break_gjcc",
"fieldtype": "Column Break"
},
{
"default": "0",
"description": "Enable this option to calculate daily depreciation by considering the total number of days in the entire depreciation period, (including leap years) while using daily pro-rata based depreciation",
"fieldname": "calculate_depr_using_total_days",
"fieldtype": "Check",
"label": "Calculate daily depreciation using total days in depreciation period"
},
{
"description": "Payment Request created from Sales Order or Purchase Order will be in Draft status. When disabled document will be in unsaved state.",
"fieldname": "payment_request_settings",
"fieldtype": "Tab Break",
"label": "Payment Request"
},
{
"default": "1",
"fieldname": "create_pr_in_draft_status",
"fieldtype": "Check",
"label": "Create in Draft Status"
} }
], ],
"icon": "icon-cog", "icon": "icon-cog",
@@ -496,7 +429,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2024-07-26 06:48:52.714630", "modified": "2023-08-28 00:12:02.740633",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounts Settings", "name": "Accounts Settings",

View File

@@ -14,55 +14,6 @@ from erpnext.stock.utils import check_pending_reposting
class AccountsSettings(Document): class AccountsSettings(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
acc_frozen_upto: DF.Date | None
add_taxes_from_item_tax_template: DF.Check
allow_multi_currency_invoices_against_single_party_account: DF.Check
allow_stale: DF.Check
auto_reconcile_payments: DF.Check
automatically_fetch_payment_terms: DF.Check
automatically_process_deferred_accounting_entry: DF.Check
book_asset_depreciation_entry_automatically: DF.Check
book_deferred_entries_based_on: DF.Literal["Days", "Months"]
book_deferred_entries_via_journal_entry: DF.Check
book_tax_discount_loss: DF.Check
calculate_depr_using_total_days: DF.Check
check_supplier_invoice_uniqueness: DF.Check
create_pr_in_draft_status: DF.Check
credit_controller: DF.Link | None
delete_linked_ledger_entries: DF.Check
determine_address_tax_category_from: DF.Literal["Billing Address", "Shipping Address"]
enable_common_party_accounting: DF.Check
enable_fuzzy_matching: DF.Check
enable_immutable_ledger: DF.Check
enable_party_matching: DF.Check
frozen_accounts_modifier: DF.Link | None
general_ledger_remarks_length: DF.Int
ignore_account_closing_balance: DF.Check
make_payment_via_journal_entry: DF.Check
merge_similar_account_heads: DF.Check
over_billing_allowance: DF.Currency
post_change_gl_entries: DF.Check
receivable_payable_remarks_length: DF.Int
role_allowed_to_over_bill: DF.Link | None
round_row_wise_tax: DF.Check
show_balance_in_coa: DF.Check
show_inclusive_tax_in_print: DF.Check
show_payment_schedule_in_print: DF.Check
show_taxes_as_table_in_print: DF.Check
stale_days: DF.Int
submit_journal_entries: DF.Check
unlink_advance_payment_on_cancelation_of_order: DF.Check
unlink_payment_on_cancellation_of_invoice: DF.Check
# end: auto-generated types
def validate(self): def validate(self):
old_doc = self.get_doc_before_save() old_doc = self.get_doc_before_save()
clear_cache = False clear_cache = False

View File

@@ -1,11 +1,8 @@
frappe.ui.form.on("Accounts Settings", {
refresh: function (frm) { frappe.ui.form.on('Accounts Settings', {
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( frm.set_df_property("frozen_accounts_modifier", "label", "Role Allowed to Close Books & Make Changes to Closed Periods");
"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

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

View File

@@ -1,113 +0,0 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2024-10-16 16:57:12.085072",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"company",
"voucher_type",
"voucher_no",
"against_voucher_type",
"against_voucher_no",
"amount",
"currency",
"event"
],
"fields": [
{
"fieldname": "voucher_type",
"fieldtype": "Link",
"label": "Voucher Type",
"options": "DocType",
"read_only": 1
},
{
"fieldname": "voucher_no",
"fieldtype": "Dynamic Link",
"label": "Voucher No",
"options": "voucher_type",
"read_only": 1
},
{
"fieldname": "against_voucher_type",
"fieldtype": "Link",
"label": "Against Voucher Type",
"options": "DocType",
"read_only": 1
},
{
"fieldname": "against_voucher_no",
"fieldtype": "Dynamic Link",
"label": "Against Voucher No",
"options": "against_voucher_type",
"read_only": 1
},
{
"fieldname": "amount",
"fieldtype": "Currency",
"label": "Amount",
"read_only": 1
},
{
"fieldname": "currency",
"fieldtype": "Link",
"label": "Currency",
"options": "Currency",
"read_only": 1
},
{
"fieldname": "event",
"fieldtype": "Data",
"label": "Event",
"read_only": 1
},
{
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company",
"read_only": 1
}
],
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-11-05 10:31:28.736671",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Advance Payment Ledger Entry",
"owner": "Administrator",
"permissions": [
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"share": 1
},
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"share": 1
},
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Auditor",
"share": 1
}
],
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}

View File

@@ -1,27 +0,0 @@
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class AdvancePaymentLedgerEntry(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
against_voucher_no: DF.DynamicLink | None
against_voucher_type: DF.Link | None
amount: DF.Currency
company: DF.Link | None
currency: DF.Link | None
event: DF.Data | None
voucher_no: DF.DynamicLink | None
voucher_type: DF.Link | None
# end: auto-generated types
pass

View File

@@ -1,222 +0,0 @@
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import nowdate, today
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
class TestAdvancePaymentLedgerEntry(AccountsTestMixin, FrappeTestCase):
"""
Integration tests for AdvancePaymentLedgerEntry.
Use this class for testing interactions between multiple components.
"""
def setUp(self):
self.create_company()
self.create_usd_receivable_account()
self.create_usd_payable_account()
self.create_item()
self.clear_old_entries()
def tearDown(self):
frappe.db.rollback()
def create_sales_order(self, qty=1, rate=100, currency="INR", do_not_submit=False):
"""
Helper method
"""
so = make_sales_order(
company=self.company,
customer=self.customer,
currency=currency,
item=self.item,
qty=qty,
rate=rate,
transaction_date=today(),
do_not_submit=do_not_submit,
)
return so
def create_purchase_order(self, qty=1, rate=100, currency="INR", do_not_submit=False):
"""
Helper method
"""
po = create_purchase_order(
company=self.company,
customer=self.supplier,
currency=currency,
item=self.item,
qty=qty,
rate=rate,
transaction_date=today(),
do_not_submit=do_not_submit,
)
return po
def test_so_advance_paid_and_currency_with_payment(self):
self.create_customer("_Test USD Customer", "USD")
so = self.create_sales_order(currency="USD", do_not_submit=True)
so.conversion_rate = 80
so.submit()
pe_exchange_rate = 85
pe = get_payment_entry(so.doctype, so.name, bank_account=self.cash)
pe.reference_no = "1"
pe.reference_date = nowdate()
pe.paid_from = self.debtors_usd
pe.paid_from_account_currency = "USD"
pe.source_exchange_rate = pe_exchange_rate
pe.paid_amount = so.grand_total
pe.received_amount = pe_exchange_rate * pe.paid_amount
pe.references[0].outstanding_amount = 100
pe.references[0].total_amount = 100
pe.references[0].allocated_amount = 100
pe.save().submit()
so.reload()
self.assertEqual(so.advance_paid, 100)
self.assertEqual(so.party_account_currency, "USD")
# cancel advance payment
pe.reload()
pe.cancel()
so.reload()
self.assertEqual(so.advance_paid, 0)
self.assertEqual(so.party_account_currency, "USD")
def test_so_advance_paid_and_currency_with_journal(self):
self.create_customer("_Test USD Customer", "USD")
so = self.create_sales_order(currency="USD", do_not_submit=True)
so.conversion_rate = 80
so.submit()
je_exchange_rate = 85
je = frappe.get_doc(
{
"doctype": "Journal Entry",
"company": self.company,
"voucher_type": "Journal Entry",
"posting_date": so.transaction_date,
"multi_currency": True,
"accounts": [
{
"account": self.debtors_usd,
"party_type": "Customer",
"party": so.customer,
"credit": 8500,
"credit_in_account_currency": 100,
"is_advance": "Yes",
"reference_type": so.doctype,
"reference_name": so.name,
"exchange_rate": je_exchange_rate,
},
{
"account": self.cash,
"debit": 8500,
"debit_in_account_currency": 8500,
},
],
}
)
je.save().submit()
so.reload()
self.assertEqual(so.advance_paid, 100)
self.assertEqual(so.party_account_currency, "USD")
# cancel advance payment
je.reload()
je.cancel()
so.reload()
self.assertEqual(so.advance_paid, 0)
self.assertEqual(so.party_account_currency, "USD")
def test_po_advance_paid_and_currency_with_payment(self):
self.create_supplier("_Test USD Supplier", "USD")
po = self.create_purchase_order(currency="USD", do_not_submit=True)
po.conversion_rate = 80
po.submit()
pe_exchange_rate = 85
pe = get_payment_entry(po.doctype, po.name, bank_account=self.cash)
pe.reference_no = "1"
pe.reference_date = nowdate()
pe.paid_to = self.creditors_usd
pe.paid_to_account_currency = "USD"
pe.target_exchange_rate = pe_exchange_rate
pe.received_amount = po.grand_total
pe.paid_amount = pe_exchange_rate * pe.received_amount
pe.references[0].outstanding_amount = 100
pe.references[0].total_amount = 100
pe.references[0].allocated_amount = 100
pe.save().submit()
po.reload()
self.assertEqual(po.advance_paid, 100)
self.assertEqual(po.party_account_currency, "USD")
# cancel advance payment
pe.reload()
pe.cancel()
po.reload()
self.assertEqual(po.advance_paid, 0)
self.assertEqual(po.party_account_currency, "USD")
def test_po_advance_paid_and_currency_with_journal(self):
self.create_supplier("_Test USD Supplier", "USD")
po = self.create_purchase_order(currency="USD", do_not_submit=True)
po.conversion_rate = 80
po.submit()
je_exchange_rate = 85
je = frappe.get_doc(
{
"doctype": "Journal Entry",
"company": self.company,
"voucher_type": "Journal Entry",
"posting_date": po.transaction_date,
"multi_currency": True,
"accounts": [
{
"account": self.creditors_usd,
"party_type": "Supplier",
"party": po.supplier,
"debit": 8500,
"debit_in_account_currency": 100,
"is_advance": "Yes",
"reference_type": po.doctype,
"reference_name": po.name,
"exchange_rate": je_exchange_rate,
},
{
"account": self.cash,
"credit": 8500,
"credit_in_account_currency": 8500,
},
],
}
)
je.save().submit()
po.reload()
self.assertEqual(po.advance_paid, 100)
self.assertEqual(po.party_account_currency, "USD")
# cancel advance payment
je.reload()
je.cancel()
po.reload()
self.assertEqual(po.advance_paid, 0)
self.assertEqual(po.party_account_currency, "USD")

View File

@@ -6,22 +6,4 @@ from frappe.model.document import Document
class AdvanceTax(Document): class AdvanceTax(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
account_head: DF.Link | None
allocated_amount: DF.Currency
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
reference_detail: DF.Data | None
reference_name: DF.DynamicLink | None
reference_type: DF.Link | None
# end: auto-generated types
pass pass

View File

@@ -101,7 +101,7 @@
"fieldname": "rate", "fieldname": "rate",
"fieldtype": "Float", "fieldtype": "Float",
"in_list_view": 1, "in_list_view": 1,
"label": "Tax Rate", "label": "Rate",
"oldfieldname": "rate", "oldfieldname": "rate",
"oldfieldtype": "Currency" "oldfieldtype": "Currency"
}, },

View File

@@ -7,33 +7,4 @@ from frappe.model.document import Document
class AdvanceTaxesandCharges(Document): class AdvanceTaxesandCharges(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
account_head: DF.Link
add_deduct_tax: DF.Literal["Add", "Deduct"]
allocated_amount: DF.Currency
base_tax_amount: DF.Currency
base_total: DF.Currency
charge_type: DF.Literal[
"", "Actual", "On Paid Amount", "On Previous Row Amount", "On Previous Row Total"
]
cost_center: DF.Link | None
currency: DF.Link | None
description: DF.SmallText
included_in_paid_amount: DF.Check
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
rate: DF.Float
row_id: DF.Data | None
tax_amount: DF.Currency
total: DF.Currency
# end: auto-generated types
pass pass

View File

@@ -7,19 +7,4 @@ from frappe.model.document import Document
class AllowedDimension(Document): class AllowedDimension(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
accounting_dimension: DF.Link | None
dimension_value: DF.DynamicLink | None
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
# end: auto-generated types
pass pass

View File

@@ -11,7 +11,6 @@
{ {
"fieldname": "company", "fieldname": "company",
"fieldtype": "Link", "fieldtype": "Link",
"ignore_user_permissions": 1,
"in_list_view": 1, "in_list_view": 1,
"label": "Company", "label": "Company",
"options": "Company", "options": "Company",
@@ -20,7 +19,7 @@
], ],
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2024-01-03 11:13:02.669632", "modified": "2020-05-01 12:32:34.044911",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Allowed To Transact With", "name": "Allowed To Transact With",
@@ -29,6 +28,5 @@
"quick_entry": 1, "quick_entry": 1,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"states": [],
"track_changes": 1 "track_changes": 1
} }

View File

@@ -6,18 +6,4 @@ from frappe.model.document import Document
class AllowedToTransactWith(Document): class AllowedToTransactWith(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
company: DF.Link
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
# end: auto-generated types
pass pass

View File

@@ -7,19 +7,4 @@ from frappe.model.document import Document
class ApplicableOnAccount(Document): class ApplicableOnAccount(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
applicable_on_account: DF.Link
is_mandatory: DF.Check
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
# end: auto-generated types
pass pass

View File

@@ -1,36 +1,38 @@
// 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 { }
frm.set_df_property("address_and_contact", "hidden", 0); else {
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);
} }
@@ -38,32 +40,30 @@ 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", 'bank_transaction_field', 'options', options
"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,45 +90,35 @@ 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({ me.linkHandler = Plaid.create({ // eslint-disable-line no-undef
// 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( frappe.msgprint(__("There was an issue connecting to Plaid's authentication server. Check browser console for more information"));
__(
"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 frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.update_bank_account_ids', {
.xcall(
"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.update_bank_account_ids",
{
response: response, response: response,
} }).then(() => {
) frappe.show_alert({ message: __('Plaid Link Updated'), indicator: 'green' });
.then(() => {
frappe.show_alert({ message: __("Plaid Link Updated"), indicator: "green" });
}); });
} }
}; };

View File

@@ -10,25 +10,6 @@ from frappe.model.document import Document
class Bank(Document): class Bank(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
from erpnext.accounts.doctype.bank_transaction_mapping.bank_transaction_mapping import (
BankTransactionMapping,
)
bank_name: DF.Data
bank_transaction_mapping: DF.Table[BankTransactionMapping]
plaid_access_token: DF.Data | None
swift_number: DF.Data | None
website: DF.Data | None
# end: auto-generated types
def onload(self): def onload(self):
"""Load address and contacts in `__onload`""" """Load address and contacts in `__onload`"""
load_address_and_contact(self) load_address_and_contact(self)

View File

@@ -1,49 +1,45 @@
// 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( 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() {
__(
"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", ""); 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

@@ -208,49 +208,8 @@
"label": "Disabled" "label": "Disabled"
} }
], ],
"links": [ "links": [],
{ "modified": "2023-09-22 21:31:34.763977",
"group": "Transactions",
"link_doctype": "Payment Request",
"link_fieldname": "bank_account"
},
{
"group": "Transactions",
"link_doctype": "Payment Order",
"link_fieldname": "bank_account"
},
{
"group": "Transactions",
"link_doctype": "Bank Guarantee",
"link_fieldname": "bank_account"
},
{
"group": "Transactions",
"link_doctype": "Bank Transaction",
"link_fieldname": "bank_account"
},
{
"group": "Accounting",
"link_doctype": "Payment Entry",
"link_fieldname": "bank_account"
},
{
"group": "Accounting",
"link_doctype": "Journal Entry",
"link_fieldname": "bank_account"
},
{
"group": "Party",
"link_doctype": "Customer",
"link_fieldname": "default_bank_account"
},
{
"group": "Party",
"link_doctype": "Supplier",
"link_fieldname": "default_bank_account"
}
],
"modified": "2024-10-30 09:41:14.113414",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Bank Account", "name": "Bank Account",

View File

@@ -9,37 +9,9 @@ from frappe.contacts.address_and_contact import (
load_address_and_contact, load_address_and_contact,
) )
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import comma_and, get_link_to_form
class BankAccount(Document): class BankAccount(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
account: DF.Link | None
account_name: DF.Data
account_subtype: DF.Link | None
account_type: DF.Link | None
bank: DF.Link
bank_account_no: DF.Data | None
branch_code: DF.Data | None
company: DF.Link | None
disabled: DF.Check
iban: DF.Data | None
integration_id: DF.Data | None
is_company_account: DF.Check
is_default: DF.Check
last_integration_date: DF.Date | None
mask: DF.Data | None
party: DF.DynamicLink | None
party_type: DF.Link | None
# end: auto-generated types
def onload(self): def onload(self):
"""Load address and contacts in `__onload`""" """Load address and contacts in `__onload`"""
load_address_and_contact(self) load_address_and_contact(self)
@@ -48,25 +20,11 @@ class BankAccount(Document):
self.name = self.account_name + " - " + self.bank self.name = self.account_name + " - " + self.bank
def on_trash(self): def on_trash(self):
delete_contact_and_address("Bank Account", self.name) delete_contact_and_address("BankAccount", self.name)
def validate(self): def validate(self):
self.validate_company() self.validate_company()
self.validate_iban() self.validate_iban()
self.validate_account()
self.update_default_bank_account()
def validate_account(self):
if self.account:
if accounts := frappe.db.get_all(
"Bank Account", filters={"account": self.account, "name": ["!=", self.name]}, as_list=1
):
frappe.throw(
_("'{0}' account is already used by {1}. Use another account.").format(
frappe.bold(self.account),
frappe.bold(comma_and([get_link_to_form(self.doctype, x[0]) for x in accounts])),
)
)
def validate_company(self): def validate_company(self):
if self.is_company_account and not self.company: if self.is_company_account and not self.company:
@@ -101,51 +59,19 @@ 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( return frappe.db.get_value(party_type, party, "default_bank_account")
"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

@@ -0,0 +1,20 @@
from frappe import _
def get_data():
return {
"fieldname": "bank_account",
"non_standard_fieldnames": {
"Customer": "default_bank_account",
"Supplier": "default_bank_account",
},
"transactions": [
{
"label": _("Payments"),
"items": ["Payment Entry", "Payment Request", "Payment Order", "Payroll Entry"],
},
{"label": _("Party"), "items": ["Customer", "Supplier"]},
{"items": ["Bank Guarantee"]},
{"items": ["Journal Entry"]},
],
}

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 = f"BankAccount.validate_iban() failed for valid IBAN {iban}" msg = "BankAccount.validate_iban() failed for valid IBAN {}".format(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 = f"BankAccount.validate_iban() accepted invalid IBAN {not_iban}" msg = "BankAccount.validate_iban() accepted invalid IBAN {}".format(not_iban)
with self.assertRaises(ValidationError, msg=msg): with self.assertRaises(ValidationError, msg=msg):
bank_account.validate_iban() bank_account.validate_iban()

View File

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

@@ -6,15 +6,4 @@ from frappe.model.document import Document
class BankAccountSubtype(Document): class BankAccountSubtype(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
account_subtype: DF.Data | None
# end: auto-generated types
pass pass

View File

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

@@ -7,15 +7,4 @@ from frappe.model.document import Document
class BankAccountType(Document): class BankAccountType(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
account_type: DF.Data | None
# end: auto-generated types
pass pass

View File

@@ -2,66 +2,80 @@
// 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")
? locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"] let default_bank_account = frappe.defaults.get_user_default("Company")?
: ""; 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.trigger("get_payment_entries")); frm.add_custom_button(__('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');
if (frm.doc.payment_entries.length) {
frm.add_custom_button(__("Update Clearance Date"), () => frm.trigger("update_clearance_date"));
frm.change_custom_button_type(__("Get Payment Entries"), null, "default");
frm.change_custom_button_type(__("Update Clearance Date"), 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(); frm.refresh_field("payment_entries");
}, frm.refresh_fields();
if (!frm.doc.payment_entries.length) {
frm.change_custom_button_type(__('Get Payment Entries'), null, 'primary');
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 () { callback: function(r, rt) {
frm.refresh(); frm.refresh_field("payment_entries");
},
if (frm.doc.payment_entries.length) {
frm.add_custom_button(__('Update Clearance Date'), () =>
frm.trigger("update_clearance_date")
);
frm.change_custom_button_type(__('Get Payment Entries'), null, 'default');
frm.change_custom_button_type(__('Update Clearance Date'), null, 'primary');
}
}
}); });
}, }
}); });

View File

@@ -5,9 +5,7 @@
import frappe import frappe
from frappe import _, msgprint from frappe import _, msgprint
from frappe.model.document import Document from frappe.model.document import Document
from frappe.query_builder.custom import ConstantColumn from frappe.utils import flt, fmt_money, getdate
from frappe.utils import flt, fmt_money, get_link_to_form, getdate
from pypika import Order
import erpnext import erpnext
@@ -15,28 +13,6 @@ form_grid_templates = {"journal_entries": "templates/form_grid/bank_reconciliati
class BankClearance(Document): class BankClearance(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
from erpnext.accounts.doctype.bank_clearance_detail.bank_clearance_detail import (
BankClearanceDetail,
)
account: DF.Link
account_currency: DF.Link | None
bank_account: DF.Link | None
from_date: DF.Date
include_pos_transactions: DF.Check
include_reconciled_entries: DF.Check
payment_entries: DF.Table[BankClearanceDetail]
to_date: DF.Date
# end: auto-generated types
@frappe.whitelist() @frappe.whitelist()
def get_payment_entries(self): def get_payment_entries(self):
if not (self.from_date and self.to_date): if not (self.from_date and self.to_date):
@@ -96,11 +72,8 @@ class BankClearance(Document):
if d.cheque_date and getdate(d.clearance_date) < getdate(d.cheque_date): if d.cheque_date and getdate(d.clearance_date) < getdate(d.cheque_date):
frappe.throw( frappe.throw(
_("Row #{0}: For {1} Clearance date {2} cannot be before Cheque Date {3}").format( _("Row #{0}: Clearance date {1} cannot be before Cheque Date {2}").format(
d.idx, d.idx, d.clearance_date, d.cheque_date
get_link_to_form(d.payment_document, d.payment_entry),
d.clearance_date,
d.cheque_date,
) )
) )
@@ -108,16 +81,6 @@ class BankClearance(Document):
if not d.clearance_date: if not d.clearance_date:
d.clearance_date = None d.clearance_date = None
if d.payment_document == "Sales Invoice":
frappe.db.set_value(
"Sales Invoice Payment",
{"parent": d.payment_entry, "account": self.get("account"), "amount": [">", 0]},
"clearance_date",
d.clearance_date,
)
else:
# using db_set to trigger notification
payment_entry = frappe.get_doc(d.payment_document, d.payment_entry) payment_entry = frappe.get_doc(d.payment_document, d.payment_entry)
payment_entry.db_set("clearance_date", d.clearance_date) payment_entry.db_set("clearance_date", d.clearance_date)
@@ -140,7 +103,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,
@@ -154,7 +117,9 @@ 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,
) )
@@ -163,12 +128,12 @@ 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,
if(paid_from=%(account)s, paid_amount + total_taxes_and_charges, 0) as credit, if(paid_from=%(account)s, paid_amount + total_taxes_and_charges, 0) as credit,
if(paid_from=%(account)s, 0, received_amount + total_taxes_and_charges) as debit, if(paid_from=%(account)s, 0, received_amount) as debit,
posting_date, ifnull(party,if(paid_from=%(account)s,paid_to,paid_from)) as against_account, clearance_date, posting_date, ifnull(party,if(paid_from=%(account)s,paid_to,paid_from)) as against_account, clearance_date,
if(paid_to=%(account)s, paid_to_account_currency, paid_from_account_currency) as account_currency if(paid_to=%(account)s, paid_to_account_currency, paid_from_account_currency) as account_currency
from `tabPayment Entry` from `tabPayment Entry`
@@ -178,7 +143,9 @@ 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,
@@ -190,65 +157,45 @@ def get_payment_entries_for_bank_clearance(
pos_sales_invoices, pos_purchase_invoices = [], [] pos_sales_invoices, pos_purchase_invoices = [], []
if include_pos_transactions: if include_pos_transactions:
si_payment = frappe.qb.DocType("Sales Invoice Payment") pos_sales_invoices = frappe.db.sql(
si = frappe.qb.DocType("Sales Invoice") """
acc = frappe.qb.DocType("Account") select
"Sales Invoice Payment" as payment_document, sip.name as payment_entry, sip.amount as debit,
si.posting_date, si.customer as against_account, sip.clearance_date,
account.account_currency, 0 as credit
from `tabSales Invoice Payment` sip, `tabSales Invoice` si, `tabAccount` account
where
sip.account=%(account)s and si.docstatus=1 and sip.parent = si.name
and account.name = sip.account and si.posting_date >= %(from)s and si.posting_date <= %(to)s
order by
si.posting_date ASC, si.name DESC
""",
{"account": account, "from": from_date, "to": to_date},
as_dict=1,
)
pos_sales_invoices = ( pos_purchase_invoices = frappe.db.sql(
frappe.qb.from_(si_payment) """
.inner_join(si) select
.on(si_payment.parent == si.name) "Purchase Invoice" as payment_document, pi.name as payment_entry, pi.paid_amount as credit,
.inner_join(acc) pi.posting_date, pi.supplier as against_account, pi.clearance_date,
.on(si_payment.account == acc.name) account.account_currency, 0 as debit
.select( from `tabPurchase Invoice` pi, `tabAccount` account
ConstantColumn("Sales Invoice").as_("payment_document"), where
si.name.as_("payment_entry"), pi.cash_bank_account=%(account)s and pi.docstatus=1 and account.name = pi.cash_bank_account
si_payment.reference_no.as_("cheque_number"), and pi.posting_date >= %(from)s and pi.posting_date <= %(to)s
si_payment.amount.as_("debit"), order by
si.posting_date, pi.posting_date ASC, pi.name DESC
si.customer.as_("against_account"), """,
si_payment.clearance_date, {"account": account, "from": from_date, "to": to_date},
acc.account_currency, as_dict=1,
ConstantColumn(0).as_("credit"),
) )
.where(
(si.docstatus == 1)
& (si_payment.account == account)
& (si.posting_date >= from_date)
& (si.posting_date <= to_date)
)
.orderby(si.posting_date)
.orderby(si.name, order=Order.desc)
).run(as_dict=True)
pi = frappe.qb.DocType("Purchase Invoice")
pos_purchase_invoices = (
frappe.qb.from_(pi)
.inner_join(acc)
.on(pi.cash_bank_account == acc.name)
.select(
ConstantColumn("Purchase Invoice").as_("payment_document"),
pi.name.as_("payment_entry"),
pi.paid_amount.as_("credit"),
pi.posting_date,
pi.supplier.as_("against_account"),
pi.clearance_date,
acc.account_currency,
ConstantColumn(0).as_("debit"),
)
.where(
(pi.docstatus == 1)
& (pi.cash_bank_account == account)
& (pi.posting_date >= from_date)
& (pi.posting_date <= to_date)
)
.orderby(pi.posting_date)
.orderby(pi.name, order=Order.desc)
).run(as_dict=True)
entries = ( entries = (
list(payment_entries) + list(journal_entries) + list(pos_sales_invoices) + list(pos_purchase_invoices) list(payment_entries)
+ list(journal_entries)
+ list(pos_sales_invoices)
+ list(pos_purchase_invoices)
) )
return entries return entries

View File

@@ -6,29 +6,16 @@ import unittest
import frappe import frappe
from frappe.utils import add_months, getdate from frappe.utils import add_months, getdate
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.stock.doctype.item.test_item import create_item
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
from erpnext.tests.utils import if_lending_app_installed, if_lending_app_not_installed from erpnext.tests.utils import if_lending_app_installed, if_lending_app_not_installed
class TestBankClearance(unittest.TestCase): class TestBankClearance(unittest.TestCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
create_warehouse(
warehouse_name="_Test Warehouse",
properties={"parent_warehouse": "All Warehouses - _TC"},
company="_Test Company",
)
create_item("_Test Item")
create_cost_center(cost_center_name="_Test Cost Center", company="_Test Company")
clear_payment_entries() clear_payment_entries()
clear_loan_transactions() clear_loan_transactions()
clear_pos_sales_invoices()
make_bank_account() make_bank_account()
add_transactions() add_transactions()
@@ -81,7 +68,9 @@ 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(loan.name, "_Test Customer", getdate(), loan.loan_amount) repayment_entry = create_repayment_entry(
loan.name, "_Test Customer", getdate(), loan.loan_amount
)
repayment_entry.save() repayment_entry.save()
repayment_entry.submit() repayment_entry.submit()
@@ -96,41 +85,11 @@ class TestBankClearance(unittest.TestCase):
bank_clearance.get_payment_entries() bank_clearance.get_payment_entries()
self.assertEqual(len(bank_clearance.payment_entries), 3) self.assertEqual(len(bank_clearance.payment_entries), 3)
def test_update_clearance_date_on_si(self):
sales_invoice = make_pos_sales_invoice()
date = getdate()
bank_clearance = frappe.get_doc("Bank Clearance")
bank_clearance.account = "_Test Bank Clearance - _TC"
bank_clearance.from_date = add_months(date, -1)
bank_clearance.to_date = date
bank_clearance.include_pos_transactions = 1
bank_clearance.get_payment_entries()
self.assertNotEqual(len(bank_clearance.payment_entries), 0)
for payment in bank_clearance.payment_entries:
if payment.payment_entry == sales_invoice.name:
payment.clearance_date = date
bank_clearance.update_clearance_date()
si_clearance_date = frappe.db.get_value(
"Sales Invoice Payment",
{"parent": sales_invoice.name, "account": bank_clearance.account},
"clearance_date",
)
self.assertEqual(si_clearance_date, date)
def clear_payment_entries(): def clear_payment_entries():
frappe.db.delete("Payment Entry") frappe.db.delete("Payment Entry")
def clear_pos_sales_invoices():
frappe.db.delete("Sales Invoice", {"is_pos": 1})
@if_lending_app_installed @if_lending_app_installed
def clear_loan_transactions(): def clear_loan_transactions():
for dt in [ for dt in [
@@ -158,45 +117,9 @@ def add_transactions():
def make_payment_entry(): def make_payment_entry():
from erpnext.buying.doctype.supplier.test_supplier import create_supplier pi = make_purchase_invoice(supplier="_Test Supplier", qty=1, rate=690)
supplier = create_supplier(supplier_name="_Test Supplier")
pi = make_purchase_invoice(
supplier=supplier,
supplier_warehouse="_Test Warehouse - _TC",
expense_account="Cost of Goods Sold - _TC",
uom="Nos",
qty=1,
rate=690,
)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank Clearance - _TC") pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank Clearance - _TC")
pe.reference_no = "Conrad Oct 18" pe.reference_no = "Conrad Oct 18"
pe.reference_date = "2018-10-24" pe.reference_date = "2018-10-24"
pe.insert() pe.insert()
pe.submit() pe.submit()
def make_pos_sales_invoice():
from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import (
make_customer,
)
mode_of_payment = frappe.get_doc({"doctype": "Mode of Payment", "name": "Cash"})
if not frappe.db.get_value("Mode of Payment Account", {"company": "_Test Company", "parent": "Cash"}):
mode_of_payment.append(
"accounts", {"company": "_Test Company", "default_account": "_Test Bank Clearance - _TC"}
)
mode_of_payment.save()
customer = make_customer(customer="_Test Customer")
si = create_sales_invoice(customer=customer, item="_Test Item", is_pos=1, qty=1, rate=1000, do_not_save=1)
si.set("payments", [])
si.append(
"payments", {"mode_of_payment": "Cash", "account": "_Test Bank Clearance - _TC", "amount": 1000}
)
si.insert()
si.submit()
return si

View File

@@ -6,25 +6,4 @@ from frappe.model.document import Document
class BankClearanceDetail(Document): class BankClearanceDetail(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
against_account: DF.Data | None
amount: DF.Data | None
cheque_date: DF.Date | None
cheque_number: DF.Data | None
clearance_date: DF.Date | None
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
payment_document: DF.Link | None
payment_entry: DF.DynamicLink | None
posting_date: DF.Date | None
# end: auto-generated types
pass pass

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("bank_account", function () { frm.set_query("project", function() {
return { return {
filters: { filters: {
company: frm.doc.company, customer: frm.doc.customer
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,33 +41,34 @@ 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);
}, }
}); });

View File

@@ -8,40 +8,6 @@ from frappe.model.document import Document
class BankGuarantee(Document): class BankGuarantee(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
account: DF.Link | None
amended_from: DF.Link | None
amount: DF.Currency
bank: DF.Link | None
bank_account: DF.Link | None
bank_account_no: DF.Data | None
bank_guarantee_number: DF.Data | None
bg_type: DF.Literal["", "Receiving", "Providing"]
branch_code: DF.Data | None
charges: DF.Currency
customer: DF.Link | None
end_date: DF.Date | None
fixed_deposit_number: DF.Data | None
iban: DF.Data | None
margin_money: DF.Currency
more_information: DF.TextEditor | None
name_of_beneficiary: DF.Data | None
project: DF.Link | None
reference_docname: DF.DynamicLink | None
reference_doctype: DF.Link | None
start_date: DF.Date
supplier: DF.Link | None
swift_number: DF.Data | None
validity: DF.Int
# end: auto-generated types
def validate(self): def validate(self):
if not (self.customer or self.supplier): if not (self.customer or self.supplier):
frappe.throw(_("Select the customer or supplier.")) frappe.throw(_("Select the customer or supplier."))

View File

@@ -8,22 +8,21 @@ 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 = `<div class="text-muted text-center">${__( let no_bank_transactions_text =
"No Matching Bank Transactions Found" `<div class="text-muted text-center">${__("No Matching Bank Transactions Found")}</div>`
)}</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) {
@@ -38,11 +37,14 @@ 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", () => frm.trigger("make_reconciliation_tool")); frappe.require("bank-reconciliation-tool.bundle.js", () =>
frm.trigger("make_reconciliation_tool")
);
frm.add_custom_button(__("Upload Bank Statement"), () => frm.add_custom_button(__("Upload Bank Statement"), () =>
frappe.call({ frappe.call({
method: "erpnext.accounts.doctype.bank_statement_import.bank_statement_import.upload_bank_statement", method:
"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,
@@ -52,17 +54,17 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
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() {
if (!frm.doc.bank_account) {
frappe.msgprint(__("Please select Bank Account"));
return;
}
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: {
@@ -73,22 +75,33 @@ 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("Bank Account", frm.doc.bank_account, "account", (r) => { frappe.db.get_value(
frappe.db.get_value("Account", r.account, "account_currency", (r) => { "Bank Account",
frm.doc.bank_account,
"account",
(r) => {
frappe.db.get_value(
"Account",
r.account,
"account_currency",
(r) => {
frm.doc.account_currency = r.account_currency; frm.doc.account_currency = r.account_currency;
frm.trigger("render_chart"); frm.trigger("render_chart");
}); }
}); );
}
);
frm.trigger("get_account_opening_balance"); frm.trigger("get_account_opening_balance");
}, },
@@ -107,7 +120,11 @@ 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(frm.get_field("reconciliation_tool_cards").$wrapper, true, 30); frappe.utils.scroll_to(
frm.get_field("reconciliation_tool_cards").$wrapper,
true,
30
);
} }
}); });
} }
@@ -116,10 +133,11 @@ 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: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance", method:
"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: frm.doc.bank_statement_from_date,
}, },
callback: (response) => { callback: (response) => {
frm.set_value("account_opening_balance", response.message); frm.set_value("account_opening_balance", response.message);
@@ -131,7 +149,8 @@ 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: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance", method:
"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,
@@ -144,30 +163,41 @@ 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, {
bank_statement_closing_balance: frm.doc.bank_statement_closing_balance, $reconciliation_tool_cards: frm.get_field(
"reconciliation_tool_cards"
).$wrapper,
bank_statement_closing_balance:
frm.doc.bank_statement_closing_balance,
cleared_balance: frm.cleared_balance, cleared_balance: frm.cleared_balance,
currency: frm.doc.account_currency, 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 = frm.bank_reconciliation_data_table_manager = new erpnext.accounts.bank_reconciliation.DataTableManager(
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").$wrapper, $reconciliation_tool_dt: frm.get_field(
$no_bank_transactions: frm.get_field("no_bank_transactions").$wrapper, "reconciliation_tool_dt"
).$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: frm.doc.bank_statement_closing_balance, bank_statement_closing_balance:
frm.doc.bank_statement_closing_balance,
cards_manager: frm.cards_manager, cards_manager: frm.cards_manager,
}); }
);
} }
}, },
}); });

View File

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

View File

@@ -9,39 +9,18 @@ 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
from erpnext.accounts.party import get_party_account
from erpnext.accounts.report.bank_reconciliation_statement.bank_reconciliation_statement import ( from erpnext.accounts.report.bank_reconciliation_statement.bank_reconciliation_statement import (
get_amounts_not_reflected_in_system, get_amounts_not_reflected_in_system,
get_entries, get_entries,
) )
from erpnext.accounts.utils import get_account_currency, get_balance_on from erpnext.accounts.utils import get_account_currency, get_balance_on
from erpnext.setup.utils import get_exchange_rate
class BankReconciliationTool(Document): class BankReconciliationTool(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
account_currency: DF.Link | None
account_opening_balance: DF.Currency
bank_account: DF.Link | None
bank_statement_closing_balance: DF.Currency
bank_statement_from_date: DF.Date | None
bank_statement_to_date: DF.Date | None
company: DF.Link | None
filter_by_reference_date: DF.Check
from_reference_date: DF.Date | None
to_reference_date: DF.Date | None
# end: auto-generated types
pass pass
@@ -82,7 +61,9 @@ 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({"account": account, "report_date": till_date, "include_pos_transactions": 1}) filters = frappe._dict(
{"account": account, "report_date": till_date, "include_pos_transactions": 1}
)
data = get_entries(filters) 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"])
@@ -95,7 +76,10 @@ 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(total_debit) + flt(total_credit) + amounts_not_reflected_in_system flt(balance_as_per_system)
- flt(total_debit)
+ flt(total_credit)
+ amounts_not_reflected_in_system
) )
return bank_bal return bank_bal
@@ -146,7 +130,7 @@ def create_journal_entry_bts(
bank_transaction = frappe.db.get_values( bank_transaction = frappe.db.get_values(
"Bank Transaction", "Bank Transaction",
bank_transaction_name, bank_transaction_name,
fieldname=["name", "deposit", "withdrawal", "bank_account", "currency"], fieldname=["name", "deposit", "withdrawal", "bank_account"],
as_dict=True, as_dict=True,
)[0] )[0]
company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account") company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account")
@@ -160,94 +144,29 @@ def create_journal_entry_bts(
) )
company = frappe.get_value("Account", company_account, "company") company = frappe.get_value("Account", company_account, "company")
company_default_currency = frappe.get_cached_value("Company", company, "default_currency")
company_account_currency = frappe.get_cached_value("Account", company_account, "account_currency")
second_account_currency = frappe.get_cached_value("Account", second_account, "account_currency")
# determine if multi-currency Journal or not
is_multi_currency = (
True
if company_default_currency != company_account_currency
or company_default_currency != second_account_currency
or company_default_currency != bank_transaction.currency
else False
)
accounts = [] accounts = []
second_account_dict = { # Multi Currency?
accounts.append(
{
"account": second_account, "account": second_account,
"account_currency": second_account_currency,
"credit_in_account_currency": bank_transaction.deposit, "credit_in_account_currency": bank_transaction.deposit,
"debit_in_account_currency": bank_transaction.withdrawal, "debit_in_account_currency": bank_transaction.withdrawal,
"party_type": party_type, "party_type": party_type,
"party": party, "party": party,
"cost_center": get_default_cost_center(company), "cost_center": get_default_cost_center(company),
} }
)
company_account_dict = { accounts.append(
{
"account": company_account, "account": company_account,
"account_currency": company_account_currency,
"bank_account": bank_transaction.bank_account, "bank_account": bank_transaction.bank_account,
"credit_in_account_currency": bank_transaction.withdrawal, "credit_in_account_currency": bank_transaction.withdrawal,
"debit_in_account_currency": bank_transaction.deposit, "debit_in_account_currency": bank_transaction.deposit,
"cost_center": get_default_cost_center(company), "cost_center": get_default_cost_center(company),
} }
# convert transaction amount to company currency
if is_multi_currency:
exc_rate = get_exchange_rate(bank_transaction.currency, company_default_currency, posting_date)
withdrawal_in_company_currency = flt(exc_rate * abs(bank_transaction.withdrawal))
deposit_in_company_currency = flt(exc_rate * abs(bank_transaction.deposit))
else:
withdrawal_in_company_currency = bank_transaction.withdrawal
deposit_in_company_currency = bank_transaction.deposit
# if second account is of foreign currency, convert and set debit and credit fields.
if second_account_currency != company_default_currency:
exc_rate = get_exchange_rate(second_account_currency, company_default_currency, posting_date)
second_account_dict.update(
{
"exchange_rate": exc_rate,
"credit": deposit_in_company_currency,
"debit": withdrawal_in_company_currency,
"credit_in_account_currency": flt(deposit_in_company_currency / exc_rate) or 0,
"debit_in_account_currency": flt(withdrawal_in_company_currency / exc_rate) or 0,
}
) )
else:
second_account_dict.update(
{
"exchange_rate": 1,
"credit": deposit_in_company_currency,
"debit": withdrawal_in_company_currency,
"credit_in_account_currency": deposit_in_company_currency,
"debit_in_account_currency": withdrawal_in_company_currency,
}
)
# if company account is of foreign currency, convert and set debit and credit fields.
if company_account_currency != company_default_currency:
exc_rate = get_exchange_rate(company_account_currency, company_default_currency, posting_date)
company_account_dict.update(
{
"exchange_rate": exc_rate,
"credit": withdrawal_in_company_currency,
"debit": deposit_in_company_currency,
}
)
else:
company_account_dict.update(
{
"exchange_rate": 1,
"credit": withdrawal_in_company_currency,
"debit": deposit_in_company_currency,
"credit_in_account_currency": withdrawal_in_company_currency,
"debit_in_account_currency": deposit_in_company_currency,
}
)
accounts.append(second_account_dict)
accounts.append(company_account_dict)
journal_entry_dict = { journal_entry_dict = {
"voucher_type": entry_type, "voucher_type": entry_type,
@@ -257,9 +176,6 @@ def create_journal_entry_bts(
"cheque_no": reference_number, "cheque_no": reference_number,
"mode_of_payment": mode_of_payment, "mode_of_payment": mode_of_payment,
} }
if is_multi_currency:
journal_entry_dict.update({"multi_currency": True})
journal_entry = frappe.new_doc("Journal Entry") journal_entry = frappe.new_doc("Journal Entry")
journal_entry.update(journal_entry_dict) journal_entry.update(journal_entry_dict)
journal_entry.set("accounts", accounts) journal_entry.set("accounts", accounts)
@@ -305,56 +221,54 @@ def create_payment_entry_bts(
bank_transaction = frappe.db.get_values( bank_transaction = frappe.db.get_values(
"Bank Transaction", "Bank Transaction",
bank_transaction_name, bank_transaction_name,
fieldname=["name", "unallocated_amount", "deposit", "bank_account", "currency"], fieldname=["name", "unallocated_amount", "deposit", "bank_account"],
as_dict=True, as_dict=True,
)[0] )[0]
paid_amount = bank_transaction.unallocated_amount
payment_type = "Receive" if bank_transaction.deposit > 0.0 else "Pay" payment_type = "Receive" if bank_transaction.deposit > 0.0 else "Pay"
bank_account = frappe.get_cached_value("Bank Account", bank_transaction.bank_account, "account") company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account")
company = frappe.get_cached_value("Account", bank_account, "company") company = frappe.get_value("Account", company_account, "company")
party_account = get_party_account(party_type, party, company) payment_entry_dict = {
"company": company,
"payment_type": payment_type,
"reference_no": reference_number,
"reference_date": reference_date,
"party_type": party_type,
"party": party,
"posting_date": posting_date,
"paid_amount": paid_amount,
"received_amount": paid_amount,
}
payment_entry = frappe.new_doc("Payment Entry")
bank_currency = bank_transaction.currency payment_entry.update(payment_entry_dict)
party_currency = frappe.get_cached_value("Account", party_account, "account_currency")
exc_rate = get_exchange_rate(bank_currency, party_currency, posting_date) if mode_of_payment:
payment_entry.mode_of_payment = mode_of_payment
if project:
payment_entry.project = project
if cost_center:
payment_entry.cost_center = cost_center
if payment_type == "Receive":
payment_entry.paid_to = company_account
else:
payment_entry.paid_from = company_account
amt_in_bank_acc_currency = bank_transaction.unallocated_amount payment_entry.validate()
amount_in_party_currency = bank_transaction.unallocated_amount * exc_rate
pe = frappe.new_doc("Payment Entry")
pe.payment_type = payment_type
pe.company = company
pe.reference_no = reference_number
pe.reference_date = reference_date
pe.party_type = party_type
pe.party = party
pe.posting_date = posting_date
pe.paid_from = party_account if payment_type == "Receive" else bank_account
pe.paid_to = party_account if payment_type == "Pay" else bank_account
pe.paid_from_account_currency = party_currency if payment_type == "Receive" else bank_currency
pe.paid_to_account_currency = party_currency if payment_type == "Pay" else bank_currency
pe.paid_amount = amount_in_party_currency if payment_type == "Receive" else amt_in_bank_acc_currency
pe.received_amount = amount_in_party_currency if payment_type == "Pay" else amt_in_bank_acc_currency
pe.mode_of_payment = mode_of_payment
pe.project = project
pe.cost_center = cost_center
pe.validate()
if allow_edit: if allow_edit:
return pe return payment_entry
pe.insert() payment_entry.insert()
pe.submit()
payment_entry.submit()
vouchers = json.dumps( vouchers = json.dumps(
[ [
{ {
"payment_doctype": "Payment Entry", "payment_doctype": "Payment Entry",
"payment_name": pe.name, "payment_name": payment_entry.name,
"amount": amt_in_bank_acc_currency, "amount": paid_amount,
} }
] ]
) )
@@ -441,13 +355,7 @@ def reconcile_vouchers(bank_transaction_name, vouchers):
vouchers = json.loads(vouchers) vouchers = json.loads(vouchers)
transaction = frappe.get_doc("Bank Transaction", bank_transaction_name) transaction = frappe.get_doc("Bank Transaction", bank_transaction_name)
transaction.add_payment_entries(vouchers) transaction.add_payment_entries(vouchers)
transaction.validate_duplicate_references() return frappe.get_doc("Bank Transaction", bank_transaction_name)
transaction.allocate_payment_entries()
transaction.update_allocated_amount()
transaction.set_status()
transaction.save()
return transaction
@frappe.whitelist() @frappe.whitelist()
@@ -483,12 +391,8 @@ def get_linked_payments(
def subtract_allocations(gl_account, vouchers): def subtract_allocations(gl_account, vouchers):
"Look up & subtract any existing Bank Transaction allocations" "Look up & subtract any existing Bank Transaction allocations"
copied = [] copied = []
voucher_docs = [(voucher.get("doctype"), voucher.get("name")) for voucher in vouchers]
voucher_allocated_amounts = get_total_allocated_amount(voucher_docs)
for voucher in vouchers: for voucher in vouchers:
rows = voucher_allocated_amounts.get((voucher.get("doctype"), voucher.get("name"))) or [] rows = get_total_allocated_amount(voucher.get("doctype"), voucher.get("name"))
filtered_row = list(filter(lambda row: row.get("gl_account") == gl_account, rows)) filtered_row = list(filter(lambda row: row.get("gl_account") == gl_account, rows))
if amount := None if not filtered_row else filtered_row[0]["total"]: if amount := None if not filtered_row else filtered_row[0]["total"]:
@@ -502,26 +406,14 @@ def check_matching(
bank_account, bank_account,
company, company,
transaction, transaction,
document_types=None, document_types,
from_date=None, from_date,
to_date=None, to_date,
filter_by_reference_date=None, filter_by_reference_date,
from_reference_date=None, from_reference_date,
to_reference_date=None, 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,
@@ -533,28 +425,37 @@ 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(query.run(as_dict=True)) matching_vouchers.extend(frappe.db.sql(query, filters, as_dict=True))
return sorted(matching_vouchers, key=lambda x: x["rank"], reverse=True) if matching_vouchers else [] return (
sorted(matching_vouchers, key=lambda x: x["rank"], reverse=True) if matching_vouchers else []
)
def get_queries( def get_queries(
bank_account, bank_account,
company, company,
transaction, transaction,
document_types=None, document_types,
from_date=None, from_date,
to_date=None, to_date,
filter_by_reference_date=None, filter_by_reference_date,
from_reference_date=None, from_reference_date,
to_reference_date=None, to_reference_date,
exact_match=None, exact_match,
common_filters=None,
): ):
# 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"
@@ -575,7 +476,6 @@ 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 []
) )
@@ -587,15 +487,14 @@ def get_matching_queries(
bank_account, bank_account,
company, company,
transaction, transaction,
document_types=None, document_types,
exact_match=None, exact_match,
account_from_to=None, account_from_to,
from_date=None, from_date,
to_date=None, to_date,
filter_by_reference_date=None, filter_by_reference_date,
from_reference_date=None, from_reference_date,
to_reference_date=None, to_reference_date,
common_filters=None,
): ):
queries = [] queries = []
currency = get_account_currency(bank_account) currency = get_account_currency(bank_account)
@@ -610,7 +509,6 @@ 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)
@@ -623,17 +521,16 @@ 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, common_filters) query = get_si_matching_query(exact_match, currency)
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, common_filters) query = get_pi_matching_query(exact_match, currency)
queries.append(query) queries.append(query)
if "bank_transaction" in document_types: if "bank_transaction" in document_types:
@@ -654,13 +551,17 @@ 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 = frappe.qb.terms.Case().when(bt.reference_number == transaction.reference_number, 1).else_(0) ref_rank = (
frappe.qb.terms.Case().when(bt.reference_number == transaction.reference_number, 1).else_(0)
)
unallocated_rank = ( 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 == transaction.party) & bt.party.isnotnull() (bt.party_type == transaction.party_type)
& (bt.party == transaction.party)
& bt.party.isnotnull()
) )
party_rank = frappe.qb.terms.Case().when(party_condition, 1).else_(0) party_rank = frappe.qb.terms.Case().when(party_condition, 1).else_(0)
@@ -684,7 +585,7 @@ def get_bt_matching_query(exact_match, transaction):
.where(amount_condition) .where(amount_condition)
.where(bt.docstatus == 1) .where(bt.docstatus == 1)
) )
return query return str(query)
def get_pe_matching_query( def get_pe_matching_query(
@@ -696,7 +597,6 @@ 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"
@@ -712,7 +612,9 @@ 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 == transaction.party) & pe.party.isnotnull() (pe.party_type == transaction.party_type)
& (pe.party == transaction.party)
& pe.party.isnotnull()
) )
party_rank = frappe.qb.terms.Case().when(party_condition, 1).else_(0) party_rank = frappe.qb.terms.Case().when(party_condition, 1).else_(0)
@@ -726,7 +628,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.base_paid_amount_after_tax.as_("paid_amount"), pe.paid_amount,
pe.reference_no, pe.reference_no,
pe.reference_date, pe.reference_date,
pe.party, pe.party,
@@ -737,16 +639,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) == common_filters.bank_account) .where(getattr(pe, account_from_to) == Parameter("%(bank_account)s"))
.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 is True: if frappe.flags.auto_reconcile_vouchers == True:
query = query.where(ref_condition) query = query.where(ref_condition)
return query return str(query)
def get_je_matching_query( def get_je_matching_query(
@@ -757,7 +659,6 @@ 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
@@ -797,29 +698,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 == common_filters.bank_account) .where(jea.account == Parameter("%(bank_account)s"))
.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 is True: if frappe.flags.auto_reconcile_vouchers == True:
query = query.where(ref_condition) query = query.where(ref_condition)
return query return str(query)
def get_si_matching_query(exact_match, currency, common_filters): def get_si_matching_query(exact_match, currency):
# 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 == common_filters.amount amount_equality = sip.amount == Parameter("%(amount)s")
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 == common_filters.party party_condition = si.customer == Parameter("%(party)s")
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 = (
@@ -840,23 +741,23 @@ def get_si_matching_query(exact_match, currency, common_filters):
) )
.where(si.docstatus == 1) .where(si.docstatus == 1)
.where(sip.clearance_date.isnull()) .where(sip.clearance_date.isnull())
.where(sip.account == common_filters.bank_account) .where(sip.account == Parameter("%(bank_account)s"))
.where(amount_condition) .where(amount_condition)
.where(si.currency == currency) .where(si.currency == currency)
) )
return query return str(query)
def get_pi_matching_query(exact_match, currency, common_filters): def get_pi_matching_query(exact_match, currency):
# 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 == common_filters.amount amount_equality = purchase_invoice.paid_amount == Parameter("%(amount)s")
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 == common_filters.party party_condition = purchase_invoice.supplier == Parameter("%(party)s")
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 = (
@@ -876,9 +777,9 @@ def get_pi_matching_query(exact_match, currency, common_filters):
.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 == common_filters.bank_account) .where(purchase_invoice.cash_bank_account == Parameter("%(bank_account)s"))
.where(amount_condition) .where(amount_condition)
.where(purchase_invoice.currency == currency) .where(purchase_invoice.currency == currency)
) )
return query return str(query)

View File

@@ -1,11 +1,12 @@
# 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 from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, today from frappe.utils import add_days, flt, getdate, 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,
@@ -21,7 +22,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")
qb.from_(bank_dt).delete().where(bank_dt.name == "HDFC").run() q = qb.from_(bank_dt).delete().where(bank_dt.name == "HDFC").run()
self.create_bank_account() self.create_bank_account()
def tearDown(self): def tearDown(self):
@@ -75,7 +76,6 @@ class TestBankReconciliationTool(AccountsTestMixin, FrappeTestCase):
"deposit": 100, "deposit": 100,
"bank_account": self.bank_account, "bank_account": self.bank_account,
"reference_number": "123", "reference_number": "123",
"currency": "INR",
} }
) )
.save() .save()

View File

@@ -7,7 +7,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.with_doc("Bank Statement Import", frm.doc.name).then(() => { frappe.model
.with_doc("Bank Statement Import", frm.doc.name)
.then(() => {
frm.refresh(); frm.refresh();
}); });
}); });
@@ -36,9 +38,20 @@ 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 = __("Skipping {0} of {1}, {2}", [data.current, data.total, eta_message]); message = __(
"Skipping {0} of {1}, {2}",
[
data.current,
data.total,
eta_message,
]
);
} }
frm.dashboard.show_progress(__("Import Progress"), percent, message); frm.dashboard.show_progress(
__("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
@@ -80,12 +93,15 @@ 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.trigger("export_errored_rows")); frm.add_custom_button(__("Export Errored Rows"), () =>
frm.trigger("export_errored_rows")
);
} }
if (frm.doc.status.includes("Success")) { if (frm.doc.status.includes("Success")) {
frm.add_custom_button(__("Go to {0} List", [__(frm.doc.reference_doctype)]), () => frm.add_custom_button(
frappe.set_route("List", frm.doc.reference_doctype) __("Go to {0} List", [__(frm.doc.reference_doctype)]),
() => frappe.set_route("List", frm.doc.reference_doctype)
); );
} }
}, },
@@ -102,8 +118,13 @@ 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 = frm.doc.status === "Pending" ? __("Start Import") : __("Retry"); let label =
frm.page.set_primary_action(label, () => frm.events.start_import(frm)); frm.doc.status === "Pending"
? __("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());
} }
@@ -120,41 +141,30 @@ frappe.ui.form.on("Bank Statement Import", {
}, },
show_import_status(frm) { show_import_status(frm) {
if (frm.doc.status == "Pending") return; let import_log = JSON.parse(frm.doc.statement_import_log || "[]");
let successful_records = import_log.filter((log) => log.success);
frappe.call({ let failed_records = import_log.filter((log) => !log.success);
method: "erpnext.accounts.doctype.bank_statement_import.bank_statement_import.get_import_status", if (successful_records.length === 0) return;
args: {
docname: frm.doc.name,
},
callback: function (r) {
let successful_records = cint(r.message.success);
let failed_records = cint(r.message.failed);
let total_records = cint(r.message.total_records);
if (!total_records) {
return;
}
let message; let message;
if (failed_records === 0) { if (failed_records.length === 0) {
let message_args = [successful_records]; let message_args = [successful_records.length];
if (frm.doc.import_type === "Insert New Records") { if (frm.doc.import_type === "Insert New Records") {
message = message =
successful_records > 1 successful_records.length > 1
? __("Successfully imported {0} records.", message_args) ? __("Successfully imported {0} records.", message_args)
: __("Successfully imported {0} record.", message_args); : __("Successfully imported {0} record.", message_args);
} else { } else {
message = message =
successful_records > 1 successful_records.length > 1
? __("Successfully updated {0} records.", message_args) ? __("Successfully updated {0} records.", message_args)
: __("Successfully updated {0} record.", message_args); : __("Successfully updated {0} record.", message_args);
} }
} else { } else {
let message_args = [successful_records, total_records]; let message_args = [successful_records.length, import_log.length];
if (frm.doc.import_type === "Insert New Records") { if (frm.doc.import_type === "Insert New Records") {
message = message =
successful_records > 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
@@ -165,7 +175,7 @@ frappe.ui.form.on("Bank Statement Import", {
); );
} else { } else {
message = message =
successful_records > 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
@@ -176,11 +186,8 @@ frappe.ui.form.on("Bank Statement Import", {
); );
} }
} }
frm.dashboard.set_headline(message); frm.dashboard.set_headline(message);
}, },
});
},
show_report_error_button(frm) { show_report_error_button(frm) {
if (frm.doc.status === "Error") { if (frm.doc.status === "Error") {
@@ -219,7 +226,8 @@ frappe.ui.form.on("Bank Statement Import", {
}, },
download_template() { download_template() {
let method = "/api/method/frappe.core.doctype.data_import.data_import.download_template"; let method =
"/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",
@@ -232,7 +240,7 @@ frappe.ui.form.on("Bank Statement Import", {
"description", "description",
"reference_number", "reference_number",
"bank_account", "bank_account",
"currency", "currency"
], ],
}, },
}); });
@@ -301,9 +309,12 @@ frappe.ui.form.on("Bank Statement Import", {
// method: 'frappe.core.doctype.data_import.data_import.get_preview_from_template', // method: 'frappe.core.doctype.data_import.data_import.get_preview_from_template',
show_import_preview(frm, preview_data) { show_import_preview(frm, preview_data) {
let import_log = preview_data.import_log; let import_log = JSON.parse(frm.doc.statement_import_log || "[]");
if (frm.import_preview && frm.import_preview.doctype === frm.doc.reference_doctype) { if (
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();
@@ -319,10 +330,19 @@ frappe.ui.form.on("Bank Statement Import", {
frm, frm,
events: { events: {
remap_column(changed_map) { remap_column(changed_map) {
let template_options = JSON.parse(frm.doc.template_options || "{}"); let template_options = JSON.parse(
template_options.column_to_field_map = template_options.column_to_field_map || {}; frm.doc.template_options || "{}"
Object.assign(template_options.column_to_field_map, changed_map); );
frm.set_value("template_options", JSON.stringify(template_options)); template_options.column_to_field_map =
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"));
}, },
}, },
@@ -340,15 +360,6 @@ frappe.ui.form.on("Bank Statement Import", {
); );
}, },
export_import_log(frm) {
open_url_post(
"/api/method/erpnext.accounts.doctype.bank_statement_import.bank_statement_import.download_import_log",
{
data_import_name: frm.doc.name,
}
);
},
show_import_warnings(frm, preview_data) { show_import_warnings(frm, preview_data) {
let columns = preview_data.columns; let columns = preview_data.columns;
let warnings = JSON.parse(frm.doc.template_warnings || "[]"); let warnings = JSON.parse(frm.doc.template_warnings || "[]");
@@ -365,7 +376,8 @@ 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);
@@ -380,7 +392,9 @@ 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})` : ""); (w.field.parent !== frm.doc.reference_doctype
? ` (${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>`;
@@ -399,9 +413,10 @@ 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">${__("Column {0}", [ let column_number = `<span class="text-uppercase">${__(
warning.col, "Column {0}",
])}</span>`; [warning.col]
)}</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})`;
} }
@@ -424,45 +439,52 @@ frappe.ui.form.on("Bank Statement Import", {
frm.trigger("show_import_log"); frm.trigger("show_import_log");
}, },
render_import_log(frm) { show_import_log(frm) {
frappe.call({ let import_log = JSON.parse(frm.doc.statement_import_log || "[]");
method: "erpnext.accounts.doctype.bank_statement_import.bank_statement_import.get_import_logs", let logs = import_log;
args: { frm.toggle_display("import_log", false);
docname: frm.doc.name, frm.toggle_display("import_log_section", logs.length > 0);
},
callback: function (r) {
let logs = r.message;
if (logs.length === 0) return; if (logs.length === 0) {
frm.get_field("import_log_preview").$wrapper.empty();
frm.toggle_display("import_log_section", true); return;
}
let rows = logs let rows = logs
.map((log) => { .map((log) => {
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 = __("Successfully imported {0}", [ html = __(
"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 = __("Successfully updated {0}", [ html = __(
"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 = JSON.parse(log.messages || "[]") let messages = log.messages
.map(JSON.parse)
.map((m) => { .map((m) => {
let title = m.title ? `<strong>${m.title}</strong>` : ""; let title = m.title
let message = m.message ? `<div>${m.message}</div>` : ""; ? `<strong>${m.title}</strong>`
: "";
let message = m.message
? `<div>${m.message}</div>`
: "";
return title + message; return title + message;
}) })
.join(""); .join("");
@@ -485,7 +507,7 @@ frappe.ui.form.on("Bank Statement Import", {
} }
return `<tr> return `<tr>
<td>${JSON.parse(log.row_indexes).join(", ")}</td> <td>${log.row_indexes.join(", ")}</td>
<td> <td>
<div class="indicator ${indicator_color}">${title}</div> <div class="indicator ${indicator_color}">${title}</div>
</td> </td>
@@ -513,33 +535,4 @@ frappe.ui.form.on("Bank Statement Import", {
</table> </table>
`); `);
}, },
});
},
show_import_log(frm) {
frm.toggle_display("import_log_section", false);
if (frm.is_new() || frm.import_in_progress) {
return;
}
frappe.call({
method: "frappe.client.get_count",
args: {
doctype: "Data Import Log",
filters: {
data_import: frm.doc.name,
},
},
callback: function (r) {
let count = r.message;
if (count < 5000) {
frm.trigger("render_import_log");
} else {
frm.toggle_display("import_log_section", false);
frm.add_custom_button(__("Export Import Log"), () => frm.trigger("export_import_log"));
}
},
});
},
}); });

View File

@@ -11,8 +11,6 @@
"bank_account", "bank_account",
"bank", "bank",
"column_break_4", "column_break_4",
"custom_delimiters",
"delimiter_options",
"google_sheets_url", "google_sheets_url",
"refresh_google_sheet", "refresh_google_sheet",
"html_5", "html_5",
@@ -26,6 +24,7 @@
"section_import_preview", "section_import_preview",
"import_preview", "import_preview",
"import_log_section", "import_log_section",
"statement_import_log",
"show_failed_logs", "show_failed_logs",
"import_log_preview", "import_log_preview",
"reference_doctype", "reference_doctype",
@@ -195,23 +194,15 @@
"fieldtype": "Column Break" "fieldtype": "Column Break"
}, },
{ {
"default": "0", "fieldname": "statement_import_log",
"fieldname": "custom_delimiters", "fieldtype": "Code",
"fieldtype": "Check", "label": "Statement Import Log",
"label": "Custom delimiters" "options": "JSON"
},
{
"default": ",;\\t|",
"depends_on": "custom_delimiters",
"description": "If your CSV uses a different delimiter, add that character here, ensuring no spaces or additional characters are included.",
"fieldname": "delimiter_options",
"fieldtype": "Data",
"label": "Delimiter options"
} }
], ],
"hide_toolbar": 1, "hide_toolbar": 1,
"links": [], "links": [],
"modified": "2024-06-25 17:32:07.658250", "modified": "2022-09-07 11:11:40.293317",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Bank Statement Import", "name": "Bank Statement Import",

View File

@@ -20,33 +20,8 @@ INVALID_VALUES = ("", None)
class BankStatementImport(DataImport): class BankStatementImport(DataImport):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
bank: DF.Link | None
bank_account: DF.Link
company: DF.Link
custom_delimiters: DF.Check
delimiter_options: DF.Data | None
google_sheets_url: DF.Data | None
import_file: DF.Attach | None
import_type: DF.Literal["", "Insert New Records", "Update Existing Records"]
mute_emails: DF.Check
reference_doctype: DF.Link
show_failed_logs: DF.Check
status: DF.Literal["Pending", "Success", "Partial Success", "Error"]
submit_after_import: DF.Check
template_options: DF.Code | None
template_warnings: DF.Code | None
# end: auto-generated types
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super(BankStatementImport, self).__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()
@@ -55,6 +30,7 @@ class BankStatementImport(DataImport):
or (doc_before_save and doc_before_save.import_file != self.import_file) or (doc_before_save and doc_before_save.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,6 +45,7 @@ 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
) )
@@ -121,15 +98,10 @@ def download_errored_template(data_import_name):
data_import.export_errored_rows() data_import.export_errored_rows()
@frappe.whitelist()
def download_import_log(data_import_name):
return frappe.get_doc("Bank Statement Import", data_import_name).download_import_log()
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
@@ -139,7 +111,9 @@ def parse_data_from_template(raw_data):
return data return data
def start_import(data_import, bank_account, import_file_path, google_sheets_url, bank, template_options): def start_import(
data_import, bank_account, import_file_path, google_sheets_url, bank, template_options
):
"""This method runs in background job""" """This method runs in background job"""
update_mapping_db(bank, template_options) update_mapping_db(bank, template_options)
@@ -150,9 +124,6 @@ def start_import(data_import, bank_account, import_file_path, google_sheets_url,
import_file = ImportFile("Bank Transaction", file=file, import_type="Insert New Records") 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)
@@ -247,47 +218,6 @@ def write_xlsx(data, sheet_name, wb=None, column_widths=None, file_path=None):
return True return True
@frappe.whitelist()
def get_import_status(docname):
import_status = {}
data_import = frappe.get_doc("Bank Statement Import", docname)
import_status["status"] = data_import.status
logs = frappe.get_all(
"Data Import Log",
fields=["count(*) as count", "success"],
filters={"data_import": docname},
group_by="success",
)
total_payload_count = 0
for log in logs:
total_payload_count += log.get("count", 0)
if log.get("success"):
import_status["success"] = log.get("count")
else:
import_status["failed"] = log.get("count")
import_status["total_records"] = total_payload_count
return import_status
@frappe.whitelist()
def get_import_logs(docname: str):
frappe.has_permission("Bank Statement Import")
return frappe.get_all(
"Data Import Log",
fields=["success", "docname", "messages", "exception", "row_indexes"],
filters={"data_import": docname},
limit_page_length=5000,
order_by="log_index",
)
@frappe.whitelist() @frappe.whitelist()
def upload_bank_statement(**args): def upload_bank_statement(**args):
args = frappe._dict(args) args = frappe._dict(args)

View File

@@ -1,34 +1,36 @@
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((d) => d !== data.data_import); imports_in_progress = imports_in_progress.filter(
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,3 +1,5 @@
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
@@ -17,7 +19,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) -> tuple | None: def match(self) -> Union[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,
@@ -48,40 +50,39 @@ class AutoMatchbyAccountIBAN:
result = self.match_account_in_party() result = self.match_account_in_party()
return result return result
def match_account_in_party(self) -> tuple | None: def match_account_in_party(self) -> Union[Tuple, None]:
""" """Check if there is a IBAN/Account No. match in Customer/Supplier/Employee"""
Returns (Party Type, Party) if a matching account is found in Bank Account or Employee: result = None
1. Get party from a matching (iban/account no) Bank Account parties = get_parties_in_order(self.deposit)
2. If not found, get party from Employee with matching bank account details (iban/account no) or_filters = self.get_or_filters()
"""
if not (self.bank_party_account_number or self.bank_party_iban):
# Nothing to match
return None
# Search for a matching Bank Account that has party set for party in parties:
party_result = frappe.db.get_all( party_result = frappe.db.get_all(
"Bank Account", "Bank Account", or_filters=or_filters, pluck="party", limit_page_length=1
or_filters=self.get_or_filters(),
filters={"party_type": ("is", "set"), "party": ("is", "set")},
fields=["party", "party_type"],
limit_page_length=1,
) )
if result := party_result[0] if party_result else None:
return (result["party_type"], result["party"])
# If no party is found, search in Employee (since it has bank account details) if party == "Employee" and not party_result:
employee_result = frappe.db.get_all( # Search in Bank Accounts first for Employee, and then Employee record
"Employee", or_filters=self.get_or_filters("Employee"), pluck="name", limit_page_length=1 if "bank_account_no" in or_filters:
or_filters["bank_ac_no"] = or_filters.pop("bank_account_no")
party_result = frappe.db.get_all(
party, or_filters=or_filters, pluck="name", limit_page_length=1
) )
if employee_result:
return ("Employee", employee_result[0])
def get_or_filters(self, party: str | None = None) -> dict: if party_result:
"""Return OR filters for Bank Account and IBAN""" result = (
party,
party_result[0],
)
break
return result
def get_or_filters(self) -> dict:
or_filters = {} or_filters = {}
if self.bank_party_account_number: if self.bank_party_account_number:
bank_ac_field = "bank_ac_no" if party == "Employee" else "bank_account_no" or_filters["bank_account_no"] = self.bank_party_account_number
or_filters[bank_ac_field] = self.bank_party_account_number
if self.bank_party_iban: if self.bank_party_iban:
or_filters["iban"] = self.bank_party_iban or_filters["iban"] = self.bank_party_iban
@@ -96,7 +97,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) -> tuple | None: def match(self) -> Union[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
@@ -104,7 +105,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) -> tuple | None: def match_party_name_desc_in_party(self) -> Union[Tuple, None]:
"""Fuzzy search party name and/or description against parties in the system""" """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)
@@ -128,7 +129,7 @@ class AutoMatchbyPartyNameDescription:
return result return result
def fuzzy_search_and_return_result(self, party, names, field) -> tuple | None: def fuzzy_search_and_return_result(self, party, names, field) -> Union[Tuple, None]:
skip = False skip = False
result = process.extract(query=self.get(field), choices=names, scorer=fuzz.token_set_ratio) result = process.extract(query=self.get(field), choices=names, scorer=fuzz.token_set_ratio)
party_name, skip = self.process_fuzzy_result(result) party_name, skip = self.process_fuzzy_result(result)
@@ -141,7 +142,7 @@ class AutoMatchbyPartyNameDescription:
party_name, party_name,
), skip ), skip
def process_fuzzy_result(self, result: list | None): def process_fuzzy_result(self, result: Union[list, None]):
""" """
If there are multiple valid close matches return None as result may be faulty. 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,10 +33,16 @@ 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 ["Payment Entry", "Journal Entry", "Sales Invoice", "Purchase Invoice", "Bank Transaction"]; return [
}, "Payment Entry",
"Journal Entry",
"Sales Invoice",
"Purchase Invoice",
"Bank Transaction",
];
}
}); });
frappe.ui.form.on("Bank Transaction Payments", { frappe.ui.form.on("Bank Transaction Payments", {
@@ -48,11 +54,10 @@ 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("erpnext.accounts.doctype.bank_transaction.bank_transaction.unclear_reference_payment", { .xcall(
doctype: cdt, "erpnext.accounts.doctype.bank_transaction.bank_transaction.unclear_reference_payment",
docname: cdn, { doctype: cdt, docname: cdn, bt_name: frm.doc.name }
bt_name: frm.doc.name, )
})
.then((e) => { .then((e) => {
if (e == "success") { if (e == "success") {
frappe.show_alert({ frappe.show_alert({

View File

@@ -13,7 +13,6 @@
"status", "status",
"bank_account", "bank_account",
"company", "company",
"amended_from",
"section_break_4", "section_break_4",
"deposit", "deposit",
"withdrawal", "withdrawal",
@@ -26,10 +25,10 @@
"transaction_id", "transaction_id",
"transaction_type", "transaction_type",
"section_break_14", "section_break_14",
"column_break_oufv",
"payment_entries", "payment_entries",
"section_break_18", "section_break_18",
"allocated_amount", "allocated_amount",
"amended_from",
"column_break_17", "column_break_17",
"unallocated_amount", "unallocated_amount",
"party_section", "party_section",
@@ -139,12 +138,10 @@
"fieldtype": "Section Break" "fieldtype": "Section Break"
}, },
{ {
"allow_on_submit": 1,
"fieldname": "allocated_amount", "fieldname": "allocated_amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Allocated Amount", "label": "Allocated Amount",
"options": "currency", "options": "currency"
"read_only": 1
}, },
{ {
"fieldname": "amended_from", "fieldname": "amended_from",
@@ -160,12 +157,10 @@
"fieldtype": "Column Break" "fieldtype": "Column Break"
}, },
{ {
"allow_on_submit": 1,
"fieldname": "unallocated_amount", "fieldname": "unallocated_amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Unallocated Amount", "label": "Unallocated Amount",
"options": "currency", "options": "currency"
"read_only": 1
}, },
{ {
"fieldname": "party_section", "fieldname": "party_section",
@@ -230,15 +225,11 @@
"fieldname": "bank_party_account_number", "fieldname": "bank_party_account_number",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Party Account No. (Bank Statement)" "label": "Party Account No. (Bank Statement)"
},
{
"fieldname": "column_break_oufv",
"fieldtype": "Column Break"
} }
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2023-11-18 18:32:47.203694", "modified": "2023-06-06 13:58:12.821411",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Bank Transaction", "name": "Bank Transaction",

View File

@@ -2,141 +2,78 @@
# For license information, please see license.txt # For license information, please see license.txt
import frappe import frappe
from frappe import _
from frappe.model.docstatus import DocStatus
from frappe.model.document import Document
from frappe.utils import flt from frappe.utils import flt
from erpnext.controllers.status_updater import StatusUpdater
class BankTransaction(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING class BankTransaction(StatusUpdater):
def after_insert(self):
self.unallocated_amount = abs(flt(self.withdrawal) - flt(self.deposit))
if TYPE_CHECKING: def on_submit(self):
from frappe.types import DF self.clear_linked_payment_entries()
from erpnext.accounts.doctype.bank_transaction_payments.bank_transaction_payments import (
BankTransactionPayments,
)
allocated_amount: DF.Currency
amended_from: DF.Link | None
bank_account: DF.Link | None
bank_party_account_number: DF.Data | None
bank_party_iban: DF.Data | None
bank_party_name: DF.Data | None
company: DF.Link | None
currency: DF.Link | None
date: DF.Date | None
deposit: DF.Currency
description: DF.SmallText | None
naming_series: DF.Literal["ACC-BTN-.YYYY.-"]
party: DF.DynamicLink | None
party_type: DF.Link | None
payment_entries: DF.Table[BankTransactionPayments]
reference_number: DF.Data | None
status: DF.Literal["", "Pending", "Settled", "Unreconciled", "Reconciled", "Cancelled"]
transaction_id: DF.Data | None
transaction_type: DF.Data | None
unallocated_amount: DF.Currency
withdrawal: DF.Currency
# end: auto-generated types
def before_validate(self):
self.update_allocated_amount()
def validate(self):
self.validate_duplicate_references()
self.validate_currency()
def validate_currency(self):
"""
Bank Transaction should be on the same currency as the Bank Account.
"""
if self.currency and self.bank_account:
if account := frappe.get_cached_value("Bank Account", self.bank_account, "account"):
account_currency = frappe.get_cached_value("Account", account, "account_currency")
if self.currency != account_currency:
frappe.throw(
_(
"Transaction currency: {0} cannot be different from Bank Account({1}) currency: {2}"
).format(
frappe.bold(self.currency),
frappe.bold(self.bank_account),
frappe.bold(account_currency),
)
)
def set_status(self):
if self.docstatus == 2:
self.db_set("status", "Cancelled")
elif self.docstatus == 1:
if self.unallocated_amount > 0:
self.db_set("status", "Unreconciled")
elif self.unallocated_amount <= 0:
self.db_set("status", "Reconciled")
def validate_duplicate_references(self):
"""Make sure the same voucher is not allocated twice within the same Bank Transaction"""
if not self.payment_entries:
return
pe = []
for row in self.payment_entries:
reference = (row.payment_document, row.payment_entry)
if reference in pe:
frappe.throw(
_("{0} {1} is allocated twice in this Bank Transaction").format(
row.payment_document, row.payment_entry
)
)
pe.append(reference)
def update_allocated_amount(self):
allocated_amount = (
sum(p.allocated_amount for p in self.payment_entries) if self.payment_entries else 0.0
)
unallocated_amount = abs(flt(self.withdrawal) - flt(self.deposit)) - allocated_amount
self.allocated_amount = flt(allocated_amount, self.precision("allocated_amount"))
self.unallocated_amount = flt(unallocated_amount, self.precision("unallocated_amount"))
def before_submit(self):
self.allocate_payment_entries()
self.set_status() self.set_status()
if frappe.db.get_single_value("Accounts Settings", "enable_party_matching"): if frappe.db.get_single_value("Accounts Settings", "enable_party_matching"):
self.auto_set_party() self.auto_set_party()
def before_update_after_submit(self): _saving_flag = False
self.validate_duplicate_references()
self.allocate_payment_entries() # nosemgrep: frappe-semgrep-rules.rules.frappe-modifying-but-not-comitting
self.update_allocated_amount() def on_update_after_submit(self):
self.set_status() "Run on save(). Avoid recursion caused by multiple saves"
if not self._saving_flag:
self._saving_flag = True
self.clear_linked_payment_entries()
self.update_allocations()
self._saving_flag = False
def on_cancel(self): def on_cancel(self):
for payment_entry in self.payment_entries: self.clear_linked_payment_entries(for_cancel=True)
self.clear_linked_payment_entry(payment_entry, for_cancel=True) self.set_status(update=True)
self.set_status() def update_allocations(self):
"The doctype does not allow modifications after submission, so write to the db direct"
if self.payment_entries:
allocated_amount = sum(p.allocated_amount for p in self.payment_entries)
else:
allocated_amount = 0.0
amount = abs(flt(self.withdrawal) - flt(self.deposit))
self.db_set("allocated_amount", flt(allocated_amount))
self.db_set("unallocated_amount", amount - flt(allocated_amount))
self.reload()
self.set_status(update=True)
def add_payment_entries(self, vouchers): def add_payment_entries(self, vouchers):
"Add the vouchers with zero allocation. Save() will perform the allocations and clearance" "Add the vouchers with zero allocation. Save() will perform the allocations and clearance"
if 0.0 >= self.unallocated_amount: if 0.0 >= self.unallocated_amount:
frappe.throw(_("Bank Transaction {0} is already fully reconciled").format(self.name)) frappe.throw(frappe._("Bank Transaction {0} is already fully reconciled").format(self.name))
added = False
for voucher in vouchers: for voucher in vouchers:
self.append( # Can't add same voucher twice
"payment_entries", found = False
{ for pe in self.payment_entries:
if (
pe.payment_document == voucher["payment_doctype"]
and pe.payment_entry == voucher["payment_name"]
):
found = True
if not found:
pe = {
"payment_document": voucher["payment_doctype"], "payment_document": voucher["payment_doctype"],
"payment_entry": voucher["payment_name"], "payment_entry": voucher["payment_name"],
"allocated_amount": 0.0, # Temporary "allocated_amount": 0.0, # Temporary
}, }
) child = self.append("payment_entries", pe)
added = True
# runs on_update_after_submit
if added:
self.save()
def allocate_payment_entries(self): def allocate_payment_entries(self):
"""Refactored from bank reconciliation tool. """Refactored from bank reconciliation tool.
@@ -152,56 +89,60 @@ class BankTransaction(Document):
- 0 > a: Error: already over-allocated - 0 > a: Error: already over-allocated
- clear means: set the latest transaction date as clearance date - clear means: set the latest transaction date as clearance date
""" """
gl_bank_account = frappe.db.get_value("Bank Account", self.bank_account, "account")
remaining_amount = self.unallocated_amount remaining_amount = self.unallocated_amount
to_remove = []
payment_entry_docs = [(pe.payment_document, pe.payment_entry) for pe in self.payment_entries]
pe_bt_allocations = get_total_allocated_amount(payment_entry_docs)
for payment_entry in self.payment_entries: for payment_entry in self.payment_entries:
if payment_entry.allocated_amount == 0.0: if payment_entry.allocated_amount == 0.0:
unallocated_amount, should_clear, latest_transaction = get_clearance_details( unallocated_amount, should_clear, latest_transaction = get_clearance_details(
self, self, payment_entry
payment_entry,
pe_bt_allocations.get((payment_entry.payment_document, payment_entry.payment_entry))
or [],
) )
if 0.0 == unallocated_amount: if 0.0 == unallocated_amount:
if should_clear: if should_clear:
latest_transaction.clear_linked_payment_entry(payment_entry) latest_transaction.clear_linked_payment_entry(payment_entry)
to_remove.append(payment_entry) self.db_delete_payment_entry(payment_entry)
elif remaining_amount <= 0.0: elif remaining_amount <= 0.0:
to_remove.append(payment_entry) self.db_delete_payment_entry(payment_entry)
elif 0.0 < unallocated_amount <= remaining_amount: elif 0.0 < unallocated_amount and unallocated_amount <= remaining_amount:
payment_entry.allocated_amount = unallocated_amount payment_entry.db_set("allocated_amount", unallocated_amount)
remaining_amount -= unallocated_amount remaining_amount -= unallocated_amount
if should_clear: if should_clear:
latest_transaction.clear_linked_payment_entry(payment_entry) latest_transaction.clear_linked_payment_entry(payment_entry)
elif 0.0 < unallocated_amount: elif 0.0 < unallocated_amount and unallocated_amount > remaining_amount:
payment_entry.allocated_amount = remaining_amount payment_entry.db_set("allocated_amount", remaining_amount)
remaining_amount = 0.0 remaining_amount = 0.0
elif 0.0 > unallocated_amount: elif 0.0 > unallocated_amount:
frappe.throw(_("Voucher {0} is over-allocated by {1}").format(unallocated_amount)) self.db_delete_payment_entry(payment_entry)
frappe.throw(frappe._("Voucher {0} is over-allocated by {1}").format(unallocated_amount))
for payment_entry in to_remove: self.reload()
self.remove(payment_entry)
def db_delete_payment_entry(self, payment_entry):
frappe.db.delete("Bank Transaction Payments", {"name": payment_entry.name})
@frappe.whitelist() @frappe.whitelist()
def remove_payment_entries(self): def remove_payment_entries(self):
for payment_entry in self.payment_entries: for payment_entry in self.payment_entries:
self.remove_payment_entry(payment_entry) self.remove_payment_entry(payment_entry)
# runs on_update_after_submit
self.save() # runs before_update_after_submit self.save()
def remove_payment_entry(self, payment_entry): def remove_payment_entry(self, payment_entry):
"Clear payment entry and clearance" "Clear payment entry and clearance"
self.clear_linked_payment_entry(payment_entry, for_cancel=True) self.clear_linked_payment_entry(payment_entry, for_cancel=True)
self.remove(payment_entry) self.remove(payment_entry)
def clear_linked_payment_entries(self, for_cancel=False):
if for_cancel:
for payment_entry in self.payment_entries:
self.clear_linked_payment_entry(payment_entry, for_cancel)
else:
self.allocate_payment_entries()
def clear_linked_payment_entry(self, payment_entry, for_cancel=False): def clear_linked_payment_entry(self, payment_entry, for_cancel=False):
clearance_date = None if for_cancel else self.date clearance_date = None if for_cancel else self.date
set_voucher_clearance( set_voucher_clearance(
@@ -214,8 +155,6 @@ class BankTransaction(Document):
if self.party_type and self.party: if self.party_type and self.party:
return return
result = None
try:
result = AutoMatchParty( result = AutoMatchParty(
bank_party_account_number=self.bank_party_account_number, bank_party_account_number=self.bank_party_account_number,
bank_party_iban=self.bank_party_iban, bank_party_iban=self.bank_party_iban,
@@ -223,13 +162,12 @@ class BankTransaction(Document):
description=self.description, description=self.description,
deposit=self.deposit, deposit=self.deposit,
).match() ).match()
except Exception:
frappe.log_error(title=_("Error in party matching for Bank Transaction {0}").format(self.name))
if not result: if result:
return party_type, party = result
frappe.db.set_value(
self.party_type, self.party = result "Bank Transaction", self.name, field={"party_type": party_type, "party": party}
)
@frappe.whitelist() @frappe.whitelist()
@@ -238,7 +176,7 @@ def get_doctypes_for_bank_reconciliation():
return frappe.get_hooks("bank_reconciliation_doctypes") return frappe.get_hooks("bank_reconciliation_doctypes")
def get_clearance_details(transaction, payment_entry, bt_allocations): def get_clearance_details(transaction, payment_entry):
""" """
There should only be one bank gle for a voucher. There should only be one bank gle for a voucher.
Could be none for a Bank Transaction. Could be none for a Bank Transaction.
@@ -247,6 +185,9 @@ def get_clearance_details(transaction, payment_entry, bt_allocations):
""" """
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(
payment_entry.payment_document, payment_entry.payment_entry
)
unallocated_amount = min( unallocated_amount = min(
transaction.unallocated_amount, transaction.unallocated_amount,
@@ -258,7 +199,9 @@ def get_clearance_details(transaction, payment_entry, bt_allocations):
if gle["gl_account"] == gl_bank_account: if gle["gl_account"] == gl_bank_account:
if gle["amount"] <= 0.0: if gle["amount"] <= 0.0:
frappe.throw( frappe.throw(
_("Voucher {0} value is broken: {1}").format(payment_entry.payment_entry, gle["amount"]) frappe._("Voucher {0} value is broken: {1}").format(
payment_entry.payment_entry, gle["amount"]
)
) )
unmatched_gles -= 1 unmatched_gles -= 1
@@ -279,7 +222,7 @@ def get_clearance_details(transaction, payment_entry, bt_allocations):
def get_related_bank_gl_entries(doctype, docname): def get_related_bank_gl_entries(doctype, docname):
# nosemgrep: frappe-semgrep-rules.rules.frappe-using-db-sql # nosemgrep: frappe-semgrep-rules.rules.frappe-using-db-sql
return frappe.db.sql( result = frappe.db.sql(
""" """
SELECT SELECT
ABS(gle.credit_in_account_currency - gle.debit_in_account_currency) AS amount, ABS(gle.credit_in_account_currency - gle.debit_in_account_currency) AS amount,
@@ -297,58 +240,52 @@ def get_related_bank_gl_entries(doctype, docname):
dict(doctype=doctype, docname=docname), dict(doctype=doctype, docname=docname),
as_dict=True, as_dict=True,
) )
return result
def get_total_allocated_amount(docs): def get_total_allocated_amount(doctype, docname):
""" """
Gets the sum of allocations for a voucher on each bank GL account Gets the sum of allocations for a voucher on each bank GL account
along with the latest bank transaction name & date along with the latest bank transaction name & date
NOTE: query may also include just saved vouchers/payments but with zero allocated_amount NOTE: query may also include just saved vouchers/payments but with zero allocated_amount
""" """
if not docs:
return {}
# nosemgrep: frappe-semgrep-rules.rules.frappe-using-db-sql # nosemgrep: frappe-semgrep-rules.rules.frappe-using-db-sql
result = frappe.db.sql( result = frappe.db.sql(
""" """
SELECT total, latest_name, latest_date, gl_account, payment_document, payment_entry FROM ( SELECT total, latest_name, latest_date, gl_account FROM (
SELECT SELECT
ROW_NUMBER() OVER w AS rownum, ROW_NUMBER() OVER w AS rownum,
SUM(btp.allocated_amount) OVER(PARTITION BY ba.account, btp.payment_document, btp.payment_entry) AS total, SUM(btp.allocated_amount) OVER(PARTITION BY ba.account) AS total,
FIRST_VALUE(bt.name) OVER w AS latest_name, FIRST_VALUE(bt.name) OVER w AS latest_name,
FIRST_VALUE(bt.date) OVER w AS latest_date, FIRST_VALUE(bt.date) OVER w AS latest_date,
ba.account AS gl_account, ba.account AS gl_account
btp.payment_document,
btp.payment_entry
FROM FROM
`tabBank Transaction Payments` btp `tabBank Transaction Payments` btp
LEFT JOIN `tabBank Transaction` bt ON bt.name=btp.parent LEFT JOIN `tabBank Transaction` bt ON bt.name=btp.parent
LEFT JOIN `tabBank Account` ba ON ba.name=bt.bank_account LEFT JOIN `tabBank Account` ba ON ba.name=bt.bank_account
WHERE WHERE
(btp.payment_document, btp.payment_entry) IN %(docs)s btp.payment_document = %(doctype)s
AND btp.payment_entry = %(docname)s
AND bt.docstatus = 1 AND bt.docstatus = 1
WINDOW w AS (PARTITION BY ba.account, btp.payment_document, btp.payment_entry ORDER BY bt.date DESC) WINDOW w AS (PARTITION BY ba.account ORDER BY bt.date desc)
) temp ) temp
WHERE WHERE
rownum = 1 rownum = 1
""", """,
dict(docs=docs), dict(doctype=doctype, docname=docname),
as_dict=True, as_dict=True,
) )
payment_allocation_details = {}
for row in result: for row in result:
# Why is this *sometimes* a byte string? # Why is this *sometimes* a byte string?
if isinstance(row["latest_name"], bytes): if isinstance(row["latest_name"], bytes):
row["latest_name"] = row["latest_name"].decode() row["latest_name"] = row["latest_name"].decode()
row["latest_date"] = frappe.utils.getdate(row["latest_date"]) row["latest_date"] = frappe.utils.getdate(row["latest_date"])
payment_allocation_details.setdefault((row["payment_document"], row["payment_entry"]), []).append(row) return result
return payment_allocation_details
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)
@@ -387,7 +324,9 @@ 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(payment_entry.payment_document, payment_entry.payment_entry, "amount_paid") return frappe.db.get_value(
payment_entry.payment_document, payment_entry.payment_entry, "amount_paid"
)
elif payment_entry.payment_document == "Bank Transaction": elif payment_entry.payment_document == "Bank Transaction":
dep, wth = frappe.db.get_value( dep, wth = frappe.db.get_value(
@@ -397,7 +336,9 @@ def get_paid_amount(payment_entry, currency, gl_bank_account):
else: else:
frappe.throw( frappe.throw(
f"Please reconcile {payment_entry.payment_document}: {payment_entry.payment_entry} manually" "Please reconcile {0}: {1} manually".format(
payment_entry.payment_document, payment_entry.payment_entry
)
) )
@@ -409,17 +350,15 @@ def set_voucher_clearance(doctype, docname, clearance_date, self):
and len(get_reconciled_bank_transactions(doctype, docname)) < 2 and len(get_reconciled_bank_transactions(doctype, docname)) < 2
): ):
return return
frappe.db.set_value(doctype, docname, "clearance_date", clearance_date)
if doctype == "Sales Invoice": elif doctype == "Sales Invoice":
frappe.db.set_value( frappe.db.set_value(
"Sales Invoice Payment", "Sales Invoice Payment",
dict(parenttype=doctype, parent=docname), dict(parenttype=doctype, parent=docname),
"clearance_date", "clearance_date",
clearance_date, clearance_date,
) )
return
frappe.db.set_value(doctype, docname, "clearance_date", clearance_date)
elif doctype == "Bank Transaction": elif doctype == "Bank Transaction":
# For when a second bank transaction has fixed another, e.g. refund # For when a second bank transaction has fixed another, e.g. refund
@@ -427,7 +366,6 @@ def set_voucher_clearance(doctype, docname, clearance_date, self):
if clearance_date: if clearance_date:
vouchers = [{"payment_doctype": "Bank Transaction", "payment_name": self.name}] vouchers = [{"payment_doctype": "Bank Transaction", "payment_name": self.name}]
bt.add_payment_entries(vouchers) bt.add_payment_entries(vouchers)
bt.save()
else: else:
for pe in bt.payment_entries: for pe in bt.payment_entries:
if pe.payment_document == self.doctype and pe.payment_entry == self.name: if pe.payment_document == self.doctype and pe.payment_entry == self.name:
@@ -449,21 +387,3 @@ def unclear_reference_payment(doctype, docname, bt_name):
bt = frappe.get_doc("Bank Transaction", bt_name) bt = frappe.get_doc("Bank Transaction", bt_name)
set_voucher_clearance(doctype, docname, None, bt) set_voucher_clearance(doctype, docname, None, bt)
return docname return docname
def remove_from_bank_transaction(doctype, docname):
"""Remove a (cancelled) voucher from all Bank Transactions."""
for bt_name in get_reconciled_bank_transactions(doctype, docname):
bt = frappe.get_doc("Bank Transaction", bt_name)
if bt.docstatus == DocStatus.cancelled():
continue
modified = False
for pe in bt.payment_entries:
if pe.payment_document == doctype and pe.payment_entry == docname:
bt.remove(pe)
modified = True
if modified:
bt.save()

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(b"csv"): if frappe.safe_encode(fname).lower().endswith("csv".encode("utf-8")):
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(b"xlsx"): elif frappe.safe_encode(fname).lower().endswith("xlsx".encode("utf-8")):
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

@@ -2,10 +2,10 @@
# See license.txt # See license.txt
import json import json
import unittest
import frappe import frappe
from frappe import utils from frappe import utils
from frappe.model.docstatus import DocStatus
from frappe.tests.utils import FrappeTestCase from frappe.tests.utils import FrappeTestCase
from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import ( from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import (
@@ -32,16 +32,8 @@ class TestBankTransaction(FrappeTestCase):
frappe.db.delete(dt) frappe.db.delete(dt)
clear_loan_transactions() clear_loan_transactions()
make_pos_profile() make_pos_profile()
add_transactions()
# generate and use a uniq hash identifier for 'Bank Account' and it's linked GL 'Account' to avoid validation error add_vouchers()
uniq_identifier = frappe.generate_hash(length=10)
gl_account = create_gl_account("_Test Bank " + uniq_identifier)
bank_account = create_bank_account(
gl_account=gl_account, bank_account_name="Checking Account " + uniq_identifier
)
add_transactions(bank_account=bank_account)
add_vouchers(gl_account=gl_account)
# This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction. # This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction.
def test_linked_payments(self): def test_linked_payments(self):
@@ -89,29 +81,6 @@ class TestBankTransaction(FrappeTestCase):
clearance_date = frappe.db.get_value("Payment Entry", payment.name, "clearance_date") clearance_date = frappe.db.get_value("Payment Entry", payment.name, "clearance_date")
self.assertFalse(clearance_date) self.assertFalse(clearance_date)
def test_cancel_voucher(self):
bank_transaction = frappe.get_doc(
"Bank Transaction",
dict(description="1512567 BG/000003025 OPSKATTUZWXXX AT776000000098709849 Herr G"),
)
payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1700))
vouchers = json.dumps(
[
{
"payment_doctype": "Payment Entry",
"payment_name": payment.name,
"amount": bank_transaction.unallocated_amount,
}
]
)
reconcile_vouchers(bank_transaction.name, vouchers)
payment.reload()
payment.cancel()
bank_transaction.reload()
self.assertEqual(bank_transaction.docstatus, DocStatus.submitted())
self.assertEqual(bank_transaction.unallocated_amount, 1700)
self.assertEqual(bank_transaction.payment_entries, [])
# Check if ERPNext can correctly filter a linked payments based on the debit/credit amount # Check if ERPNext can correctly filter a linked payments based on the debit/credit amount
def test_debit_credit_output(self): def test_debit_credit_output(self):
bank_transaction = frappe.get_doc( bank_transaction = frappe.get_doc(
@@ -227,9 +196,7 @@ def clear_loan_transactions():
frappe.db.delete("Loan Repayment") frappe.db.delete("Loan Repayment")
def create_bank_account( def create_bank_account(bank_name="Citi Bank", account_name="_Test Bank - _TC"):
bank_name="Citi Bank", gl_account="_Test Bank - _TC", bank_account_name="Checking Account"
):
try: try:
frappe.get_doc( frappe.get_doc(
{ {
@@ -241,35 +208,21 @@ def create_bank_account(
pass pass
try: try:
bank_account = frappe.get_doc( frappe.get_doc(
{ {
"doctype": "Bank Account", "doctype": "Bank Account",
"account_name": bank_account_name, "account_name": "Checking Account",
"bank": bank_name, "bank": bank_name,
"account": gl_account, "account": account_name,
} }
).insert(ignore_if_duplicate=True) ).insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError: except frappe.DuplicateEntryError:
pass pass
return bank_account.name
def add_transactions():
create_bank_account()
def create_gl_account(gl_account_name="_Test Bank - _TC"):
gl_account = frappe.get_doc(
{
"doctype": "Account",
"company": "_Test Company",
"parent_account": "Current Assets - _TC",
"account_type": "Bank",
"is_group": 0,
"account_name": gl_account_name,
}
).insert()
return gl_account.name
def add_transactions(bank_account="_Test Bank - _TC"):
doc = frappe.get_doc( doc = frappe.get_doc(
{ {
"doctype": "Bank Transaction", "doctype": "Bank Transaction",
@@ -277,7 +230,7 @@ def add_transactions(bank_account="_Test Bank - _TC"):
"date": "2018-10-23", "date": "2018-10-23",
"deposit": 1200, "deposit": 1200,
"currency": "INR", "currency": "INR",
"bank_account": bank_account, "bank_account": "Checking Account - Citi Bank",
} }
).insert() ).insert()
doc.submit() doc.submit()
@@ -289,7 +242,7 @@ def add_transactions(bank_account="_Test Bank - _TC"):
"date": "2018-10-23", "date": "2018-10-23",
"deposit": 1700, "deposit": 1700,
"currency": "INR", "currency": "INR",
"bank_account": bank_account, "bank_account": "Checking Account - Citi Bank",
} }
).insert() ).insert()
doc.submit() doc.submit()
@@ -301,7 +254,7 @@ def add_transactions(bank_account="_Test Bank - _TC"):
"date": "2018-10-26", "date": "2018-10-26",
"withdrawal": 690, "withdrawal": 690,
"currency": "INR", "currency": "INR",
"bank_account": bank_account, "bank_account": "Checking Account - Citi Bank",
} }
).insert() ).insert()
doc.submit() doc.submit()
@@ -313,7 +266,7 @@ def add_transactions(bank_account="_Test Bank - _TC"):
"date": "2018-10-27", "date": "2018-10-27",
"deposit": 3900, "deposit": 3900,
"currency": "INR", "currency": "INR",
"bank_account": bank_account, "bank_account": "Checking Account - Citi Bank",
} }
).insert() ).insert()
doc.submit() doc.submit()
@@ -325,13 +278,13 @@ def add_transactions(bank_account="_Test Bank - _TC"):
"date": "2018-10-27", "date": "2018-10-27",
"withdrawal": 109080, "withdrawal": 109080,
"currency": "INR", "currency": "INR",
"bank_account": bank_account, "bank_account": "Checking Account - Citi Bank",
} }
).insert() ).insert()
doc.submit() doc.submit()
def add_vouchers(gl_account="_Test Bank - _TC"): def add_vouchers():
try: try:
frappe.get_doc( frappe.get_doc(
{ {
@@ -347,7 +300,7 @@ def add_vouchers(gl_account="_Test Bank - _TC"):
pi = make_purchase_invoice(supplier="Conrad Electronic", qty=1, rate=690) pi = make_purchase_invoice(supplier="Conrad Electronic", qty=1, rate=690)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account) pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
pe.reference_no = "Conrad Oct 18" pe.reference_no = "Conrad Oct 18"
pe.reference_date = "2018-10-24" pe.reference_date = "2018-10-24"
pe.insert() pe.insert()
@@ -366,14 +319,14 @@ def add_vouchers(gl_account="_Test Bank - _TC"):
pass pass
pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1200) pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1200)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account) pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
pe.reference_no = "Herr G Oct 18" pe.reference_no = "Herr G Oct 18"
pe.reference_date = "2018-10-24" pe.reference_date = "2018-10-24"
pe.insert() pe.insert()
pe.submit() pe.submit()
pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1700) pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1700)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account) pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
pe.reference_no = "Herr G Nov 18" pe.reference_no = "Herr G Nov 18"
pe.reference_date = "2018-11-01" pe.reference_date = "2018-11-01"
pe.insert() pe.insert()
@@ -404,10 +357,10 @@ def add_vouchers(gl_account="_Test Bank - _TC"):
pass pass
pi = make_purchase_invoice(supplier="Poore Simon's", qty=1, rate=3900, is_paid=1, do_not_save=1) pi = make_purchase_invoice(supplier="Poore Simon's", qty=1, rate=3900, is_paid=1, do_not_save=1)
pi.cash_bank_account = gl_account pi.cash_bank_account = "_Test Bank - _TC"
pi.insert() pi.insert()
pi.submit() pi.submit()
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account) pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
pe.reference_no = "Poore Simon's Oct 18" pe.reference_no = "Poore Simon's Oct 18"
pe.reference_date = "2018-10-28" pe.reference_date = "2018-10-28"
pe.paid_amount = 690 pe.paid_amount = 690
@@ -416,7 +369,7 @@ def add_vouchers(gl_account="_Test Bank - _TC"):
pe.submit() pe.submit()
si = create_sales_invoice(customer="Poore Simon's", qty=1, rate=3900) si = create_sales_invoice(customer="Poore Simon's", qty=1, rate=3900)
pe = get_payment_entry("Sales Invoice", si.name, bank_account=gl_account) pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
pe.reference_no = "Poore Simon's Oct 18" pe.reference_no = "Poore Simon's Oct 18"
pe.reference_date = "2018-10-28" pe.reference_date = "2018-10-28"
pe.insert() pe.insert()
@@ -436,13 +389,19 @@ 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("Mode of Payment Account", {"company": "_Test Company", "parent": "Cash"}): if not frappe.db.get_value(
mode_of_payment.append("accounts", {"company": "_Test Company", "default_account": gl_account}) "Mode of Payment Account", {"company": "_Test Company", "parent": "Cash"}
):
mode_of_payment.append(
"accounts", {"company": "_Test Company", "default_account": "_Test Bank - _TC"}
)
mode_of_payment.save() mode_of_payment.save()
si = create_sales_invoice(customer="Fayva", qty=1, rate=109080, do_not_save=1) si = create_sales_invoice(customer="Fayva", qty=1, rate=109080, do_not_save=1)
si.is_pos = 1 si.is_pos = 1
si.append("payments", {"mode_of_payment": "Cash", "account": gl_account, "amount": 109080}) si.append(
"payments", {"mode_of_payment": "Cash", "account": "_Test Bank - _TC", "amount": 109080}
)
si.insert() si.insert()
si.submit() si.submit()

View File

@@ -6,19 +6,4 @@ from frappe.model.document import Document
class BankTransactionMapping(Document): class BankTransactionMapping(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
bank_transaction_field: DF.Literal
file_field: DF.Data
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
# end: auto-generated types
pass pass

View File

@@ -6,21 +6,4 @@ from frappe.model.document import Document
class BankTransactionPayments(Document): class BankTransactionPayments(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
allocated_amount: DF.Currency
clearance_date: DF.Date | None
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
payment_document: DF.Link
payment_entry: DF.DynamicLink
# end: auto-generated types
pass pass

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