Compare commits

..

1 Commits

Author SHA1 Message Date
Ankush Menat
d3117cca0c perf: add indexes on payment entry reference
Adds index on:
1. reference doctype
2. reference name

*Why not composite index?*

There are three type of queries on this doctype

- filtering ref_doctype - doctype index helps here
- filtering ref_name - name index helps here
- filtering both - name index helps here too. Since it has sufficiently
  high cardinality. Composite index wont help in case where ref_doctype
  isn't specfied.
2022-12-12 12:32:14 +05:30
2068 changed files with 163763 additions and 129900 deletions

View File

@@ -2,32 +2,65 @@
"env": { "env": {
"browser": true, "browser": true,
"node": true, "node": true,
"es2022": true "es6": true
}, },
"parserOptions": { "parserOptions": {
"ecmaVersion": 11,
"sourceType": "module" "sourceType": "module"
}, },
"extends": "eslint:recommended", "extends": "eslint:recommended",
"rules": { "rules": {
"indent": "off", "indent": [
"brace-style": "off", "error",
"no-mixed-spaces-and-tabs": "off", "tab",
"no-useless-escape": "off", { "SwitchCase": 1 }
"space-unary-ops": ["error", { "words": true }], ],
"linebreak-style": "off", "brace-style": [
"quotes": ["off"], "error",
"semi": "off", "1tbs"
"camelcase": "off", ],
"no-unused-vars": "off", "space-unary-ops": [
"no-console": ["warn"], "error",
"no-extra-boolean-cast": ["off"], { "words": true }
"no-control-regex": ["off"] ],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"off"
],
"semi": [
"warn",
"always"
],
"camelcase": [
"off"
],
"no-unused-vars": [
"warn"
],
"no-redeclare": [
"warn"
],
"no-console": [
"warn"
],
"no-extra-boolean-cast": [
"off"
],
"no-control-regex": [
"off"
],
"space-before-blocks": "warn",
"keyword-spacing": "warn",
"comma-spacing": "warn",
"key-spacing": "warn"
}, },
"root": true, "root": true,
"globals": { "globals": {
"frappe": true, "frappe": true,
"Vue": true, "Vue": true,
"SetVueGlobals": true,
"erpnext": true, "erpnext": true,
"hub": true, "hub": true,
"$": true, "$": true,
@@ -64,10 +97,8 @@
"is_null": true, "is_null": true,
"in_list": true, "in_list": true,
"has_common": true, "has_common": true,
"posthog": true,
"has_words": true, "has_words": true,
"validate_email": true, "validate_email": true,
"open_web_template_values_editor": true,
"get_number_format": true, "get_number_format": true,
"format_number": true, "format_number": true,
"format_currency": true, "format_currency": true,
@@ -123,6 +154,7 @@
"before": true, "before": true,
"beforeEach": true, "beforeEach": true,
"onScan": true, "onScan": true,
"html2canvas": true,
"extend_cscript": true, "extend_cscript": true,
"localforage": true "localforage": true
} }

View File

@@ -66,8 +66,7 @@ ignore =
F841, F841,
E713, E713,
E712, E712,
B023, B023
B028
max-line-length = 200 max-line-length = 200

View File

@@ -3,71 +3,52 @@ import requests
from urllib.parse import urlparse from urllib.parse import urlparse
WEBSITE_REPOS = [ docs_repos = [
"frappe_docs",
"erpnext_documentation",
"erpnext_com", "erpnext_com",
"frappe_io", "frappe_io",
] ]
DOCUMENTATION_DOMAINS = [
"docs.erpnext.com",
"frappeframework.com",
]
def uri_validator(x):
result = urlparse(x)
return all([result.scheme, result.netloc, result.path])
def is_valid_url(url: str) -> bool: def docs_link_exists(body):
parts = urlparse(url) for line in body.splitlines():
return all((parts.scheme, parts.netloc, parts.path)) for word in line.split():
if word.startswith('http') and uri_validator(word):
parsed_url = urlparse(word)
def is_documentation_link(word: str) -> bool: if parsed_url.netloc == "github.com":
if not word.startswith("http") or not is_valid_url(word): parts = parsed_url.path.split('/')
return False if len(parts) == 5 and parts[1] == "frappe" and parts[2] in docs_repos:
return True
parsed_url = urlparse(word) elif parsed_url.netloc == "docs.erpnext.com":
if parsed_url.netloc in DOCUMENTATION_DOMAINS: return True
return True
if parsed_url.netloc == "github.com":
parts = parsed_url.path.split("/")
if len(parts) == 5 and parts[1] == "frappe" and parts[2] in WEBSITE_REPOS:
return True
return False
def contains_documentation_link(body: str) -> bool:
return any(
is_documentation_link(word)
for line in body.splitlines()
for word in line.split()
)
def check_pull_request(number: str) -> "tuple[int, str]":
response = requests.get(f"https://api.github.com/repos/frappe/erpnext/pulls/{number}")
if not response.ok:
return 1, "Pull Request Not Found! ⚠️"
payload = response.json()
title = (payload.get("title") or "").lower().strip()
head_sha = (payload.get("head") or {}).get("sha")
body = (payload.get("body") or "").lower()
if (
not title.startswith("feat")
or not head_sha
or "no-docs" in body
or "backport" in body
):
return 0, "Skipping documentation checks... 🏃"
if contains_documentation_link(body):
return 0, "Documentation Link Found. You're Awesome! 🎉"
return 1, "Documentation Link Not Found! ⚠️"
if __name__ == "__main__": if __name__ == "__main__":
exit_code, message = check_pull_request(sys.argv[1]) pr = sys.argv[1]
print(message) response = requests.get("https://api.github.com/repos/frappe/erpnext/pulls/{}".format(pr))
sys.exit(exit_code)
if response.ok:
payload = response.json()
title = (payload.get("title") or "").lower().strip()
head_sha = (payload.get("head") or {}).get("sha")
body = (payload.get("body") or "").lower()
if (title.startswith("feat")
and head_sha
and "no-docs" not in body
and "backport" not in body
):
if docs_link_exists(body):
print("Documentation Link Found. You're Awesome! 🎉")
else:
print("Documentation Link Not Found! ⚠️")
sys.exit(1)
else:
print("Skipping documentation checks... 🏃")

View File

@@ -4,15 +4,12 @@ set -e
cd ~ || exit cd ~ || exit
sudo apt update sudo apt update && sudo apt install redis-server libcups2-dev
sudo apt remove mysql-server mysql-client
sudo apt install libcups2-dev redis-server mariadb-client-10.6
pip install frappe-bench pip install frappe-bench
githubbranch=${GITHUB_BASE_REF:-${GITHUB_REF##*/}}
frappeuser=${FRAPPE_USER:-"frappe"} frappeuser=${FRAPPE_USER:-"frappe"}
frappebranch=${FRAPPE_BRANCH:-$githubbranch} frappebranch=${FRAPPE_BRANCH:-${GITHUB_BASE_REF:-${GITHUB_REF##*/}}}
git clone "https://github.com/${frappeuser}/frappe" --branch "${frappebranch}" --depth 1 git clone "https://github.com/${frappeuser}/frappe" --branch "${frappebranch}" --depth 1
bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench
@@ -27,14 +24,14 @@ fi
if [ "$DB" == "mariadb" ];then if [ "$DB" == "mariadb" ];then
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'" mysql --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'"
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'" mysql --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'" mysql --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE DATABASE test_frappe" mysql --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE DATABASE test_frappe"
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'" mysql --host 127.0.0.1 --port 3306 -u root -proot -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "FLUSH PRIVILEGES" mysql --host 127.0.0.1 --port 3306 -u root -proot -e "FLUSH PRIVILEGES"
fi fi
if [ "$DB" == "postgres" ];then if [ "$DB" == "postgres" ];then
@@ -44,17 +41,12 @@ fi
install_whktml() { install_whktml() {
if [ "$(lsb_release -rs)" = "22.04" ]; then wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz
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 tar -xf /tmp/wkhtmltox.tar.xz -C /tmp
sudo apt install /tmp/wkhtmltox.deb sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf
else sudo chmod o+x /usr/local/bin/wkhtmltopdf
echo "Please update this script to support wkhtmltopdf for $(lsb_release -ds)"
exit 1
fi
} }
install_whktml & install_whktml &
wkpid=$!
cd ~/frappe-bench || exit cd ~/frappe-bench || exit
@@ -63,13 +55,11 @@ sed -i 's/schedule:/# schedule:/g' Procfile
sed -i 's/socketio:/# socketio:/g' Procfile sed -i 's/socketio:/# socketio:/g' Procfile
sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile
bench get-app payments --branch ${githubbranch%"-hotfix"} bench get-app payments
bench get-app erpnext "${GITHUB_WORKSPACE}" bench get-app erpnext "${GITHUB_WORKSPACE}"
if [ "$TYPE" == "server" ]; then bench setup requirements --dev; fi if [ "$TYPE" == "server" ]; then bench setup requirements --dev; fi
wait $wkpid bench start &> bench_run_logs.txt &
bench start &>> ~/frappe-bench/bench_start.log &
CI=Yes bench build --app frappe & CI=Yes bench build --app frappe &
bench --site test_site reinstall --yes bench --site test_site reinstall --yes

View File

@@ -11,6 +11,6 @@
"root_login": "root", "root_login": "root",
"root_password": "root", "root_password": "root",
"host_name": "http://test_site:8000", "host_name": "http://test_site:8000",
"install_apps": ["payments", "erpnext"], "install_apps": ["erpnext"],
"throttle_user_limit": 100 "throttle_user_limit": 100
} }

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:
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

@@ -9,22 +9,21 @@ jobs:
name: linters name: linters
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v2
- name: Set up Python 3.10 - name: Set up Python 3.10
uses: actions/setup-python@v4 uses: actions/setup-python@v2
with: with:
python-version: '3.10' python-version: '3.10'
cache: pip
- name: Install and Run Pre-commit - name: Install and Run Pre-commit
uses: pre-commit/action@v3.0.0 uses: pre-commit/action@v2.0.3
- name: Download Semgrep rules - name: Download Semgrep rules
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules
- name: Download semgrep - name: Download semgrep
run: pip install semgrep run: pip install semgrep==0.97.0
- name: Run Semgrep rules - name: Run Semgrep rules
run: semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness run: semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness

View File

@@ -23,12 +23,12 @@ jobs:
services: services:
mysql: mysql:
image: mariadb:10.6 image: mariadb:10.3
env: env:
MARIADB_ROOT_PASSWORD: 'root' MARIADB_ROOT_PASSWORD: 'root'
ports: ports:
- 3306:3306 - 3306:3306
options: --health-cmd="mariadb-admin ping" --health-interval=5s --health-timeout=2s --health-retries=3 options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
steps: steps:
- name: Clone - name: Clone
@@ -43,14 +43,14 @@ jobs:
fi fi
- name: Setup Python - name: Setup Python
uses: "actions/setup-python@v4" uses: "gabrielfalcao/pyenv-action@v9"
with: with:
python-version: '3.10' versions: 3.10:latest, 3.7:latest
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v2 uses: actions/setup-node@v2
with: with:
node-version: 18 node-version: 14
check-latest: true check-latest: true
- name: Add to Hosts - name: Add to Hosts
@@ -92,6 +92,7 @@ jobs:
- name: Install - name: Install
run: | run: |
pip install frappe-bench pip install frappe-bench
pyenv global $(pyenv versions | grep '3.10')
bash ${GITHUB_WORKSPACE}/.github/helper/install.sh bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
env: env:
DB: mariadb DB: mariadb
@@ -100,60 +101,42 @@ jobs:
- name: Run Patch Tests - name: Run Patch Tests
run: | run: |
cd ~/frappe-bench/ cd ~/frappe-bench/
bench remove-app payments --force wget https://erpnext.com/files/v10-erpnext.sql.gz
jq 'del(.install_apps)' ~/frappe-bench/sites/test_site/site_config.json > tmp.json bench --site test_site --force restore ~/frappe-bench/v10-erpnext.sql.gz
mv tmp.json ~/frappe-bench/sites/test_site/site_config.json
wget https://erpnext.com/files/v13-erpnext.sql.gz
bench --site test_site --force restore ~/frappe-bench/v13-erpnext.sql.gz
git -C "apps/frappe" remote set-url upstream https://github.com/frappe/frappe.git git -C "apps/frappe" remote set-url upstream https://github.com/frappe/frappe.git
git -C "apps/erpnext" remote set-url upstream https://github.com/frappe/erpnext.git git -C "apps/erpnext" remote set-url upstream https://github.com/frappe/erpnext.git
pyenv global $(pyenv versions | grep '3.7')
for version in $(seq 12 13)
do
echo "Updating to v$version"
branch_name="version-$version-hotfix"
function update_to_version() { git -C "apps/frappe" fetch --depth 1 upstream $branch_name:$branch_name
version=$1 git -C "apps/erpnext" fetch --depth 1 upstream $branch_name:$branch_name
branch_name="version-$version-hotfix" git -C "apps/frappe" checkout -q -f $branch_name
echo "Updating to v$version" git -C "apps/erpnext" checkout -q -f $branch_name
# Fetch and checkout branches rm -rf ~/frappe-bench/env
git -C "apps/frappe" fetch --depth 1 upstream $branch_name:$branch_name bench setup env
git -C "apps/erpnext" fetch --depth 1 upstream $branch_name:$branch_name bench pip install -e ./apps/payments
git -C "apps/frappe" checkout -q -f $branch_name bench pip install -e ./apps/erpnext
git -C "apps/erpnext" checkout -q -f $branch_name
# Resetup env and install apps bench --site test_site migrate
pgrep honcho | xargs kill done
rm -rf ~/frappe-bench/env
bench -v setup env
bench pip install -e ./apps/erpnext
bench start &>> ~/frappe-bench/bench_start.log &
bench --site test_site migrate
}
update_to_version 14
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##*/}}"
git -C "apps/erpnext" checkout -q -f "$GITHUB_SHA" git -C "apps/erpnext" checkout -q -f "$GITHUB_SHA"
pgrep honcho | xargs kill pyenv global $(pyenv versions | grep '3.10')
rm -rf ~/frappe-bench/env rm -rf ~/frappe-bench/env
bench -v setup env bench -v setup env
bench pip install -e ./apps/payments
bench pip install -e ./apps/erpnext bench pip install -e ./apps/erpnext
bench start &>> ~/frappe-bench/bench_start.log &
bench --site test_site migrate bench --site test_site migrate
bench --site test_site install-app payments
- name: Show bench output
if: ${{ always() }}
run: |
cd ~/frappe-bench
cat bench_start.log || true
cd logs
for f in ./*.log*; do
echo "Printing log: $f";
cat $f
done

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 v14
- name: Setup Node.js
uses: actions/setup-node@v2 uses: actions/setup-node@v2
with: with:
node-version: 20 node-version: 14
- 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
@@ -30,4 +28,4 @@ jobs:
GIT_AUTHOR_EMAIL: "developers@frappe.io" GIT_AUTHOR_EMAIL: "developers@frappe.io"
GIT_COMMITTER_NAME: "Frappe PR Bot" GIT_COMMITTER_NAME: "Frappe PR Bot"
GIT_COMMITTER_EMAIL: "developers@frappe.io" GIT_COMMITTER_EMAIL: "developers@frappe.io"
run: npx semantic-release run: npx semantic-release

View File

@@ -1,38 +0,0 @@
# This action:
#
# 1. Generates release notes using github API.
# 2. Strips unnecessary info like chore/style etc from notes.
# 3. Updates release info.
# This action needs to be maintained on all branches that do releases.
name: 'Release Notes'
on:
workflow_dispatch:
inputs:
tag_name:
description: 'Tag of release like v13.0.0'
required: true
type: string
release:
types: [released]
permissions:
contents: read
jobs:
regen-notes:
name: 'Regenerate release notes'
runs-on: ubuntu-latest
steps:
- name: Update notes
run: |
NEW_NOTES=$(gh api --method POST -H "Accept: application/vnd.github+json" /repos/frappe/erpnext/releases/generate-notes -f tag_name=$RELEASE_TAG | jq -r '.body' | sed -E '/^\* (chore|ci|test|docs|style)/d' )
RELEASE_ID=$(gh api -H "Accept: application/vnd.github+json" /repos/frappe/erpnext/releases/tags/$RELEASE_TAG | jq -r '.id')
gh api --method PATCH -H "Accept: application/vnd.github+json" /repos/frappe/erpnext/releases/$RELEASE_ID -f body="$NEW_NOTES"
env:
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
RELEASE_TAG: ${{ github.event.inputs.tag_name || github.event.release.tag_name }}

View File

@@ -21,7 +21,7 @@ jobs:
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: 18 node-version: 14
check-latest: true check-latest: true
- name: Check commit titles - name: Check commit titles

View File

@@ -7,18 +7,21 @@ on:
- '**.css' - '**.css'
- '**.md' - '**.md'
- '**.html' - '**.html'
schedule: - '**.csv'
# Run everday at midnight UTC / 5:30 IST push:
- cron: "0 0 * * *" branches: [ develop ]
paths-ignore:
- '**.js'
- '**.md'
workflow_dispatch: workflow_dispatch:
inputs: inputs:
user: user:
description: 'Frappe Framework repository user (add your username for forks)' description: 'user'
required: true required: true
default: 'frappe' default: 'frappe'
type: string type: string
branch: branch:
description: 'Frappe Framework branch' description: 'Branch name'
default: 'develop' default: 'develop'
required: false required: false
type: string type: string
@@ -47,7 +50,7 @@ jobs:
MARIADB_ROOT_PASSWORD: 'root' MARIADB_ROOT_PASSWORD: 'root'
ports: ports:
- 3306:3306 - 3306:3306
options: --health-cmd="mariadb-admin ping" --health-interval=5s --health-timeout=2s --health-retries=3 options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
steps: steps:
- name: Clone - name: Clone
@@ -69,7 +72,7 @@ jobs:
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v2 uses: actions/setup-node@v2
with: with:
node-version: 18 node-version: 14
check-latest: true check-latest: true
- name: Add to Hosts - name: Add to Hosts
@@ -117,12 +120,32 @@ 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 }}
ORCHESTRATOR_URL: http://test-orchestrator.frappe.io ORCHESTRATOR_URL: http://test-orchestrator.frappe.io
- name: Show bench output - name: Upload coverage data
if: ${{ always() }} uses: actions/upload-artifact@v3
run: cat ~/frappe-bench/bench_start.log || true 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

@@ -59,7 +59,7 @@ jobs:
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v2 uses: actions/setup-node@v2
with: with:
node-version: 18 node-version: 14
check-latest: true check-latest: true
- name: Add to Hosts - name: Add to Hosts

View File

@@ -15,8 +15,6 @@ pull_request_rules:
- or: - or:
- base=version-13 - base=version-13
- base=version-12 - base=version-12
- base=version-14
- base=version-15
actions: actions:
close: close:
comment: comment:

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,36 +15,13 @@ 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-eslint
rev: v8.44.0
hooks:
- id: eslint
types_or: [javascript]
args: ['--quiet']
# 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/PyCQA/flake8 - repo: https://github.com/PyCQA/flake8
rev: 6.0.0 rev: 5.0.4
hooks: hooks:
- id: flake8 - id: flake8
additional_dependencies: [ additional_dependencies: [
'flake8-bugbear', 'flake8-bugbear',
'flake8-tuple',
] ]
args: ['--config', '.github/helper/.flake8_strict'] args: ['--config', '.github/helper/.flake8_strict']
exclude: ".*setup.py$" exclude: ".*setup.py$"
@@ -55,8 +32,8 @@ repos:
- id: black - id: black
additional_dependencies: ['click==8.0.4'] additional_dependencies: ['click==8.0.4']
- repo: https://github.com/PyCQA/isort - repo: https://github.com/timothycrosley/isort
rev: 5.12.0 rev: 5.9.1
hooks: hooks:
- id: isort - id: isort
exclude: ".*setup.py$" exclude: ".*setup.py$"

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",
@@ -21,4 +21,4 @@
], ],
"@semantic-release/github" "@semantic-release/github"
] ]
} }

View File

@@ -3,22 +3,26 @@
# These owners will be the default owners for everything in # These owners will be the default owners for everything in
# the repo. Unless a later match takes precedence, # the repo. Unless a later match takes precedence,
erpnext/accounts/ @deepeshgarg007 @ruthra-kumar erpnext/accounts/ @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
erpnext/assets/ @anandbaburajan @deepeshgarg007 erpnext/assets/ @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
erpnext/regional @deepeshgarg007 @ruthra-kumar erpnext/loan_management/ @nextchamp-saqib @deepeshgarg007
erpnext/selling @deepeshgarg007 @ruthra-kumar erpnext/regional @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
erpnext/support/ @deepeshgarg007 erpnext/selling @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
pos* erpnext/support/ @nextchamp-saqib @deepeshgarg007
pos* @nextchamp-saqib
erpnext/buying/ @rohitwaghchaure @s-aga-r erpnext/buying/ @rohitwaghchaure @s-aga-r
erpnext/maintenance/ @rohitwaghchaure @s-aga-r erpnext/maintenance/ @rohitwaghchaure @s-aga-r
erpnext/manufacturing/ @rohitwaghchaure @s-aga-r erpnext/manufacturing/ @rohitwaghchaure @s-aga-r
erpnext/quality_management/ @rohitwaghchaure @s-aga-r erpnext/quality_management/ @rohitwaghchaure @s-aga-r
erpnext/stock/ @rohitwaghchaure @s-aga-r erpnext/stock/ @rohitwaghchaure @s-aga-r
erpnext/subcontracting @rohitwaghchaure @s-aga-r
erpnext/controllers/ @deepeshgarg007 @rohitwaghchaure erpnext/crm/ @NagariaHussain
erpnext/patches/ @deepeshgarg007 erpnext/education/ @rutwikhdev
erpnext/projects/ @ruchamahabal
.github/ @deepeshgarg007 erpnext/controllers/ @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure
pyproject.toml @phot0n erpnext/patches/ @deepeshgarg007 @nextchamp-saqib
.github/ @ankush
pyproject.toml @ankush

View File

@@ -65,7 +65,7 @@ New passwords will be created for the ERPNext "Administrator" user, the MariaDB
1. [Frappe School](https://frappe.school) - Learn Frappe Framework and ERPNext from the various courses by the maintainers or from the community. 1. [Frappe School](https://frappe.school) - Learn Frappe Framework and ERPNext from the various courses by the maintainers or from the community.
2. [Official documentation](https://docs.erpnext.com/) - Extensive documentation for ERPNext. 2. [Official documentation](https://docs.erpnext.com/) - Extensive documentation for ERPNext.
3. [Discussion Forum](https://discuss.erpnext.com/) - Engage with community of ERPNext users and service providers. 3. [Discussion Forum](https://discuss.erpnext.com/) - Engage with community of ERPNext users and service providers.
4. [Telegram Group](https://erpnext_public.t.me) - Get instant help from huge community of users. 4. [Telegram Group](https://t.me/erpnexthelp) - Get instant help from huge community of users.
## Contributing ## Contributing

View File

@@ -1,9 +1,8 @@
import functools
import inspect import inspect
import frappe import frappe
__version__ = "15.14.2" __version__ = "14.0.0-dev"
def get_default_company(user=None): def get_default_company(user=None):
@@ -121,14 +120,12 @@ def get_region(company=None):
You can also set global company flag in `frappe.flags.company` You can also set global company flag in `frappe.flags.company`
""" """
if company or frappe.flags.company:
if not company: return frappe.get_cached_value("Company", company or frappe.flags.company, "country")
company = frappe.local.flags.company elif frappe.flags.country:
return frappe.flags.country
if company: else:
return frappe.get_cached_value("Company", company, "country") return frappe.get_system_settings("country")
return frappe.flags.country or frappe.get_system_settings("country")
def allow_regional(fn): def allow_regional(fn):
@@ -139,7 +136,6 @@ def allow_regional(fn):
def myfunction(): def myfunction():
pass""" pass"""
@functools.wraps(fn)
def caller(*args, **kwargs): def caller(*args, **kwargs):
overrides = frappe.get_hooks("regional_overrides", {}).get(get_region()) overrides = frappe.get_hooks("regional_overrides", {}).get(get_region())
function_path = f"{inspect.getmodule(fn).__name__}.{fn.__name__}" function_path = f"{inspect.getmodule(fn).__name__}.{fn.__name__}"

View File

@@ -4,19 +4,18 @@
"creation": "2020-07-17 11:25:34.593061", "creation": "2020-07-17 11:25:34.593061",
"docstatus": 0, "docstatus": 0,
"doctype": "Dashboard Chart", "doctype": "Dashboard Chart",
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"erpnext.utils.get_fiscal_year()\",\"to_fiscal_year\":\"erpnext.utils.get_fiscal_year()\"}", "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"frappe.sys_defaults.fiscal_year\",\"to_fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}",
"filters_json": "{\"period\":\"Monthly\",\"budget_against\":\"Cost Center\",\"show_cumulative\":0}", "filters_json": "{\"period\":\"Monthly\",\"budget_against\":\"Cost Center\",\"show_cumulative\":0}",
"idx": 0, "idx": 0,
"is_public": 1, "is_public": 1,
"is_standard": 1, "is_standard": 1,
"modified": "2023-07-19 13:13:13.307073", "modified": "2020-07-22 12:24:49.144210",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Budget Variance", "name": "Budget Variance",
"number_of_groups": 0, "number_of_groups": 0,
"owner": "Administrator", "owner": "Administrator",
"report_name": "Budget Variance Report", "report_name": "Budget Variance Report",
"roles": [],
"timeseries": 0, "timeseries": 0,
"type": "Bar", "type": "Bar",
"use_report_chart": 1, "use_report_chart": 1,

View File

@@ -4,19 +4,18 @@
"creation": "2020-07-17 11:25:34.448572", "creation": "2020-07-17 11:25:34.448572",
"docstatus": 0, "docstatus": 0,
"doctype": "Dashboard Chart", "doctype": "Dashboard Chart",
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"erpnext.utils.get_fiscal_year()\",\"to_fiscal_year\":\"erpnext.utils.get_fiscal_year()\"}", "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"frappe.sys_defaults.fiscal_year\",\"to_fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}",
"filters_json": "{\"filter_based_on\":\"Fiscal Year\",\"period_start_date\":\"2020-04-01\",\"period_end_date\":\"2021-03-31\",\"periodicity\":\"Yearly\",\"include_default_book_entries\":1}", "filters_json": "{\"filter_based_on\":\"Fiscal Year\",\"period_start_date\":\"2020-04-01\",\"period_end_date\":\"2021-03-31\",\"periodicity\":\"Yearly\",\"include_default_book_entries\":1}",
"idx": 0, "idx": 0,
"is_public": 1, "is_public": 1,
"is_standard": 1, "is_standard": 1,
"modified": "2023-07-19 13:08:56.470390", "modified": "2020-07-22 12:33:48.888943",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Profit and Loss", "name": "Profit and Loss",
"number_of_groups": 0, "number_of_groups": 0,
"owner": "Administrator", "owner": "Administrator",
"report_name": "Profit and Loss Statement", "report_name": "Profit and Loss Statement",
"roles": [],
"timeseries": 0, "timeseries": 0,
"type": "Bar", "type": "Bar",
"use_report_chart": 1, "use_report_chart": 1,

View File

@@ -136,7 +136,7 @@ def convert_deferred_revenue_to_income(
send_mail(deferred_process) send_mail(deferred_process)
def get_booking_dates(doc, item, posting_date=None, prev_posting_date=None): def get_booking_dates(doc, item, posting_date=None):
if not posting_date: if not posting_date:
posting_date = add_days(today(), -1) posting_date = add_days(today(), -1)
@@ -146,42 +146,39 @@ def get_booking_dates(doc, item, posting_date=None, prev_posting_date=None):
"deferred_revenue_account" if doc.doctype == "Sales Invoice" else "deferred_expense_account" "deferred_revenue_account" if doc.doctype == "Sales Invoice" else "deferred_expense_account"
) )
if not prev_posting_date: prev_gl_entry = frappe.db.sql(
prev_gl_entry = frappe.db.sql( """
""" select name, posting_date from `tabGL Entry` where company=%s and account=%s and
select name, posting_date from `tabGL Entry` where company=%s and account=%s and voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
voucher_type=%s and voucher_no=%s and voucher_detail_no=%s and is_cancelled = 0
and is_cancelled = 0 order by posting_date desc limit 1
order by posting_date desc limit 1 """,
""", (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, )
)
prev_gl_via_je = frappe.db.sql( prev_gl_via_je = frappe.db.sql(
""" """
SELECT p.name, p.posting_date FROM `tabJournal Entry` p, `tabJournal Entry Account` c SELECT p.name, p.posting_date FROM `tabJournal Entry` p, `tabJournal Entry Account` c
WHERE p.name = c.parent and p.company=%s and c.account=%s 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_type=%s and c.reference_name=%s
and c.reference_detail_no=%s and c.docstatus < 2 order by posting_date desc limit 1 and c.reference_detail_no=%s and c.docstatus < 2 order by posting_date desc limit 1
""", """,
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
as_dict=True, as_dict=True,
) )
if prev_gl_via_je: if prev_gl_via_je:
if (not prev_gl_entry) or ( if (not prev_gl_entry) or (
prev_gl_entry and prev_gl_entry[0].posting_date < prev_gl_via_je[0].posting_date prev_gl_entry and prev_gl_entry[0].posting_date < prev_gl_via_je[0].posting_date
): ):
prev_gl_entry = prev_gl_via_je prev_gl_entry = prev_gl_via_je
if prev_gl_entry:
start_date = getdate(add_days(prev_gl_entry[0].posting_date, 1))
else:
start_date = item.service_start_date
if prev_gl_entry:
start_date = getdate(add_days(prev_gl_entry[0].posting_date, 1))
else: else:
start_date = getdate(add_days(prev_posting_date, 1)) start_date = item.service_start_date
end_date = get_last_day(start_date) end_date = get_last_day(start_date)
if end_date >= item.service_end_date: if end_date >= item.service_end_date:
end_date = item.service_end_date end_date = item.service_end_date
@@ -341,18 +338,12 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
"enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense" "enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense"
) )
accounts_frozen_upto = frappe.db.get_single_value("Accounts Settings", "acc_frozen_upto") accounts_frozen_upto = frappe.get_cached_value("Accounts Settings", "None", "acc_frozen_upto")
def _book_deferred_revenue_or_expense( def _book_deferred_revenue_or_expense(
item, item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on
via_journal_entry,
submit_journal_entry,
book_deferred_entries_based_on,
prev_posting_date=None,
): ):
start_date, end_date, last_gl_entry = get_booking_dates( start_date, end_date, last_gl_entry = get_booking_dates(doc, item, posting_date=posting_date)
doc, item, posting_date=posting_date, prev_posting_date=prev_posting_date
)
if not (start_date and end_date): if not (start_date and end_date):
return return
@@ -386,12 +377,9 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
if not amount: if not amount:
return return
gl_posting_date = end_date
prev_posting_date = None
# check if books nor frozen till endate: # check if books nor frozen till endate:
if accounts_frozen_upto and getdate(end_date) <= getdate(accounts_frozen_upto): if accounts_frozen_upto and (end_date) <= getdate(accounts_frozen_upto):
gl_posting_date = get_last_day(add_days(accounts_frozen_upto, 1)) end_date = get_last_day(add_days(accounts_frozen_upto, 1))
prev_posting_date = end_date
if via_journal_entry: if via_journal_entry:
book_revenue_via_journal_entry( book_revenue_via_journal_entry(
@@ -400,7 +388,7 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
debit_account, debit_account,
amount, amount,
base_amount, base_amount,
gl_posting_date, end_date,
project, project,
account_currency, account_currency,
item.cost_center, item.cost_center,
@@ -416,7 +404,7 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
against, against,
amount, amount,
base_amount, base_amount,
gl_posting_date, end_date,
project, project,
account_currency, account_currency,
item.cost_center, item.cost_center,
@@ -430,11 +418,7 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
if getdate(end_date) < getdate(posting_date) and not last_gl_entry: if getdate(end_date) < getdate(posting_date) and not last_gl_entry:
_book_deferred_revenue_or_expense( _book_deferred_revenue_or_expense(
item, item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on
via_journal_entry,
submit_journal_entry,
book_deferred_entries_based_on,
prev_posting_date,
) )
via_journal_entry = cint( via_journal_entry = cint(

View File

@@ -1,83 +1,67 @@
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors // Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt // License: GNU General Public License v3. See license.txt
frappe.ui.form.on("Account", { frappe.ui.form.on('Account', {
setup: function (frm) { setup: function(frm) {
frm.add_fetch("parent_account", "report_type", "report_type"); frm.add_fetch('parent_account', 'report_type', 'report_type');
frm.add_fetch("parent_account", "root_type", "root_type"); frm.add_fetch('parent_account', 'root_type', 'root_type');
}, },
onload: function (frm) { onload: function(frm) {
frm.set_query("parent_account", function (doc) { frm.set_query('parent_account', function(doc) {
return { return {
filters: { filters: {
is_group: 1, "is_group": 1,
company: doc.company, "company": doc.company
}, }
}; };
}); });
}, },
refresh: function (frm) { refresh: function(frm) {
frm.toggle_display("account_name", frm.is_new()); frm.toggle_display('account_name', frm.is_new());
// hide fields if group // hide fields if group
frm.toggle_display(["tax_rate"], cint(frm.doc.is_group) == 0); frm.toggle_display(['account_type', 'tax_rate'], cint(frm.doc.is_group) == 0);
// disable fields // disable fields
frm.toggle_enable(["is_group", "company"], false); frm.toggle_enable(['is_group', 'company'], false);
if (cint(frm.doc.is_group) == 0) { if (cint(frm.doc.is_group) == 0) {
frm.toggle_display( frm.toggle_display('freeze_account', frm.doc.__onload
"freeze_account", && frm.doc.__onload.can_freeze_account);
frm.doc.__onload && frm.doc.__onload.can_freeze_account
);
} }
// read-only for root accounts // read-only for root accounts
if (!frm.is_new()) { if (!frm.is_new()) {
if (!frm.doc.parent_account) { if (!frm.doc.parent_account) {
frm.set_read_only(); frm.set_read_only();
frm.set_intro( frm.set_intro(__("This is a root account and cannot be edited."));
__("This is a root account and cannot be edited.")
);
} else { } else {
// credit days and type if customer or supplier // credit days and type if customer or supplier
frm.set_intro(null); frm.set_intro(null);
frm.trigger("account_type"); frm.trigger('account_type');
// show / hide convert buttons // show / hide convert buttons
frm.trigger("add_toolbar_buttons"); frm.trigger('add_toolbar_buttons');
} }
if (frm.has_perm("write")) { if (frm.has_perm('write')) {
frm.add_custom_button( frm.add_custom_button(__('Merge Account'), function () {
__("Merge Account"), frm.trigger("merge_account");
function () { }, __('Actions'));
frm.trigger("merge_account"); frm.add_custom_button(__('Update Account Name / Number'), function () {
}, frm.trigger("update_account_number");
__("Actions") }, __('Actions'));
);
frm.add_custom_button(
__("Update Account Name / Number"),
function () {
frm.trigger("update_account_number");
},
__("Actions")
);
} }
} }
}, },
account_type: function (frm) { account_type: function (frm) {
if (frm.doc.is_group == 0) { if (frm.doc.is_group == 0) {
frm.toggle_display(["tax_rate"], frm.doc.account_type == "Tax"); frm.toggle_display(['tax_rate'], frm.doc.account_type == 'Tax');
frm.toggle_display("warehouse", frm.doc.account_type == "Stock"); frm.toggle_display('warehouse', frm.doc.account_type == 'Stock');
} }
}, },
add_toolbar_buttons: function (frm) { add_toolbar_buttons: function(frm) {
frm.add_custom_button( frm.add_custom_button(__('Chart of Accounts'), () => {
__("Chart of Accounts"), frappe.set_route("Tree", "Account");
() => { }, __('View'));
frappe.set_route("Tree", "Account");
},
__("View")
);
if (frm.doc.is_group == 1) { if (frm.doc.is_group == 1) {
frm.add_custom_button(__('Convert to Non-Group'), function () { frm.add_custom_button(__('Convert to Non-Group'), function () {
@@ -95,88 +79,84 @@ frappe.ui.form.on("Account", {
frm.add_custom_button(__('General Ledger'), function () { frm.add_custom_button(__('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": frappe.sys_defaults.year_start_date,
"to_date": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2], "to_date": frappe.sys_defaults.year_end_date,
"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'), function () {
__("Convert to Group"), return frappe.call({
function () { doc: frm.doc,
return frappe.call({ method: 'convert_ledger_to_group',
doc: frm.doc, callback: function() {
method: "convert_ledger_to_group", frm.refresh();
callback: function () { }
frm.refresh(); });
}, }, __('Actions'));
});
},
__("Actions")
);
} }
}, },
merge_account: function (frm) { merge_account: function(frm) {
var d = new frappe.ui.Dialog({ var d = new frappe.ui.Dialog({
title: __("Merge with Existing Account"), title: __('Merge with Existing Account'),
fields: [ fields: [
{ {
label: "Name", "label" : "Name",
fieldname: "name", "fieldname": "name",
fieldtype: "Data", "fieldtype": "Data",
reqd: 1, "reqd": 1,
default: frm.doc.name, "default": frm.doc.name
}, }
], ],
primary_action: function () { primary_action: function() {
var data = d.get_values(); var data = d.get_values();
frappe.call({ frappe.call({
method: "erpnext.accounts.doctype.account.account.merge_account", method: "erpnext.accounts.doctype.account.account.merge_account",
args: { args: {
old: frm.doc.name, old: frm.doc.name,
new: data.name, new: data.name,
is_group: frm.doc.is_group,
root_type: frm.doc.root_type,
company: frm.doc.company
}, },
callback: function (r) { callback: function(r) {
if (!r.exc) { if(!r.exc) {
if (r.message) { if(r.message) {
frappe.set_route("Form", "Account", r.message); frappe.set_route("Form", "Account", r.message);
} }
d.hide(); d.hide();
} }
}, }
}); });
}, },
primary_action_label: __("Merge"), primary_action_label: __('Merge')
}); });
d.show(); d.show();
}, },
update_account_number: function (frm) { update_account_number: function(frm) {
var d = new frappe.ui.Dialog({ var d = new frappe.ui.Dialog({
title: __("Update Account Number / Name"), title: __('Update Account Number / Name'),
fields: [ fields: [
{ {
label: "Account Name", "label": "Account Name",
fieldname: "account_name", "fieldname": "account_name",
fieldtype: "Data", "fieldtype": "Data",
reqd: 1, "reqd": 1,
default: frm.doc.account_name, "default": frm.doc.account_name
}, },
{ {
label: "Account Number", "label": "Account Number",
fieldname: "account_number", "fieldname": "account_number",
fieldtype: "Data", "fieldtype": "Data",
default: frm.doc.account_number, "default": frm.doc.account_number
}, }
], ],
primary_action: function () { primary_action: function() {
var data = d.get_values(); var data = d.get_values();
if ( if(data.account_number === frm.doc.account_number && data.account_name === frm.doc.account_name) {
data.account_number === frm.doc.account_number &&
data.account_name === frm.doc.account_name
) {
d.hide(); d.hide();
return; return;
} }
@@ -186,29 +166,23 @@ frappe.ui.form.on("Account", {
args: { args: {
account_number: data.account_number, account_number: data.account_number,
account_name: data.account_name, account_name: data.account_name,
name: frm.doc.name, name: frm.doc.name
}, },
callback: function (r) { callback: function(r) {
if (!r.exc) { if(!r.exc) {
if (r.message) { if(r.message) {
frappe.set_route("Form", "Account", r.message); frappe.set_route("Form", "Account", r.message);
} else { } else {
frm.set_value( frm.set_value("account_number", data.account_number);
"account_number", frm.set_value("account_name", data.account_name);
data.account_number
);
frm.set_value(
"account_name",
data.account_name
);
} }
d.hide(); d.hide();
} }
}, }
}); });
}, },
primary_action_label: __("Update"), primary_action_label: __('Update')
}); });
d.show(); d.show();
}, }
}); });

View File

@@ -18,6 +18,7 @@
"root_type", "root_type",
"report_type", "report_type",
"account_currency", "account_currency",
"inter_company_account",
"column_break1", "column_break1",
"parent_account", "parent_account",
"account_type", "account_type",
@@ -33,11 +34,15 @@
{ {
"fieldname": "properties", "fieldname": "properties",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"oldfieldtype": "Section Break" "oldfieldtype": "Section Break",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "column_break0", "fieldname": "column_break0",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1,
"width": "50%" "width": "50%"
}, },
{ {
@@ -48,7 +53,9 @@
"no_copy": 1, "no_copy": 1,
"oldfieldname": "account_name", "oldfieldname": "account_name",
"oldfieldtype": "Data", "oldfieldtype": "Data",
"reqd": 1 "reqd": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "account_number", "fieldname": "account_number",
@@ -56,13 +63,17 @@
"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 "read_only": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"default": "0", "default": "0",
"fieldname": "is_group", "fieldname": "is_group",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Is Group" "label": "Is Group",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "company", "fieldname": "company",
@@ -74,7 +85,9 @@
"options": "Company", "options": "Company",
"read_only": 1, "read_only": 1,
"remember_last_selected_value": 1, "remember_last_selected_value": 1,
"reqd": 1 "reqd": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "root_type", "fieldname": "root_type",
@@ -82,7 +95,9 @@
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Root Type", "label": "Root Type",
"options": "\nAsset\nLiability\nIncome\nExpense\nEquity", "options": "\nAsset\nLiability\nIncome\nExpense\nEquity",
"read_only": 1 "read_only": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "report_type", "fieldname": "report_type",
@@ -90,18 +105,32 @@
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Report Type", "label": "Report Type",
"options": "\nBalance Sheet\nProfit and Loss", "options": "\nBalance Sheet\nProfit and Loss",
"read_only": 1 "read_only": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"depends_on": "eval:doc.is_group==0", "depends_on": "eval:doc.is_group==0",
"fieldname": "account_currency", "fieldname": "account_currency",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Currency", "label": "Currency",
"options": "Currency" "options": "Currency",
"show_days": 1,
"show_seconds": 1
},
{
"default": "0",
"fieldname": "inter_company_account",
"fieldtype": "Check",
"label": "Inter Company Account",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "column_break1", "fieldname": "column_break1",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1,
"width": "50%" "width": "50%"
}, },
{ {
@@ -113,7 +142,9 @@
"oldfieldtype": "Link", "oldfieldtype": "Link",
"options": "Account", "options": "Account",
"reqd": 1, "reqd": 1,
"search_index": 1 "search_index": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"description": "Setting Account Type helps in selecting this Account in transactions.", "description": "Setting Account Type helps in selecting this Account in transactions.",
@@ -123,7 +154,9 @@
"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\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\nDepreciation\nEquity\nExpense Account\nExpenses Included In Asset Valuation\nExpenses Included In Valuation\nFixed Asset\nIncome Account\nPayable\nReceivable\nRound Off\nStock\nStock Adjustment\nStock Received But Not Billed\nService Received But Not Billed\nTax\nTemporary",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"description": "Rate at which this tax is applied", "description": "Rate at which this tax is applied",
@@ -131,7 +164,9 @@
"fieldtype": "Float", "fieldtype": "Float",
"label": "Rate", "label": "Rate",
"oldfieldname": "tax_rate", "oldfieldname": "tax_rate",
"oldfieldtype": "Currency" "oldfieldtype": "Currency",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"description": "If the account is frozen, entries are allowed to restricted users.", "description": "If the account is frozen, entries are allowed to restricted users.",
@@ -140,13 +175,17 @@
"label": "Frozen", "label": "Frozen",
"oldfieldname": "freeze_account", "oldfieldname": "freeze_account",
"oldfieldtype": "Select", "oldfieldtype": "Select",
"options": "No\nYes" "options": "No\nYes",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "balance_must_be", "fieldname": "balance_must_be",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Balance must be", "label": "Balance must be",
"options": "\nDebit\nCredit" "options": "\nDebit\nCredit",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "lft", "fieldname": "lft",
@@ -155,7 +194,9 @@
"label": "Lft", "label": "Lft",
"print_hide": 1, "print_hide": 1,
"read_only": 1, "read_only": 1,
"search_index": 1 "search_index": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "rgt", "fieldname": "rgt",
@@ -164,7 +205,9 @@
"label": "Rgt", "label": "Rgt",
"print_hide": 1, "print_hide": 1,
"read_only": 1, "read_only": 1,
"search_index": 1 "search_index": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "old_parent", "fieldname": "old_parent",
@@ -172,27 +215,33 @@
"hidden": 1, "hidden": 1,
"label": "Old Parent", "label": "Old Parent",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"default": "0", "default": "0",
"depends_on": "eval:(doc.report_type == 'Profit and Loss' && !doc.is_group)", "depends_on": "eval:(doc.report_type == 'Profit and Loss' && !doc.is_group)",
"fieldname": "include_in_gross", "fieldname": "include_in_gross",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Include in gross" "label": "Include in gross",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"default": "0", "default": "0",
"fieldname": "disabled", "fieldname": "disabled",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Disable" "label": "Disable",
"show_days": 1,
"show_seconds": 1
} }
], ],
"icon": "fa fa-money", "icon": "fa fa-money",
"idx": 1, "idx": 1,
"is_tree": 1, "is_tree": 1,
"links": [], "links": [],
"modified": "2023-07-20 18:18:44.405723", "modified": "2020-06-11 15:15:54.338622",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Account", "name": "Account",
@@ -243,6 +292,7 @@
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Accounts Manager", "role": "Accounts Manager",
"set_user_permissions": 1,
"share": 1, "share": 1,
"write": 1 "write": 1
} }
@@ -251,6 +301,5 @@
"show_name_in_global_search": 1, "show_name_in_global_search": 1,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "ASC", "sort_order": "ASC",
"states": [],
"track_changes": 1 "track_changes": 1
} }

View File

@@ -18,70 +18,7 @@ class BalanceMismatchError(frappe.ValidationError):
pass pass
class InvalidAccountMergeError(frappe.ValidationError):
pass
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",
"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):
@@ -108,7 +45,6 @@ class Account(NestedSet):
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_root_details() self.validate_root_details()
validate_field_number("Account", self.name, self.account_number, self.company, "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()
@@ -118,21 +54,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):
if self.parent_account:
if self.account_type in [
"Direct Income",
"Indirect Income",
"Current Asset",
"Current Liability",
"Direct Expense",
"Indirect Expense",
]:
parent_account_type = frappe.db.get_value("Account", self.parent_account, ["account_type"])
if parent_account_type == self.account_type:
throw(_("Only Parent can be of type {0}").format(self.account_type))
def validate_parent(self): def validate_parent(self):
"""Fetch Parent Details and validate parent account""" """Fetch Parent Details and validate parent account"""
@@ -189,24 +110,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()
@@ -301,11 +204,8 @@ class Account(NestedSet):
) )
def validate_account_currency(self): def validate_account_currency(self):
self.currency_explicitly_specified = True
if not self.account_currency: if not self.account_currency:
self.account_currency = frappe.get_cached_value("Company", self.company, "default_currency") self.account_currency = frappe.get_cached_value("Company", self.company, "default_currency")
self.currency_explicitly_specified = False
gl_currency = frappe.db.get_value("GL Entry", {"account": self.name}, "account_currency") gl_currency = frappe.db.get_value("GL Entry", {"account": self.name}, "account_currency")
@@ -351,10 +251,8 @@ class Account(NestedSet):
{ {
"company": company, "company": company,
# parent account's currency should be passed down to child account's curreny # parent account's currency should be passed down to child account's curreny
# if currency explicitly specified by user, child will inherit. else, default currency will be used. # if it is None, it picks it up from default company currency, which might be unintended
"account_currency": self.account_currency "account_currency": erpnext.get_company_currency(company),
if self.currency_explicitly_specified
else erpnext.get_company_currency(company),
"parent_account": parent_acc_name_map[company], "parent_account": parent_acc_name_map[company],
} }
) )
@@ -496,13 +394,7 @@ def update_account_number(name, account_name, account_number=None, from_descenda
if ancestors and not allow_independent_account_creation: if ancestors and not allow_independent_account_creation:
for ancestor in ancestors: for ancestor in ancestors:
old_name = frappe.db.get_value( if frappe.db.get_value("Account", {"account_name": old_acc_name, "company": ancestor}, "name"):
"Account",
{"account_number": old_acc_number, "account_name": old_acc_name, "company": ancestor},
"name",
)
if old_name:
# same account in parent company exists # same account in parent company exists
allow_child_account_creation = _("Allow Account Creation Against Child Company") allow_child_account_creation = _("Allow Account Creation Against Child Company")
@@ -542,34 +434,25 @@ def update_account_number(name, account_name, account_number=None, from_descenda
@frappe.whitelist() @frappe.whitelist()
def merge_account(old, new): def merge_account(old, new, is_group, root_type, company):
# Validate properties before merging # Validate properties before merging
new_account = frappe.get_cached_doc("Account", new) new_account = frappe.get_cached_doc("Account", new)
old_account = frappe.get_cached_doc("Account", old)
if not new_account: if not new_account:
throw(_("Account {0} does not exist").format(new)) throw(_("Account {0} does not exist").format(new))
if ( if (new_account.is_group, new_account.root_type, new_account.company) != (
cint(new_account.is_group), cint(is_group),
new_account.root_type, root_type,
new_account.company, company,
cstr(new_account.account_currency),
) != (
cint(old_account.is_group),
old_account.root_type,
old_account.company,
cstr(old_account.account_currency),
): ):
throw( throw(
msg=_( _(
"""Merging is only possible if following properties are same in both records. Is Group, Root Type, Company and Account Currency""" """Merging is only possible if following properties are same in both records. Is Group, Root Type, Company"""
), )
title=("Invalid Accounts"),
exc=InvalidAccountMergeError,
) )
if old_account.is_group and new_account.parent_account == old: if is_group and new_account.parent_account == old:
new_account.db_set("parent_account", frappe.get_cached_value("Account", old, "parent_account")) new_account.db_set("parent_account", frappe.get_cached_value("Account", old, "parent_account"))
frappe.rename_doc("Account", old, new, merge=1, force=1) frappe.rename_doc("Account", old, new, merge=1, force=1)

View File

@@ -56,41 +56,36 @@ frappe.treeview_settings["Account"] = {
accounts = nodes; accounts = nodes;
} }
frappe.db.get_single_value("Accounts Settings", "show_balance_in_coa").then((value) => { const get_balances = frappe.call({
if(value) { method: 'erpnext.accounts.utils.get_account_balances',
args: {
accounts: accounts,
company: cur_tree.args.company
},
});
const get_balances = frappe.call({ get_balances.then(r => {
method: 'erpnext.accounts.utils.get_account_balances', if (!r.message || r.message.length == 0) return;
args: {
accounts: accounts,
company: cur_tree.args.company
},
});
get_balances.then(r => { for (let account of r.message) {
if (!r.message || r.message.length == 0) return;
for (let account of r.message) { const node = cur_tree.nodes && cur_tree.nodes[account.value];
if (!node || node.is_root) continue;
const node = cur_tree.nodes && cur_tree.nodes[account.value]; // show Dr if positive since balance is calculated as debit - credit else show Cr
if (!node || node.is_root) continue; const balance = account.balance_in_account_currency || account.balance;
const dr_or_cr = balance > 0 ? "Dr": "Cr";
const format = (value, currency) => format_currency(Math.abs(value), currency);
// show Dr if positive since balance is calculated as debit - credit else show Cr if (account.balance!==undefined) {
const balance = account.balance_in_account_currency || account.balance; node.parent && node.parent.find('.balance-area').remove();
const dr_or_cr = balance > 0 ? "Dr": "Cr"; $('<span class="balance-area pull-right">'
const format = (value, currency) => format_currency(Math.abs(value), currency); + (account.balance_in_account_currency ?
(format(account.balance_in_account_currency, account.account_currency) + " / ") : "")
if (account.balance!==undefined) { + format(account.balance, account.company_currency)
node.parent && node.parent.find('.balance-area').remove(); + " " + dr_or_cr
$('<span class="balance-area pull-right">' + '</span>').insertBefore(node.$ul);
+ (account.balance_in_account_currency ? }
(format(account.balance_in_account_currency, account.account_currency) + " / ") : "")
+ format(account.balance, account.company_currency)
+ " " + dr_or_cr
+ '</span>').insertBefore(node.$ul);
}
}
});
} }
}); });
}, },
@@ -194,8 +189,8 @@ frappe.treeview_settings["Account"] = {
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": frappe.sys_defaults.year_start_date,
"to_date": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2], "to_date": frappe.sys_defaults.year_end_date,
"company": frappe.treeview_settings['Account'].treeview.page.fields_dict.company.get_value() "company": frappe.treeview_settings['Account'].treeview.page.fields_dict.company.get_value()
}; };
frappe.set_route("query-report", "General Ledger"); frappe.set_route("query-report", "General Ledger");

View File

@@ -29,7 +29,6 @@ def create_charts(
"root_type", "root_type",
"is_group", "is_group",
"tax_rate", "tax_rate",
"account_currency",
]: ]:
account_number = cstr(child.get("account_number")).strip() account_number = cstr(child.get("account_number")).strip()
@@ -96,17 +95,7 @@ def identify_is_group(child):
is_group = child.get("is_group") is_group = child.get("is_group")
elif len( elif len(
set(child.keys()) set(child.keys())
- set( - set(["account_name", "account_type", "root_type", "is_group", "tax_rate", "account_number"])
[
"account_name",
"account_type",
"root_type",
"is_group",
"tax_rate",
"account_number",
"account_currency",
]
)
): ):
is_group = 1 is_group = 1
else: else:
@@ -196,7 +185,6 @@ def get_account_tree_from_existing_company(existing_company):
"root_type", "root_type",
"tax_rate", "tax_rate",
"account_number", "account_number",
"account_currency",
], ],
order_by="lft, rgt", order_by="lft, rgt",
) )
@@ -279,7 +267,6 @@ def build_tree_from_json(chart_template, chart_data=None, from_coa_importer=Fals
"root_type", "root_type",
"is_group", "is_group",
"tax_rate", "tax_rate",
"account_currency",
]: ]:
continue continue

View File

@@ -0,0 +1,289 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
"""
Import chart of accounts from OpenERP sources
"""
import ast
import json
import os
from xml.etree import ElementTree as ET
import frappe
from frappe.utils.csvutils import read_csv_content
path = "/Users/nabinhait/projects/odoo/addons"
accounts = {}
charts = {}
all_account_types = []
all_roots = {}
def go():
global accounts, charts
default_account_types = get_default_account_types()
country_dirs = []
for basepath, folders, files in os.walk(path):
basename = os.path.basename(basepath)
if basename.startswith("l10n_"):
country_dirs.append(basename)
for country_dir in country_dirs:
accounts, charts = {}, {}
country_path = os.path.join(path, country_dir)
manifest = ast.literal_eval(open(os.path.join(country_path, "__openerp__.py")).read())
data_files = (
manifest.get("data", []) + manifest.get("init_xml", []) + manifest.get("update_xml", [])
)
files_path = [os.path.join(country_path, d) for d in data_files]
xml_roots = get_xml_roots(files_path)
csv_content = get_csv_contents(files_path)
prefix = country_dir if csv_content else None
account_types = get_account_types(
xml_roots.get("account.account.type", []), csv_content.get("account.account.type", []), prefix
)
account_types.update(default_account_types)
if xml_roots:
make_maps_for_xml(xml_roots, account_types, country_dir)
if csv_content:
make_maps_for_csv(csv_content, account_types, country_dir)
make_account_trees()
make_charts()
create_all_roots_file()
def get_default_account_types():
default_types_root = []
default_types_root.append(
ET.parse(os.path.join(path, "account", "data", "data_account_type.xml")).getroot()
)
return get_account_types(default_types_root, None, prefix="account")
def get_xml_roots(files_path):
xml_roots = frappe._dict()
for filepath in files_path:
fname = os.path.basename(filepath)
if fname.endswith(".xml"):
tree = ET.parse(filepath)
root = tree.getroot()
for node in root[0].findall("record"):
if node.get("model") in [
"account.account.template",
"account.chart.template",
"account.account.type",
]:
xml_roots.setdefault(node.get("model"), []).append(root)
break
return xml_roots
def get_csv_contents(files_path):
csv_content = {}
for filepath in files_path:
fname = os.path.basename(filepath)
for file_type in ["account.account.template", "account.account.type", "account.chart.template"]:
if fname.startswith(file_type) and fname.endswith(".csv"):
with open(filepath, "r") as csvfile:
try:
csv_content.setdefault(file_type, []).append(read_csv_content(csvfile.read()))
except Exception as e:
continue
return csv_content
def get_account_types(root_list, csv_content, prefix=None):
types = {}
account_type_map = {
"cash": "Cash",
"bank": "Bank",
"tr_cash": "Cash",
"tr_bank": "Bank",
"receivable": "Receivable",
"tr_receivable": "Receivable",
"account rec": "Receivable",
"payable": "Payable",
"tr_payable": "Payable",
"equity": "Equity",
"stocks": "Stock",
"stock": "Stock",
"tax": "Tax",
"tr_tax": "Tax",
"tax-out": "Tax",
"tax-in": "Tax",
"charges_personnel": "Chargeable",
"fixed asset": "Fixed Asset",
"cogs": "Cost of Goods Sold",
}
for root in root_list:
for node in root[0].findall("record"):
if node.get("model") == "account.account.type":
data = {}
for field in node.findall("field"):
if (
field.get("name") == "code"
and field.text.lower() != "none"
and account_type_map.get(field.text)
):
data["account_type"] = account_type_map[field.text]
node_id = prefix + "." + node.get("id") if prefix else node.get("id")
types[node_id] = data
if csv_content and csv_content[0][0] == "id":
for row in csv_content[1:]:
row_dict = dict(zip(csv_content[0], row))
data = {}
if row_dict.get("code") and account_type_map.get(row_dict["code"]):
data["account_type"] = account_type_map[row_dict["code"]]
if data and data.get("id"):
node_id = prefix + "." + data.get("id") if prefix else data.get("id")
types[node_id] = data
return types
def make_maps_for_xml(xml_roots, account_types, country_dir):
"""make maps for `charts` and `accounts`"""
for model, root_list in xml_roots.items():
for root in root_list:
for node in root[0].findall("record"):
if node.get("model") == "account.account.template":
data = {}
for field in node.findall("field"):
if field.get("name") == "name":
data["name"] = field.text
if field.get("name") == "parent_id":
parent_id = field.get("ref") or field.get("eval")
data["parent_id"] = parent_id
if field.get("name") == "user_type":
value = field.get("ref")
if account_types.get(value, {}).get("account_type"):
data["account_type"] = account_types[value]["account_type"]
if data["account_type"] not in all_account_types:
all_account_types.append(data["account_type"])
data["children"] = []
accounts[node.get("id")] = data
if node.get("model") == "account.chart.template":
data = {}
for field in node.findall("field"):
if field.get("name") == "name":
data["name"] = field.text
if field.get("name") == "account_root_id":
data["account_root_id"] = field.get("ref")
data["id"] = country_dir
charts.setdefault(node.get("id"), {}).update(data)
def make_maps_for_csv(csv_content, account_types, country_dir):
for content in csv_content.get("account.account.template", []):
for row in content[1:]:
data = dict(zip(content[0], row))
account = {
"name": data.get("name"),
"parent_id": data.get("parent_id:id") or data.get("parent_id/id"),
"children": [],
}
user_type = data.get("user_type/id") or data.get("user_type:id")
if account_types.get(user_type, {}).get("account_type"):
account["account_type"] = account_types[user_type]["account_type"]
if account["account_type"] not in all_account_types:
all_account_types.append(account["account_type"])
accounts[data.get("id")] = account
if not account.get("parent_id") and data.get("chart_template_id:id"):
chart_id = data.get("chart_template_id:id")
charts.setdefault(chart_id, {}).update({"account_root_id": data.get("id")})
for content in csv_content.get("account.chart.template", []):
for row in content[1:]:
if row:
data = dict(zip(content[0], row))
charts.setdefault(data.get("id"), {}).update(
{
"account_root_id": data.get("account_root_id:id") or data.get("account_root_id/id"),
"name": data.get("name"),
"id": country_dir,
}
)
def make_account_trees():
"""build tree hierarchy"""
for id in accounts.keys():
account = accounts[id]
if account.get("parent_id"):
if accounts.get(account["parent_id"]):
# accounts[account["parent_id"]]["children"].append(account)
accounts[account["parent_id"]][account["name"]] = account
del account["parent_id"]
del account["name"]
# remove empty children
for id in accounts.keys():
if "children" in accounts[id] and not accounts[id].get("children"):
del accounts[id]["children"]
def make_charts():
"""write chart files in app/setup/doctype/company/charts"""
for chart_id in charts:
src = charts[chart_id]
if not src.get("name") or not src.get("account_root_id"):
continue
if not src["account_root_id"] in accounts:
continue
filename = src["id"][5:] + "_" + chart_id
print("building " + filename)
chart = {}
chart["name"] = src["name"]
chart["country_code"] = src["id"][5:]
chart["tree"] = accounts[src["account_root_id"]]
for key, val in chart["tree"].items():
if key in ["name", "parent_id"]:
chart["tree"].pop(key)
if type(val) == dict:
val["root_type"] = ""
if chart:
fpath = os.path.join(
"erpnext", "erpnext", "accounts", "doctype", "account", "chart_of_accounts", filename + ".json"
)
with open(fpath, "r") as chartfile:
old_content = chartfile.read()
if not old_content or (
json.loads(old_content).get("is_active", "No") == "No"
and json.loads(old_content).get("disabled", "No") == "No"
):
with open(fpath, "w") as chartfile:
chartfile.write(json.dumps(chart, indent=4, sort_keys=True))
all_roots.setdefault(filename, chart["tree"].keys())
def create_all_roots_file():
with open("all_roots.txt", "w") as f:
for filename, roots in sorted(all_roots.items()):
f.write(filename)
f.write("\n----------------------\n")
for r in sorted(roots):
f.write(r.encode("utf-8"))
f.write("\n")
f.write("\n\n\n")
if __name__ == "__main__":
go()

View File

@@ -26,7 +26,7 @@
"0360 Bauliche Investitionen in fremden (gepachteten) Betriebs- und Geschäftsgebäuden": {"account_type": "Fixed Asset"}, "0360 Bauliche Investitionen in fremden (gepachteten) Betriebs- und Geschäftsgebäuden": {"account_type": "Fixed Asset"},
"0370 Bauliche Investitionen in fremden (gepachteten) Wohn- und Sozialgebäuden": {"account_type": "Fixed Asset"}, "0370 Bauliche Investitionen in fremden (gepachteten) Wohn- und Sozialgebäuden": {"account_type": "Fixed Asset"},
"0390 Kumulierte Abschreibungen zu Grundstücken ": {"account_type": "Fixed Asset"}, "0390 Kumulierte Abschreibungen zu Grundstücken ": {"account_type": "Fixed Asset"},
"0400 Maschinen und Geräte ": {"account_type": "Fixed Asset"}, "0400 Maschinen und Geräte ": {"account_type": "Fixed Asset"},
"0500 Maschinenwerkzeuge ": {"account_type": "Fixed Asset"}, "0500 Maschinenwerkzeuge ": {"account_type": "Fixed Asset"},
"0510 Allgemeine Werkzeuge und Handwerkzeuge ": {"account_type": "Fixed Asset"}, "0510 Allgemeine Werkzeuge und Handwerkzeuge ": {"account_type": "Fixed Asset"},
"0520 Prototypen, Formen, Modelle ": {"account_type": "Fixed Asset"}, "0520 Prototypen, Formen, Modelle ": {"account_type": "Fixed Asset"},
@@ -65,41 +65,42 @@
"0980 Geleistete Anzahlungen auf Finanzanlagen ": {"account_type": "Fixed Asset"}, "0980 Geleistete Anzahlungen auf Finanzanlagen ": {"account_type": "Fixed Asset"},
"0990 Kumulierte Abschreibungen zu Finanzanlagen ": {"account_type": "Fixed Asset"}, "0990 Kumulierte Abschreibungen zu Finanzanlagen ": {"account_type": "Fixed Asset"},
"root_type": "Asset" "root_type": "Asset"
}, },
"Klasse 1 Aktiva: Vorr\u00e4te": { "Klasse 1 Aktiva: Vorr\u00e4te": {
"1000 Bezugsverrechnung": {"account_type": "Stock"}, "1000 Bezugsverrechnung": {"account_type": "Stock"},
"1100 Rohstoffe": {"account_type": "Stock"}, "1100 Rohstoffe": {"account_type": "Stock"},
"1200 Bezogene Teile": {"account_type": "Stock"}, "1200 Bezogene Teile": {"account_type": "Stock"},
"1300 Hilfsstoffe": {"account_type": "Stock"}, "1300 Hilfsstoffe": {"account_type": "Stock"},
"1350 Betriebsstoffe": {"account_type": "Stock"}, "1350 Betriebsstoffe": {"account_type": "Stock"},
"1360 Vorrat Energietraeger": {"account_type": "Stock"}, "1360 Vorrat Energietraeger": {"account_type": "Stock"},
"1400 Unfertige Erzeugnisse": {"account_type": "Stock"}, "1400 Unfertige Erzeugnisse": {"account_type": "Stock"},
"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"
}, },
"Klasse 3 Passiva: Verbindlichkeiten": { "Klasse 3 Passiva: Verbindlichkeiten": {
"3000 Allgemeine Verbindlichkeiten (Schuld)": {"account_type": "Payable"}, "3000 Allgemeine Verbindlichkeiten (Schuld)": {"account_type": "Payable"},
"3010 R\u00fcckstellungen f\u00fcr Pensionen": {"account_type": "Payable"}, "3010 R\u00fcckstellungen f\u00fcr Pensionen": {"account_type": "Payable"},
"3020 Steuerr\u00fcckstellungen": {"account_type": "Tax"}, "3020 Steuerr\u00fcckstellungen": {"account_type": "Tax"},
"3041 Sonstige R\u00fcckstellungen": {"account_type": "Payable"}, "3041 Sonstige R\u00fcckstellungen": {"account_type": "Payable"},
"3110 Verbindlichkeiten gegen\u00fcber Bank": {"account_type": "Payable"}, "3110 Verbindlichkeiten gegen\u00fcber Bank": {"account_type": "Payable"},
"3150 Verbindlichkeiten Darlehen": {"account_type": "Payable"}, "3150 Verbindlichkeiten Darlehen": {"account_type": "Payable"},
"3185 Verbindlichkeiten Kreditkarte": {"account_type": "Payable"}, "3185 Verbindlichkeiten Kreditkarte": {"account_type": "Payable"},
"3380 Verbindlichkeiten aus der Annahme gezogener Wechsel u. d. Ausstellungen eigener Wechsel": { "3380 Verbindlichkeiten aus der Annahme gezogener Wechsel u. d. Ausstellungen eigener Wechsel": {
"account_type": "Payable" "account_type": "Payable"
}, },
"3400 Verbindlichkeiten gegen\u00fc. verb. Untern., Verbindl. gegen\u00fc. Untern., mit denen eine Beteiligungsverh\u00e4lnis besteht": {}, "3400 Verbindlichkeiten gegen\u00fc. verb. Untern., Verbindl. gegen\u00fc. Untern., mit denen eine Beteiligungsverh\u00e4lnis besteht": {},
"3460 Verbindlichkeiten gegenueber Gesellschaftern": {"account_type": "Payable"}, "3460 Verbindlichkeiten gegenueber Gesellschaftern": {"account_type": "Payable"},
"3470 Einlagen stiller Gesellschafter": {"account_type": "Payable"}, "3470 Einlagen stiller Gesellschafter": {"account_type": "Payable"},
"3585 Verbindlichkeiten Lohnsteuer": {"account_type": "Tax"}, "3585 Verbindlichkeiten Lohnsteuer": {"account_type": "Tax"},
"3590 Verbindlichkeiten Kommunalabgaben": {"account_type": "Tax"}, "3590 Verbindlichkeiten Kommunalabgaben": {"account_type": "Tax"},
"3595 Verbindlichkeiten Dienstgeberbeitrag": {"account_type": "Tax"}, "3595 Verbindlichkeiten Dienstgeberbeitrag": {"account_type": "Tax"},
"3600 Verbindlichkeiten Sozialversicherung": {"account_type": "Payable"}, "3600 Verbindlichkeiten Sozialversicherung": {"account_type": "Payable"},
"3640 Verbindlichkeiten Loehne und Gehaelter": {"account_type": "Payable"}, "3640 Verbindlichkeiten Loehne und Gehaelter": {"account_type": "Payable"},
"3700 Sonstige Verbindlichkeiten": {"account_type": "Payable"}, "3700 Sonstige Verbindlichkeiten": {"account_type": "Payable"},
"3900 Passive Rechnungsabgrenzungsposten": {"account_type": "Payable"}, "3900 Passive Rechnungsabgrenzungsposten": {"account_type": "Payable"},
"3100 Anleihen (einschlie\u00dflich konvertibler)": {"account_type": "Payable"}, "3100 Anleihen (einschlie\u00dflich konvertibler)": {"account_type": "Payable"},
@@ -118,13 +119,13 @@
}, },
"3515 Umsatzsteuer Inland 10%": { "3515 Umsatzsteuer Inland 10%": {
"account_type": "Tax" "account_type": "Tax"
}, },
"3520 Umsatzsteuer aus i.g. Erwerb 20%": { "3520 Umsatzsteuer aus i.g. Erwerb 20%": {
"account_type": "Tax" "account_type": "Tax"
}, },
"3525 Umsatzsteuer aus i.g. Erwerb 10%": { "3525 Umsatzsteuer aus i.g. Erwerb 10%": {
"account_type": "Tax" "account_type": "Tax"
}, },
"3560 Umsatzsteuer-Evidenzkonto f\u00fcr erhaltene Anzahlungen auf Bestellungen": {}, "3560 Umsatzsteuer-Evidenzkonto f\u00fcr erhaltene Anzahlungen auf Bestellungen": {},
"3360 Verbindlichkeiten aus Lieferungen u. Leistungen EU": { "3360 Verbindlichkeiten aus Lieferungen u. Leistungen EU": {
"account_type": "Payable" "account_type": "Payable"
@@ -140,7 +141,7 @@
"account_type": "Tax" "account_type": "Tax"
}, },
"root_type": "Liability" "root_type": "Liability"
}, },
"Klasse 2 Aktiva: Umlaufverm\u00f6gen, Rechnungsabgrenzungen": { "Klasse 2 Aktiva: Umlaufverm\u00f6gen, Rechnungsabgrenzungen": {
"2030 Forderungen aus Lieferungen und Leistungen Inland (0% USt, umsatzsteuerfrei)": { "2030 Forderungen aus Lieferungen und Leistungen Inland (0% USt, umsatzsteuerfrei)": {
"account_type": "Receivable" "account_type": "Receivable"
@@ -153,7 +154,7 @@
}, },
"2040 Forderungen aus Lieferungen und Leistungen Inland (sonstiger USt-Satz)": { "2040 Forderungen aus Lieferungen und Leistungen Inland (sonstiger USt-Satz)": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
"2100 Forderungen aus Lieferungen und Leistungen EU": { "2100 Forderungen aus Lieferungen und Leistungen EU": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
@@ -191,7 +192,7 @@
"account_type": "Receivable" "account_type": "Receivable"
}, },
"2570 Einfuhrumsatzsteuer (bezahlt)": {"account_type": "Tax"}, "2570 Einfuhrumsatzsteuer (bezahlt)": {"account_type": "Tax"},
"2460 Eingeforderte aber noch nicht eingezahlte Einlagen": { "2460 Eingeforderte aber noch nicht eingezahlte Einlagen": {
"account_type": "Receivable" "account_type": "Receivable"
}, },
@@ -242,10 +243,10 @@
}, },
"2800 Guthaben bei Bank": { "2800 Guthaben bei Bank": {
"account_type": "Bank" "account_type": "Bank"
}, },
"2801 Guthaben bei Bank - Sparkonto": { "2801 Guthaben bei Bank - Sparkonto": {
"account_type": "Bank" "account_type": "Bank"
}, },
"2810 Guthaben bei Paypal": { "2810 Guthaben bei Paypal": {
"account_type": "Bank" "account_type": "Bank"
}, },
@@ -263,19 +264,19 @@
}, },
"2895 Schwebende Geldbewegugen": { "2895 Schwebende Geldbewegugen": {
"account_type": "Bank" "account_type": "Bank"
}, },
"2513 Vorsteuer Inland 5%": { "2513 Vorsteuer Inland 5%": {
"account_type": "Tax" "account_type": "Tax"
}, },
"2515 Vorsteuer Inland 20%": { "2515 Vorsteuer Inland 20%": {
"account_type": "Tax" "account_type": "Tax"
}, },
"2520 Vorsteuer aus innergemeinschaftlichem Erwerb 10%": { "2520 Vorsteuer aus innergemeinschaftlichem Erwerb 10%": {
"account_type": "Tax" "account_type": "Tax"
}, },
"2525 Vorsteuer aus innergemeinschaftlichem Erwerb 20%": { "2525 Vorsteuer aus innergemeinschaftlichem Erwerb 20%": {
"account_type": "Tax" "account_type": "Tax"
}, },
"2530 Vorsteuer \u00a719/Art 19 ( reverse charge ) ": { "2530 Vorsteuer \u00a719/Art 19 ( reverse charge ) ": {
"account_type": "Tax" "account_type": "Tax"
}, },
@@ -285,16 +286,16 @@
"root_type": "Asset" "root_type": "Asset"
}, },
"Klasse 4: Betriebliche Erträge": { "Klasse 4: Betriebliche Erträge": {
"4000 Erlöse 20 %": {"account_type": "Income Account"}, "4000 Erlöse 20 %": {"account_type": "Income Account"},
"4020 Erl\u00f6se 0 % steuerbefreit": {"account_type": "Income Account"}, "4020 Erl\u00f6se 0 % steuerbefreit": {"account_type": "Income Account"},
"4010 Erl\u00f6se 10 %": {"account_type": "Income Account"}, "4010 Erl\u00f6se 10 %": {"account_type": "Income Account"},
"4030 Erl\u00f6se 13 %": {"account_type": "Income Account"}, "4030 Erl\u00f6se 13 %": {"account_type": "Income Account"},
"4040 Erl\u00f6se 0 % innergemeinschaftliche Lieferungen": {"account_type": "Income Account"}, "4040 Erl\u00f6se 0 % innergemeinschaftliche Lieferungen": {"account_type": "Income Account"},
"4400 Erl\u00f6sreduktion 0 % steuerbefreit": {"account_type": "Expense Account"}, "4400 Erl\u00f6sreduktion 0 % steuerbefreit": {"account_type": "Expense Account"},
"4410 Erl\u00f6sreduktion 10 %": {"account_type": "Expense Account"}, "4410 Erl\u00f6sreduktion 10 %": {"account_type": "Expense Account"},
"4420 Erl\u00f6sreduktion 20 %": {"account_type": "Expense Account"}, "4420 Erl\u00f6sreduktion 20 %": {"account_type": "Expense Account"},
"4430 Erl\u00f6sreduktion 13 %": {"account_type": "Expense Account"}, "4430 Erl\u00f6sreduktion 13 %": {"account_type": "Expense Account"},
"4440 Erl\u00f6sreduktion 0 % innergemeinschaftliche Lieferungen": {"account_type": "Expense Account"}, "4440 Erl\u00f6sreduktion 0 % innergemeinschaftliche Lieferungen": {"account_type": "Expense Account"},
"4500 Ver\u00e4nderungen des Bestandes an fertigen und unfertigen Erzeugn. sowie an noch nicht abrechenbaren Leistungen": {"account_type": "Income Account"}, "4500 Ver\u00e4nderungen des Bestandes an fertigen und unfertigen Erzeugn. sowie an noch nicht abrechenbaren Leistungen": {"account_type": "Income Account"},
"4580 Aktivierte Eigenleistungen": {"account_type": "Income Account"}, "4580 Aktivierte Eigenleistungen": {"account_type": "Income Account"},
"4600 Erl\u00f6se aus dem Abgang vom Anlageverm\u00f6gen, ausgen. Finanzanlagen": {"account_type": "Income Account"}, "4600 Erl\u00f6se aus dem Abgang vom Anlageverm\u00f6gen, ausgen. Finanzanlagen": {"account_type": "Income Account"},
@@ -303,15 +304,15 @@
"4700 Ertr\u00e4ge aus der Aufl\u00f6sung von R\u00fcckstellungen": {"account_type": "Income Account"}, "4700 Ertr\u00e4ge aus der Aufl\u00f6sung von R\u00fcckstellungen": {"account_type": "Income Account"},
"4800 \u00dcbrige betriebliche Ertr\u00e4ge": {"account_type": "Income Account"}, "4800 \u00dcbrige betriebliche Ertr\u00e4ge": {"account_type": "Income Account"},
"root_type": "Income" "root_type": "Income"
}, },
"Klasse 5: Aufwand f\u00fcr Material und Leistungen": { "Klasse 5: Aufwand f\u00fcr Material und Leistungen": {
"5000 Einkauf Partnerleistungen": {"account_type": "Cost of Goods Sold"}, "5000 Einkauf Partnerleistungen": {"account_type": "Cost of Goods Sold"},
"5100 Verbrauch an Rohstoffen": {"account_type": "Cost of Goods Sold"}, "5100 Verbrauch an Rohstoffen": {"account_type": "Cost of Goods Sold"},
"5200 Verbrauch von bezogenen Fertig- und Einzelteilen": {"account_type": "Cost of Goods Sold"}, "5200 Verbrauch von bezogenen Fertig- und Einzelteilen": {"account_type": "Cost of Goods Sold"},
"5300 Verbrauch von Hilfsstoffen": {"account_type": "Cost of Goods Sold"}, "5300 Verbrauch von Hilfsstoffen": {"account_type": "Cost of Goods Sold"},
"5340 Verbrauch Verpackungsmaterial": {"account_type": "Cost of Goods Sold"}, "5340 Verbrauch Verpackungsmaterial": {"account_type": "Cost of Goods Sold"},
"5470 Verbrauch von Kleinmaterial": {"account_type": "Cost of Goods Sold"}, "5470 Verbrauch von Kleinmaterial": {"account_type": "Cost of Goods Sold"},
"5450 Verbrauch von Reinigungsmaterial": {"account_type": "Cost of Goods Sold"}, "5450 Verbrauch von Reinigungsmaterial": {"account_type": "Cost of Goods Sold"},
"5400 Verbrauch von Betriebsstoffen": {"account_type": "Cost of Goods Sold"}, "5400 Verbrauch von Betriebsstoffen": {"account_type": "Cost of Goods Sold"},
"5500 Verbrauch von Werkzeugen und anderen Erzeugungshilfsmittel": {"account_type": "Cost of Goods Sold"}, "5500 Verbrauch von Werkzeugen und anderen Erzeugungshilfsmittel": {"account_type": "Cost of Goods Sold"},
"5600 Verbrauch von Brenn- und Treibstoffen, Energie und Wasser": {"account_type": "Cost of Goods Sold"}, "5600 Verbrauch von Brenn- und Treibstoffen, Energie und Wasser": {"account_type": "Cost of Goods Sold"},
@@ -339,7 +340,7 @@
"6700 Sonstige Sozialaufwendungen": {"account_type": "Payable"}, "6700 Sonstige Sozialaufwendungen": {"account_type": "Payable"},
"6900 Aufwandsstellenrechnung Personal": {"account_type": "Payable"}, "6900 Aufwandsstellenrechnung Personal": {"account_type": "Payable"},
"root_type": "Expense" "root_type": "Expense"
}, },
"Klasse 7: Abschreibungen und sonstige betriebliche Aufwendungen": { "Klasse 7: Abschreibungen und sonstige betriebliche Aufwendungen": {
"7010 Abschreibungen auf das Anlageverm\u00f6gen (ausgenommen Finanzanlagen)": {"account_type": "Depreciation"}, "7010 Abschreibungen auf das Anlageverm\u00f6gen (ausgenommen Finanzanlagen)": {"account_type": "Depreciation"},
"7100 Sonstige Steuern und Geb\u00fchren": {"account_type": "Tax"}, "7100 Sonstige Steuern und Geb\u00fchren": {"account_type": "Tax"},
@@ -348,7 +349,7 @@
"7310 Fahrrad - Aufwand": {"account_type": "Expense Account"}, "7310 Fahrrad - Aufwand": {"account_type": "Expense Account"},
"7320 Kfz - Aufwand": {"account_type": "Expense Account"}, "7320 Kfz - Aufwand": {"account_type": "Expense Account"},
"7330 LKW - Aufwand": {"account_type": "Expense Account"}, "7330 LKW - Aufwand": {"account_type": "Expense Account"},
"7340 Lastenrad - Aufwand": {"account_type": "Expense Account"}, "7340 Lastenrad - Aufwand": {"account_type": "Expense Account"},
"7350 Reise- und Fahraufwand": {"account_type": "Expense Account"}, "7350 Reise- und Fahraufwand": {"account_type": "Expense Account"},
"7360 Tag- und N\u00e4chtigungsgelder": {"account_type": "Expense Account"}, "7360 Tag- und N\u00e4chtigungsgelder": {"account_type": "Expense Account"},
"7380 Nachrichtenaufwand": {"account_type": "Expense Account"}, "7380 Nachrichtenaufwand": {"account_type": "Expense Account"},
@@ -408,7 +409,7 @@
"8990 Gewinnabfuhr bzw. Verlust\u00fcberrechnung aus Ergebnisabf\u00fchrungsvertr\u00e4gen": {"account_type": "Expense Account"}, "8990 Gewinnabfuhr bzw. Verlust\u00fcberrechnung aus Ergebnisabf\u00fchrungsvertr\u00e4gen": {"account_type": "Expense Account"},
"8350 nicht ausgenutzte Lieferantenskonti": {"account_type": "Expense Account"}, "8350 nicht ausgenutzte Lieferantenskonti": {"account_type": "Expense Account"},
"root_type": "Income" "root_type": "Income"
}, },
"Klasse 9 Passiva: Eigenkapital, R\u00fccklagen, stille Einlagen, Abschlusskonten": { "Klasse 9 Passiva: Eigenkapital, R\u00fccklagen, stille Einlagen, Abschlusskonten": {
"9000 Gezeichnetes bzw. gewidmetes Kapital": { "9000 Gezeichnetes bzw. gewidmetes Kapital": {
"account_type": "Equity" "account_type": "Equity"
@@ -434,5 +435,5 @@
}, },
"root_type": "Equity" "root_type": "Equity"
} }
} }
} }

View File

@@ -437,20 +437,12 @@
}, },
"Sales": { "Sales": {
"Sales from Other Regions": { "Sales from Other Regions": {
"Sales from Other Region": { "Sales from Other Region": {}
"account_type": "Income Account"
}
}, },
"Sales of same region": { "Sales of same region": {
"Management Consultancy Fees 1": { "Management Consultancy Fees 1": {},
"account_type": "Income Account" "Sales Account": {},
}, "Sales of I/C": {}
"Sales Account": {
"account_type": "Income Account"
},
"Sales of I/C": {
"account_type": "Income Account"
}
} }
}, },
"root_type": "Income" "root_type": "Income"

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

@@ -1,38 +1,38 @@
{ {
"country_code": "de", "country_code": "de",
"name": "SKR03 mit Kontonummern", "name": "SKR03 mit Kontonummern",
"tree": { "tree": {
"Aktiva": { "Aktiva": {
"is_group": 1, "is_group": 1,
"root_type": "Asset", "root_type": "Asset",
"A - Anlagevermögen": { "A - Anlagevermögen": {
"is_group": 1, "is_group": 1,
"EDV-Software": { "EDV-Software": {
"account_number": "0027", "account_number": "0027",
"account_type": "Fixed Asset" "account_type": "Fixed Asset"
}, },
"Geschäftsausstattung": { "Gesch\u00e4ftsausstattung": {
"account_number": "0410", "account_number": "0410",
"account_type": "Fixed Asset" "account_type": "Fixed Asset"
}, },
"Büroeinrichtung": { "B\u00fcroeinrichtung": {
"account_number": "0420", "account_number": "0420",
"account_type": "Fixed Asset" "account_type": "Fixed Asset"
}, },
"Darlehen": { "Darlehen": {
"account_number": "0565" "account_number": "0565"
}, },
"Maschinen": { "Maschinen": {
"account_number": "0210", "account_number": "0210",
"account_type": "Fixed Asset" "account_type": "Fixed Asset"
}, },
"Betriebsausstattung": { "Betriebsausstattung": {
"account_number": "0400", "account_number": "0400",
"account_type": "Fixed Asset" "account_type": "Fixed Asset"
}, },
"Ladeneinrichtung": { "Ladeneinrichtung": {
"account_number": "0430", "account_number": "0430",
"account_type": "Fixed Asset" "account_type": "Fixed Asset"
}, },
"Accumulated Depreciation": { "Accumulated Depreciation": {
"account_type": "Accumulated Depreciation" "account_type": "Accumulated Depreciation"
@@ -53,58 +53,43 @@
}, },
"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": {
"account_number": "1590" "account_number": "1590"
}, },
"Verrechnungskonto Gewinnermittlung § 4 Abs. 3 EStG, nicht ergebniswirksam": { "Gewinnermittlung \u00a74/3 nicht Ergebniswirksam": {
"account_number": "1371" "account_number": "1371"
}, },
"Abziehbare Vorsteuer": { "Abziehbare Vorsteuer": {
"account_type": "Tax",
"is_group": 1, "is_group": 1,
"Abziehbare Vorsteuer 7 %": { "Abziehbare Vorsteuer 7%": {
"account_number": "1571", "account_number": "1571"
"account_type": "Tax",
"tax_rate": 7.0
}, },
"Abziehbare Vorsteuer 19 %": { "Abziehbare Vorsteuer 19%": {
"account_number": "1576", "account_number": "1576"
"account_type": "Tax",
"tax_rate": 19.0
}, },
"Abziehbare Vorsteuer nach § 13b UStG 19 %": { "Abziehbare Vorsteuer nach \u00a713b UStG 19%": {
"account_number": "1577", "account_number": "1577"
"account_type": "Tax", },
"tax_rate": 19.0 "Leistungen \u00a713b UStG 19% Vorsteuer, 19% Umsatzsteuer": {
"account_number": "3120"
} }
} }
}, },
"III. Wertpapiere": { "III. Wertpapiere": {
"is_group": 1, "is_group": 1
"Anteile an verbundenen Unternehmen (Umlaufvermögen)": {
"account_number": "1340"
},
"Anteile an herrschender oder mit Mehrheit beteiligter Gesellschaft": {
"account_number": "1344"
},
"Sonstige Wertpapiere": {
"account_number": "1348"
}
}, },
"IV. Kassenbestand, Bundesbankguthaben, Guthaben bei Kreditinstituten und Schecks.": { "IV. Kassenbestand, Bundesbankguthaben, Guthaben bei Kreditinstituten und Schecks.": {
"is_group": 1, "is_group": 1,
"Kasse": { "Kasse": {
"is_group": 1,
"account_type": "Cash", "account_type": "Cash",
"is_group": 1,
"Kasse": { "Kasse": {
"is_group": 1,
"account_number": "1000", "account_number": "1000",
"account_type": "Cash" "account_type": "Cash"
} }
@@ -126,21 +111,21 @@
"C - Rechnungsabgrenzungsposten": { "C - Rechnungsabgrenzungsposten": {
"is_group": 1, "is_group": 1,
"Aktive Rechnungsabgrenzung": { "Aktive Rechnungsabgrenzung": {
"account_number": "0980" "account_number": "0980"
} }
}, },
"D - Aktive latente Steuern": { "D - Aktive latente Steuern": {
"is_group": 1, "is_group": 1,
"Aktive latente Steuern": { "Aktive latente Steuern": {
"account_number": "0983" "account_number": "0983"
} }
}, },
"E - Aktiver Unterschiedsbetrag aus der Vermögensverrechnung": { "E - Aktiver Unterschiedsbetrag aus der Vermögensverrechnung": {
"is_group": 1 "is_group": 1
} }
}, },
"Passiva": { "Passiva": {
"is_group": 1, "is_group": 1,
"root_type": "Liability", "root_type": "Liability",
"A. Eigenkapital": { "A. Eigenkapital": {
"is_group": 1, "is_group": 1,
@@ -185,13 +170,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"
} }
}, },
@@ -220,32 +200,26 @@
}, },
"Umsatzsteuer": { "Umsatzsteuer": {
"is_group": 1, "is_group": 1,
"Umsatzsteuer 7 %": { "account_type": "Tax",
"account_number": "1771", "Umsatzsteuer 7%": {
"account_type": "Tax", "account_number": "1771"
"tax_rate": 7.0
}, },
"Umsatzsteuer 19 %": { "Umsatzsteuer 19%": {
"account_number": "1776", "account_number": "1776"
"account_type": "Tax",
"tax_rate": 19.0
}, },
"Umsatzsteuer-Vorauszahlung": { "Umsatzsteuer-Vorauszahlung": {
"account_number": "1780", "account_number": "1780"
"account_type": "Tax"
}, },
"Umsatzsteuer-Vorauszahlung 1/11": { "Umsatzsteuer-Vorauszahlung 1/11": {
"account_number": "1781" "account_number": "1781"
}, },
"Umsatzsteuer nach § 13b UStG 19 %": { "Umsatzsteuer \u00a7 13b UStG 19%": {
"account_number": "1787", "account_number": "1787"
"account_type": "Tax",
"tax_rate": 19.0
}, },
"Umsatzsteuer Vorjahr": { "Umsatzsteuer Vorjahr": {
"account_number": "1790" "account_number": "1790"
}, },
"Umsatzsteuer frühere Jahre": { "Umsatzsteuer fr\u00fchere Jahre": {
"account_number": "1791" "account_number": "1791"
} }
} }
@@ -260,56 +234,44 @@
"E. Passive latente Steuern": { "E. Passive latente Steuern": {
"is_group": 1 "is_group": 1
} }
}, },
"Erlöse u. Erträge 2/8": { "Erl\u00f6se u. Ertr\u00e4ge 2/8": {
"is_group": 1, "is_group": 1,
"root_type": "Income", "root_type": "Income",
"Erlöskonten 8": { "Erl\u00f6skonten 8": {
"is_group": 1, "is_group": 1,
"Erlöse": { "Erl\u00f6se": {
"account_number": "8200", "account_number": "8200",
"account_type": "Income Account" "account_type": "Income Account"
}, },
"Erlöse USt. 19 %": { "Erl\u00f6se USt. 19%": {
"account_number": "8400", "account_number": "8400",
"account_type": "Income Account" "account_type": "Income Account"
}, },
"Erlöse USt. 7 %": { "Erl\u00f6se USt. 7%": {
"account_number": "8300", "account_number": "8300",
"account_type": "Income Account" "account_type": "Income Account"
} }
}, },
"Ertragskonten 2": { "Ertragskonten 2": {
"is_group": 1, "is_group": 1,
"sonstige Zinsen und ähnliche Erträge": { "sonstige Zinsen und \u00e4hnliche Ertr\u00e4ge": {
"account_number": "2650", "account_number": "2650",
"account_type": "Income Account" "account_type": "Income Account"
}, },
"Außerordentliche Erträge": { "Au\u00dferordentliche Ertr\u00e4ge": {
"account_number": "2500", "account_number": "2500",
"account_type": "Income Account" "account_type": "Income Account"
}, },
"Sonstige Erträge": { "Sonstige Ertr\u00e4ge": {
"account_number": "2700", "account_number": "2700",
"account_type": "Income Account" "account_type": "Income Account"
} }
} }
}, },
"Aufwendungen 2/4": { "Aufwendungen 2/4": {
"is_group": 1, "is_group": 1,
"root_type": "Expense", "root_type": "Expense",
"Fremdleistungen": {
"account_number": "3100",
"account_type": "Expense Account"
},
"Fremdleistungen ohne Vorsteuer": {
"account_number": "3109",
"account_type": "Expense Account"
},
"Bauleistungen eines im Inland ansässigen Unternehmers 19 % Vorsteuer und 19 % Umsatzsteuer": {
"account_number": "3120",
"account_type": "Expense Account"
},
"Wareneingang": { "Wareneingang": {
"account_number": "3200" "account_number": "3200"
}, },
@@ -336,234 +298,234 @@
"Gegenkonto 4996-4998": { "Gegenkonto 4996-4998": {
"account_number": "4999" "account_number": "4999"
}, },
"Abschreibungen": { "Abschreibungen": {
"is_group": 1, "is_group": 1,
"Abschreibungen auf Sachanlagen (ohne AfA auf Kfz und Gebäude)": { "Abschreibungen auf Sachanlagen (ohne AfA auf Kfz und Gebäude)": {
"account_number": "4830", "account_number": "4830",
"account_type": "Accumulated Depreciation" "account_type": "Accumulated Depreciation"
}, },
"Abschreibungen auf Gebäude": { "Abschreibungen auf Gebäude": {
"account_number": "4831", "account_number": "4831",
"account_type": "Depreciation" "account_type": "Depreciation"
}, },
"Abschreibungen auf Kfz": { "Abschreibungen auf Kfz": {
"account_number": "4832", "account_number": "4832",
"account_type": "Depreciation" "account_type": "Depreciation"
}, },
"Sofortabschreibung GWG": { "Sofortabschreibung GWG": {
"account_number": "4855", "account_number": "4855",
"account_type": "Expense Account" "account_type": "Expense Account"
} }
}, },
"Kfz-Kosten": { "Kfz-Kosten": {
"is_group": 1, "is_group": 1,
"Kfz-Steuer": { "Kfz-Steuer": {
"account_number": "4510", "account_number": "4510",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Kfz-Versicherungen": { "Kfz-Versicherungen": {
"account_number": "4520", "account_number": "4520",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"laufende Kfz-Betriebskosten": { "laufende Kfz-Betriebskosten": {
"account_number": "4530", "account_number": "4530",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Kfz-Reparaturen": { "Kfz-Reparaturen": {
"account_number": "4540", "account_number": "4540",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Fremdfahrzeuge": { "Fremdfahrzeuge": {
"account_number": "4570", "account_number": "4570",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"sonstige Kfz-Kosten": { "sonstige Kfz-Kosten": {
"account_number": "4580", "account_number": "4580",
"account_type": "Expense Account" "account_type": "Expense Account"
} }
}, },
"Personalkosten": { "Personalkosten": {
"is_group": 1, "is_group": 1,
"Gehälter": { "Geh\u00e4lter": {
"account_number": "4120", "account_number": "4120",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"gesetzliche soziale Aufwendungen": { "gesetzliche soziale Aufwendungen": {
"account_number": "4130", "account_number": "4130",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Aufwendungen für Altersvorsorge": { "Aufwendungen f\u00fcr Altersvorsorge": {
"account_number": "4165", "account_number": "4165",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Vermögenswirksame Leistungen": { "Verm\u00f6genswirksame Leistungen": {
"account_number": "4170", "account_number": "4170",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Aushilfslöhne": { "Aushilfsl\u00f6hne": {
"account_number": "4190", "account_number": "4190",
"account_type": "Expense Account" "account_type": "Expense Account"
} }
}, },
"Raumkosten": { "Raumkosten": {
"is_group": 1, "is_group": 1,
"Miete und Nebenkosten": { "Miete und Nebenkosten": {
"account_number": "4210", "account_number": "4210",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Gas, Wasser, Strom (Verwaltung, Vertrieb)": { "Gas, Wasser, Strom (Verwaltung, Vertrieb)": {
"account_number": "4240", "account_number": "4240",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Reinigung": { "Reinigung": {
"account_number": "4250", "account_number": "4250",
"account_type": "Expense Account" "account_type": "Expense Account"
} }
}, },
"Reparatur/Instandhaltung": { "Reparatur/Instandhaltung": {
"is_group": 1, "is_group": 1,
"Reparaturen und Instandhaltungen von anderen Anlagen und Betriebs- und Geschäftsausstattung": { "Reparatur u. Instandh. von Anlagen/Maschinen u. Betriebs- u. Gesch\u00e4ftsausst.": {
"account_number": "4805", "account_number": "4805",
"account_type": "Expense Account" "account_type": "Expense Account"
} }
}, },
"Versicherungsbeiträge": { "Versicherungsbeitr\u00e4ge": {
"is_group": 1, "is_group": 1,
"Versicherungen": { "Versicherungen": {
"account_number": "4360", "account_number": "4360",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Beiträge": { "Beitr\u00e4ge": {
"account_number": "4380", "account_number": "4380",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"sonstige Ausgaben": { "sonstige Ausgaben": {
"account_number": "4390", "account_number": "4390",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"steuerlich abzugsfähige Verspätungszuschläge und Zwangsgelder": { "steuerlich abzugsf\u00e4hige Versp\u00e4tungszuschl\u00e4ge und Zwangsgelder": {
"account_number": "4396", "account_number": "4396",
"account_type": "Expense Account" "account_type": "Expense Account"
} }
}, },
"Werbe-/Reisekosten": { "Werbe-/Reisekosten": {
"is_group": 1, "is_group": 1,
"Werbekosten": { "Werbekosten": {
"account_number": "4610", "account_number": "4610",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Aufmerksamkeiten": { "Aufmerksamkeiten": {
"account_number": "4653", "account_number": "4653",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"nicht abzugsfähige Betriebsausg. aus Werbe-, Repräs.- u. Reisekosten": { "nicht abzugsf\u00e4hige Betriebsausg. aus Werbe-, Repr\u00e4s.- u. Reisekosten": {
"account_number": "4665", "account_number": "4665",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Reisekosten Unternehmer": { "Reisekosten Unternehmer": {
"account_number": "4670", "account_number": "4670",
"account_type": "Expense Account" "account_type": "Expense Account"
} }
}, },
"verschiedene Kosten": { "verschiedene Kosten": {
"is_group": 1, "is_group": 1,
"Porto": { "Porto": {
"account_number": "4910", "account_number": "4910",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Telekom": { "Telekom": {
"account_number": "4920", "account_number": "4920",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Mobilfunk D2": { "Mobilfunk D2": {
"account_number": "4921", "account_number": "4921",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Internet": { "Internet": {
"account_number": "4922", "account_number": "4922",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Bürobedarf": { "B\u00fcrobedarf": {
"account_number": "4930", "account_number": "4930",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Zeitschriften, Bücher": { "Zeitschriften, B\u00fccher": {
"account_number": "4940", "account_number": "4940",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Fortbildungskosten": { "Fortbildungskosten": {
"account_number": "4945", "account_number": "4945",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Buchführungskosten": { "Buchf\u00fchrungskosten": {
"account_number": "4955", "account_number": "4955",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Abschluß- u. Prüfungskosten": { "Abschlu\u00df- u. Pr\u00fcfungskosten": {
"account_number": "4957", "account_number": "4957",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Nebenkosten des Geldverkehrs": { "Nebenkosten des Geldverkehrs": {
"account_number": "4970", "account_number": "4970",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Werkzeuge und Kleingeräte": { "Werkzeuge und Kleinger\u00e4te": {
"account_number": "4985", "account_number": "4985",
"account_type": "Expense Account" "account_type": "Expense Account"
} }
}, },
"Zinsaufwendungen": { "Zinsaufwendungen": {
"is_group": 1, "is_group": 1,
"Zinsaufwendungen für kurzfristige Verbindlichkeiten": { "Zinsaufwendungen f\u00fcr kurzfristige Verbindlichkeiten": {
"account_number": "2110", "account_number": "2110",
"account_type": "Expense Account" "account_type": "Expense Account"
}, },
"Zinsaufwendungen für KFZ Finanzierung": { "Zinsaufwendungen f\u00fcr KFZ Finanzierung": {
"account_number": "2121", "account_number": "2121",
"account_type": "Expense Account" "account_type": "Expense Account"
} }
} }
}, },
"Anfangsbestand 9": { "Anfangsbestand 9": {
"is_group": 1, "is_group": 1,
"root_type": "Equity", "root_type": "Equity",
"Saldenvortragskonten": { "Saldenvortragskonten": {
"is_group": 1, "is_group": 1,
"Saldenvortrag Sachkonten": { "Saldenvortrag Sachkonten": {
"account_number": "9000" "account_number": "9000"
}, },
"Saldenvorträge Debitoren": { "Saldenvortr\u00e4ge Debitoren": {
"account_number": "9008" "account_number": "9008"
}, },
"Saldenvorträge Kreditoren": { "Saldenvortr\u00e4ge Kreditoren": {
"account_number": "9009" "account_number": "9009"
} }
} }
}, },
"Privatkonten 1": { "Privatkonten 1": {
"is_group": 1, "is_group": 1,
"root_type": "Equity", "root_type": "Equity",
"Privatentnahmen/-einlagen": { "Privatentnahmen/-einlagen": {
"is_group": 1, "is_group": 1,
"Privatentnahme allgemein": { "Privatentnahme allgemein": {
"account_number": "1800" "account_number": "1800"
}, },
"Privatsteuern": { "Privatsteuern": {
"account_number": "1810" "account_number": "1810"
}, },
"Sonderausgaben beschränkt abzugsfähig": { "Sonderausgaben beschr\u00e4nkt abzugsf\u00e4hig": {
"account_number": "1820" "account_number": "1820"
}, },
"Sonderausgaben unbeschränkt abzugsfähig": { "Sonderausgaben unbeschr\u00e4nkt abzugsf\u00e4hig": {
"account_number": "1830" "account_number": "1830"
}, },
"Außergewöhnliche Belastungen": { "Au\u00dfergew\u00f6hnliche Belastungen": {
"account_number": "1850" "account_number": "1850"
}, },
"Privateinlagen": { "Privateinlagen": {
"account_number": "1890" "account_number": "1890"
} }
} }
} }
} }
} }

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

@@ -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

@@ -69,7 +69,8 @@
"Persediaan Barang": { "Persediaan Barang": {
"Persediaan Barang": { "Persediaan Barang": {
"account_number": "1141.000", "account_number": "1141.000",
"account_type": "Stock" "account_type": "Stock",
"is_group": 1
}, },
"Uang Muka Pembelian": { "Uang Muka Pembelian": {
"Uang Muka Pembelian": { "Uang Muka Pembelian": {
@@ -669,8 +670,7 @@
}, },
"Penjualan Barang Dagangan": { "Penjualan Barang Dagangan": {
"Penjualan": { "Penjualan": {
"account_number": "4110.000", "account_number": "4110.000"
"account_type": "Income Account"
}, },
"Potongan Penjualan": { "Potongan Penjualan": {
"account_number": "4130.000" "account_number": "4130.000"

View File

@@ -109,7 +109,8 @@
} }
}, },
"INVENTARIOS": { "INVENTARIOS": {
"account_type": "Stock" "account_type": "Stock",
"is_group": 1
} }
}, },
"ACTIVO LARGO PLAZO": { "ACTIVO LARGO PLAZO": {
@@ -397,18 +398,10 @@
"INGRESOS POR SERVICIOS 1": {} "INGRESOS POR SERVICIOS 1": {}
}, },
"VENTAS": { "VENTAS": {
"VENTAS EXPORTACION": { "VENTAS EXPORTACION": {},
"account_type": "Income Account" "VENTAS INMUEBLES": {},
}, "VENTAS NACIONALES": {},
"VENTAS INMUEBLES": { "VENTAS NACIONALES AL DETAL": {}
"account_type": "Income Account"
},
"VENTAS NACIONALES": {
"account_type": "Income Account"
},
"VENTAS NACIONALES AL DETAL": {
"account_type": "Income Account"
}
} }
} }
}, },

View File

@@ -2,13 +2,75 @@
"country_code": "nl", "country_code": "nl",
"name": "Netherlands - Grootboekschema", "name": "Netherlands - Grootboekschema",
"tree": { "tree": {
"FABRIKAGEREKENINGEN": {
"is_group": 1,
"root_type": "Expense"
},
"FINANCIELE REKENINGEN, KORTLOPENDE VORDERINGEN EN SCHULDEN": { "FINANCIELE REKENINGEN, KORTLOPENDE VORDERINGEN EN SCHULDEN": {
"Bank": { "Bank": {
"RABO Bank": { "RABO Bank": {
"account_type": "Bank" "account_type": "Bank"
}, },
"account_type": "Bank" "account_type": "Bank"
}, },
"KORTLOPENDE SCHULDEN": {
"Af te dragen Btw-verlegd": {
"account_type": "Tax"
},
"Afdracht loonheffing": {},
"Btw af te dragen hoog": {
"account_type": "Tax"
},
"Btw af te dragen laag": {
"account_type": "Tax"
},
"Btw af te dragen overig": {
"account_type": "Tax"
},
"Btw oude jaren": {
"account_type": "Tax"
},
"Btw te vorderen hoog": {
"account_type": "Tax"
},
"Btw te vorderen laag": {
"account_type": "Tax"
},
"Btw te vorderen overig": {
"account_type": "Tax"
},
"Btw-afdracht": {
"account_type": "Tax"
},
"Crediteuren": {
"account_type": "Payable"
},
"Dividend": {},
"Dividendbelasting": {},
"Energiekosten 1": {},
"Investeringsaftrek": {},
"Loonheffing": {},
"Overige te betalen posten": {},
"Pensioenpremies 1": {},
"Premie WIR": {},
"Rekening-courant inkoopvereniging": {},
"Rente": {},
"Sociale lasten 1": {},
"Stock Recieved niet gefactureerd": {
"account_type": "Stock Received But Not Billed"
},
"Tanti\u00e8mes 1": {},
"Te vorderen Btw-verlegd": {
"account_type": "Tax"
},
"Telefoon/telefax 1": {},
"Termijnen onderh. werk": {},
"Vakantiedagen": {},
"Vakantiegeld 1": {},
"Vakantiezegels": {},
"Vennootschapsbelasting": {},
"Vooruit ontvangen bedr.": {}
},
"LIQUIDE MIDDELEN": { "LIQUIDE MIDDELEN": {
"ABN-AMRO bank": {}, "ABN-AMRO bank": {},
"Bankbetaalkaarten": {}, "Bankbetaalkaarten": {},
@@ -29,110 +91,6 @@
}, },
"account_type": "Cash" "account_type": "Cash"
}, },
"TUSSENREKENINGEN": {
"Betaalwijze cadeaubonnen": {
"account_type": "Cash"
},
"Betaalwijze chipknip": {
"account_type": "Cash"
},
"Betaalwijze contant": {
"account_type": "Cash"
},
"Betaalwijze pin": {
"account_type": "Cash"
},
"Inkopen Nederland hoog": {
"account_type": "Cash"
},
"Inkopen Nederland laag": {
"account_type": "Cash"
},
"Inkopen Nederland onbelast": {
"account_type": "Cash"
},
"Inkopen Nederland overig": {
"account_type": "Cash"
},
"Inkopen Nederland verlegd": {
"account_type": "Cash"
},
"Inkopen binnen EU hoog": {
"account_type": "Cash"
},
"Inkopen binnen EU laag": {
"account_type": "Cash"
},
"Inkopen binnen EU overig": {
"account_type": "Cash"
},
"Inkopen buiten EU hoog": {
"account_type": "Cash"
},
"Inkopen buiten EU laag": {
"account_type": "Cash"
},
"Inkopen buiten EU overig": {
"account_type": "Cash"
},
"Kassa 1": {
"account_type": "Cash"
},
"Kassa 2": {
"account_type": "Cash"
},
"Netto lonen": {
"account_type": "Cash"
},
"Tegenrekening Inkopen": {
"account_type": "Cash"
},
"Tussenrek. autom. betalingen": {
"account_type": "Cash"
},
"Tussenrek. autom. loonbetalingen": {
"account_type": "Cash"
},
"Tussenrek. cadeaubonbetalingen": {
"account_type": "Cash"
},
"Tussenrekening balans": {
"account_type": "Cash"
},
"Tussenrekening chipknip": {
"account_type": "Cash"
},
"Tussenrekening correcties": {
"account_type": "Cash"
},
"Tussenrekening pin": {
"account_type": "Cash"
},
"Vraagposten": {
"account_type": "Cash"
},
"VOORRAAD GRONDSTOFFEN, HULPMATERIALEN EN HANDELSGOEDEREN": {
"Emballage": {},
"Gereed product 1": {},
"Gereed product 2": {},
"Goederen 1": {},
"Goederen 2": {},
"Goederen in consignatie": {},
"Goederen onderweg": {},
"Grondstoffen 1": {},
"Grondstoffen 2": {},
"Halffabrikaten 1": {},
"Halffabrikaten 2": {},
"Hulpstoffen 1": {},
"Hulpstoffen 2": {},
"Kantoorbenodigdheden": {},
"Onderhanden werk": {},
"Verpakkingsmateriaal": {},
"Zegels": {},
"root_type": "Asset"
},
"root_type": "Asset"
},
"VORDERINGEN": { "VORDERINGEN": {
"Debiteuren": { "Debiteuren": {
"account_type": "Receivable" "account_type": "Receivable"
@@ -146,299 +104,278 @@
"Voorziening dubieuze debiteuren": {} "Voorziening dubieuze debiteuren": {}
}, },
"root_type": "Asset" "root_type": "Asset"
}, },
"KORTLOPENDE SCHULDEN": { "INDIRECTE KOSTEN": {
"Af te dragen Btw-verlegd": {
"account_type": "Tax"
},
"Afdracht loonheffing": {},
"Btw af te dragen hoog": {
"account_type": "Tax"
},
"Btw af te dragen laag": {
"account_type": "Tax"
},
"Btw af te dragen overig": {
"account_type": "Tax"
},
"Btw oude jaren": {
"account_type": "Tax"
},
"Btw te vorderen hoog": {
"account_type": "Tax"
},
"Btw te vorderen laag": {
"account_type": "Tax"
},
"Btw te vorderen overig": {
"account_type": "Tax"
},
"Btw-afdracht": {
"account_type": "Tax"
},
"Crediteuren": {
"account_type": "Payable"
},
"Dividend": {},
"Dividendbelasting": {},
"Energiekosten 1": {},
"Investeringsaftrek": {},
"Loonheffing": {},
"Overige te betalen posten": {},
"Pensioenpremies 1": {},
"Premie WIR": {},
"Rekening-courant inkoopvereniging": {},
"Rente": {},
"Sociale lasten 1": {},
"Stock Recieved niet gefactureerd": {
"account_type": "Stock Received But Not Billed"
},
"Tanti\u00e8mes 1": {},
"Te vorderen Btw-verlegd": {
"account_type": "Tax"
},
"Telefoon/telefax 1": {},
"Termijnen onderh. werk": {},
"Vakantiedagen": {},
"Vakantiegeld 1": {},
"Vakantiezegels": {},
"Vennootschapsbelasting": {},
"Vooruit ontvangen bedr.": {},
"is_group": 1,
"root_type": "Liability"
},
"FABRIKAGEREKENINGEN": {
"is_group": 1, "is_group": 1,
"root_type": "Expense", "root_type": "Expense"
"INDIRECTE KOSTEN": { },
"is_group": 1, "KOSTENREKENINGEN": {
"root_type": "Expense" "AFSCHRIJVINGEN": {
}, "Aanhangwagens": {},
"KOSTENREKENINGEN": { "Aankoopkosten": {},
"AFSCHRIJVINGEN": { "Aanloopkosten": {},
"Aanhangwagens": {}, "Auteursrechten": {},
"Aankoopkosten": {}, "Bedrijfsgebouwen": {},
"Aanloopkosten": {}, "Bedrijfsinventaris": {
"Auteursrechten": {},
"Bedrijfsgebouwen": {},
"Bedrijfsinventaris": {
"account_type": "Depreciation"
},
"Drankvergunningen": {},
"Fabrieksinventaris": {
"account_type": "Depreciation"
},
"Gebouwen": {},
"Gereedschappen": {},
"Goodwill": {},
"Grondverbetering": {},
"Heftrucks": {},
"Kantine-inventaris": {},
"Kantoorinventaris": {
"account_type": "Depreciation"
},
"Kantoormachines": {},
"Licenties": {},
"Machines 1": {},
"Magazijninventaris": {},
"Octrooien": {},
"Ontwikkelingskosten": {},
"Pachtersinvestering": {},
"Parkeerplaats": {},
"Personenauto's": {
"account_type": "Depreciation"
},
"Rijwielen en bromfietsen": {},
"Tonnagevergunningen": {},
"Verbouwingen": {},
"Vergunningen": {},
"Voorraadverschillen": {},
"Vrachtauto's": {},
"Winkels": {},
"Woon-winkelhuis": {},
"account_type": "Depreciation" "account_type": "Depreciation"
}, },
"ALGEMENE KOSTEN": { "Drankvergunningen": {},
"Accountantskosten": {}, "Fabrieksinventaris": {
"Advieskosten": {}, "account_type": "Depreciation"
"Assuranties 1": {},
"Bankkosten": {},
"Juridische kosten": {},
"Overige algemene kosten": {},
"Toev. Ass. eigen risico": {}
}, },
"BEDRIJFSKOSTEN": { "Gebouwen": {},
"Assuranties 2": {}, "Gereedschappen": {},
"Energie (krachtstroom)": {}, "Goodwill": {},
"Gereedschappen 1": {}, "Grondverbetering": {},
"Hulpmaterialen 1": {}, "Heftrucks": {},
"Huur inventaris": {}, "Kantine-inventaris": {},
"Huur machines": {}, "Kantoorinventaris": {
"Leasing invent.operational": {}, "account_type": "Depreciation"
"Leasing mach. operational": {},
"Onderhoud inventaris": {},
"Onderhoud machines": {},
"Ophalen/vervoer afval": {},
"Overige bedrijfskosten": {}
}, },
"FINANCIERINGSKOSTEN 1": { "Kantoormachines": {},
"Overige rentebaten": {}, "Licenties": {},
"Overige rentelasten": {}, "Machines 1": {},
"Rente bankkrediet": {}, "Magazijninventaris": {},
"Rente huurkoopcontracten": {}, "Octrooien": {},
"Rente hypotheek": {}, "Ontwikkelingskosten": {},
"Rente leasecontracten": {}, "Pachtersinvestering": {},
"Rente lening o/g": {}, "Parkeerplaats": {},
"Rente lening u/g": {} "Personenauto's": {
"account_type": "Depreciation"
}, },
"HUISVESTINGSKOSTEN": { "Rijwielen en bromfietsen": {},
"Assurantie onroerend goed": {}, "Tonnagevergunningen": {},
"Belastingen onr. Goed": {}, "Verbouwingen": {},
"Energiekosten": {}, "Vergunningen": {},
"Groot onderhoud onr. Goed": {}, "Voorraadverschillen": {},
"Huur": {}, "Vrachtauto's": {},
"Huurwaarde woongedeelte": {}, "Winkels": {},
"Onderhoud onroerend goed": {}, "Woon-winkelhuis": {},
"Ontvangen huren": {}, "account_type": "Depreciation"
"Overige huisvestingskosten": {}, },
"Pacht": {}, "ALGEMENE KOSTEN": {
"Schoonmaakkosten": {}, "Accountantskosten": {},
"Toevoeging egalisatieres. Groot onderhoud": {} "Advieskosten": {},
}, "Assuranties 1": {},
"KANTOORKOSTEN": { "Bankkosten": {},
"Administratiekosten": {}, "Juridische kosten": {},
"Contributies/abonnementen": {}, "Overige algemene kosten": {},
"Huur kantoorapparatuur": {}, "Toev. Ass. eigen risico": {}
"Internetaansluiting": {}, },
"Kantoorbenodigdh./drukw.": {}, "BEDRIJFSKOSTEN": {
"Onderhoud kantoorinvent.": {}, "Assuranties 2": {},
"Overige kantoorkosten": {}, "Energie (krachtstroom)": {},
"Porti": {}, "Gereedschappen 1": {},
"Telefoon/telefax": {} "Hulpmaterialen 1": {},
}, "Huur inventaris": {},
"OVERIGE BATEN EN LASTEN": { "Huur machines": {},
"Betaalde schadevergoed.": {}, "Leasing invent.operational": {},
"Boekverlies vaste activa": {}, "Leasing mach. operational": {},
"Boekwinst van vaste activa": {}, "Onderhoud inventaris": {},
"K.O. regeling OB": {}, "Onderhoud machines": {},
"Kasverschillen": {}, "Ophalen/vervoer afval": {},
"Kosten loonbelasting": {}, "Overige bedrijfskosten": {}
"Kosten omzetbelasting": {}, },
"Nadelige koersverschillen": {}, "FINANCIERINGSKOSTEN 1": {
"Naheffing bedrijfsver.": {}, "Overige rentebaten": {},
"Ontvangen schadevergoed.": {}, "Overige rentelasten": {},
"Overige baten": {}, "Rente bankkrediet": {},
"Overige lasten": {}, "Rente huurkoopcontracten": {},
"Voordelige koersverschil.": {} "Rente hypotheek": {},
}, "Rente leasecontracten": {},
"PERSONEELSKOSTEN": { "Rente lening o/g": {},
"Autokostenvergoeding": {}, "Rente lening u/g": {}
"Bedrijfskleding": {}, },
"Belastingvrije uitkeringen": {}, "HUISVESTINGSKOSTEN": {
"Bijzondere beloningen": {}, "Assurantie onroerend goed": {},
"Congressen, seminars en symposia": {}, "Belastingen onr. Goed": {},
"Gereedschapsgeld": {}, "Energiekosten": {},
"Geschenken personeel": {}, "Groot onderhoud onr. Goed": {},
"Gratificaties": {}, "Huur": {},
"Inhouding pensioenpremies": {}, "Huurwaarde woongedeelte": {},
"Inhouding sociale lasten": {}, "Onderhoud onroerend goed": {},
"Kantinekosten": {}, "Ontvangen huren": {},
"Lonen en salarissen": {}, "Overige huisvestingskosten": {},
"Loonwerk": {}, "Pacht": {},
"Managementvergoedingen": {}, "Schoonmaakkosten": {},
"Opleidingskosten": {}, "Toevoeging egalisatieres. Groot onderhoud": {}
"Oprenting stamrechtverpl.": {}, },
"Overhevelingstoeslag": {}, "KANTOORKOSTEN": {
"Overige kostenverg.": {}, "Administratiekosten": {},
"Overige personeelskosten": {}, "Contributies/abonnementen": {},
"Overige uitkeringen": {}, "Huur kantoorapparatuur": {},
"Pensioenpremies": {}, "Internetaansluiting": {},
"Provisie 1": {}, "Kantoorbenodigdh./drukw.": {},
"Reiskosten": {}, "Onderhoud kantoorinvent.": {},
"Rijwielvergoeding": {}, "Overige kantoorkosten": {},
"Sociale lasten": {}, "Porti": {},
"Tanti\u00e8mes": {}, "Telefoon/telefax": {}
"Thuiswerkers": {}, },
"Toev. Backservice pens.verpl.": {}, "OVERIGE BATEN EN LASTEN": {
"Toevoeging pensioenverpl.": {}, "Betaalde schadevergoed.": {},
"Uitkering ziekengeld": {}, "Boekverlies vaste activa": {},
"Uitzendkrachten": {}, "Boekwinst van vaste activa": {},
"Vakantiebonnen": {}, "K.O. regeling OB": {},
"Vakantiegeld": {}, "Kasverschillen": {},
"Vergoeding studiekosten": {}, "Kosten loonbelasting": {},
"Wervingskosten personeel": {} "Kosten omzetbelasting": {},
}, "Nadelige koersverschillen": {},
"VERKOOPKOSTEN": { "Naheffing bedrijfsver.": {},
"Advertenties": {}, "Ontvangen schadevergoed.": {},
"Afschrijving dubieuze deb.": {}, "Overige baten": {},
"Beurskosten": {}, "Overige lasten": {},
"Etalagekosten": {}, "Voordelige koersverschil.": {}
"Exportkosten": {}, },
"Kascorrecties": {}, "PERSONEELSKOSTEN": {
"Overige verkoopkosten": {}, "Autokostenvergoeding": {},
"Provisie": {}, "Bedrijfskleding": {},
"Reclame": {}, "Belastingvrije uitkeringen": {},
"Reis en verblijfkosten": {}, "Bijzondere beloningen": {},
"Relatiegeschenken": {}, "Congressen, seminars en symposia": {},
"Representatiekosten": {}, "Gereedschapsgeld": {},
"Uitgaande vrachten": {}, "Geschenken personeel": {},
"Veilingkosten": {}, "Gratificaties": {},
"Verpakkingsmateriaal 1": {}, "Inhouding pensioenpremies": {},
"Websitekosten": {} "Inhouding sociale lasten": {},
}, "Kantinekosten": {},
"VERVOERSKOSTEN": { "Lonen en salarissen": {},
"Assuranties auto's": {}, "Loonwerk": {},
"Brandstoffen": {}, "Managementvergoedingen": {},
"Leasing auto's": {}, "Opleidingskosten": {},
"Onderhoud personenauto's": {}, "Oprenting stamrechtverpl.": {},
"Onderhoud vrachtauto's": {}, "Overhevelingstoeslag": {},
"Overige vervoerskosten": {}, "Overige kostenverg.": {},
"Priv\u00e9-gebruik auto's": {}, "Overige personeelskosten": {},
"Wegenbelasting": {} "Overige uitkeringen": {},
}, "Pensioenpremies": {},
"VOORRAAD GEREED PRODUCT EN ONDERHANDEN WERK": { "Provisie 1": {},
"Betalingskort. crediteuren": {}, "Reiskosten": {},
"Garantiekosten": {}, "Rijwielvergoeding": {},
"Hulpmaterialen": {}, "Sociale lasten": {},
"Inkomende vrachten": { "Tanti\u00e8mes": {},
"account_type": "Expenses Included In Valuation" "Thuiswerkers": {},
}, "Toev. Backservice pens.verpl.": {},
"Inkoop import buiten EU hoog": {}, "Toevoeging pensioenverpl.": {},
"Inkoop import buiten EU laag": {}, "Uitkering ziekengeld": {},
"Inkoop import buiten EU overig": {}, "Uitzendkrachten": {},
"Inkoopbonussen": {}, "Vakantiebonnen": {},
"Inkoopkosten": {}, "Vakantiegeld": {},
"Inkoopprovisie": {}, "Vergoeding studiekosten": {},
"Inkopen BTW verlegd": {}, "Wervingskosten personeel": {}
"Inkopen EU hoog tarief": {}, },
"Inkopen EU laag tarief": {}, "VERKOOPKOSTEN": {
"Inkopen EU overig": {}, "Advertenties": {},
"Inkopen hoog": {}, "Afschrijving dubieuze deb.": {},
"Inkopen laag": {}, "Beurskosten": {},
"Inkopen nul": {}, "Etalagekosten": {},
"Inkopen overig": {}, "Exportkosten": {},
"Invoerkosten": {}, "Kascorrecties": {},
"Kosten inkoopvereniging": {}, "Overige verkoopkosten": {},
"Kostprijs omzet grondstoffen": { "Provisie": {},
"account_type": "Cost of Goods Sold" "Reclame": {},
}, "Reis en verblijfkosten": {},
"Kostprijs omzet handelsgoederen": {}, "Relatiegeschenken": {},
"Onttrekking uitgev.garantie": {}, "Representatiekosten": {},
"Priv\u00e9-gebruik goederen": {}, "Uitgaande vrachten": {},
"Stock aanpassing": { "Veilingkosten": {},
"account_type": "Stock Adjustment" "Verpakkingsmateriaal 1": {},
}, "Websitekosten": {}
"Tegenrekening inkoop": {}, },
"Toev. Voorz. incour. grondst.": {}, "VERVOERSKOSTEN": {
"Toevoeging garantieverpl.": {}, "Assuranties auto's": {},
"Toevoeging voorz. incour. handelsgoed.": {}, "Brandstoffen": {},
"Uitbesteed werk": {}, "Leasing auto's": {},
"Voorz. Incourourant grondst.": {}, "Onderhoud personenauto's": {},
"Voorz.incour. handelsgoed.": {}, "Onderhoud vrachtauto's": {},
"root_type": "Expense" "Overige vervoerskosten": {},
}, "Priv\u00e9-gebruik auto's": {},
"root_type": "Expense" "Wegenbelasting": {}
} },
"root_type": "Expense"
},
"TUSSENREKENINGEN": {
"Betaalwijze cadeaubonnen": {
"account_type": "Cash"
},
"Betaalwijze chipknip": {
"account_type": "Cash"
},
"Betaalwijze contant": {
"account_type": "Cash"
},
"Betaalwijze pin": {
"account_type": "Cash"
},
"Inkopen Nederland hoog": {
"account_type": "Cash"
},
"Inkopen Nederland laag": {
"account_type": "Cash"
},
"Inkopen Nederland onbelast": {
"account_type": "Cash"
},
"Inkopen Nederland overig": {
"account_type": "Cash"
},
"Inkopen Nederland verlegd": {
"account_type": "Cash"
},
"Inkopen binnen EU hoog": {
"account_type": "Cash"
},
"Inkopen binnen EU laag": {
"account_type": "Cash"
},
"Inkopen binnen EU overig": {
"account_type": "Cash"
},
"Inkopen buiten EU hoog": {
"account_type": "Cash"
},
"Inkopen buiten EU laag": {
"account_type": "Cash"
},
"Inkopen buiten EU overig": {
"account_type": "Cash"
},
"Kassa 1": {
"account_type": "Cash"
},
"Kassa 2": {
"account_type": "Cash"
},
"Netto lonen": {
"account_type": "Cash"
},
"Tegenrekening Inkopen": {
"account_type": "Cash"
},
"Tussenrek. autom. betalingen": {
"account_type": "Cash"
},
"Tussenrek. autom. loonbetalingen": {
"account_type": "Cash"
},
"Tussenrek. cadeaubonbetalingen": {
"account_type": "Cash"
},
"Tussenrekening balans": {
"account_type": "Cash"
},
"Tussenrekening chipknip": {
"account_type": "Cash"
},
"Tussenrekening correcties": {
"account_type": "Cash"
},
"Tussenrekening pin": {
"account_type": "Cash"
},
"Vraagposten": {
"account_type": "Cash"
},
"root_type": "Asset"
}, },
"VASTE ACTIVA, EIGEN VERMOGEN, LANGLOPEND VREEMD VERMOGEN EN VOORZIENINGEN": { "VASTE ACTIVA, EIGEN VERMOGEN, LANGLOPEND VREEMD VERMOGEN EN VOORZIENINGEN": {
"EIGEN VERMOGEN": { "EIGEN VERMOGEN": {
@@ -665,7 +602,7 @@
"account_type": "Equity" "account_type": "Equity"
} }
}, },
"root_type": "Equity" "root_type": "Asset"
}, },
"VERKOOPRESULTATEN": { "VERKOOPRESULTATEN": {
"Diensten fabric. 0% niet-EU": {}, "Diensten fabric. 0% niet-EU": {},
@@ -690,6 +627,67 @@
"Verleende Kredietbep. fabricage": {}, "Verleende Kredietbep. fabricage": {},
"Verleende Kredietbep. handel": {}, "Verleende Kredietbep. handel": {},
"root_type": "Income" "root_type": "Income"
},
"VOORRAAD GEREED PRODUCT EN ONDERHANDEN WERK": {
"Betalingskort. crediteuren": {},
"Garantiekosten": {},
"Hulpmaterialen": {},
"Inkomende vrachten": {
"account_type": "Expenses Included In Valuation"
},
"Inkoop import buiten EU hoog": {},
"Inkoop import buiten EU laag": {},
"Inkoop import buiten EU overig": {},
"Inkoopbonussen": {},
"Inkoopkosten": {},
"Inkoopprovisie": {},
"Inkopen BTW verlegd": {},
"Inkopen EU hoog tarief": {},
"Inkopen EU laag tarief": {},
"Inkopen EU overig": {},
"Inkopen hoog": {},
"Inkopen laag": {},
"Inkopen nul": {},
"Inkopen overig": {},
"Invoerkosten": {},
"Kosten inkoopvereniging": {},
"Kostprijs omzet grondstoffen": {
"account_type": "Cost of Goods Sold"
},
"Kostprijs omzet handelsgoederen": {},
"Onttrekking uitgev.garantie": {},
"Priv\u00e9-gebruik goederen": {},
"Stock aanpassing": {
"account_type": "Stock Adjustment"
},
"Tegenrekening inkoop": {},
"Toev. Voorz. incour. grondst.": {},
"Toevoeging garantieverpl.": {},
"Toevoeging voorz. incour. handelsgoed.": {},
"Uitbesteed werk": {},
"Voorz. Incourourant grondst.": {},
"Voorz.incour. handelsgoed.": {},
"root_type": "Expense"
},
"VOORRAAD GRONDSTOFFEN, HULPMATERIALEN EN HANDELSGOEDEREN": {
"Emballage": {},
"Gereed product 1": {},
"Gereed product 2": {},
"Goederen 1": {},
"Goederen 2": {},
"Goederen in consignatie": {},
"Goederen onderweg": {},
"Grondstoffen 1": {},
"Grondstoffen 2": {},
"Halffabrikaten 1": {},
"Halffabrikaten 2": {},
"Hulpstoffen 1": {},
"Hulpstoffen 2": {},
"Kantoorbenodigdheden": {},
"Onderhanden werk": {},
"Verpakkingsmateriaal": {},
"Zegels": {},
"root_type": "Asset"
} }
} }
} }

View File

@@ -5,18 +5,10 @@
import unittest import unittest
import frappe import frappe
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 merge_account, update_account_number
InvalidAccountMergeError,
merge_account,
update_account_number,
)
from erpnext.stock import get_company_default_inventory_account, get_warehouse_account from erpnext.stock import get_company_default_inventory_account, get_warehouse_account
test_dependencies = ["Company"]
class TestAccount(unittest.TestCase): class TestAccount(unittest.TestCase):
def test_rename_account(self): def test_rename_account(self):
@@ -52,53 +44,49 @@ class TestAccount(unittest.TestCase):
frappe.delete_doc("Account", "1211-11-4 - 6 - Debtors 1 - Test - - _TC") frappe.delete_doc("Account", "1211-11-4 - 6 - Debtors 1 - Test - - _TC")
def test_merge_account(self): def test_merge_account(self):
create_account( if not frappe.db.exists("Account", "Current Assets - _TC"):
account_name="Current Assets", acc = frappe.new_doc("Account")
is_group=1, acc.account_name = "Current Assets"
parent_account="Application of Funds (Assets) - _TC", acc.is_group = 1
company="_Test Company", acc.parent_account = "Application of Funds (Assets) - _TC"
) acc.company = "_Test Company"
acc.insert()
create_account( if not frappe.db.exists("Account", "Securities and Deposits - _TC"):
account_name="Securities and Deposits", acc = frappe.new_doc("Account")
is_group=1, acc.account_name = "Securities and Deposits"
parent_account="Current Assets - _TC", acc.parent_account = "Current Assets - _TC"
company="_Test Company", acc.is_group = 1
) acc.company = "_Test Company"
acc.insert()
create_account( if not frappe.db.exists("Account", "Earnest Money - _TC"):
account_name="Earnest Money", acc = frappe.new_doc("Account")
parent_account="Securities and Deposits - _TC", acc.account_name = "Earnest Money"
company="_Test Company", acc.parent_account = "Securities and Deposits - _TC"
) acc.company = "_Test Company"
acc.insert()
create_account( if not frappe.db.exists("Account", "Cash In Hand - _TC"):
account_name="Cash In Hand", acc = frappe.new_doc("Account")
is_group=1, acc.account_name = "Cash In Hand"
parent_account="Current Assets - _TC", acc.is_group = 1
company="_Test Company", acc.parent_account = "Current Assets - _TC"
) acc.company = "_Test Company"
acc.insert()
create_account( if not frappe.db.exists("Account", "Accumulated Depreciation - _TC"):
account_name="Receivable INR", acc = frappe.new_doc("Account")
parent_account="Current Assets - _TC", acc.account_name = "Accumulated Depreciation"
company="_Test Company", acc.parent_account = "Fixed Assets - _TC"
account_currency="INR", acc.company = "_Test Company"
) acc.account_type = "Accumulated Depreciation"
acc.insert()
create_account(
account_name="Receivable USD",
parent_account="Current Assets - _TC",
company="_Test Company",
account_currency="USD",
)
doc = frappe.get_doc("Account", "Securities and Deposits - _TC")
parent = frappe.db.get_value("Account", "Earnest Money - _TC", "parent_account") parent = frappe.db.get_value("Account", "Earnest Money - _TC", "parent_account")
self.assertEqual(parent, "Securities and Deposits - _TC") self.assertEqual(parent, "Securities and Deposits - _TC")
merge_account("Securities and Deposits - _TC", "Cash In Hand - _TC") merge_account(
"Securities and Deposits - _TC", "Cash In Hand - _TC", doc.is_group, doc.root_type, doc.company
)
parent = frappe.db.get_value("Account", "Earnest Money - _TC", "parent_account") parent = frappe.db.get_value("Account", "Earnest Money - _TC", "parent_account")
# Parent account of the child account changes after merging # Parent account of the child account changes after merging
@@ -107,28 +95,30 @@ class TestAccount(unittest.TestCase):
# Old account doesn't exist after merging # Old account doesn't exist after merging
self.assertFalse(frappe.db.exists("Account", "Securities and Deposits - _TC")) self.assertFalse(frappe.db.exists("Account", "Securities and Deposits - _TC"))
doc = frappe.get_doc("Account", "Current Assets - _TC")
# Raise error as is_group property doesn't match # Raise error as is_group property doesn't match
self.assertRaises( self.assertRaises(
InvalidAccountMergeError, frappe.ValidationError,
merge_account, merge_account,
"Current Assets - _TC", "Current Assets - _TC",
"Accumulated Depreciation - _TC", "Accumulated Depreciation - _TC",
doc.is_group,
doc.root_type,
doc.company,
) )
doc = frappe.get_doc("Account", "Capital Stock - _TC")
# Raise error as root_type property doesn't match # Raise error as root_type property doesn't match
self.assertRaises( self.assertRaises(
InvalidAccountMergeError, frappe.ValidationError,
merge_account, merge_account,
"Capital Stock - _TC", "Capital Stock - _TC",
"Softwares - _TC", "Softwares - _TC",
) doc.is_group,
doc.root_type,
# Raise error as currency doesn't match doc.company,
self.assertRaises(
InvalidAccountMergeError,
merge_account,
"Receivable INR - _TC",
"Receivable USD - _TC",
) )
def test_account_sync(self): def test_account_sync(self):
@@ -198,58 +188,6 @@ class TestAccount(unittest.TestCase):
frappe.delete_doc("Account", "1234 - Test Rename Sync Account - _TC4") frappe.delete_doc("Account", "1234 - Test Rename Sync Account - _TC4")
frappe.delete_doc("Account", "1234 - Test Rename Sync Account - _TC5") frappe.delete_doc("Account", "1234 - Test Rename Sync Account - _TC5")
def test_account_currency_sync(self):
"""
In a parent->child company setup, child should inherit parent account currency if explicitly specified.
"""
make_test_records("Company")
frappe.local.flags.pop("ignore_root_company_validation", None)
def create_bank_account():
acc = frappe.new_doc("Account")
acc.account_name = "_Test Bank JPY"
acc.parent_account = "Temporary Accounts - _TC6"
acc.company = "_Test Company 6"
return acc
acc = create_bank_account()
# Explicitly set currency
acc.account_currency = "JPY"
acc.insert()
self.assertTrue(
frappe.db.exists(
{
"doctype": "Account",
"account_name": "_Test Bank JPY",
"account_currency": "JPY",
"company": "_Test Company 7",
}
)
)
frappe.delete_doc("Account", "_Test Bank JPY - _TC6")
frappe.delete_doc("Account", "_Test Bank JPY - _TC7")
acc = create_bank_account()
# default currency is used
acc.insert()
self.assertTrue(
frappe.db.exists(
{
"doctype": "Account",
"account_name": "_Test Bank JPY",
"account_currency": "USD",
"company": "_Test Company 7",
}
)
)
frappe.delete_doc("Account", "_Test Bank JPY - _TC6")
frappe.delete_doc("Account", "_Test Bank JPY - _TC7")
def test_child_company_account_rename_sync(self): def test_child_company_account_rename_sync(self):
frappe.local.flags.pop("ignore_root_company_validation", None) frappe.local.flags.pop("ignore_root_company_validation", None)
@@ -325,19 +263,6 @@ class TestAccount(unittest.TestCase):
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
@@ -372,7 +297,7 @@ def _make_test_records(verbose=None):
# fixed asset depreciation # fixed asset depreciation
["_Test Fixed Asset", "Current Assets", 0, "Fixed Asset", None], ["_Test Fixed Asset", "Current Assets", 0, "Fixed Asset", None],
["_Test Accumulated Depreciations", "Current Assets", 0, "Accumulated Depreciation", None], ["_Test Accumulated Depreciations", "Current Assets", 0, "Accumulated Depreciation", None],
["_Test Depreciations", "Expenses", 0, "Depreciation", None], ["_Test Depreciations", "Expenses", 0, None, None],
["_Test Gain/Loss on Asset Disposal", "Expenses", 0, None, None], ["_Test Gain/Loss on Asset Disposal", "Expenses", 0, None, None],
# Receivable / Payable Account # Receivable / Payable Account
["_Test Receivable", "Current Assets", 0, "Receivable", None], ["_Test Receivable", "Current Assets", 0, "Receivable", None],
@@ -420,20 +345,11 @@ def create_account(**kwargs):
"Account", filters={"account_name": kwargs.get("account_name"), "company": kwargs.get("company")} "Account", filters={"account_name": kwargs.get("account_name"), "company": kwargs.get("company")}
) )
if account: if account:
account = frappe.get_doc("Account", account) return account
account.update(
dict(
is_group=kwargs.get("is_group", 0),
parent_account=kwargs.get("parent_account"),
)
)
account.save()
return account.name
else: else:
account = frappe.get_doc( account = frappe.get_doc(
dict( dict(
doctype="Account", doctype="Account",
is_group=kwargs.get("is_group", 0),
account_name=kwargs.get("account_name"), account_name=kwargs.get("account_name"),
account_type=kwargs.get("account_type"), account_type=kwargs.get("account_type"),
parent_account=kwargs.get("parent_account"), parent_account=kwargs.get("parent_account"),

View File

@@ -1,8 +0,0 @@
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
// frappe.ui.form.on("Account Closing Balance", {
// refresh(frm) {
// },
// });

View File

@@ -1,164 +0,0 @@
{
"actions": [],
"creation": "2023-02-21 15:20:59.586811",
"default_view": "List",
"doctype": "DocType",
"document_type": "Document",
"engine": "InnoDB",
"field_order": [
"closing_date",
"account",
"cost_center",
"debit",
"credit",
"account_currency",
"debit_in_account_currency",
"credit_in_account_currency",
"project",
"company",
"finance_book",
"period_closing_voucher",
"is_period_closing_voucher_entry"
],
"fields": [
{
"fieldname": "closing_date",
"fieldtype": "Date",
"in_filter": 1,
"in_list_view": 1,
"label": "Closing Date",
"oldfieldname": "posting_date",
"oldfieldtype": "Date",
"search_index": 1
},
{
"fieldname": "account",
"fieldtype": "Link",
"in_filter": 1,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Account",
"oldfieldname": "account",
"oldfieldtype": "Link",
"options": "Account",
"search_index": 1
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
"in_filter": 1,
"in_list_view": 1,
"label": "Cost Center",
"oldfieldname": "cost_center",
"oldfieldtype": "Link",
"options": "Cost Center"
},
{
"fieldname": "debit",
"fieldtype": "Currency",
"label": "Debit Amount",
"oldfieldname": "debit",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency"
},
{
"fieldname": "credit",
"fieldtype": "Currency",
"label": "Credit Amount",
"oldfieldname": "credit",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency"
},
{
"fieldname": "account_currency",
"fieldtype": "Link",
"label": "Account Currency",
"options": "Currency"
},
{
"fieldname": "debit_in_account_currency",
"fieldtype": "Currency",
"label": "Debit Amount in Account Currency",
"options": "account_currency"
},
{
"fieldname": "credit_in_account_currency",
"fieldtype": "Currency",
"label": "Credit Amount in Account Currency",
"options": "account_currency"
},
{
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"options": "Project"
},
{
"fieldname": "company",
"fieldtype": "Link",
"in_filter": 1,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Company",
"oldfieldname": "company",
"oldfieldtype": "Link",
"options": "Company",
"search_index": 1
},
{
"fieldname": "finance_book",
"fieldtype": "Link",
"label": "Finance Book",
"options": "Finance Book"
},
{
"fieldname": "period_closing_voucher",
"fieldtype": "Link",
"in_standard_filter": 1,
"label": "Period Closing Voucher",
"options": "Period Closing Voucher",
"search_index": 1
},
{
"default": "0",
"fieldname": "is_period_closing_voucher_entry",
"fieldtype": "Check",
"label": "Is Period Closing Voucher Entry"
}
],
"icon": "fa fa-list",
"in_create": 1,
"links": [],
"modified": "2023-03-06 08:56:36.393237",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Account Closing Balance",
"owner": "Administrator",
"permissions": [
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User"
},
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager"
},
{
"export": 1,
"read": 1,
"report": 1,
"role": "Auditor"
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@@ -1,150 +0,0 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe.model.document import Document
from frappe.utils import cint, cstr
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
)
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
def make_closing_entries(closing_entries, voucher_name, company, closing_date):
accounting_dimensions = get_accounting_dimensions()
previous_closing_entries = get_previous_closing_entries(
company, closing_date, accounting_dimensions
)
combined_entries = closing_entries + previous_closing_entries
merged_entries = aggregate_with_last_account_closing_balance(
combined_entries, accounting_dimensions
)
for key, value in merged_entries.items():
cle = frappe.new_doc("Account Closing Balance")
cle.update(value)
cle.update(value["dimensions"])
cle.update(
{
"period_closing_voucher": voucher_name,
"closing_date": closing_date,
}
)
cle.flags.ignore_permissions = True
cle.flags.ignore_links = True
cle.submit()
def aggregate_with_last_account_closing_balance(entries, accounting_dimensions):
merged_entries = {}
for entry in entries:
key, key_values = generate_key(entry, accounting_dimensions)
merged_entries.setdefault(
key,
{
"debit": 0,
"credit": 0,
"debit_in_account_currency": 0,
"credit_in_account_currency": 0,
},
)
merged_entries[key]["dimensions"] = key_values
merged_entries[key]["debit"] += entry.get("debit")
merged_entries[key]["credit"] += entry.get("credit")
merged_entries[key]["debit_in_account_currency"] += entry.get("debit_in_account_currency")
merged_entries[key]["credit_in_account_currency"] += entry.get("credit_in_account_currency")
return merged_entries
def generate_key(entry, accounting_dimensions):
key = [
cstr(entry.get("account")),
cstr(entry.get("account_currency")),
cstr(entry.get("cost_center")),
cstr(entry.get("project")),
cstr(entry.get("finance_book")),
cint(entry.get("is_period_closing_voucher_entry")),
]
key_values = {
"company": cstr(entry.get("company")),
"account": cstr(entry.get("account")),
"account_currency": cstr(entry.get("account_currency")),
"cost_center": cstr(entry.get("cost_center")),
"project": cstr(entry.get("project")),
"finance_book": cstr(entry.get("finance_book")),
"is_period_closing_voucher_entry": cint(entry.get("is_period_closing_voucher_entry")),
}
for dimension in accounting_dimensions:
key.append(cstr(entry.get(dimension)))
key_values[dimension] = cstr(entry.get(dimension))
return tuple(key), key_values
def get_previous_closing_entries(company, closing_date, accounting_dimensions):
entries = []
last_period_closing_voucher = frappe.db.get_all(
"Period Closing Voucher",
filters={"docstatus": 1, "company": company, "posting_date": ("<", closing_date)},
fields=["name"],
order_by="posting_date desc",
limit=1,
)
if last_period_closing_voucher:
account_closing_balance = frappe.qb.DocType("Account Closing Balance")
query = frappe.qb.from_(account_closing_balance).select(
account_closing_balance.company,
account_closing_balance.account,
account_closing_balance.account_currency,
account_closing_balance.debit,
account_closing_balance.credit,
account_closing_balance.debit_in_account_currency,
account_closing_balance.credit_in_account_currency,
account_closing_balance.cost_center,
account_closing_balance.project,
account_closing_balance.finance_book,
account_closing_balance.is_period_closing_voucher_entry,
)
for dimension in accounting_dimensions:
query = query.select(account_closing_balance[dimension])
query = query.where(
account_closing_balance.period_closing_voucher == last_period_closing_voucher[0].name
)
entries = query.run(as_dict=1)
return entries

View File

@@ -1,9 +0,0 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
# import frappe
from frappe.tests.utils import FrappeTestCase
class TestAccountClosingBalance(FrappeTestCase):
pass

View File

@@ -15,17 +15,6 @@ frappe.ui.form.on('Accounting Dimension', {
}; };
}); });
frm.set_query("offsetting_account", "dimension_defaults", function(doc, cdt, cdn) {
let d = locals[cdt][cdn];
return {
filters: {
company: d.company,
root_type: ["in", ["Asset", "Liability"]],
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);

View File

@@ -13,25 +13,6 @@ from frappe.utils import cstr
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()
@@ -58,8 +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()
self.validate_dimension_defaults()
def validate_document_type_change(self): def validate_document_type_change(self):
doctype_before_save = frappe.db.get_value("Accounting Dimension", self.name, "document_type") doctype_before_save = frappe.db.get_value("Accounting Dimension", self.name, "document_type")
if doctype_before_save != self.document_type: if doctype_before_save != self.document_type:
@@ -67,27 +46,17 @@ class AccountingDimension(Document):
message += _("Please create a new Accounting Dimension if required.") message += _("Please create a new Accounting Dimension if required.")
frappe.throw(message) frappe.throw(message)
def validate_dimension_defaults(self):
companies = []
for default in self.get("dimension_defaults"):
if default.company not in companies:
companies.append(default.company)
else:
frappe.throw(_("Company {0} is added more than once").format(frappe.bold(default.company)))
def after_insert(self): def after_insert(self):
if frappe.flags.in_test: if frappe.flags.in_test:
make_dimension_in_accounting_doctypes(doc=self) make_dimension_in_accounting_doctypes(doc=self)
else: else:
frappe.enqueue( frappe.enqueue(make_dimension_in_accounting_doctypes, doc=self, queue="long")
make_dimension_in_accounting_doctypes, doc=self, queue="long", enqueue_after_commit=True
)
def on_trash(self): def on_trash(self):
if frappe.flags.in_test: if frappe.flags.in_test:
delete_accounting_dimension(doc=self) delete_accounting_dimension(doc=self)
else: else:
frappe.enqueue(delete_accounting_dimension, doc=self, queue="long", enqueue_after_commit=True) frappe.enqueue(delete_accounting_dimension, doc=self, queue="long")
def set_fieldname_and_label(self): def set_fieldname_and_label(self):
if not self.label: if not self.label:
@@ -284,28 +253,21 @@ 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):
dimension_filters = frappe.db.sql(
c = frappe.qb.DocType("Accounting Dimension Detail") """
p = frappe.qb.DocType("Accounting Dimension") SELECT label, fieldname, document_type
dimension_filters = ( FROM `tabAccounting Dimension`
frappe.qb.from_(p) WHERE disabled = 0
.select(p.label, p.fieldname, p.document_type) """,
.where(p.disabled == 0) as_dict=1,
.run(as_dict=1)
)
default_dimensions = (
frappe.qb.from_(c)
.inner_join(p)
.on(c.parent == p.name)
.select(p.fieldname, c.company, c.default_dimension)
.run(as_dict=1)
) )
if isinstance(with_cost_center_and_project, str): default_dimensions = frappe.db.sql(
if with_cost_center_and_project.lower().strip() == "true": """SELECT p.fieldname, c.company, c.default_dimension
with_cost_center_and_project = True FROM `tabAccounting Dimension Detail` c, `tabAccounting Dimension` p
else: WHERE c.parent = p.name""",
with_cost_center_and_project = False as_dict=1,
)
if with_cost_center_and_project: if with_cost_center_and_project:
dimension_filters.extend( dimension_filters.extend(
@@ -321,30 +283,3 @@ def get_dimensions(with_cost_center_and_project=False):
default_dimensions_map[dimension.company][dimension.fieldname] = dimension.default_dimension default_dimensions_map[dimension.company][dimension.fieldname] = dimension.default_dimension
return dimension_filters, default_dimensions_map return dimension_filters, default_dimensions_map
def create_accounting_dimensions_for_doctype(doctype):
accounting_dimensions = frappe.db.get_all(
"Accounting Dimension", fields=["fieldname", "label", "document_type", "disabled"]
)
if not accounting_dimensions:
return
for d in accounting_dimensions:
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname})
if field:
continue
df = {
"fieldname": d.fieldname,
"label": d.label,
"fieldtype": "Link",
"options": d.document_type,
"insert_after": "accounting_dimensions_section",
}
create_custom_field(doctype, df, ignore_validate=True)
frappe.clear_cache(doctype=doctype)

View File

@@ -84,22 +84,12 @@ def create_dimension():
frappe.set_user("Administrator") frappe.set_user("Administrator")
if not frappe.db.exists("Accounting Dimension", {"document_type": "Department"}): if not frappe.db.exists("Accounting Dimension", {"document_type": "Department"}):
dimension = frappe.get_doc( frappe.get_doc(
{ {
"doctype": "Accounting Dimension", "doctype": "Accounting Dimension",
"document_type": "Department", "document_type": "Department",
} }
) ).insert()
dimension.append(
"dimension_defaults",
{
"company": "_Test Company",
"reference_document": "Department",
"default_dimension": "_Test Department - _TC",
},
)
dimension.insert()
dimension.save()
else: else:
dimension = frappe.get_doc("Accounting Dimension", "Department") dimension = frappe.get_doc("Accounting Dimension", "Department")
dimension.disabled = 0 dimension.disabled = 0

View File

@@ -8,10 +8,7 @@
"reference_document", "reference_document",
"default_dimension", "default_dimension",
"mandatory_for_bs", "mandatory_for_bs",
"mandatory_for_pl", "mandatory_for_pl"
"column_break_lqns",
"automatically_post_balancing_accounting_entry",
"offsetting_account"
], ],
"fields": [ "fields": [
{ {
@@ -53,23 +50,6 @@
"fieldtype": "Check", "fieldtype": "Check",
"in_list_view": 1, "in_list_view": 1,
"label": "Mandatory For Profit and Loss Account" "label": "Mandatory For Profit and Loss Account"
},
{
"default": "0",
"fieldname": "automatically_post_balancing_accounting_entry",
"fieldtype": "Check",
"label": "Automatically post balancing accounting entry"
},
{
"fieldname": "offsetting_account",
"fieldtype": "Link",
"label": "Offsetting Account",
"mandatory_depends_on": "eval: doc.automatically_post_balancing_accounting_entry",
"options": "Account"
},
{
"fieldname": "column_break_lqns",
"fieldtype": "Column Break"
} }
], ],
"istable": 1, "istable": 1,

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

@@ -68,16 +68,6 @@ frappe.ui.form.on('Accounting Dimension Filter', {
frm.refresh_field("dimensions"); frm.refresh_field("dimensions");
frm.trigger('setup_filters'); frm.trigger('setup_filters');
}, },
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.
* Hence it's not "restricted" on any value.
*/
if (!frm.doc.apply_restriction_on_values) {
frm.set_value("allow_or_restrict", "Restrict");
frm.clear_table("dimensions");
frm.refresh_field("dimensions");
}
}
}); });
frappe.ui.form.on('Allowed Dimension', { frappe.ui.form.on('Allowed Dimension', {

View File

@@ -10,7 +10,6 @@
"disabled", "disabled",
"column_break_2", "column_break_2",
"company", "company",
"apply_restriction_on_values",
"allow_or_restrict", "allow_or_restrict",
"section_break_4", "section_break_4",
"accounts", "accounts",
@@ -25,80 +24,94 @@
"fieldtype": "Select", "fieldtype": "Select",
"in_list_view": 1, "in_list_view": 1,
"label": "Accounting Dimension", "label": "Accounting Dimension",
"reqd": 1 "reqd": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "column_break_2", "fieldname": "column_break_2",
"fieldtype": "Column Break" "fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "section_break_4", "fieldname": "section_break_4",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hide_border": 1 "hide_border": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "column_break_6", "fieldname": "column_break_6",
"fieldtype": "Column Break" "fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"depends_on": "eval:doc.apply_restriction_on_values == 1;",
"fieldname": "allow_or_restrict", "fieldname": "allow_or_restrict",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Allow Or Restrict Dimension", "label": "Allow Or Restrict Dimension",
"options": "Allow\nRestrict", "options": "Allow\nRestrict",
"reqd": 1 "reqd": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "accounts", "fieldname": "accounts",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Applicable On Account", "label": "Applicable On Account",
"options": "Applicable On Account", "options": "Applicable On Account",
"reqd": 1 "reqd": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"depends_on": "eval:doc.accounting_dimension && doc.apply_restriction_on_values", "depends_on": "eval:doc.accounting_dimension",
"fieldname": "dimensions", "fieldname": "dimensions",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Applicable Dimension", "label": "Applicable Dimension",
"mandatory_depends_on": "eval:doc.apply_restriction_on_values == 1;", "options": "Allowed Dimension",
"options": "Allowed Dimension" "reqd": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"default": "0", "default": "0",
"fieldname": "disabled", "fieldname": "disabled",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Disabled" "label": "Disabled",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "company", "fieldname": "company",
"fieldtype": "Link", "fieldtype": "Link",
"label": "Company", "label": "Company",
"options": "Company", "options": "Company",
"reqd": 1 "reqd": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "dimension_filter_help", "fieldname": "dimension_filter_help",
"fieldtype": "HTML", "fieldtype": "HTML",
"label": "Dimension Filter Help" "label": "Dimension Filter Help",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "section_break_10", "fieldname": "section_break_10",
"fieldtype": "Section Break" "fieldtype": "Section Break",
}, "show_days": 1,
{ "show_seconds": 1
"default": "1",
"fieldname": "apply_restriction_on_values",
"fieldtype": "Check",
"label": "Apply restriction on dimension values"
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2023-06-07 14:59:41.869117", "modified": "2021-02-03 12:04:58.678402",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounting Dimension Filter", "name": "Accounting Dimension Filter",
"naming_rule": "Expression",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
@@ -141,6 +154,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

@@ -8,34 +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):
# 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:
self.allow_or_restrict = "Restrict"
self.set("dimensions", [])
def validate(self): def validate(self):
self.validate_applicable_accounts() self.validate_applicable_accounts()
@@ -72,12 +44,12 @@ def get_dimension_filter_map():
a.applicable_on_account, d.dimension_value, p.accounting_dimension, a.applicable_on_account, d.dimension_value, p.accounting_dimension,
p.allow_or_restrict, a.is_mandatory p.allow_or_restrict, a.is_mandatory
FROM FROM
`tabApplicable On Account` a, `tabApplicable On Account` a, `tabAllowed Dimension` d,
`tabAccounting Dimension Filter` p `tabAccounting Dimension Filter` p
LEFT JOIN `tabAllowed Dimension` d ON d.parent = p.name
WHERE WHERE
p.name = a.parent p.name = a.parent
AND p.disabled = 0 AND p.disabled = 0
AND p.name = d.parent
""", """,
as_dict=1, as_dict=1,
) )
@@ -104,5 +76,4 @@ def build_map(map_object, dimension, account, filter_value, allow_or_restrict, i
(dimension, account), (dimension, account),
{"allowed_dimensions": [], "is_mandatory": is_mandatory, "allow_or_restrict": allow_or_restrict}, {"allowed_dimensions": [], "is_mandatory": is_mandatory, "allow_or_restrict": allow_or_restrict},
) )
if filter_value: map_object[(dimension, account)]["allowed_dimensions"].append(filter_value)
map_object[(dimension, account)]["allowed_dimensions"].append(filter_value)

View File

@@ -64,7 +64,6 @@ def create_accounting_dimension_filter():
"accounting_dimension": "Cost Center", "accounting_dimension": "Cost Center",
"allow_or_restrict": "Allow", "allow_or_restrict": "Allow",
"company": "_Test Company", "company": "_Test Company",
"apply_restriction_on_values": 1,
"accounts": [ "accounts": [
{ {
"applicable_on_account": "Sales - _TC", "applicable_on_account": "Sales - _TC",
@@ -86,7 +85,6 @@ def create_accounting_dimension_filter():
"doctype": "Accounting Dimension Filter", "doctype": "Accounting Dimension Filter",
"accounting_dimension": "Department", "accounting_dimension": "Department",
"allow_or_restrict": "Allow", "allow_or_restrict": "Allow",
"apply_restriction_on_values": 1,
"company": "_Test Company", "company": "_Test Company",
"accounts": [{"applicable_on_account": "Sales - _TC", "is_mandatory": 1}], "accounts": [{"applicable_on_account": "Sales - _TC", "is_mandatory": 1}],
"dimensions": [{"accounting_dimension": "Department", "dimension_value": "Accounts - _TC"}], "dimensions": [{"accounting_dimension": "Department", "dimension_value": "Accounts - _TC"}],

View File

@@ -20,11 +20,5 @@ frappe.ui.form.on('Accounting Period', {
} }
}); });
} }
frm.set_query("document_type", "closed_documents", () => {
return {
query: "erpnext.controllers.queries.get_doctypes_for_closing",
}
});
} }
}); });

View File

@@ -11,28 +11,7 @@ class OverlapError(frappe.ValidationError):
pass pass
class ClosedAccountingPeriod(frappe.ValidationError):
pass
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()
@@ -86,42 +65,3 @@ class AccountingPeriod(Document):
"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},
) )
def validate_accounting_period_on_doc_save(doc, method=None):
if doc.doctype == "Bank Clearance":
return
elif doc.doctype == "Asset":
if doc.is_existing_asset:
return
else:
date = doc.available_for_use_date
elif doc.doctype == "Asset Repair":
date = doc.completion_date
else:
date = doc.posting_date
ap = frappe.qb.DocType("Accounting Period")
cd = frappe.qb.DocType("Closed Document")
accounting_period = (
frappe.qb.from_(ap)
.from_(cd)
.select(ap.name)
.where(
(ap.name == cd.parent)
& (ap.company == doc.company)
& (cd.closed == 1)
& (cd.document_type == doc.doctype)
& (date >= ap.start_date)
& (date <= ap.end_date)
)
).run(as_dict=1)
if accounting_period:
frappe.throw(
_("You cannot create a {0} within the closed Accounting Period {1}").format(
doc.doctype, frappe.bold(accounting_period[0]["name"])
),
ClosedAccountingPeriod,
)

View File

@@ -6,11 +6,9 @@ import unittest
import frappe import frappe
from frappe.utils import add_months, nowdate from frappe.utils import add_months, nowdate
from erpnext.accounts.doctype.accounting_period.accounting_period import ( from erpnext.accounts.doctype.accounting_period.accounting_period import OverlapError
ClosedAccountingPeriod,
OverlapError,
)
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.general_ledger import ClosedAccountingPeriod
test_dependencies = ["Item"] test_dependencies = ["Item"]
@@ -35,9 +33,9 @@ class TestAccountingPeriod(unittest.TestCase):
ap1.save() ap1.save()
doc = create_sales_invoice( doc = create_sales_invoice(
do_not_save=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC" do_not_submit=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC"
) )
self.assertRaises(ClosedAccountingPeriod, doc.save) self.assertRaises(ClosedAccountingPeriod, doc.submit)
def tearDown(self): def tearDown(self):
for d in frappe.get_all("Accounting Period"): for d in frappe.get_all("Accounting Period"):

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,
@@ -18,8 +19,8 @@
"column_break_17", "column_break_17",
"enable_common_party_accounting", "enable_common_party_accounting",
"allow_multi_currency_invoices_against_single_party_account", "allow_multi_currency_invoices_against_single_party_account",
"journals_section", "report_setting_section",
"merge_similar_account_heads", "use_custom_cash_flow",
"deferred_accounting_settings_section", "deferred_accounting_settings_section",
"book_deferred_entries_based_on", "book_deferred_entries_based_on",
"column_break_18", "column_break_18",
@@ -30,17 +31,12 @@
"determine_address_tax_category_from", "determine_address_tax_category_from",
"column_break_19", "column_break_19",
"add_taxes_from_item_tax_template", "add_taxes_from_item_tax_template",
"book_tax_discount_loss",
"round_row_wise_tax",
"print_settings", "print_settings",
"show_inclusive_tax_in_print", "show_inclusive_tax_in_print",
"show_taxes_as_table_in_print",
"column_break_12", "column_break_12",
"show_payment_schedule_in_print", "show_payment_schedule_in_print",
"currency_exchange_section", "currency_exchange_section",
"allow_stale", "allow_stale",
"section_break_jpd0",
"auto_reconcile_payments",
"stale_days", "stale_days",
"invoicing_settings_tab", "invoicing_settings_tab",
"accounts_transactions_settings_section", "accounts_transactions_settings_section",
@@ -58,19 +54,9 @@
"closing_settings_tab", "closing_settings_tab",
"period_closing_settings_section", "period_closing_settings_section",
"acc_frozen_upto", "acc_frozen_upto",
"ignore_account_closing_balance",
"column_break_25", "column_break_25",
"frozen_accounts_modifier", "frozen_accounts_modifier",
"tab_break_dpet", "report_settings_sb"
"show_balance_in_coa",
"banking_tab",
"enable_party_matching",
"enable_fuzzy_matching",
"reports_tab",
"remarks_section",
"general_ledger_remarks_length",
"column_break_lvjk",
"receivable_payable_remarks_length"
], ],
"fields": [ "fields": [
{ {
@@ -181,9 +167,20 @@
"fieldtype": "Int", "fieldtype": "Int",
"label": "Stale Days" "label": "Stale Days"
}, },
{
"fieldname": "report_settings_sb",
"fieldtype": "Section Break",
"label": "Report Settings"
},
{
"default": "0",
"description": "Only select this if you have set up the Cash Flow Mapper documents",
"fieldname": "use_custom_cash_flow",
"fieldtype": "Check",
"label": "Enable Custom Cash Flow Format"
},
{ {
"default": "0", "default": "0",
"description": "Payment Terms from orders will be fetched into the invoices as is",
"fieldname": "automatically_fetch_payment_terms", "fieldname": "automatically_fetch_payment_terms",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Automatically Fetch Payment Terms from Order" "label": "Automatically Fetch Payment Terms from Order"
@@ -339,121 +336,17 @@
"fieldtype": "Tab Break", "fieldtype": "Tab Break",
"label": "POS" "label": "POS"
}, },
{
"fieldname": "report_setting_section",
"fieldtype": "Section Break",
"label": "Report Setting"
},
{ {
"default": "0", "default": "0",
"description": "Enabling this will allow creation of multi-currency invoices against single party account in company currency", "description": "Enabling this will allow creation of multi-currency invoices against single party account in company currency",
"fieldname": "allow_multi_currency_invoices_against_single_party_account", "fieldname": "allow_multi_currency_invoices_against_single_party_account",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Allow multi-currency invoices against single party account " "label": "Allow multi-currency invoices against single party account "
},
{
"fieldname": "tab_break_dpet",
"fieldtype": "Tab Break",
"label": "Chart Of Accounts"
},
{
"default": "1",
"fieldname": "show_balance_in_coa",
"fieldtype": "Check",
"label": "Show Balances in Chart Of Accounts"
},
{
"default": "0",
"description": "Split Early Payment Discount Loss into Income and Tax Loss",
"fieldname": "book_tax_discount_loss",
"fieldtype": "Check",
"label": "Book Tax Loss on Early Payment Discount"
},
{
"fieldname": "journals_section",
"fieldtype": "Section Break",
"label": "Journals"
},
{
"default": "0",
"description": "Rows with Same Account heads will be merged on Ledger",
"fieldname": "merge_similar_account_heads",
"fieldtype": "Check",
"label": "Merge Similar Account Heads"
},
{
"fieldname": "section_break_jpd0",
"fieldtype": "Section Break",
"label": "Payment Reconciliations"
},
{
"default": "0",
"fieldname": "auto_reconcile_payments",
"fieldtype": "Check",
"label": "Auto Reconcile Payments"
},
{
"default": "0",
"fieldname": "show_taxes_as_table_in_print",
"fieldtype": "Check",
"label": "Show Taxes as Table in Print"
},
{
"fieldname": "banking_tab",
"fieldtype": "Tab Break",
"label": "Banking"
},
{
"default": "0",
"description": "Auto match and set the Party in Bank Transactions",
"fieldname": "enable_party_matching",
"fieldtype": "Check",
"label": "Enable Automatic Party Matching"
},
{
"default": "0",
"depends_on": "enable_party_matching",
"description": "Approximately match the description/party name against parties",
"fieldname": "enable_fuzzy_matching",
"fieldtype": "Check",
"label": "Enable Fuzzy Matching"
},
{
"default": "0",
"description": "Financial reports will be generated using GL Entry doctypes (should be enabled if Period Closing Voucher is not posted for all years sequentially or missing) ",
"fieldname": "ignore_account_closing_balance",
"fieldtype": "Check",
"label": "Ignore Account Closing Balance"
},
{
"default": "0",
"description": "Tax Amount will be rounded on a row(items) level",
"fieldname": "round_row_wise_tax",
"fieldtype": "Check",
"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"
} }
], ],
"icon": "icon-cog", "icon": "icon-cog",
@@ -461,7 +354,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2024-01-30 14:04:26.553554", "modified": "2022-11-27 21:49:52.538655",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounts Settings", "name": "Accounts Settings",

View File

@@ -14,78 +14,21 @@ from erpnext.stock.utils import check_pending_reposting
class AccountsSettings(Document): class AccountsSettings(Document):
# begin: auto-generated types def on_update(self):
# This code is auto-generated. Do not modify anything in this block. frappe.clear_cache()
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
check_supplier_invoice_uniqueness: 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_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() frappe.db.set_default(
clear_cache = False "add_taxes_from_item_tax_template", self.get("add_taxes_from_item_tax_template", 0)
)
if old_doc.add_taxes_from_item_tax_template != self.add_taxes_from_item_tax_template: frappe.db.set_default(
frappe.db.set_default( "enable_common_party_accounting", self.get("enable_common_party_accounting", 0)
"add_taxes_from_item_tax_template", self.get("add_taxes_from_item_tax_template", 0) )
)
clear_cache = True
if old_doc.enable_common_party_accounting != self.enable_common_party_accounting:
frappe.db.set_default(
"enable_common_party_accounting", self.get("enable_common_party_accounting", 0)
)
clear_cache = True
self.validate_stale_days() self.validate_stale_days()
self.enable_payment_schedule_in_print()
if old_doc.show_payment_schedule_in_print != self.show_payment_schedule_in_print: self.validate_pending_reposts()
self.enable_payment_schedule_in_print()
if old_doc.acc_frozen_upto != self.acc_frozen_upto:
self.validate_pending_reposts()
if clear_cache:
frappe.clear_cache()
def validate_stale_days(self): def validate_stale_days(self):
if not self.allow_stale and cint(self.stale_days) <= 0: if not self.allow_stale and cint(self.stale_days) <= 0:

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

@@ -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

@@ -8,6 +8,9 @@ frappe.ui.form.on('Bank', {
}, },
refresh: function(frm) { refresh: function(frm) {
add_fields_to_mapping_table(frm); add_fields_to_mapping_table(frm);
frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Bank' };
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) {
@@ -102,7 +105,7 @@ erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
} }
onScriptLoaded(me) { onScriptLoaded(me) {
me.linkHandler = Plaid.create({ // eslint-disable-line no-undef me.linkHandler = Plaid.create({
env: me.plaid_env, env: me.plaid_env,
token: me.token, token: me.token,
onSuccess: me.plaid_success onSuccess: me.plaid_success
@@ -115,10 +118,6 @@ erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
} }
plaid_success(token, response) { plaid_success(token, response) {
frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.update_bank_account_ids', { frappe.show_alert({ message: __('Plaid Link Updated'), indicator: 'green' });
response: response,
}).then(() => {
frappe.show_alert({ message: __('Plaid Link Updated'), indicator: 'green' });
});
} }
}; };

View File

@@ -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

@@ -13,7 +13,6 @@
"account_type", "account_type",
"account_subtype", "account_subtype",
"column_break_7", "column_break_7",
"disabled",
"is_default", "is_default",
"is_company_account", "is_company_account",
"company", "company",
@@ -200,16 +199,10 @@
"fieldtype": "Data", "fieldtype": "Data",
"in_global_search": 1, "in_global_search": 1,
"label": "Branch Code" "label": "Branch Code"
},
{
"default": "0",
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled"
} }
], ],
"links": [], "links": [],
"modified": "2023-09-22 21:31:34.763977", "modified": "2022-05-04 15:49:42.620630",
"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)
@@ -53,17 +25,6 @@ class BankAccount(Document):
def validate(self): def validate(self):
self.validate_company() self.validate_company()
self.validate_iban() self.validate_iban()
self.validate_account()
def validate_account(self):
if self.account:
if accounts := frappe.db.get_all("Bank Account", filters={"account": self.account}, 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:
@@ -109,6 +70,7 @@ def make_bank_account(doctype, docname):
return doc return doc
@frappe.whitelist()
def get_party_bank_account(party_type, party): def get_party_bank_account(party_type, party):
return frappe.db.get_value(party_type, party, "default_bank_account") return frappe.db.get_value(party_type, party, "default_bank_account")

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

@@ -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

@@ -37,11 +37,14 @@ frappe.ui.form.on("Bank Clearance", {
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.change_custom_button_type(__('Get Payment Entries'), null, 'primary'); if (frm.doc.account && frm.doc.from_date && frm.doc.to_date) {
frm.add_custom_button(__('Get Payment Entries'), () =>
frm.trigger("get_payment_entries")
);
frm.change_custom_button_type('Get Payment Entries', null, 'primary');
}
}, },
update_clearance_date: function(frm) { update_clearance_date: function(frm) {
@@ -53,8 +56,8 @@ frappe.ui.form.on("Bank Clearance", {
frm.refresh_fields(); frm.refresh_fields();
if (!frm.doc.payment_entries.length) { if (!frm.doc.payment_entries.length) {
frm.change_custom_button_type(__('Get Payment Entries'), null, 'primary'); frm.change_custom_button_type('Get Payment Entries', null, 'primary');
frm.change_custom_button_type(__('Update Clearance Date'), null, 'default'); frm.change_custom_button_type('Update Clearance Date', null, 'default');
} }
} }
}); });
@@ -72,8 +75,8 @@ frappe.ui.form.on("Bank Clearance", {
frm.trigger("update_clearance_date") frm.trigger("update_clearance_date")
); );
frm.change_custom_button_type(__('Get Payment Entries'), null, 'default'); frm.change_custom_button_type('Get Payment Entries', null, 'default');
frm.change_custom_button_type(__('Update Clearance Date'), null, 'primary'); frm.change_custom_button_type('Update Clearance Date', null, 'primary');
} }
} }
}); });

View File

@@ -7,7 +7,6 @@ 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.query_builder.custom import ConstantColumn
from frappe.utils import flt, fmt_money, getdate from frappe.utils import flt, fmt_money, getdate
from pypika import Order
import erpnext import erpnext
@@ -15,28 +14,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):
@@ -45,24 +22,159 @@ class BankClearance(Document):
if not self.account: if not self.account:
frappe.throw(_("Account is mandatory to get payment entries")) frappe.throw(_("Account is mandatory to get payment entries"))
entries = [] condition = ""
if not self.include_reconciled_entries:
condition = "and (clearance_date IS NULL or clearance_date='0000-00-00')"
# get entries from all the apps journal_entries = frappe.db.sql(
for method_name in frappe.get_hooks("get_payment_entries_for_bank_clearance"): """
entries += ( select
frappe.get_attr(method_name)( "Journal Entry" as payment_document, t1.name as payment_entry,
self.from_date, t1.cheque_no as cheque_number, t1.cheque_date,
self.to_date, sum(t2.debit_in_account_currency) as debit, sum(t2.credit_in_account_currency) as credit,
self.account, t1.posting_date, t2.against_account, t1.clearance_date, t2.account_currency
self.bank_account, from
self.include_reconciled_entries, `tabJournal Entry` t1, `tabJournal Entry Account` t2
self.include_pos_transactions, where
) t2.parent = t1.name and t2.account = %(account)s and t1.docstatus=1
or [] and t1.posting_date >= %(from)s and t1.posting_date <= %(to)s
and ifnull(t1.is_opening, 'No') = 'No' {condition}
group by t2.account, t1.name
order by t1.posting_date ASC, t1.name DESC
""".format(
condition=condition
),
{"account": self.account, "from": self.from_date, "to": self.to_date},
as_dict=1,
)
if self.bank_account:
condition += "and bank_account = %(bank_account)s"
payment_entries = frappe.db.sql(
"""
select
"Payment Entry" as payment_document, name as payment_entry,
reference_no as cheque_number, reference_date as cheque_date,
if(paid_from=%(account)s, paid_amount, 0) as credit,
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,
if(paid_to=%(account)s, paid_to_account_currency, paid_from_account_currency) as account_currency
from `tabPayment Entry`
where
(paid_from=%(account)s or paid_to=%(account)s) and docstatus=1
and posting_date >= %(from)s and posting_date <= %(to)s
{condition}
order by
posting_date ASC, name DESC
""".format(
condition=condition
),
{
"account": self.account,
"from": self.from_date,
"to": self.to_date,
"bank_account": self.bank_account,
},
as_dict=1,
)
loan_disbursement = frappe.qb.DocType("Loan Disbursement")
loan_disbursements = (
frappe.qb.from_(loan_disbursement)
.select(
ConstantColumn("Loan Disbursement").as_("payment_document"),
loan_disbursement.name.as_("payment_entry"),
loan_disbursement.disbursed_amount.as_("credit"),
ConstantColumn(0).as_("debit"),
loan_disbursement.reference_number.as_("cheque_number"),
loan_disbursement.reference_date.as_("cheque_date"),
loan_disbursement.disbursement_date.as_("posting_date"),
loan_disbursement.applicant.as_("against_account"),
)
.where(loan_disbursement.docstatus == 1)
.where(loan_disbursement.disbursement_date >= self.from_date)
.where(loan_disbursement.disbursement_date <= self.to_date)
.where(loan_disbursement.clearance_date.isnull())
.where(loan_disbursement.disbursement_account.isin([self.bank_account, self.account]))
.orderby(loan_disbursement.disbursement_date)
.orderby(loan_disbursement.name, order=frappe.qb.desc)
).run(as_dict=1)
loan_repayment = frappe.qb.DocType("Loan Repayment")
query = (
frappe.qb.from_(loan_repayment)
.select(
ConstantColumn("Loan Repayment").as_("payment_document"),
loan_repayment.name.as_("payment_entry"),
loan_repayment.amount_paid.as_("debit"),
ConstantColumn(0).as_("credit"),
loan_repayment.reference_number.as_("cheque_number"),
loan_repayment.reference_date.as_("cheque_date"),
loan_repayment.applicant.as_("against_account"),
loan_repayment.posting_date,
)
.where(loan_repayment.docstatus == 1)
.where(loan_repayment.clearance_date.isnull())
.where(loan_repayment.posting_date >= self.from_date)
.where(loan_repayment.posting_date <= self.to_date)
.where(loan_repayment.payment_account.isin([self.bank_account, self.account]))
)
if frappe.db.has_column("Loan Repayment", "repay_from_salary"):
query = query.where((loan_repayment.repay_from_salary == 0))
query = query.orderby(loan_repayment.posting_date).orderby(
loan_repayment.name, order=frappe.qb.desc
)
loan_repayments = query.run(as_dict=True)
pos_sales_invoices, pos_purchase_invoices = [], []
if self.include_pos_transactions:
pos_sales_invoices = frappe.db.sql(
"""
select
"Sales Invoice Payment" as payment_document, sip.name as payment_entry, sip.amount as debit,
si.posting_date, si.customer as against_account, sip.clearance_date,
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": self.account, "from": self.from_date, "to": self.to_date},
as_dict=1,
)
pos_purchase_invoices = frappe.db.sql(
"""
select
"Purchase Invoice" as payment_document, pi.name as payment_entry, pi.paid_amount as credit,
pi.posting_date, pi.supplier as against_account, pi.clearance_date,
account.account_currency, 0 as debit
from `tabPurchase Invoice` pi, `tabAccount` account
where
pi.cash_bank_account=%(account)s and pi.docstatus=1 and account.name = pi.cash_bank_account
and pi.posting_date >= %(from)s and pi.posting_date <= %(to)s
order by
pi.posting_date ASC, pi.name DESC
""",
{"account": self.account, "from": self.from_date, "to": self.to_date},
as_dict=1,
) )
entries = sorted( entries = sorted(
entries, list(payment_entries)
+ list(journal_entries)
+ list(pos_sales_invoices)
+ list(pos_purchase_invoices)
+ list(loan_disbursements)
+ list(loan_repayments),
key=lambda k: getdate(k["posting_date"]), key=lambda k: getdate(k["posting_date"]),
) )
@@ -115,134 +227,3 @@ class BankClearance(Document):
msgprint(_("Clearance Date updated")) msgprint(_("Clearance Date updated"))
else: else:
msgprint(_("Clearance Date not mentioned")) msgprint(_("Clearance Date not mentioned"))
def get_payment_entries_for_bank_clearance(
from_date, to_date, account, bank_account, include_reconciled_entries, include_pos_transactions
):
entries = []
condition = ""
if not include_reconciled_entries:
condition = "and (clearance_date IS NULL or clearance_date='0000-00-00')"
journal_entries = frappe.db.sql(
"""
select
"Journal Entry" as payment_document, t1.name as payment_entry,
t1.cheque_no as cheque_number, t1.cheque_date,
sum(t2.debit_in_account_currency) as debit, sum(t2.credit_in_account_currency) as credit,
t1.posting_date, t2.against_account, t1.clearance_date, t2.account_currency
from
`tabJournal Entry` t1, `tabJournal Entry Account` t2
where
t2.parent = t1.name and t2.account = %(account)s and t1.docstatus=1
and t1.posting_date >= %(from)s and t1.posting_date <= %(to)s
and ifnull(t1.is_opening, 'No') = 'No' {condition}
group by t2.account, t1.name
order by t1.posting_date ASC, t1.name DESC
""".format(
condition=condition
),
{"account": account, "from": from_date, "to": to_date},
as_dict=1,
)
if bank_account:
condition += "and bank_account = %(bank_account)s"
payment_entries = frappe.db.sql(
"""
select
"Payment Entry" as payment_document, name as payment_entry,
reference_no as cheque_number, reference_date as cheque_date,
if(paid_from=%(account)s, paid_amount + total_taxes_and_charges, 0) as credit,
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,
if(paid_to=%(account)s, paid_to_account_currency, paid_from_account_currency) as account_currency
from `tabPayment Entry`
where
(paid_from=%(account)s or paid_to=%(account)s) and docstatus=1
and posting_date >= %(from)s and posting_date <= %(to)s
{condition}
order by
posting_date ASC, name DESC
""".format(
condition=condition
),
{
"account": account,
"from": from_date,
"to": to_date,
"bank_account": bank_account,
},
as_dict=1,
)
pos_sales_invoices, pos_purchase_invoices = [], []
if include_pos_transactions:
si_payment = frappe.qb.DocType("Sales Invoice Payment")
si = frappe.qb.DocType("Sales Invoice")
acc = frappe.qb.DocType("Account")
pos_sales_invoices = (
frappe.qb.from_(si_payment)
.inner_join(si)
.on(si_payment.parent == si.name)
.inner_join(acc)
.on(si_payment.account == acc.name)
.select(
ConstantColumn("Sales Invoice").as_("payment_document"),
si.name.as_("payment_entry"),
si_payment.reference_no.as_("cheque_number"),
si_payment.amount.as_("debit"),
si.posting_date,
si.customer.as_("against_account"),
si_payment.clearance_date,
acc.account_currency,
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 = (
list(payment_entries)
+ list(journal_entries)
+ list(pos_sales_invoices)
+ list(pos_purchase_invoices)
)
return entries

View File

@@ -8,76 +8,26 @@ from frappe.utils import add_months, getdate
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.tests.utils import if_lending_app_installed, if_lending_app_not_installed from erpnext.loan_management.doctype.loan.test_loan import (
create_loan,
create_loan_accounts,
create_loan_type,
create_repayment_entry,
make_loan_disbursement_entry,
)
class TestBankClearance(unittest.TestCase): class TestBankClearance(unittest.TestCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
clear_payment_entries()
clear_loan_transactions()
make_bank_account() make_bank_account()
create_loan_accounts()
create_loan_masters()
add_transactions() add_transactions()
# Basic test case to test if bank clearance tool doesn't break # Basic test case to test if bank clearance tool doesn't break
# Detailed test can be added later # Detailed test can be added later
@if_lending_app_not_installed
def test_bank_clearance(self): def test_bank_clearance(self):
bank_clearance = frappe.get_doc("Bank Clearance")
bank_clearance.account = "_Test Bank Clearance - _TC"
bank_clearance.from_date = add_months(getdate(), -1)
bank_clearance.to_date = getdate()
bank_clearance.get_payment_entries()
self.assertEqual(len(bank_clearance.payment_entries), 1)
@if_lending_app_installed
def test_bank_clearance_with_loan(self):
from lending.loan_management.doctype.loan.test_loan import (
create_loan,
create_loan_accounts,
create_loan_product,
create_repayment_entry,
make_loan_disbursement_entry,
)
def create_loan_masters():
create_loan_product(
"Clearance Loan",
"Clearance Loan",
2000000,
13.5,
25,
0,
5,
"Cash",
"_Test Bank Clearance - _TC",
"_Test Bank Clearance - _TC",
"Loan Account - _TC",
"Interest Income Account - _TC",
"Penalty Income Account - _TC",
)
def make_loan():
loan = create_loan(
"_Test Customer",
"Clearance Loan",
280000,
"Repay Over Number of Periods",
20,
applicant_type="Customer",
)
loan.submit()
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.save()
repayment_entry.submit()
create_loan_accounts()
create_loan_masters()
make_loan()
bank_clearance = frappe.get_doc("Bank Clearance") bank_clearance = frappe.get_doc("Bank Clearance")
bank_clearance.account = "_Test Bank Clearance - _TC" bank_clearance.account = "_Test Bank Clearance - _TC"
bank_clearance.from_date = add_months(getdate(), -1) bank_clearance.from_date = add_months(getdate(), -1)
@@ -86,19 +36,6 @@ class TestBankClearance(unittest.TestCase):
self.assertEqual(len(bank_clearance.payment_entries), 3) self.assertEqual(len(bank_clearance.payment_entries), 3)
def clear_payment_entries():
frappe.db.delete("Payment Entry")
@if_lending_app_installed
def clear_loan_transactions():
for dt in [
"Loan Disbursement",
"Loan Repayment",
]:
frappe.db.delete(dt)
def make_bank_account(): def make_bank_account():
if not frappe.db.get_value("Account", "_Test Bank Clearance - _TC"): if not frappe.db.get_value("Account", "_Test Bank Clearance - _TC"):
frappe.get_doc( frappe.get_doc(
@@ -112,8 +49,42 @@ def make_bank_account():
).insert() ).insert()
def create_loan_masters():
create_loan_type(
"Clearance Loan",
2000000,
13.5,
25,
0,
5,
"Cash",
"_Test Bank Clearance - _TC",
"_Test Bank Clearance - _TC",
"Loan Account - _TC",
"Interest Income Account - _TC",
"Penalty Income Account - _TC",
)
def add_transactions(): def add_transactions():
make_payment_entry() make_payment_entry()
make_loan()
def make_loan():
loan = create_loan(
"_Test Customer",
"Clearance Loan",
280000,
"Repay Over Number of Periods",
20,
applicant_type="Customer",
)
loan.submit()
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.save()
repayment_entry.submit()
def make_payment_entry(): def make_payment_entry():

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

@@ -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

@@ -18,30 +18,16 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
}, },
onload: function (frm) { onload: function (frm) {
// Set default filter dates
let today = frappe.datetime.get_today()
frm.doc.bank_statement_from_date = frappe.datetime.add_months(today, -1);
frm.doc.bank_statement_to_date = today;
frm.trigger('bank_account'); frm.trigger('bank_account');
}, },
filter_by_reference_date: function (frm) {
if (frm.doc.filter_by_reference_date) {
frm.set_value("bank_statement_from_date", "");
frm.set_value("bank_statement_to_date", "");
} else {
frm.set_value("from_reference_date", "");
frm.set_value("to_reference_date", "");
}
},
refresh: function (frm) { refresh: function (frm) {
frm.disable_save();
frappe.require("bank-reconciliation-tool.bundle.js", () => frappe.require("bank-reconciliation-tool.bundle.js", () =>
frm.trigger("make_reconciliation_tool") frm.trigger("make_reconciliation_tool")
); );
frm.upload_statement_button = frm.page.set_secondary_action(
frm.add_custom_button(__("Upload Bank Statement"), () => __("Upload Bank Statement"),
() =>
frappe.call({ frappe.call({
method: method:
"erpnext.accounts.doctype.bank_statement_import.bank_statement_import.upload_bank_statement", "erpnext.accounts.doctype.bank_statement_import.bank_statement_import.upload_bank_statement",
@@ -63,26 +49,10 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
}, },
}) })
); );
},
frm.add_custom_button(__('Auto Reconcile'), function() { after_save: function (frm) {
frappe.call({ frm.trigger("make_reconciliation_tool");
method: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.auto_reconcile_vouchers",
args: {
bank_account: frm.doc.bank_account,
from_date: frm.doc.bank_statement_from_date,
to_date: frm.doc.bank_statement_to_date,
filter_by_reference_date: frm.doc.filter_by_reference_date,
from_reference_date: frm.doc.from_reference_date,
to_reference_date: frm.doc.to_reference_date,
},
})
});
frm.add_custom_button(__('Get Unreconciled Entries'), function() {
frm.trigger("make_reconciliation_tool");
});
frm.change_custom_button_type(__('Get Unreconciled Entries'), null, 'primary');
}, },
bank_account: function (frm) { bank_account: function (frm) {
@@ -96,7 +66,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
r.account, r.account,
"account_currency", "account_currency",
(r) => { (r) => {
frm.doc.account_currency = r.account_currency; frm.currency = r.account_currency;
frm.trigger("render_chart"); frm.trigger("render_chart");
} }
); );
@@ -137,7 +107,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
"erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance", "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance",
args: { args: {
bank_account: frm.doc.bank_account, bank_account: frm.doc.bank_account,
till_date: frappe.datetime.add_days(frm.doc.bank_statement_from_date, -1) till_date: 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);
@@ -162,19 +132,19 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
} }
}, },
render_chart(frm) { render_chart: frappe.utils.debounce((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: frm.get_field(
"reconciliation_tool_cards" "reconciliation_tool_cards"
).$wrapper, ).$wrapper,
bank_statement_closing_balance: bank_statement_closing_balance:
frm.doc.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.currency,
} }
); );
}, }, 500),
render(frm) { render(frm) {
if (frm.doc.bank_account) { if (frm.doc.bank_account) {
@@ -190,9 +160,6 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
).$wrapper, ).$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,
from_reference_date: frm.doc.from_reference_date,
to_reference_date: frm.doc.to_reference_date,
bank_statement_closing_balance: bank_statement_closing_balance:
frm.doc.bank_statement_closing_balance, frm.doc.bank_statement_closing_balance,
cards_manager: frm.cards_manager, cards_manager: frm.cards_manager,

View File

@@ -10,11 +10,7 @@
"column_break_1", "column_break_1",
"bank_statement_from_date", "bank_statement_from_date",
"bank_statement_to_date", "bank_statement_to_date",
"from_reference_date",
"to_reference_date",
"filter_by_reference_date",
"column_break_2", "column_break_2",
"account_currency",
"account_opening_balance", "account_opening_balance",
"bank_statement_closing_balance", "bank_statement_closing_balance",
"section_break_1", "section_break_1",
@@ -40,13 +36,13 @@
"fieldtype": "Column Break" "fieldtype": "Column Break"
}, },
{ {
"depends_on": "eval: doc.bank_account && !doc.filter_by_reference_date", "depends_on": "eval: doc.bank_account",
"fieldname": "bank_statement_from_date", "fieldname": "bank_statement_from_date",
"fieldtype": "Date", "fieldtype": "Date",
"label": "From Date" "label": "From Date"
}, },
{ {
"depends_on": "eval: doc.bank_account && !doc.filter_by_reference_date", "depends_on": "eval: doc.bank_statement_from_date",
"fieldname": "bank_statement_to_date", "fieldname": "bank_statement_to_date",
"fieldtype": "Date", "fieldtype": "Date",
"label": "To Date" "label": "To Date"
@@ -60,7 +56,7 @@
"fieldname": "account_opening_balance", "fieldname": "account_opening_balance",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Account Opening Balance", "label": "Account Opening Balance",
"options": "account_currency", "options": "Currency",
"read_only": 1 "read_only": 1
}, },
{ {
@@ -68,7 +64,7 @@
"fieldname": "bank_statement_closing_balance", "fieldname": "bank_statement_closing_balance",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Closing Balance", "label": "Closing Balance",
"options": "account_currency" "options": "Currency"
}, },
{ {
"fieldname": "section_break_1", "fieldname": "section_break_1",
@@ -85,40 +81,14 @@
}, },
{ {
"fieldname": "no_bank_transactions", "fieldname": "no_bank_transactions",
"fieldtype": "HTML", "fieldtype": "HTML"
"options": "<div class=\"text-muted text-center\">No Matching Bank Transactions Found</div>"
},
{
"depends_on": "eval:doc.filter_by_reference_date",
"fieldname": "from_reference_date",
"fieldtype": "Date",
"label": "From Reference Date"
},
{
"depends_on": "eval:doc.filter_by_reference_date",
"fieldname": "to_reference_date",
"fieldtype": "Date",
"label": "To Reference Date"
},
{
"default": "0",
"fieldname": "filter_by_reference_date",
"fieldtype": "Check",
"label": "Filter by Reference Date"
},
{
"fieldname": "account_currency",
"fieldtype": "Link",
"hidden": 1,
"label": "Account Currency",
"options": "Currency"
} }
], ],
"hide_toolbar": 1, "hide_toolbar": 1,
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2023-03-07 11:02:24.535714", "modified": "2021-04-21 11:13:49.831769",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Bank Reconciliation Tool", "name": "Bank Reconciliation Tool",
@@ -137,6 +107,5 @@
], ],
"quick_entry": 1, "quick_entry": 1,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC"
"states": [] }
}

View File

@@ -1,101 +1,9 @@
# 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 frappe
import unittest import unittest
import frappe
from frappe import qb
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, flt, getdate, today
from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import ( class TestBankReconciliationTool(unittest.TestCase):
auto_reconcile_vouchers, pass
get_bank_transactions,
)
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
class TestBankReconciliationTool(AccountsTestMixin, FrappeTestCase):
def setUp(self):
self.create_company()
self.create_customer()
self.clear_old_entries()
bank_dt = qb.DocType("Bank")
q = qb.from_(bank_dt).delete().where(bank_dt.name == "HDFC").run()
self.create_bank_account()
def tearDown(self):
frappe.db.rollback()
def create_bank_account(self):
bank = frappe.get_doc(
{
"doctype": "Bank",
"bank_name": "HDFC",
}
).save()
self.bank_account = (
frappe.get_doc(
{
"doctype": "Bank Account",
"account_name": "HDFC _current_",
"bank": bank,
"is_company_account": True,
"account": self.bank, # account from Chart of Accounts
}
)
.insert()
.name
)
def test_auto_reconcile(self):
# make payment
from_date = add_days(today(), -1)
to_date = today()
payment = create_payment_entry(
company=self.company,
posting_date=from_date,
payment_type="Receive",
party_type="Customer",
party=self.customer,
paid_from=self.debit_to,
paid_to=self.bank,
paid_amount=100,
).save()
payment.reference_no = "123"
payment = payment.save().submit()
# make bank transaction
bank_transaction = (
frappe.get_doc(
{
"doctype": "Bank Transaction",
"date": to_date,
"deposit": 100,
"bank_account": self.bank_account,
"reference_number": "123",
"currency": "INR",
}
)
.save()
.submit()
)
# assert API output pre reconciliation
transactions = get_bank_transactions(self.bank_account, from_date, to_date)
self.assertEqual(len(transactions), 1)
self.assertEqual(transactions[0].name, bank_transaction.name)
# auto reconcile
auto_reconcile_vouchers(
bank_account=self.bank_account,
from_date=from_date,
to_date=to_date,
filter_by_reference_date=False,
)
# assert API output post reconciliation
transactions = get_bank_transactions(self.bank_account, from_date, to_date)
self.assertEqual(len(transactions), 0)

View File

@@ -352,11 +352,10 @@ frappe.ui.form.on("Bank Statement Import", {
export_errored_rows(frm) { export_errored_rows(frm) {
open_url_post( open_url_post(
"/api/method/erpnext.accounts.doctype.bank_statement_import.bank_statement_import.download_errored_template", "/api/method/frappe.core.doctype.data_import.data_import.download_errored_template",
{ {
data_import_name: frm.doc.name, data_import_name: frm.doc.name,
}, }
true
); );
}, },

View File

@@ -20,30 +20,6 @@ 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
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
statement_import_log: DF.Code | None
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(BankStatementImport, self).__init__(*args, **kwargs) super(BankStatementImport, self).__init__(*args, **kwargs)
@@ -77,20 +53,19 @@ class BankStatementImport(DataImport):
if "Bank Account" not in json.dumps(preview["columns"]): if "Bank Account" not in json.dumps(preview["columns"]):
frappe.throw(_("Please add the Bank Account column")) frappe.throw(_("Please add the Bank Account column"))
from frappe.utils.background_jobs import is_job_enqueued from frappe.utils.background_jobs import is_job_queued
from frappe.utils.scheduler import is_scheduler_inactive from frappe.utils.scheduler import is_scheduler_inactive
if is_scheduler_inactive() and not frappe.flags.in_test: if is_scheduler_inactive() and not frappe.flags.in_test:
frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive")) frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive"))
job_id = f"bank_statement_import::{self.name}" if not is_job_queued(self.name):
if not is_job_enqueued(job_id):
enqueue( enqueue(
start_import, start_import,
queue="default", queue="default",
timeout=6000, timeout=6000,
event="data_import", event="data_import",
job_id=job_id, job_name=self.name,
data_import=self.name, data_import=self.name,
bank_account=self.bank_account, bank_account=self.bank_account,
import_file_path=self.import_file, import_file_path=self.import_file,

View File

@@ -1,178 +0,0 @@
from typing import Tuple, Union
import frappe
from frappe.utils import flt
from rapidfuzz import fuzz, process
class AutoMatchParty:
"""
Matches by Account/IBAN and then by Party Name/Description sequentially.
Returns when a result is obtained.
Result (if present) is of the form: (Party Type, Party,)
"""
def __init__(self, **kwargs) -> None:
self.__dict__.update(kwargs)
def get(self, key):
return self.__dict__.get(key, None)
def match(self) -> Union[Tuple, None]:
result = None
result = AutoMatchbyAccountIBAN(
bank_party_account_number=self.bank_party_account_number,
bank_party_iban=self.bank_party_iban,
deposit=self.deposit,
).match()
fuzzy_matching_enabled = frappe.db.get_single_value("Accounts Settings", "enable_fuzzy_matching")
if not result and fuzzy_matching_enabled:
result = AutoMatchbyPartyNameDescription(
bank_party_name=self.bank_party_name, description=self.description, deposit=self.deposit
).match()
return result
class AutoMatchbyAccountIBAN:
def __init__(self, **kwargs) -> None:
self.__dict__.update(kwargs)
def get(self, key):
return self.__dict__.get(key, None)
def match(self):
if not (self.bank_party_account_number or self.bank_party_iban):
return None
result = self.match_account_in_party()
return result
def match_account_in_party(self) -> Union[Tuple, None]:
"""Check if there is a IBAN/Account No. match in Customer/Supplier/Employee"""
result = None
parties = get_parties_in_order(self.deposit)
or_filters = self.get_or_filters()
for party in parties:
party_result = frappe.db.get_all(
"Bank Account", or_filters=or_filters, pluck="party", limit_page_length=1
)
if party == "Employee" and not party_result:
# Search in Bank Accounts first for Employee, and then Employee record
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 party_result:
result = (
party,
party_result[0],
)
break
return result
def get_or_filters(self) -> dict:
or_filters = {}
if self.bank_party_account_number:
or_filters["bank_account_no"] = self.bank_party_account_number
if self.bank_party_iban:
or_filters["iban"] = self.bank_party_iban
return or_filters
class AutoMatchbyPartyNameDescription:
def __init__(self, **kwargs) -> None:
self.__dict__.update(kwargs)
def get(self, key):
return self.__dict__.get(key, None)
def match(self) -> Union[Tuple, None]:
# fuzzy search by customer/supplier & employee
if not (self.bank_party_name or self.description):
return None
result = self.match_party_name_desc_in_party()
return result
def match_party_name_desc_in_party(self) -> Union[Tuple, None]:
"""Fuzzy search party name and/or description against parties in the system"""
result = None
parties = get_parties_in_order(self.deposit)
for party in parties:
filters = {"status": "Active"} if party == "Employee" else {"disabled": 0}
names = frappe.get_all(party, filters=filters, pluck=party.lower() + "_name")
for field in ["bank_party_name", "description"]:
if not self.get(field):
continue
result, skip = self.fuzzy_search_and_return_result(party, names, field)
if result or skip:
break
if result or skip:
# Skip If: It was hard to distinguish between close matches and so match is None
# OR if the right match was found
break
return result
def fuzzy_search_and_return_result(self, party, names, field) -> Union[Tuple, None]:
skip = False
result = process.extract(query=self.get(field), choices=names, scorer=fuzz.token_set_ratio)
party_name, skip = self.process_fuzzy_result(result)
if not party_name:
return None, skip
return (
party,
party_name,
), skip
def process_fuzzy_result(self, result: Union[list, None]):
"""
If there are multiple valid close matches return None as result may be faulty.
Return the result only if one accurate match stands out.
Returns: Result, Skip (whether or not to discontinue matching)
"""
PARTY, SCORE, CUTOFF = 0, 1, 80
if not result or not len(result):
return None, False
first_result = result[0]
if len(result) == 1:
return (first_result[PARTY] if first_result[SCORE] > CUTOFF else None), True
second_result = result[1]
if first_result[SCORE] > CUTOFF:
# If multiple matches with the same score, return None but discontinue matching
# Matches were found but were too close to distinguish between
if first_result[SCORE] == second_result[SCORE]:
return None, True
return first_result[PARTY], True
else:
return None, False
def get_parties_in_order(deposit: float) -> list:
parties = ["Supplier", "Employee", "Customer"] # most -> least likely to receive
if flt(deposit) > 0:
parties = ["Customer", "Supplier", "Employee"] # most -> least likely to pay
return parties

View File

@@ -12,14 +12,8 @@ frappe.ui.form.on("Bank Transaction", {
}; };
}); });
}, },
refresh(frm) {
if (!frm.is_dirty() && frm.doc.payment_entries.length > 0) { bank_account: function(frm) {
frm.add_custom_button(__("Unreconcile Transaction"), () => {
frm.call("remove_payment_entries").then(() => frm.refresh());
});
}
},
bank_account: function (frm) {
set_bank_statement_filter(frm); set_bank_statement_filter(frm);
}, },
@@ -40,7 +34,6 @@ frappe.ui.form.on("Bank Transaction", {
"Journal Entry", "Journal Entry",
"Sales Invoice", "Sales Invoice",
"Purchase Invoice", "Purchase Invoice",
"Bank Transaction",
]; ];
} }
}); });
@@ -56,7 +49,7 @@ const update_clearance_date = (frm, cdt, cdn) => {
frappe frappe
.xcall( .xcall(
"erpnext.accounts.doctype.bank_transaction.bank_transaction.unclear_reference_payment", "erpnext.accounts.doctype.bank_transaction.bank_transaction.unclear_reference_payment",
{ doctype: cdt, docname: cdn, bt_name: frm.doc.name } { doctype: cdt, docname: cdn }
) )
.then((e) => { .then((e) => {
if (e == "success") { if (e == "success") {

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",
@@ -21,24 +20,18 @@
"currency", "currency",
"section_break_10", "section_break_10",
"description", "description",
"reference_number",
"column_break_10",
"transaction_id",
"transaction_type",
"section_break_14", "section_break_14",
"column_break_oufv", "reference_number",
"transaction_id",
"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",
"party_type", "party_type",
"party", "party"
"column_break_3czf",
"bank_party_name",
"bank_party_account_number",
"bank_party_iban"
], ],
"fields": [ "fields": [
{ {
@@ -68,7 +61,7 @@
"fieldtype": "Select", "fieldtype": "Select",
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Status", "label": "Status",
"options": "\nPending\nSettled\nUnreconciled\nReconciled\nCancelled" "options": "\nPending\nSettled\nUnreconciled\nReconciled"
}, },
{ {
"fieldname": "bank_account", "fieldname": "bank_account",
@@ -139,12 +132,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 +151,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",
@@ -201,44 +190,11 @@
"label": "Withdrawal", "label": "Withdrawal",
"oldfieldname": "credit", "oldfieldname": "credit",
"options": "currency" "options": "currency"
},
{
"fieldname": "column_break_10",
"fieldtype": "Column Break"
},
{
"fieldname": "transaction_type",
"fieldtype": "Data",
"label": "Transaction Type",
"length": 50
},
{
"fieldname": "column_break_3czf",
"fieldtype": "Column Break"
},
{
"fieldname": "bank_party_name",
"fieldtype": "Data",
"label": "Party Name/Account Holder (Bank Statement)"
},
{
"fieldname": "bank_party_iban",
"fieldtype": "Data",
"label": "Party IBAN (Bank Statement)"
},
{
"fieldname": "bank_party_account_number",
"fieldtype": "Data",
"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": "2022-03-21 19:05:04.208222",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Bank Transaction", "name": "Bank Transaction",

View File

@@ -1,220 +1,90 @@
# 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
from functools import reduce
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:
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):
self.allocated_amount = (
sum(p.allocated_amount for p in self.payment_entries) if self.payment_entries else 0.0
)
self.unallocated_amount = abs(flt(self.withdrawal) - flt(self.deposit)) - self.allocated_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"): def on_update_after_submit(self):
self.auto_set_party() self.update_allocations()
self.clear_linked_payment_entries()
def before_update_after_submit(self): self.set_status(update=True)
self.validate_duplicate_references()
self.allocate_payment_entries()
self.update_allocated_amount()
self.set_status()
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):
if self.payment_entries:
allocated_amount = reduce(
lambda x, y: flt(x) + flt(y), [x.allocated_amount for x in self.payment_entries]
)
else:
allocated_amount = 0
def add_payment_entries(self, vouchers): if allocated_amount:
"Add the vouchers with zero allocation. Save() will perform the allocations and clearance" frappe.db.set_value(self.doctype, self.name, "allocated_amount", flt(allocated_amount))
if 0.0 >= self.unallocated_amount: frappe.db.set_value(
frappe.throw(_("Bank Transaction {0} is already fully reconciled").format(self.name)) self.doctype,
self.name,
for voucher in vouchers: "unallocated_amount",
self.append( abs(flt(self.withdrawal) - flt(self.deposit)) - flt(allocated_amount),
"payment_entries",
{
"payment_document": voucher["payment_doctype"],
"payment_entry": voucher["payment_name"],
"allocated_amount": 0.0, # Temporary
},
) )
def allocate_payment_entries(self): else:
"""Refactored from bank reconciliation tool. frappe.db.set_value(self.doctype, self.name, "allocated_amount", 0)
Non-zero allocations must be amended/cleared manually frappe.db.set_value(
Get the bank transaction amount (b) and remove as we allocate self.doctype, self.name, "unallocated_amount", abs(flt(self.withdrawal) - flt(self.deposit))
For each payment_entry if allocated_amount == 0: )
- get the amount already allocated against all transactions (t), need latest date
- get the voucher amount (from gl) (v) amount = self.deposit or self.withdrawal
- allocate (a = v - t) if amount == self.allocated_amount:
- a = 0: should already be cleared, so clear & remove payment_entry frappe.db.set_value(self.doctype, self.name, "status", "Reconciled")
- 0 < a <= u: allocate a & clear
- 0 < a, a > u: allocate u self.reload()
- 0 > a: Error: already over-allocated
- clear means: set the latest transaction date as clearance date def clear_linked_payment_entries(self, for_cancel=False):
"""
remaining_amount = self.unallocated_amount
to_remove = []
for payment_entry in self.payment_entries: for payment_entry in self.payment_entries:
if payment_entry.allocated_amount == 0.0: if payment_entry.payment_document == "Sales Invoice":
unallocated_amount, should_clear, latest_transaction = get_clearance_details( self.clear_sales_invoice(payment_entry, for_cancel=for_cancel)
self, payment_entry elif payment_entry.payment_document in get_doctypes_for_bank_reconciliation():
) self.clear_simple_entry(payment_entry, for_cancel=for_cancel)
if 0.0 == unallocated_amount: def clear_simple_entry(self, payment_entry, for_cancel=False):
if should_clear: if payment_entry.payment_document == "Payment Entry":
latest_transaction.clear_linked_payment_entry(payment_entry) if (
to_remove.append(payment_entry) frappe.db.get_value("Payment Entry", payment_entry.payment_entry, "payment_type")
== "Internal Transfer"
):
if len(get_reconciled_bank_transactions(payment_entry)) < 2:
return
elif remaining_amount <= 0.0: clearance_date = self.date if not for_cancel else None
to_remove.append(payment_entry) frappe.db.set_value(
payment_entry.payment_document, payment_entry.payment_entry, "clearance_date", clearance_date
elif 0.0 < unallocated_amount <= remaining_amount:
payment_entry.allocated_amount = unallocated_amount
remaining_amount -= unallocated_amount
if should_clear:
latest_transaction.clear_linked_payment_entry(payment_entry)
elif 0.0 < unallocated_amount:
payment_entry.allocated_amount = remaining_amount
remaining_amount = 0.0
elif 0.0 > unallocated_amount:
frappe.throw(_("Voucher {0} is over-allocated by {1}").format(unallocated_amount))
for payment_entry in to_remove:
self.remove(to_remove)
@frappe.whitelist()
def remove_payment_entries(self):
for payment_entry in self.payment_entries:
self.remove_payment_entry(payment_entry)
self.save() # runs before_update_after_submit
def remove_payment_entry(self, payment_entry):
"Clear payment entry and clearance"
self.clear_linked_payment_entry(payment_entry, for_cancel=True)
self.remove(payment_entry)
def clear_linked_payment_entry(self, payment_entry, for_cancel=False):
clearance_date = None if for_cancel else self.date
set_voucher_clearance(
payment_entry.payment_document, payment_entry.payment_entry, clearance_date, self
) )
def auto_set_party(self): def clear_sales_invoice(self, payment_entry, for_cancel=False):
from erpnext.accounts.doctype.bank_transaction.auto_match_party import AutoMatchParty clearance_date = self.date if not for_cancel else None
frappe.db.set_value(
if self.party_type and self.party: "Sales Invoice Payment",
return dict(parenttype=payment_entry.payment_document, parent=payment_entry.payment_entry),
"clearance_date",
result = AutoMatchParty( clearance_date,
bank_party_account_number=self.bank_party_account_number, )
bank_party_iban=self.bank_party_iban,
bank_party_name=self.bank_party_name,
description=self.description,
deposit=self.deposit,
).match()
if not result:
return
self.party_type, self.party = result
@frappe.whitelist() @frappe.whitelist()
@@ -223,111 +93,38 @@ 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): def get_reconciled_bank_transactions(payment_entry):
""" reconciled_bank_transactions = frappe.get_all(
There should only be one bank gle for a voucher. "Bank Transaction Payments",
Could be none for a Bank Transaction. filters={"payment_entry": payment_entry.payment_entry},
But if a JE, could affect two banks. fields=["parent"],
Should only clear the voucher if all bank gles are allocated.
"""
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)
bt_allocations = get_total_allocated_amount(
payment_entry.payment_document, payment_entry.payment_entry
) )
unallocated_amount = min( return reconciled_bank_transactions
transaction.unallocated_amount,
get_paid_amount(payment_entry, transaction.currency, gl_bank_account),
)
unmatched_gles = len(gles)
latest_transaction = transaction
for gle in gles:
if gle["gl_account"] == gl_bank_account:
if gle["amount"] <= 0.0:
frappe.throw(
_("Voucher {0} value is broken: {1}").format(payment_entry.payment_entry, gle["amount"])
)
unmatched_gles -= 1
unallocated_amount = gle["amount"]
for a in bt_allocations:
if a["gl_account"] == gle["gl_account"]:
unallocated_amount = gle["amount"] - a["total"]
if frappe.utils.getdate(transaction.date) < a["latest_date"]:
latest_transaction = frappe.get_doc("Bank Transaction", a["latest_name"])
else:
# Must be a Journal Entry affecting more than one bank
for a in bt_allocations:
if a["gl_account"] == gle["gl_account"] and a["total"] == gle["amount"]:
unmatched_gles -= 1
return unallocated_amount, unmatched_gles == 0, latest_transaction
def get_related_bank_gl_entries(doctype, docname): def get_total_allocated_amount(payment_entry):
# nosemgrep: frappe-semgrep-rules.rules.frappe-using-db-sql
return frappe.db.sql( return frappe.db.sql(
""" """
SELECT SELECT
ABS(gle.credit_in_account_currency - gle.debit_in_account_currency) AS amount, SUM(btp.allocated_amount) as allocated_amount,
gle.account AS gl_account bt.name
FROM FROM
`tabGL Entry` gle `tabBank Transaction Payments` as btp
LEFT JOIN LEFT JOIN
`tabAccount` ac ON ac.name=gle.account `tabBank Transaction` bt ON bt.name=btp.parent
WHERE WHERE
ac.account_type = 'Bank' btp.payment_document = %s
AND gle.voucher_type = %(doctype)s AND
AND gle.voucher_no = %(docname)s btp.payment_entry = %s
AND is_cancelled = 0 AND
""", bt.docstatus = 1""",
dict(doctype=doctype, docname=docname), (payment_entry.payment_document, payment_entry.payment_entry),
as_dict=True, as_dict=True,
) )
def get_total_allocated_amount(doctype, docname): def get_paid_amount(payment_entry, currency, bank_account):
"""
Gets the sum of allocations for a voucher on each bank GL account
along with the latest bank transaction name & date
NOTE: query may also include just saved vouchers/payments but with zero allocated_amount
"""
# nosemgrep: frappe-semgrep-rules.rules.frappe-using-db-sql
result = frappe.db.sql(
"""
SELECT total, latest_name, latest_date, gl_account FROM (
SELECT
ROW_NUMBER() OVER w AS rownum,
SUM(btp.allocated_amount) OVER(PARTITION BY ba.account) AS total,
FIRST_VALUE(bt.name) OVER w AS latest_name,
FIRST_VALUE(bt.date) OVER w AS latest_date,
ba.account AS gl_account
FROM
`tabBank Transaction Payments` btp
LEFT JOIN `tabBank Transaction` bt ON bt.name=btp.parent
LEFT JOIN `tabBank Account` ba ON ba.name=bt.bank_account
WHERE
btp.payment_document = %(doctype)s
AND btp.payment_entry = %(docname)s
AND bt.docstatus = 1
WINDOW w AS (PARTITION BY ba.account ORDER BY bt.date desc)
) temp
WHERE
rownum = 1
""",
dict(doctype=doctype, docname=docname),
as_dict=True,
)
for row in result:
# Why is this *sometimes* a byte string?
if isinstance(row["latest_name"], bytes):
row["latest_name"] = row["latest_name"].decode()
row["latest_date"] = frappe.utils.getdate(row["latest_date"])
return result
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"
@@ -340,7 +137,7 @@ def get_paid_amount(payment_entry, currency, gl_bank_account):
) )
elif doc.payment_type == "Pay": elif doc.payment_type == "Pay":
paid_amount_field = ( paid_amount_field = (
"paid_amount" if doc.paid_from_account_currency == currency else "base_paid_amount" "paid_amount" if doc.paid_to_account_currency == currency else "base_paid_amount"
) )
return frappe.db.get_value( return frappe.db.get_value(
@@ -348,13 +145,10 @@ def get_paid_amount(payment_entry, currency, gl_bank_account):
) )
elif payment_entry.payment_document == "Journal Entry": elif payment_entry.payment_document == "Journal Entry":
return abs( return frappe.db.get_value(
frappe.db.get_value( "Journal Entry Account",
"Journal Entry Account", {"parent": payment_entry.payment_entry, "account": bank_account},
{"parent": payment_entry.payment_entry, "account": gl_bank_account}, "sum(credit_in_account_currency)",
"sum(debit_in_account_currency-credit_in_account_currency)",
)
or 0
) )
elif payment_entry.payment_document == "Expense Claim": elif payment_entry.payment_document == "Expense Claim":
@@ -372,12 +166,6 @@ def get_paid_amount(payment_entry, currency, gl_bank_account):
payment_entry.payment_document, payment_entry.payment_entry, "amount_paid" payment_entry.payment_document, payment_entry.payment_entry, "amount_paid"
) )
elif payment_entry.payment_document == "Bank Transaction":
dep, wth = frappe.db.get_value(
"Bank Transaction", payment_entry.payment_entry, ("deposit", "withdrawal")
)
return abs(flt(wth) - flt(dep))
else: else:
frappe.throw( frappe.throw(
"Please reconcile {0}: {1} manually".format( "Please reconcile {0}: {1} manually".format(
@@ -386,69 +174,18 @@ def get_paid_amount(payment_entry, currency, gl_bank_account):
) )
def set_voucher_clearance(doctype, docname, clearance_date, self): @frappe.whitelist()
if doctype in get_doctypes_for_bank_reconciliation(): def unclear_reference_payment(doctype, docname):
if ( if frappe.db.exists(doctype, docname):
doctype == "Payment Entry" doc = frappe.get_doc(doctype, docname)
and frappe.db.get_value("Payment Entry", docname, "payment_type") == "Internal Transfer"
and len(get_reconciled_bank_transactions(doctype, docname)) < 2
):
return
if doctype == "Sales Invoice": if doctype == "Sales Invoice":
frappe.db.set_value( frappe.db.set_value(
"Sales Invoice Payment", "Sales Invoice Payment",
dict(parenttype=doctype, parent=docname), dict(parenttype=doc.payment_document, parent=doc.payment_entry),
"clearance_date", "clearance_date",
clearance_date, None,
) )
return
frappe.db.set_value(doctype, docname, "clearance_date", clearance_date)
elif doctype == "Bank Transaction":
# For when a second bank transaction has fixed another, e.g. refund
bt = frappe.get_doc(doctype, docname)
if clearance_date:
vouchers = [{"payment_doctype": "Bank Transaction", "payment_name": self.name}]
bt.add_payment_entries(vouchers)
bt.save()
else: else:
for pe in bt.payment_entries: frappe.db.set_value(doc.payment_document, doc.payment_entry, "clearance_date", None)
if pe.payment_document == self.doctype and pe.payment_entry == self.name:
bt.remove(pe)
bt.save()
break
return doc.payment_entry
def get_reconciled_bank_transactions(doctype, docname):
return frappe.get_all(
"Bank Transaction Payments",
filters={"payment_document": doctype, "payment_entry": docname},
pluck="parent",
)
@frappe.whitelist()
def unclear_reference_payment(doctype, docname, bt_name):
bt = frappe.get_doc("Bank Transaction", bt_name)
set_voucher_clearance(doctype, docname, None, bt)
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,151 +0,0 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import nowdate
from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import create_bank_account
class TestAutoMatchParty(FrappeTestCase):
@classmethod
def setUpClass(cls):
create_bank_account()
frappe.db.set_single_value("Accounts Settings", "enable_party_matching", 1)
frappe.db.set_single_value("Accounts Settings", "enable_fuzzy_matching", 1)
return super().setUpClass()
@classmethod
def tearDownClass(cls):
frappe.db.set_single_value("Accounts Settings", "enable_party_matching", 0)
frappe.db.set_single_value("Accounts Settings", "enable_fuzzy_matching", 0)
def test_match_by_account_number(self):
create_supplier_for_match(account_no="000000003716541159")
doc = create_bank_transaction(
withdrawal=1200,
transaction_id="562213b0ca1bf838dab8f2c6a39bbc3b",
account_no="000000003716541159",
iban="DE02000000003716541159",
)
self.assertEqual(doc.party_type, "Supplier")
self.assertEqual(doc.party, "John Doe & Co.")
def test_match_by_iban(self):
create_supplier_for_match(iban="DE02000000003716541159")
doc = create_bank_transaction(
withdrawal=1200,
transaction_id="c5455a224602afaa51592a9d9250600d",
account_no="000000003716541159",
iban="DE02000000003716541159",
)
self.assertEqual(doc.party_type, "Supplier")
self.assertEqual(doc.party, "John Doe & Co.")
def test_match_by_party_name(self):
create_supplier_for_match(supplier_name="Jackson Ella W.")
doc = create_bank_transaction(
withdrawal=1200,
transaction_id="1f6f661f347ff7b1ea588665f473adb1",
party_name="Ella Jackson",
iban="DE04000000003716545346",
)
self.assertEqual(doc.party_type, "Supplier")
self.assertEqual(doc.party, "Jackson Ella W.")
def test_match_by_description(self):
create_supplier_for_match(supplier_name="Microsoft")
doc = create_bank_transaction(
description="Auftraggeber: microsoft payments Buchungstext: msft ..e3006b5hdy. ref. j375979555927627/5536",
withdrawal=1200,
transaction_id="8df880a2d09c3bed3fea358ca5168c5a",
party_name="",
)
self.assertEqual(doc.party_type, "Supplier")
self.assertEqual(doc.party, "Microsoft")
def test_skip_match_if_multiple_close_results(self):
create_supplier_for_match(supplier_name="Adithya Medical & General Stores")
create_supplier_for_match(supplier_name="Adithya Medical And General Stores")
doc = create_bank_transaction(
description="Paracetamol Consignment, SINV-0009",
withdrawal=24.85,
transaction_id="3a1da4ee2dc5a980138d56ef3460cbd9",
party_name="Adithya Medical & General",
)
# Mapping is skipped as both Supplier names have the same match score
self.assertEqual(doc.party_type, None)
self.assertEqual(doc.party, None)
def create_supplier_for_match(supplier_name="John Doe & Co.", iban=None, account_no=None):
if frappe.db.exists("Supplier", {"supplier_name": supplier_name}):
# Update related Bank Account details
if not (iban or account_no):
return
frappe.db.set_value(
dt="Bank Account",
dn={"party": supplier_name},
field={"iban": iban, "bank_account_no": account_no},
)
return
# Create Supplier and Bank Account for the same
supplier = frappe.new_doc("Supplier")
supplier.supplier_name = supplier_name
supplier.supplier_group = "Services"
supplier.supplier_type = "Company"
supplier.insert()
if not frappe.db.exists("Bank", "TestBank"):
bank = frappe.new_doc("Bank")
bank.bank_name = "TestBank"
bank.insert(ignore_if_duplicate=True)
if not frappe.db.exists("Bank Account", supplier.name + " - " + "TestBank"):
bank_account = frappe.new_doc("Bank Account")
bank_account.account_name = supplier.name
bank_account.bank = "TestBank"
bank_account.iban = iban
bank_account.bank_account_no = account_no
bank_account.party_type = "Supplier"
bank_account.party = supplier.name
bank_account.insert()
def create_bank_transaction(
description=None,
withdrawal=0,
deposit=0,
transaction_id=None,
party_name=None,
account_no=None,
iban=None,
):
doc = frappe.new_doc("Bank Transaction")
doc.update(
{
"doctype": "Bank Transaction",
"description": description or "1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G",
"date": nowdate(),
"withdrawal": withdrawal,
"deposit": deposit,
"currency": "INR",
"bank_account": "Checking Account - Citi Bank",
"transaction_id": transaction_id,
"bank_party_name": party_name,
"bank_party_account_number": account_no,
"bank_party_iban": iban,
}
)
doc.insert()
doc.submit()
doc.reload()
return doc

View File

@@ -2,10 +2,9 @@
# See license.txt # See license.txt
import json import json
import unittest
import frappe import frappe
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 (
@@ -16,7 +15,6 @@ from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_paymen
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
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.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.tests.utils import if_lending_app_installed
test_dependencies = ["Item", "Cost Center"] test_dependencies = ["Item", "Cost Center"]
@@ -24,24 +22,17 @@ test_dependencies = ["Item", "Cost Center"]
class TestBankTransaction(FrappeTestCase): class TestBankTransaction(FrappeTestCase):
def setUp(self): def setUp(self):
for dt in [ for dt in [
"Loan Repayment",
"Bank Transaction", "Bank Transaction",
"Payment Entry", "Payment Entry",
"Payment Entry Reference", "Payment Entry Reference",
"POS Profile", "POS Profile",
]: ]:
frappe.db.delete(dt) frappe.db.delete(dt)
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):
@@ -49,13 +40,8 @@ class TestBankTransaction(FrappeTestCase):
"Bank Transaction", "Bank Transaction",
dict(description="Re 95282925234 FE/000002917 AT171513000281183046 Conrad Electronic"), dict(description="Re 95282925234 FE/000002917 AT171513000281183046 Conrad Electronic"),
) )
linked_payments = get_linked_payments( linked_payments = get_linked_payments(bank_transaction.name, ["payment_entry", "exact_match"])
bank_transaction.name, self.assertTrue(linked_payments[0][6] == "Conrad Electronic")
["payment_entry", "exact_match"],
from_date=bank_transaction.date,
to_date=utils.today(),
)
self.assertTrue(linked_payments[0]["party"] == "Conrad Electronic")
# This test validates a simple reconciliation leading to the clearance of the bank transaction and the payment # This test validates a simple reconciliation leading to the clearance of the bank transaction and the payment
def test_reconcile(self): def test_reconcile(self):
@@ -89,42 +75,14 @@ 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(
"Bank Transaction", "Bank Transaction",
dict(description="Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07"), dict(description="Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07"),
) )
linked_payments = get_linked_payments( linked_payments = get_linked_payments(bank_transaction.name, ["payment_entry", "exact_match"])
bank_transaction.name, self.assertTrue(linked_payments[0][3])
["payment_entry", "exact_match"],
from_date=bank_transaction.date,
to_date=utils.today(),
)
self.assertTrue(linked_payments[0]["paid_amount"])
# Check error if already reconciled # Check error if already reconciled
def test_already_reconciled(self): def test_already_reconciled(self):
@@ -191,9 +149,8 @@ class TestBankTransaction(FrappeTestCase):
is not None is not None
) )
@if_lending_app_installed
def test_matching_loan_repayment(self): def test_matching_loan_repayment(self):
from lending.loan_management.doctype.loan.test_loan import create_loan_accounts from erpnext.loan_management.doctype.loan.test_loan import create_loan_accounts
create_loan_accounts() create_loan_accounts()
bank_account = frappe.get_doc( bank_account = frappe.get_doc(
@@ -219,17 +176,10 @@ class TestBankTransaction(FrappeTestCase):
repayment_entry = create_loan_and_repayment() repayment_entry = create_loan_and_repayment()
linked_payments = get_linked_payments(bank_transaction.name, ["loan_repayment", "exact_match"]) linked_payments = get_linked_payments(bank_transaction.name, ["loan_repayment", "exact_match"])
self.assertEqual(linked_payments[0]["name"], repayment_entry.name) self.assertEqual(linked_payments[0][2], repayment_entry.name)
@if_lending_app_installed def create_bank_account(bank_name="Citi Bank", account_name="_Test Bank - _TC"):
def clear_loan_transactions():
frappe.db.delete("Loan Repayment")
def create_bank_account(
bank_name="Citi Bank", gl_account="_Test Bank - _TC", bank_account_name="Checking Account"
):
try: try:
frappe.get_doc( frappe.get_doc(
{ {
@@ -241,35 +191,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 +213,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 +225,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 +237,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 +249,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 +261,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 +283,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 +302,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 +340,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 +352,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()
@@ -439,32 +375,33 @@ def add_vouchers(gl_account="_Test Bank - _TC"):
if not frappe.db.get_value( if not frappe.db.get_value(
"Mode of Payment Account", {"company": "_Test Company", "parent": "Cash"} "Mode of Payment Account", {"company": "_Test Company", "parent": "Cash"}
): ):
mode_of_payment.append("accounts", {"company": "_Test Company", "default_account": gl_account}) mode_of_payment.append(
"accounts", {"company": "_Test Company", "default_account": "_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()
@if_lending_app_installed
def create_loan_and_repayment(): def create_loan_and_repayment():
from lending.loan_management.doctype.loan.test_loan import ( from erpnext.loan_management.doctype.loan.test_loan import (
create_loan, create_loan,
create_loan_product, create_loan_type,
create_repayment_entry, create_repayment_entry,
make_loan_disbursement_entry, make_loan_disbursement_entry,
) )
from lending.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import ( from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import (
process_loan_interest_accrual_for_term_loans, process_loan_interest_accrual_for_term_loans,
) )
from erpnext.setup.doctype.employee.test_employee import make_employee from erpnext.setup.doctype.employee.test_employee import make_employee
create_loan_product( create_loan_type(
"Personal Loan",
"Personal Loan", "Personal Loan",
500000, 500000,
8.4, 8.4,
@@ -485,7 +422,7 @@ def create_loan_and_repayment():
"applicant_type": "Employee", "applicant_type": "Employee",
"company": "_Test Company", "company": "_Test Company",
"applicant": applicant, "applicant": applicant,
"loan_product": "Personal Loan", "loan_type": "Personal Loan",
"loan_amount": 5000, "loan_amount": 5000,
"repayment_method": "Repay Fixed Amount per Period", "repayment_method": "Repay Fixed Amount per Period",
"monthly_repayment_amount": 500, "monthly_repayment_amount": 500,

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

View File

@@ -22,36 +22,6 @@ class DuplicateBudgetError(frappe.ValidationError):
class Budget(Document): class Budget(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.budget_account.budget_account import BudgetAccount
accounts: DF.Table[BudgetAccount]
action_if_accumulated_monthly_budget_exceeded: DF.Literal["", "Stop", "Warn", "Ignore"]
action_if_accumulated_monthly_budget_exceeded_on_mr: DF.Literal["", "Stop", "Warn", "Ignore"]
action_if_accumulated_monthly_budget_exceeded_on_po: DF.Literal["", "Stop", "Warn", "Ignore"]
action_if_annual_budget_exceeded: DF.Literal["", "Stop", "Warn", "Ignore"]
action_if_annual_budget_exceeded_on_mr: DF.Literal["", "Stop", "Warn", "Ignore"]
action_if_annual_budget_exceeded_on_po: DF.Literal["", "Stop", "Warn", "Ignore"]
amended_from: DF.Link | None
applicable_on_booking_actual_expenses: DF.Check
applicable_on_material_request: DF.Check
applicable_on_purchase_order: DF.Check
budget_against: DF.Literal["", "Cost Center", "Project"]
company: DF.Link
cost_center: DF.Link | None
fiscal_year: DF.Link
monthly_distribution: DF.Link | None
naming_series: DF.Data | None
project: DF.Link | None
# end: auto-generated types
def validate(self): def validate(self):
if not self.get(frappe.scrub(self.budget_against)): if not self.get(frappe.scrub(self.budget_against)):
frappe.throw(_("{0} is mandatory").format(self.budget_against)) frappe.throw(_("{0} is mandatory").format(self.budget_against))
@@ -155,27 +125,14 @@ def validate_expense_against_budget(args, expense_amount=0):
if not args.account: if not args.account:
return return
default_dimensions = [ for budget_against in ["project", "cost_center"] + get_accounting_dimensions():
{
"fieldname": "project",
"document_type": "Project",
},
{
"fieldname": "cost_center",
"document_type": "Cost Center",
},
]
for dimension in default_dimensions + get_accounting_dimensions(as_list=False):
budget_against = dimension.get("fieldname")
if ( if (
args.get(budget_against) args.get(budget_against)
and args.account and args.account
and frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"}) and frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"})
): ):
doctype = dimension.get("document_type") doctype = frappe.unscrub(budget_against)
if frappe.get_cached_value("DocType", doctype, "is_tree"): if frappe.get_cached_value("DocType", doctype, "is_tree"):
lft, rgt = frappe.db.get_value(doctype, args.get(budget_against), ["lft", "rgt"]) lft, rgt = frappe.db.get_value(doctype, args.get(budget_against), ["lft", "rgt"])
@@ -227,11 +184,6 @@ def validate_budget_records(args, budget_records, expense_amount):
amount = expense_amount or get_amount(args, budget) amount = expense_amount or get_amount(args, budget)
yearly_action, monthly_action = get_actions(args, budget) yearly_action, monthly_action = get_actions(args, budget)
if yearly_action in ("Stop", "Warn"):
compare_expense_with_budget(
args, flt(budget.budget_amount), _("Annual"), yearly_action, budget.budget_against, amount
)
if monthly_action in ["Stop", "Warn"]: if monthly_action in ["Stop", "Warn"]:
budget_amount = get_accumulated_monthly_budget( budget_amount = get_accumulated_monthly_budget(
budget.monthly_distribution, args.posting_date, args.fiscal_year, budget.budget_amount budget.monthly_distribution, args.posting_date, args.fiscal_year, budget.budget_amount
@@ -243,28 +195,28 @@ def validate_budget_records(args, budget_records, expense_amount):
args, budget_amount, _("Accumulated Monthly"), monthly_action, budget.budget_against, amount args, budget_amount, _("Accumulated Monthly"), monthly_action, budget.budget_against, amount
) )
if (
yearly_action in ("Stop", "Warn")
and monthly_action != "Stop"
and yearly_action != monthly_action
):
compare_expense_with_budget(
args, flt(budget.budget_amount), _("Annual"), yearly_action, budget.budget_against, amount
)
def compare_expense_with_budget(args, budget_amount, action_for, action, budget_against, amount=0): def compare_expense_with_budget(args, budget_amount, action_for, action, budget_against, amount=0):
actual_expense = get_actual_expense(args) actual_expense = amount or get_actual_expense(args)
total_expense = actual_expense + amount if actual_expense > budget_amount:
diff = actual_expense - budget_amount
if total_expense > budget_amount:
if actual_expense > budget_amount:
error_tense = _("is already")
diff = actual_expense - budget_amount
else:
error_tense = _("will be")
diff = total_expense - budget_amount
currency = frappe.get_cached_value("Company", args.company, "default_currency") currency = frappe.get_cached_value("Company", args.company, "default_currency")
msg = _("{0} Budget for Account {1} against {2} {3} is {4}. It {5} exceed by {6}").format( msg = _("{0} Budget for Account {1} against {2} {3} is {4}. It will exceed by {5}").format(
_(action_for), _(action_for),
frappe.bold(args.account), frappe.bold(args.account),
frappe.unscrub(args.budget_against_field), args.budget_against_field,
frappe.bold(budget_against), frappe.bold(budget_against),
frappe.bold(fmt_money(budget_amount, currency=currency)), frappe.bold(fmt_money(budget_amount, currency=currency)),
error_tense,
frappe.bold(fmt_money(diff, currency=currency)), frappe.bold(fmt_money(diff, currency=currency)),
) )
@@ -275,9 +227,9 @@ def compare_expense_with_budget(args, budget_amount, action_for, action, budget_
action = "Warn" action = "Warn"
if action == "Stop": if action == "Stop":
frappe.throw(msg, BudgetError, title=_("Budget Exceeded")) frappe.throw(msg, BudgetError)
else: else:
frappe.msgprint(msg, indicator="orange", title=_("Budget Exceeded")) frappe.msgprint(msg, indicator="orange")
def get_actions(args, budget): def get_actions(args, budget):
@@ -399,9 +351,7 @@ def get_actual_expense(args):
""" """
select sum(gle.debit) - sum(gle.credit) select sum(gle.debit) - sum(gle.credit)
from `tabGL Entry` gle from `tabGL Entry` gle
where where gle.account=%(account)s
is_cancelled = 0
and gle.account=%(account)s
{condition1} {condition1}
and gle.fiscal_year=%(fiscal_year)s and gle.fiscal_year=%(fiscal_year)s
and gle.company=%(company)s and gle.company=%(company)s

View File

@@ -6,19 +6,4 @@ from frappe.model.document import Document
class BudgetAccount(Document): class BudgetAccount(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
budget_amount: DF.Currency
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
# end: auto-generated types
pass pass

View File

@@ -6,18 +6,4 @@ from frappe.model.document import Document
class CampaignItem(Document): class CampaignItem(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
campaign: DF.Link | None
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
# end: auto-generated types
pass pass

View File

@@ -0,0 +1,6 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Cash Flow Mapper', {
});

View File

@@ -0,0 +1,275 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 1,
"autoname": "field:section_name",
"beta": 0,
"creation": "2018-02-08 10:00:14.066519",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Section Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_header",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Section Header",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "e.g Adjustments for:",
"fieldname": "section_leader",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Section Leader",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_subtotal",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Section Subtotal",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_footer",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Section Footer",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "accounts",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Accounts",
"length": 0,
"no_copy": 0,
"options": "Cash Flow Mapping Template Details",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "position",
"fieldtype": "Int",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Position",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-02-15 18:28:55.034933",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Cash Flow Mapper",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "name",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}

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