Compare commits

..

5 Commits

Author SHA1 Message Date
Deepesh Garg
f82ee19619 chore: version bump 2022-06-07 17:21:54 +05:30
Deepesh Garg
0fe0fc44b5 Merge pull request #31268 from frappe/develop
chore: version-14 beta release
2022-06-07 16:57:50 +05:30
Deepesh Garg
3c6d8435a1 chore: version bump 2022-04-20 16:22:16 +05:30
mergify[bot]
c3c59f1274 fix: shift fetching fails in Employee Checkin if shift assignment has an end date (backport #30701) (#30751)
* fix: shift fetching fails in Employee Checkin if shift assignment has an end date

Co-authored-by: Ejaaz Khan <ejaazrkhan@gmail.com>
(cherry picked from commit 98cccf221e)

* test: shift fetching when assignment has an end date

(cherry picked from commit 3cddc1e97e)

Co-authored-by: Rucha Mahabal <ruchamahabal2@gmail.com>
2022-04-20 16:18:45 +05:30
Ankush Menat
fc75cfdeb6 Merge pull request #30647 from frappe/develop
chore: Release for v14.0.0-beta.3
2022-04-08 17:28:16 +05:30
1872 changed files with 315933 additions and 52504 deletions

View File

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

View File

@@ -2,6 +2,13 @@
set -e
# Check for merge conflicts before proceeding
python -m compileall -f "${GITHUB_WORKSPACE}"
if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
then echo "Found merge conflicts"
exit 1
fi
cd ~ || exit
sudo apt update && sudo apt install redis-server libcups2-dev
@@ -24,14 +31,15 @@ fi
if [ "$DB" == "mariadb" ];then
mysql --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 collation_server = 'utf8mb4_unicode_ci'"
mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL character_set_server = 'utf8mb4'"
mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
mysql --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 DATABASE test_frappe"
mysql --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 -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE DATABASE test_frappe"
mysql --host 127.0.0.1 --port 3306 -u root -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"
mysql --host 127.0.0.1 --port 3306 -u root -proot -e "FLUSH PRIVILEGES"
mysql --host 127.0.0.1 --port 3306 -u root -e "UPDATE mysql.user SET Password=PASSWORD('travis') WHERE User='root'"
mysql --host 127.0.0.1 --port 3306 -u root -e "FLUSH PRIVILEGES"
fi
if [ "$DB" == "postgres" ];then
@@ -55,7 +63,6 @@ sed -i 's/schedule:/# schedule:/g' Procfile
sed -i 's/socketio:/# socketio:/g' Procfile
sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile
bench get-app payments
bench get-app erpnext "${GITHUB_WORKSPACE}"
if [ "$TYPE" == "server" ]; then bench setup requirements --dev; fi

View File

@@ -9,7 +9,7 @@
"mail_password": "test",
"admin_password": "admin",
"root_login": "root",
"root_password": "root",
"root_password": "travis",
"host_name": "http://test_site:8000",
"install_apps": ["erpnext"],
"throttle_user_limit": 100

View File

@@ -12,7 +12,7 @@ jobs:
- name: 'Setup Environment'
uses: actions/setup-python@v2
with:
python-version: '3.10'
python-version: 3.8
- name: 'Clone repo'
uses: actions/checkout@v2

View File

@@ -1,32 +0,0 @@
# 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

@@ -11,10 +11,10 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.10
- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: '3.10'
python-version: 3.8
- name: Install and Run Pre-commit
uses: pre-commit/action@v2.0.3
@@ -22,8 +22,10 @@ jobs:
- name: Download Semgrep rules
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules
- name: Download semgrep
run: pip install semgrep==0.97.0
- name: Run Semgrep rules
run: semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness
- uses: returntocorp/semgrep-action@v1
env:
SEMGREP_TIMEOUT: 120
with:
config: >-
r/python.lang.correctness
./frappe-semgrep-rules/rules

View File

@@ -11,7 +11,7 @@ on:
workflow_dispatch:
concurrency:
group: patch-develop-${{ github.event_name }}-${{ github.event.number || github.event_name == 'workflow_dispatch' && github.run_id || '' }}
group: patch-develop-${{ github.event.number }}
cancel-in-progress: true
jobs:
@@ -25,7 +25,7 @@ jobs:
mysql:
image: mariadb:10.3
env:
MARIADB_ROOT_PASSWORD: 'root'
MYSQL_ALLOW_EMPTY_PASSWORD: YES
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
@@ -34,18 +34,10 @@ jobs:
- name: Clone
uses: actions/checkout@v2
- name: Check for valid Python & Merge Conflicts
run: |
python -m compileall -f "${GITHUB_WORKSPACE}"
if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
then echo "Found merge conflicts"
exit 1
fi
- name: Setup Python
uses: "gabrielfalcao/pyenv-action@v9"
uses: actions/setup-python@v2
with:
versions: 3.10:latest, 3.7:latest
python-version: 3.8
- name: Setup Node
uses: actions/setup-node@v2
@@ -60,7 +52,7 @@ jobs:
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }}
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
@@ -90,10 +82,7 @@ jobs:
${{ runner.os }}-yarn-
- name: Install
run: |
pip install frappe-bench
pyenv global $(pyenv versions | grep '3.10')
bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
env:
DB: mariadb
TYPE: server
@@ -107,7 +96,6 @@ jobs:
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
pyenv global $(pyenv versions | grep '3.7')
for version in $(seq 12 13)
do
echo "Updating to v$version"
@@ -119,11 +107,7 @@ jobs:
git -C "apps/frappe" checkout -q -f $branch_name
git -C "apps/erpnext" checkout -q -f $branch_name
rm -rf ~/frappe-bench/env
bench setup env
bench pip install -e ./apps/payments
bench pip install -e ./apps/erpnext
bench setup requirements --python
bench --site test_site migrate
done
@@ -131,12 +115,4 @@ jobs:
echo "Updating to latest version"
git -C "apps/frappe" checkout -q -f "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}"
git -C "apps/erpnext" checkout -q -f "$GITHUB_SHA"
pyenv global $(pyenv versions | grep '3.10')
rm -rf ~/frappe-bench/env
bench -v setup env
bench pip install -e ./apps/payments
bench pip install -e ./apps/erpnext
bench --site test_site migrate
bench --site test_site install-app payments

View File

@@ -1,30 +0,0 @@
name: Semantic Commits
on:
pull_request: {}
permissions:
contents: read
concurrency:
group: commitcheck-erpnext-${{ github.event.number }}
cancel-in-progress: true
jobs:
commitlint:
name: Check Commit Titles
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 200
- uses: actions/setup-node@v3
with:
node-version: 14
check-latest: true
- name: Check commit titles
run: |
npm install @commitlint/cli @commitlint/config-conventional
npx commitlint --verbose --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }}

View File

@@ -27,7 +27,7 @@ on:
type: string
concurrency:
group: server-mariadb-develop-${{ github.event_name }}-${{ github.event.number || github.event_name == 'workflow_dispatch' && github.run_id || '' }}
group: server-mariadb-develop-${{ github.event.number }}
cancel-in-progress: true
jobs:
@@ -39,15 +39,15 @@ jobs:
fail-fast: false
matrix:
container: [1, 2, 3, 4]
container: [1, 2, 3]
name: Python Unit Tests
services:
mysql:
image: mariadb:10.6
image: mariadb:10.3
env:
MARIADB_ROOT_PASSWORD: 'root'
MYSQL_ALLOW_EMPTY_PASSWORD: YES
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
@@ -59,15 +59,7 @@ jobs:
- name: Setup Python
uses: actions/setup-python@v2
with:
python-version: '3.11'
- name: Check for valid Python & Merge Conflicts
run: |
python -m compileall -f "${GITHUB_WORKSPACE}"
if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
then echo "Found merge conflicts"
exit 1
fi
python-version: 3.8
- name: Setup Node
uses: actions/setup-node@v2
@@ -82,7 +74,7 @@ jobs:
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }}
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
@@ -120,7 +112,7 @@ jobs:
FRAPPE_BRANCH: ${{ github.event.inputs.branch }}
- name: Run Tests
run: 'cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --with-coverage --total-builds 4 --build-number ${{ matrix.container }}'
run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --use-orchestrator --with-coverage
env:
TYPE: server
CI_BUILD_ID: ${{ github.run_id }}

View File

@@ -9,7 +9,7 @@ on:
types: [opened, labelled, synchronize, reopened]
concurrency:
group: server-postgres-develop-${{ github.event_name }}-${{ github.event.number || github.event_name == 'workflow_dispatch' && github.run_id || '' }}
group: server-postgres-develop-${{ github.event.number }}
cancel-in-progress: true
jobs:
@@ -21,7 +21,7 @@ jobs:
strategy:
fail-fast: false
matrix:
container: [1]
container: [1, 2, 3]
name: Python Unit Tests
@@ -46,15 +46,7 @@ jobs:
- name: Setup Python
uses: actions/setup-python@v2
with:
python-version: '3.10'
- name: Check for valid Python & Merge Conflicts
run: |
python -m compileall -f "${GITHUB_WORKSPACE}"
if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
then echo "Found merge conflicts"
exit 1
fi
python-version: 3.8
- name: Setup Node
uses: actions/setup-node@v2
@@ -69,7 +61,7 @@ jobs:
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }}
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
@@ -98,6 +90,7 @@ jobs:
restore-keys: |
${{ runner.os }}-yarn-
- name: Install
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
env:

View File

@@ -9,9 +9,6 @@ pull_request_rules:
- author!=nabinhait
- author!=ankush
- author!=deepeshgarg007
- author!=frappe-pr-bot
- author!=mergify[bot]
- or:
- base=version-13
- base=version-12
@@ -22,16 +19,6 @@ pull_request_rules:
@{{author}}, thanks for the contribution, but we do not accept pull requests on a stable branch. Please raise PR on an appropriate hotfix branch.
https://github.com/frappe/erpnext/wiki/Pull-Request-Checklist#which-branch
- name: Auto-close PRs on pre-release branch
conditions:
- base=version-13-pre-release
actions:
close:
comment:
message: |
@{{author}}, pre-release branch is not maintained anymore. Releases are directly done by merging hotfix branch to stable branches.
- name: backport to develop
conditions:
- label="backport develop"

View File

@@ -16,8 +16,8 @@ repos:
- id: check-merge-conflict
- id: check-ast
- repo: https://github.com/PyCQA/flake8
rev: 5.0.4
- repo: https://gitlab.com/pycqa/flake8
rev: 3.9.2
hooks:
- id: flake8
additional_dependencies: [

View File

@@ -3,26 +3,33 @@
# These owners will be the default owners for everything in
# the repo. Unless a later match takes precedence,
erpnext/accounts/ @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
erpnext/assets/ @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
erpnext/accounts/ @nextchamp-saqib @deepeshgarg007
erpnext/assets/ @nextchamp-saqib @deepeshgarg007
erpnext/erpnext_integrations/ @nextchamp-saqib
erpnext/loan_management/ @nextchamp-saqib @deepeshgarg007
erpnext/regional @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
erpnext/selling @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
erpnext/regional @nextchamp-saqib @deepeshgarg007
erpnext/selling @nextchamp-saqib @deepeshgarg007
erpnext/support/ @nextchamp-saqib @deepeshgarg007
pos* @nextchamp-saqib
erpnext/buying/ @rohitwaghchaure @s-aga-r
erpnext/maintenance/ @rohitwaghchaure @s-aga-r
erpnext/manufacturing/ @rohitwaghchaure @s-aga-r
erpnext/quality_management/ @rohitwaghchaure @s-aga-r
erpnext/stock/ @rohitwaghchaure @s-aga-r
erpnext/buying/ @marination @rohitwaghchaure @ankush
erpnext/e_commerce/ @marination
erpnext/maintenance/ @marination @rohitwaghchaure
erpnext/manufacturing/ @marination @rohitwaghchaure @ankush
erpnext/portal/ @marination
erpnext/quality_management/ @marination @rohitwaghchaure
erpnext/shopping_cart/ @marination
erpnext/stock/ @marination @rohitwaghchaure @ankush
erpnext/crm/ @NagariaHussain
erpnext/education/ @rutwikhdev
erpnext/projects/ @ruchamahabal
erpnext/crm/ @ruchamahabal @pateljannat
erpnext/education/ @ruchamahabal @pateljannat
erpnext/hr/ @ruchamahabal @pateljannat
erpnext/payroll @ruchamahabal @pateljannat
erpnext/projects/ @ruchamahabal @pateljannat
erpnext/controllers/ @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure
erpnext/patches/ @deepeshgarg007 @nextchamp-saqib
erpnext/controllers/ @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination @ankush
erpnext/patches/ @deepeshgarg007 @nextchamp-saqib @marination @ankush
erpnext/public/ @nextchamp-saqib @marination
.github/ @ankush
pyproject.toml @ankush
requirements.txt @gavindsouza

View File

@@ -82,8 +82,6 @@ GNU/General Public License (see [license.txt](license.txt))
The ERPNext code is licensed as GNU General Public License (v3) and the Documentation is licensed as Creative Commons (CC-BY-SA-3.0) and the copyright is owned by Frappe Technologies Pvt Ltd (Frappe) and Contributors.
By contributing to ERPNext, you agree that your contributions will be licensed under its GNU General Public License (v3).
## Logo and Trademark Policy
Please read our [Logo and Trademark Policy](TRADEMARK_POLICY.md).

View File

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

1
dev-requirements.txt Normal file
View File

@@ -0,0 +1 @@
hypothesis~=6.31.0

View File

@@ -2,7 +2,7 @@ import inspect
import frappe
__version__ = "14.0.0-dev"
__version__ = "14.0.0-beta.5"
def get_default_company(user=None):

View File

@@ -10,42 +10,4 @@ Entries are:
- Sales Invoice (Itemised)
- Purchase Invoice (Itemised)
All accounting entries are stored in the `General Ledger`
## Payment Ledger
Transactions on Receivable and Payable Account types will also be stored in `Payment Ledger`. This is so that payment reconciliation process only requires update on this ledger.
### Key Fields
| Field | Description |
|----------------------|----------------------------------|
| `account_type` | Receivable/Payable |
| `account` | Accounting head |
| `party` | Party Name |
| `voucher_no` | Voucher No |
| `against_voucher_no` | Linked voucher(secondary effect) |
| `amount` | can be +ve/-ve |
### Design
`debit` and `credit` have been replaced with `account_type` and `amount`. `against_voucher_no` is populated for all entries. So, outstanding amount can be calculated by summing up amount only using `against_voucher_no`.
Ex:
1. Consider an invoice for ₹100 and a partial payment of ₹80 against that invoice. Payment Ledger will have following entries.
| voucher_no | against_voucher_no | amount |
|------------|--------------------|--------|
| SINV-01 | SINV-01 | 100 |
| PAY-01 | SINV-01 | -80 |
2. Reconcile a Credit Note against an invoice using a Journal Entry
An invoice for ₹100 partially reconciled against a credit of ₹70 using a Journal Entry. Payment Ledger will have the following entries.
| voucher_no | against_voucher_no | amount |
|------------|--------------------|--------|
| SINV-01 | SINV-01 | 100 |
| | | |
| CR-NOTE-01 | CR-NOTE-01 | -70 |
| | | |
| JE-01 | CR-NOTE-01 | +70 |
| JE-01 | SINV-01 | -70 |
All accounting entries are stored in the `General Ledger`

View File

@@ -76,7 +76,7 @@ def get(
def build_result(account, dates, gl_entries):
result = [[getdate(date), 0.0] for date in dates]
root_type = frappe.get_cached_value("Account", account, "root_type")
root_type = frappe.db.get_value("Account", account, "root_type")
# start with the first date
date_index = 0

View File

@@ -37,7 +37,7 @@ class Account(NestedSet):
def autoname(self):
from erpnext.accounts.utils import get_autoname_with_number
self.name = get_autoname_with_number(self.account_number, self.account_name, self.company)
self.name = get_autoname_with_number(self.account_number, self.account_name, None, self.company)
def validate(self):
from erpnext.accounts.utils import validate_field_number
@@ -58,7 +58,7 @@ class Account(NestedSet):
def validate_parent(self):
"""Fetch Parent Details and validate parent account"""
if self.parent_account:
par = frappe.get_cached_value(
par = frappe.db.get_value(
"Account", self.parent_account, ["name", "is_group", "company"], as_dict=1
)
if not par:
@@ -82,7 +82,7 @@ class Account(NestedSet):
def set_root_and_report_type(self):
if self.parent_account:
par = frappe.get_cached_value(
par = frappe.db.get_value(
"Account", self.parent_account, ["report_type", "root_type"], as_dict=1
)
@@ -92,7 +92,7 @@ class Account(NestedSet):
self.root_type = par.root_type
if self.is_group:
db_value = self.get_doc_before_save()
db_value = frappe.db.get_value("Account", self.name, ["report_type", "root_type"], as_dict=1)
if db_value:
if self.report_type != db_value.report_type:
frappe.db.sql(
@@ -111,13 +111,13 @@ class Account(NestedSet):
)
def validate_root_details(self):
doc_before_save = self.get_doc_before_save()
if doc_before_save and not doc_before_save.parent_account:
throw(_("Root cannot be edited."), RootNotEditable)
# does not exists parent
if frappe.db.exists("Account", self.name):
if not frappe.db.get_value("Account", self.name, "parent_account"):
throw(_("Root cannot be edited."), RootNotEditable)
if not self.parent_account and not self.is_group:
throw(_("The root account {0} must be a group").format(frappe.bold(self.name)))
frappe.throw(_("The root account {0} must be a group").format(frappe.bold(self.name)))
def validate_root_company_and_sync_account_to_children(self):
# ignore validation while creating new compnay or while syncing to child companies
@@ -127,9 +127,7 @@ class Account(NestedSet):
return
ancestors = get_root_company(self.company)
if ancestors:
if frappe.get_cached_value(
"Company", self.company, "allow_account_creation_against_child_company"
):
if frappe.get_value("Company", self.company, "allow_account_creation_against_child_company"):
return
if not frappe.db.get_value(
"Account", {"account_name": self.account_name, "company": ancestors[0]}, "name"
@@ -140,7 +138,7 @@ class Account(NestedSet):
if not descendants:
return
parent_acc_name_map = {}
parent_acc_name, parent_acc_number = frappe.get_cached_value(
parent_acc_name, parent_acc_number = frappe.db.get_value(
"Account", self.parent_account, ["account_name", "account_number"]
)
filters = {
@@ -161,28 +159,27 @@ class Account(NestedSet):
self.create_account_for_child_company(parent_acc_name_map, descendants, parent_acc_name)
def validate_group_or_ledger(self):
doc_before_save = self.get_doc_before_save()
if not doc_before_save or cint(doc_before_save.is_group) == cint(self.is_group):
if self.get("__islocal"):
return
if self.check_gle_exists():
throw(_("Account with existing transaction cannot be converted to ledger"))
elif self.is_group:
if self.account_type and not self.flags.exclude_account_type_check:
throw(_("Cannot covert to Group because Account Type is selected."))
elif self.check_if_child_exists():
throw(_("Account with child nodes cannot be set as ledger"))
existing_is_group = frappe.db.get_value("Account", self.name, "is_group")
if cint(self.is_group) != cint(existing_is_group):
if self.check_gle_exists():
throw(_("Account with existing transaction cannot be converted to ledger"))
elif self.is_group:
if self.account_type and not self.flags.exclude_account_type_check:
throw(_("Cannot covert to Group because Account Type is selected."))
elif self.check_if_child_exists():
throw(_("Account with child nodes cannot be set as ledger"))
def validate_frozen_accounts_modifier(self):
doc_before_save = self.get_doc_before_save()
if not doc_before_save or doc_before_save.freeze_account == self.freeze_account:
return
frozen_accounts_modifier = frappe.get_cached_value(
"Accounts Settings", "Accounts Settings", "frozen_accounts_modifier"
)
if not frozen_accounts_modifier or frozen_accounts_modifier not in frappe.get_roles():
throw(_("You are not authorized to set Frozen value"))
old_value = frappe.db.get_value("Account", self.name, "freeze_account")
if old_value and old_value != self.freeze_account:
frozen_accounts_modifier = frappe.db.get_value(
"Accounts Settings", None, "frozen_accounts_modifier"
)
if not frozen_accounts_modifier or frozen_accounts_modifier not in frappe.get_roles():
throw(_("You are not authorized to set Frozen value"))
def validate_balance_must_be_debit_or_credit(self):
from erpnext.accounts.utils import get_balance_on
@@ -226,9 +223,9 @@ class Account(NestedSet):
)
# validate if parent of child company account to be added is a group
if frappe.get_cached_value(
"Account", self.parent_account, "is_group"
) and not frappe.get_cached_value("Account", parent_acc_name_map[company], "is_group"):
if frappe.db.get_value("Account", self.parent_account, "is_group") and not frappe.db.get_value(
"Account", parent_acc_name_map[company], "is_group"
):
msg = _(
"While creating account for Child Company {0}, parent account {1} found as a ledger account."
).format(company_bold, parent_acc_name_bold)
@@ -325,9 +322,9 @@ def get_parent_account(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql(
"""select name from tabAccount
where is_group = 1 and docstatus != 2 and company = %s
and %s like %s order by name limit %s offset %s"""
and %s like %s order by name limit %s, %s"""
% ("%s", searchfield, "%s", "%s", "%s"),
(filters["company"], "%%%s%%" % txt, page_len, start),
(filters["company"], "%%%s%%" % txt, start, page_len),
as_list=1,
)
@@ -380,15 +377,17 @@ def validate_account_number(name, account_number, company):
@frappe.whitelist()
def update_account_number(name, account_name, account_number=None, from_descendant=False):
account = frappe.get_cached_doc("Account", name)
account = frappe.db.get_value("Account", name, "company", as_dict=True)
if not account:
return
old_acc_name, old_acc_number = account.account_name, account.account_number
old_acc_name, old_acc_number = frappe.db.get_value(
"Account", name, ["account_name", "account_number"]
)
# check if account exists in parent company
ancestors = get_ancestors_of("Company", account.company)
allow_independent_account_creation = frappe.get_cached_value(
allow_independent_account_creation = frappe.get_value(
"Company", account.company, "allow_account_creation_against_child_company"
)
@@ -436,24 +435,22 @@ def update_account_number(name, account_name, account_number=None, from_descenda
@frappe.whitelist()
def merge_account(old, new, is_group, root_type, company):
# Validate properties before merging
new_account = frappe.get_cached_doc("Account", new)
if not new_account:
if not frappe.db.exists("Account", new):
throw(_("Account {0} does not exist").format(new))
if (new_account.is_group, new_account.root_type, new_account.company) != (
cint(is_group),
root_type,
company,
):
val = list(frappe.db.get_value("Account", new, ["is_group", "root_type", "company"]))
if val != [cint(is_group), root_type, company]:
throw(
_(
"""Merging is only possible if following properties are same in both records. Is Group, Root Type, Company"""
)
)
if is_group and new_account.parent_account == old:
new_account.db_set("parent_account", frappe.get_cached_value("Account", old, "parent_account"))
if is_group and frappe.db.get_value("Account", new, "parent_account") == old:
frappe.db.set_value(
"Account", new, "parent_account", frappe.db.get_value("Account", old, "parent_account")
)
frappe.rename_doc("Account", old, new, merge=1, force=1)

View File

@@ -53,7 +53,7 @@ def create_charts(
"account_number": account_number,
"account_type": child.get("account_type"),
"account_currency": child.get("account_currency")
or frappe.get_cached_value("Company", company, "default_currency"),
or frappe.db.get_value("Company", company, "default_currency"),
"tax_rate": child.get("tax_rate"),
}
)
@@ -148,7 +148,7 @@ def get_charts_for_country(country, with_standard=False):
) or frappe.local.flags.allow_unverified_charts:
charts.append(content["name"])
country_code = frappe.get_cached_value("Country", country, "code")
country_code = frappe.db.get_value("Country", country, "code")
if country_code:
folders = ("verified",)
if frappe.local.flags.allow_unverified_charts:

View File

@@ -2,438 +2,397 @@
"country_code": "at",
"name": "Austria - Chart of Accounts",
"tree": {
"Klasse 0 Aktiva: Anlageverm\u00f6gen": {
"0100 Konzessionen ": {"account_type": "Fixed Asset"},
"0110 Patentrechte und Lizenzen ": {"account_type": "Fixed Asset"},
"0120 Datenverarbeitungsprogramme ": {"account_type": "Fixed Asset"},
"0130 Marken, Warenzeichen und Musterschutzrechte, sonstige Urheberrechte ": {"account_type": "Fixed Asset"},
"0140 Pacht- und Mietrechte ": {"account_type": "Fixed Asset"},
"0150 Bezugs- und ähnliche Rechte ": {"account_type": "Fixed Asset"},
"0160 Geschäfts-/Firmenwert ": {"account_type": "Fixed Asset"},
"0170 Umgründungsmehrwert ": {"account_type": "Fixed Asset"},
"0180 Geleistete Anzahlungen auf immaterielle Vermögensgegenstände": {"account_type": "Fixed Asset"},
"0190 Kumulierte Abschreibungen zu immateriellen Vermögensgegenständen ": {"account_type": "Fixed Asset"},
"0200 Unbebaute Grundstücke, soweit nicht landwirtschaftlich genutzt ": {"account_type": "Fixed Asset"},
"0210 Bebaute Grundstücke (Grundwert) ": {"account_type": "Fixed Asset"},
"0220 Landwirtschaftlich genutzte Grundstücke ": {"account_type": "Fixed Asset"},
"0230 Grundstücksgleiche Rechte ": {"account_type": "Fixed Asset"},
"0300 Betriebs- und Geschäftsgebäude auf eigenem Grund ": {"account_type": "Fixed Asset"},
"0310 Wohn- und Sozialgebäude auf eigenem Grund ": {"account_type": "Fixed Asset"},
"0320 Betriebs- und Geschäftsgebäude auf fremdem Grund ": {"account_type": "Fixed Asset"},
"0330 Wohn- und Sozialgebäude auf fremdem Grund ": {"account_type": "Fixed Asset"},
"0340 Grundstückseinrichtungen auf eigenem Grund ": {"account_type": "Fixed Asset"},
"0350 Grundstückseinrichtungen auf fremdem Grund ": {"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"},
"0390 Kumulierte Abschreibungen zu Grundstücken ": {"account_type": "Fixed Asset"},
"0400 Maschinen und Geräte ": {"account_type": "Fixed Asset"},
"0500 Maschinenwerkzeuge ": {"account_type": "Fixed Asset"},
"0510 Allgemeine Werkzeuge und Handwerkzeuge ": {"account_type": "Fixed Asset"},
"0520 Prototypen, Formen, Modelle ": {"account_type": "Fixed Asset"},
"0530 Andere Erzeugungshilfsmittel (auch Softwarewerkzeuge)": {"account_type": "Fixed Asset"},
"0540 Hebezeuge und Montageanlagen ": {"account_type": "Fixed Asset"},
"0550 Geringwertige Vermögensgegenstände, soweit im Erzeugungsprozess ": {"account_type": "Fixed Asset"},
"0560 Festwerte technische Anlagen und Maschinen ": {"account_type": "Fixed Asset"},
"0590 Kumulierte Abschreibungen zu technischen Anlagen und Maschinen ": {"account_type": "Fixed Asset"},
"0600 Betriebs- und Geschäftsausstattung, soweit nicht gesondert angeführt ": {"account_type": "Fixed Asset"},
"0610 Andere Anlagen, soweit nicht gesondert angeführt ": {"account_type": "Fixed Asset"},
"0620 Büromaschinen, EDV-Anlagen ": {"account_type": "Fixed Asset"},
"0630 PKW und Kombis ": {"account_type": "Fixed Asset"},
"0640 LKW ": {"account_type": "Fixed Asset"},
"0650 Andere Beförderungsmittel ": {"account_type": "Fixed Asset"},
"0660 Gebinde ": {"account_type": "Fixed Asset"},
"0670 Geringwertige Vermögensgegenstände, soweit nicht im Erzeugungssprozess verwendet": {"account_type": "Fixed Asset"},
"0680 Festwerte außer technische Anlagen und Maschinen ": {"account_type": "Fixed Asset"},
"0690 Kumulierte Abschreibungen zu anderen Anlagen, Betriebs- und Geschäftsausstattung": {"account_type": "Fixed Asset"},
"0700 Geleistete Anzahlungen auf Sachanlagen ": {"account_type": "Fixed Asset"},
"0710 Anlagen in Bau ": {"account_type": "Fixed Asset"},
"0790 Kumulierte Abschreibungen zu geleisteten Anzahlungen auf Sachanlagen ": {"account_type": "Fixed Asset"},
"0800 Anteile an verbundenen Unternehmen ": {"account_type": "Fixed Asset"},
"0810 Beteiligungen an Gemeinschaftsunternehmen ": {"account_type": "Fixed Asset"},
"0820 Beteiligungen an angeschlossenen (assoziierten) Unternehmen ": {"account_type": "Fixed Asset"},
"0830 Eigene Anteile, Anteile an herrschenden oder mit Mehrheit beteiligten ": {"account_type": "Fixed Asset"},
"0840 Sonstige Beteiligungen ": {"account_type": "Fixed Asset"},
"0850 Ausleihungen an verbundene Unternehmen ": {"account_type": "Fixed Asset"},
"0860 Ausleihungen an Unternehmen mit Beteiligungsverhältnis": {"account_type": "Fixed Asset"},
"0870 Ausleihungen an Gesellschafter ": {"account_type": "Fixed Asset"},
"0880 Sonstige Ausleihungen ": {"account_type": "Fixed Asset"},
"0890 Anteile an Kapitalgesellschaften ohne Beteiligungscharakter ": {"account_type": "Fixed Asset"},
"0900 Anteile an Personengesellschaften ohne Beteiligungscharakter ": {"account_type": "Fixed Asset"},
"0910 Genossenschaftsanteile ohne Beteiligungscharakter ": {"account_type": "Fixed Asset"},
"0920 Anteile an Investmentfonds ": {"account_type": "Fixed Asset"},
"0930 Festverzinsliche Wertpapiere des Anlagevermögens ": {"account_type": "Fixed Asset"},
"0980 Geleistete Anzahlungen auf Finanzanlagen ": {"account_type": "Fixed Asset"},
"0990 Kumulierte Abschreibungen zu Finanzanlagen ": {"account_type": "Fixed Asset"},
"root_type": "Asset"
},
"Klasse 1 Aktiva: Vorr\u00e4te": {
"1000 Bezugsverrechnung": {"account_type": "Stock"},
"1100 Rohstoffe": {"account_type": "Stock"},
"1200 Bezogene Teile": {"account_type": "Stock"},
"1300 Hilfsstoffe": {"account_type": "Stock"},
"1350 Betriebsstoffe": {"account_type": "Stock"},
"1360 Vorrat Energietraeger": {"account_type": "Stock"},
"1400 Unfertige Erzeugnisse": {"account_type": "Stock"},
"1500 Fertige Erzeugnisse": {"account_type": "Stock"},
"1600 Handelswarenvorrat": {"account_type": "Stock Received But Not Billed"},
"1700 Noch nicht abrechenbare Leistungen": {"account_type": "Stock"},
"1900 Wertberichtigungen": {"account_type": "Stock"},
"1800 Geleistete Anzahlungen": {"account_type": "Stock"},
"1900 Wertberichtigungen": {"account_type": "Stock"},
"root_type": "Asset"
},
"Klasse 3 Passiva: Verbindlichkeiten": {
"3000 Allgemeine Verbindlichkeiten (Schuld)": {"account_type": "Payable"},
"3010 R\u00fcckstellungen f\u00fcr Pensionen": {"account_type": "Payable"},
"3020 Steuerr\u00fcckstellungen": {"account_type": "Tax"},
"3041 Sonstige R\u00fcckstellungen": {"account_type": "Payable"},
"3110 Verbindlichkeiten gegen\u00fcber Bank": {"account_type": "Payable"},
"3150 Verbindlichkeiten Darlehen": {"account_type": "Payable"},
"3185 Verbindlichkeiten Kreditkarte": {"account_type": "Payable"},
"3380 Verbindlichkeiten aus der Annahme gezogener Wechsel u. d. Ausstellungen eigener Wechsel": {
"account_type": "Payable"
},
"3400 Verbindlichkeiten gegen\u00fc. verb. Untern., Verbindl. gegen\u00fc. Untern., mit denen eine Beteiligungsverh\u00e4lnis besteht": {},
"3460 Verbindlichkeiten gegenueber Gesellschaftern": {"account_type": "Payable"},
"3470 Einlagen stiller Gesellschafter": {"account_type": "Payable"},
"3585 Verbindlichkeiten Lohnsteuer": {"account_type": "Tax"},
"3590 Verbindlichkeiten Kommunalabgaben": {"account_type": "Tax"},
"3595 Verbindlichkeiten Dienstgeberbeitrag": {"account_type": "Tax"},
"3600 Verbindlichkeiten Sozialversicherung": {"account_type": "Payable"},
"3640 Verbindlichkeiten Loehne und Gehaelter": {"account_type": "Payable"},
"3700 Sonstige Verbindlichkeiten": {"account_type": "Payable"},
"3900 Passive Rechnungsabgrenzungsposten": {"account_type": "Payable"},
"3100 Anleihen (einschlie\u00dflich konvertibler)": {"account_type": "Payable"},
"3200 Erhaltene Anzahlungen auf Bestellungen": {"account_type": "Payable"},
"3040 R\u00fcckstellungen f\u00fcr Abfertigung": {"account_type": "Payable"},
"3530 USt. \u00a719 (reverse charge)": {
"Summe Abschreibungen und Aufwendungen": {
"7010 bis 7080 Abschreibungen auf das Anlageverm\u00f6gen (ausgenommen Finanzanlagen)": {},
"7100 bis 7190 Sonstige Steuern": {
"account_type": "Tax"
},
"3500 Verbindlichkeiten aus Umsatzsteuer": {"account_type": "Tax"},
"3580 Umsatzsteuer Zahllast": {
"account_type": "Tax"
},
"3510 Umsatzsteuer Inland 20%": {
"account_type": "Tax"
},
"3515 Umsatzsteuer Inland 10%": {
"account_type": "Tax"
},
"3520 Umsatzsteuer aus i.g. Erwerb 20%": {
"account_type": "Tax"
},
"3525 Umsatzsteuer aus i.g. Erwerb 10%": {
"account_type": "Tax"
},
"3560 Umsatzsteuer-Evidenzkonto f\u00fcr erhaltene Anzahlungen auf Bestellungen": {},
"3360 Verbindlichkeiten aus Lieferungen u. Leistungen EU": {
"account_type": "Payable"
},
"3000 Verbindlichkeiten aus Lieferungen u. Leistungen Inland": {
"account_type": "Payable"
},
"3370 Verbindlichkeiten aus Lieferungen u. Leistungen sonst. Ausland": {
"account_type": "Payable"
},
"3400 Verbindlichkeiten gegen\u00fcber verbundenen Unternehmen": {},
"3570 Verrechnung Finanzamt": {
"account_type": "Tax"
},
"root_type": "Liability"
},
"Klasse 2 Aktiva: Umlaufverm\u00f6gen, Rechnungsabgrenzungen": {
"2030 Forderungen aus Lieferungen und Leistungen Inland (0% USt, umsatzsteuerfrei)": {
"account_type": "Receivable"
},
"2010 Forderungen aus Lieferungen und Leistungen Inland (10% USt, umsatzsteuerfrei)": {
"account_type": "Receivable"
},
"2000 Forderungen aus Lieferungen und Leistungen Inland (20% USt, umsatzsteuerfrei)": {
"account_type": "Receivable"
},
"2040 Forderungen aus Lieferungen und Leistungen Inland (sonstiger USt-Satz)": {
"account_type": "Receivable"
},
"2100 Forderungen aus Lieferungen und Leistungen EU": {
"account_type": "Receivable"
},
"2150 Forderungen aus Lieferungen und Leistungen Ausland (Nicht-EU)": {
"account_type": "Receivable"
},
"2200 Forderungen gegen\u00fcber verbundenen Unternehmen": {
"account_type": "Receivable"
},
"2250 Forderungen gegen\u00fcber Unternehmen, mit denen ein Beteiligungsverh\u00e4ltnis besteht": {
"account_type": "Receivable"
},
"2300 Sonstige Forderungen und Verm\u00f6gensgegenst\u00e4nde": {
"account_type": "Receivable"
},
"2630 Sonstige Wertpapiere": {
"account_type": "Stock"
},
"2750 Kassenbest\u00e4nde in Fremdw\u00e4hrung": {
"account_type": "Cash"
},
"2900 Aktive Rechnungsabrenzungsposten": {
"account_type": "Receivable"
},
"2600 Anteile an verbundenen Unternehmen": {
"account_type": "Equity"
},
"2680 Besitzwechsel ohne Forderungen": {
"account_type": "Receivable"
},
"2950 Aktiviertes Disagio": {
"account_type": "Receivable"
},
"2610 Eigene Anteile und Wertpapiere an mit Mehrheit beteiligten Unternehmen": {
"account_type": "Receivable"
},
"2570 Einfuhrumsatzsteuer (bezahlt)": {"account_type": "Tax"},
"2460 Eingeforderte aber noch nicht eingezahlte Einlagen": {
"account_type": "Receivable"
},
"2180 Einzelwertberichtigungen zu Forderungen aus Lief. und Leist. Ausland": {
"account_type": "Receivable"
},
"2130 Einzelwertberichtigungen zu Forderungen aus Lief. und Leist. EU": {
"account_type": "Receivable"
},
"2080 Einzelwertberichtigungen zu Forderungen aus Lief. und Leist. Inland ": {
"account_type": "Receivable"
},
"2270 Einzelwertberichtigungen zu Forderungen gegen\u00fcber Unternehmen mit denen ein Beteiligungsverh\u00e4ltnis besteht": {
"account_type": "Receivable"
},
"2230 Einzelwertberichtigungen zu Forderungen gegen\u00fcber verbundenen Unternehmen": {
"account_type": "Receivable"
},
"2470 Einzelwertberichtigungen zu sonstigen Forderungen und Verm\u00f6gensgegenst\u00e4nden": {
"account_type": "Receivable"
},
"2700 Kassenbestand": {
"account_type": "Cash"
},
"2190 Pauschalwertberichtigungen zu Forderungen aus Lief. und Leist. sonstiges Ausland": {
"account_type": "Receivable"
},
"2130 Pauschalwertberichtigungen zu Forderungen aus Lief. und Leist. EU": {
"account_type": "Receivable"
},
"2100 Pauschalwertberichtigungen zu Forderungen aus Lief. und Leist. Inland ": {
"account_type": "Receivable"
},
"2280 Pauschalwertberichtigungen zu Forderungen gegen\u00fcber Unternehmen mit denen ein Beteiligungsverh\u00e4ltnis besteht": {
"account_type": "Receivable"
},
"2240 Pauschalwertberichtigungen zu Forderungen gegen\u00fcber verbundenen Unternehmen": {
"account_type": "Receivable"
},
"2480 Pauschalwertberichtigungen zu sonstigen Forderungen und Verm\u00f6gensgegenst\u00e4nden": {
"account_type": "Receivable"
},
"2740 Postwertzeichen": {
"account_type": "Cash"
},
"2780 Schecks in Euro": {
"account_type": "Cash"
},
"2800 Guthaben bei Bank": {
"account_type": "Bank"
},
"2801 Guthaben bei Bank - Sparkonto": {
"account_type": "Bank"
},
"2810 Guthaben bei Paypal": {
"account_type": "Bank"
},
"2930 Mietvorauszahlungen": {
"account_type": "Receivable"
},
"2980 Abgrenzung latenter Steuern": {
"account_type": "Receivable"
},
"2500 Vorsteuer": {
"account_type": "Receivable"
},
"2510 Vorsteuer Inland 10%": {
"account_type": "Tax"
},
"2895 Schwebende Geldbewegugen": {
"account_type": "Bank"
},
"2513 Vorsteuer Inland 5%": {
"account_type": "Tax"
},
"2515 Vorsteuer Inland 20%": {
"account_type": "Tax"
},
"2520 Vorsteuer aus innergemeinschaftlichem Erwerb 10%": {
"account_type": "Tax"
},
"2525 Vorsteuer aus innergemeinschaftlichem Erwerb 20%": {
"account_type": "Tax"
},
"2530 Vorsteuer \u00a719/Art 19 ( reverse charge ) ": {
"account_type": "Tax"
},
"2690 Wertberichtigungen zu Wertpapieren und Anteilen": {
"account_type": "Receivable"
},
"root_type": "Asset"
"7200 bis 7290 Instandhaltung u. Reinigung durh Dritte, Entsorgung, Beleuchtung": {},
"7300 bis 7310 Transporte durch Dritte": {},
"7320 bis 7330 Kfz - Aufwand": {},
"7340 bis 7350 Reise- und Fahraufwand": {},
"7360 bis 7370 Tag- und N\u00e4chtigungsgelder": {},
"7380 bis 7390 Nachrichtenaufwand": {},
"7400 bis 7430 Miet- und Pachtaufwand": {},
"7440 bis 7470 Leasingaufwand": {},
"7480 bis 7490 Lizenzaufwand": {},
"7500 bis 7530 Aufwand f\u00fcr beigestelltes Personal": {},
"7540 bis 7570 Provisionen an Dritte": {},
"7580 bis 7590 Aufsichtsratsverg\u00fctungen": {},
"7610 bis 7620 Druckerzeugnisse und Vervielf\u00e4ltigungen": {},
"7650 bis 7680 Werbung und Repr\u00e4sentationen": {},
"7700 bis 7740 Versicherungen": {},
"7750 bis 7760 Beratungs- und Pr\u00fcfungsaufwand": {},
"7800 bis 7810 Schadensf\u00e4lle": {},
"7840 bis 7880 Verschiedene betriebliche Aufwendungen": {},
"7910 bis 7950 Aufwandsstellenrechung der Hersteller": {},
"Abschreibungen auf aktivierte Aufwendungen f\u00fcr das Ingangs. u. Erweitern des Betriebes": {},
"Abschreibungen vom Umlaufverm\u00f6gen, soweit diese die im Unternehmen \u00fcblichen Abschreibungen \u00fcbersteigen": {},
"Aufwandsstellenrechnung": {},
"Aus- und Fortbildung": {},
"Buchwert abgegangener Anlagen, ausgenommen Finanzanlagen": {},
"B\u00fcromaterial und Drucksorten": {},
"Fachliteratur und Zeitungen ": {},
"Herstellungskosten der zur Erzielung der Umsatzerl\u00f6se erbrachten Leistungen": {},
"Mitgliedsbeitr\u00e4ge": {},
"Skontoertr\u00e4ge auf sonstige betriebliche Aufwendungen": {},
"Sonstige betrieblichen Aufwendungen": {},
"Spenden und Trinkgelder": {},
"Spesen des Geldverkehrs": {},
"Verluste aus dem Abgang vom Anlageverm\u00f6gen, ausgenommen Finanzanlagen": {},
"Vertriebskosten": {},
"Verwaltungskosten": {},
"root_type": "Expense"
},
"Klasse 4: Betriebliche Erträge": {
"4000 Erlöse 20 %": {"account_type": "Income Account"},
"4020 Erl\u00f6se 0 % steuerbefreit": {"account_type": "Income Account"},
"4010 Erl\u00f6se 10 %": {"account_type": "Income Account"},
"4030 Erl\u00f6se 13 %": {"account_type": "Income Account"},
"4040 Erl\u00f6se 0 % innergemeinschaftliche Lieferungen": {"account_type": "Income Account"},
"4400 Erl\u00f6sreduktion 0 % steuerbefreit": {"account_type": "Expense Account"},
"4410 Erl\u00f6sreduktion 10 %": {"account_type": "Expense Account"},
"4420 Erl\u00f6sreduktion 20 %": {"account_type": "Expense Account"},
"4430 Erl\u00f6sreduktion 13 %": {"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"},
"4580 Aktivierte Eigenleistungen": {"account_type": "Income Account"},
"4600 Erl\u00f6se aus dem Abgang vom Anlageverm\u00f6gen, ausgen. Finanzanlagen": {"account_type": "Income Account"},
"4630 Ertr\u00e4ge aus dem Abgang vom Anlageverm\u00f6gen, ausgen. Finanzanlagen": {"account_type": "Income Account"},
"4660 Ertr\u00e4ge aus der Zuschreibung zum Anlageverm\u00f6gen, ausgen. Finanzanlagen": {"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"},
"Summe Betriebliche Ertr\u00e4ge": {
"4400 bis 4490 Erl\u00f6sschm\u00e4lerungen": {},
"4500 bis 4570 Ver\u00e4nderungen des Bestandes an fertigen und unfertigen Erzeugn. sowie an noch nicht abrechenbaren Leistungen": {},
"4580 bis 4590 andere aktivierte Eigenleistungen": {},
"4600 bis 4620 Erl\u00f6se aus dem Abgang vom Anlageverm\u00f6gen, ausgen. Finanzanlagen": {},
"4630 bis 4650 Ertr\u00e4ge aus dem Abgang vom Anlageverm\u00f6gen, ausgen. Finanzanlagen": {},
"4660 bis 4670 Ertr\u00e4ge aus der Zuschreibung zum Anlageverm\u00f6gen, ausgen. Finanzanlagen": {},
"4700 bis 4790 Ertr\u00e4ge aus der Aufl\u00f6sung von R\u00fcckstellungen": {},
"4800 bis 4990 \u00dcbrige betriebliche Ertr\u00e4ge": {},
"Erl\u00f6se 0 % Ausfuhrlieferungen/Drittl\u00e4nder": {},
"Erl\u00f6se 10 %": {},
"Erl\u00f6se 20 %": {},
"Erl\u00f6se aus im Inland stpfl. EG Lieferungen 10 % USt": {},
"Erl\u00f6se aus im Inland stpfl. EG Lieferungen 20 % USt": {},
"Erl\u00f6se i.g. Lieferungen (stfr)": {},
"root_type": "Income"
},
"Klasse 5: Aufwand f\u00fcr Material und Leistungen": {
"5000 Einkauf Partnerleistungen": {"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"},
"5300 Verbrauch von Hilfsstoffen": {"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"},
"5450 Verbrauch von Reinigungsmaterial": {"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"},
"5600 Verbrauch von Brenn- und Treibstoffen, Energie und Wasser": {"account_type": "Cost of Goods Sold"},
"5700 Bearbeitung durch Dritte": {"account_type": "Cost of Goods Sold"},
"5900 Aufwandsstellenrechnung Material": {"account_type": "Cost of Goods Sold"},
"5820 Skontoertr\u00e4ge (20% USt.)": {"account_type": "Income Account"},
"5810 Skontoertr\u00e4ge (10% USt.)": {"account_type": "Income Account"},
"5010 Handelswareneinkauf 10 %": {"account_type": "Cost of Goods Sold"},
"5020 Handelswareneinkauf 20 %": {"account_type": "Cost of Goods Sold"},
"5040 Handelswareneinkauf innergemeinschaftlicher Erwerb 10 % VSt/10 % USt": {"account_type": "Cost of Goods Sold"},
"5050 Handelswareneinkauf innergemeinschaftlicher Erwerb 20 % VSt/20 % USt": {"account_type": "Cost of Goods Sold"},
"5070 Handelswareneinkauf innergemeinschaftlicher Erwerb ohne Vorsteuerabzug und 10 % USt": {"account_type": "Cost of Goods Sold"},
"5080 Handelswareneinkauf innergemeinschaftlicher Erwerb ohne Vorsteuerabzug und 20 % USt": {"account_type": "Cost of Goods Sold"},
"root_type": "Expense"
},
"Klasse 6: Personalaufwand": {
"6000 L\u00f6hne": {"account_type": "Payable"},
"6200 Geh\u00e4lter": {"account_type": "Payable"},
"6400 Aufwendungen f\u00fcr Abfertigungen": {"account_type": "Payable"},
"6450 Aufwendungen f\u00fcr Altersversorgung": {"account_type": "Payable"},
"6500 Gesetzlicher Sozialaufwand Arbeiter": {"account_type": "Payable"},
"6560 Gesetzlicher Sozialaufwand Angestellte": {"account_type": "Payable"},
"6600 Lohnabh\u00e4ngige Abgaben und Pflichtbeitr\u00e4gte": {"account_type": "Payable"},
"6660 Gehaltsabh\u00e4ngige Abgaben und Pflichtbeitr\u00e4gte": {"account_type": "Payable"},
"6700 Sonstige Sozialaufwendungen": {"account_type": "Payable"},
"6900 Aufwandsstellenrechnung Personal": {"account_type": "Payable"},
"root_type": "Expense"
},
"Klasse 7: Abschreibungen und sonstige betriebliche Aufwendungen": {
"7010 Abschreibungen auf das Anlageverm\u00f6gen (ausgenommen Finanzanlagen)": {"account_type": "Depreciation"},
"7100 Sonstige Steuern und Geb\u00fchren": {"account_type": "Tax"},
"7200 Instandhaltung u. Reinigung durch Dritte, Entsorgung, Energie": {"account_type": "Expense Account"},
"7300 Transporte durch Dritte": {"account_type": "Expense Account"},
"7310 Fahrrad - Aufwand": {"account_type": "Expense Account"},
"7320 Kfz - Aufwand": {"account_type": "Expense Account"},
"7330 LKW - Aufwand": {"account_type": "Expense Account"},
"7340 Lastenrad - Aufwand": {"account_type": "Expense Account"},
"7350 Reise- und Fahraufwand": {"account_type": "Expense Account"},
"7360 Tag- und N\u00e4chtigungsgelder": {"account_type": "Expense Account"},
"7380 Nachrichtenaufwand": {"account_type": "Expense Account"},
"7400 Miet- und Pachtaufwand": {"account_type": "Expense Account"},
"7440 Leasingaufwand": {"account_type": "Expense Account"},
"7480 Lizenzaufwand": {"account_type": "Expense Account"},
"7500 Aufwand f\u00fcr beigestelltes Personal": {"account_type": "Expense Account"},
"7540 Provisionen an Dritte": {"account_type": "Expense Account"},
"7580 Aufsichtsratsverg\u00fctungen": {"account_type": "Expense Account"},
"7610 Druckerzeugnisse und Vervielf\u00e4ltigungen": {"account_type": "Expense Account"},
"7650 Werbung und Repr\u00e4sentationen": {"account_type": "Expense Account"},
"7700 Versicherungen": {"account_type": "Expense Account"},
"7750 Beratungs- und Pr\u00fcfungsaufwand": {"account_type": "Expense Account"},
"7800 Forderungsverluste und Schadensf\u00e4lle": {"account_type": "Expense Account"},
"7840 Verschiedene betriebliche Aufwendungen": {"account_type": "Expense Account"},
"7910 Aufwandsstellenrechung der Hersteller": {"account_type": "Expense Account"},
"7060 Sofortabschreibungen geringwertig": {"account_type": "Expense Account"},
"7070 Abschreibungen vom Umlaufverm\u00f6gen, soweit diese die im Unternehmen \u00fcblichen Abschreibungen \u00fcbersteigen": {"account_type": "Depreciation"},
"7900 Aufwandsstellenrechnung": {"account_type": "Expense Account"},
"7770 Aus- und Fortbildung": {"account_type": "Expense Account"},
"7820 Buchwert abgegangener Anlagen, ausgenommen Finanzanlagen": {"account_type": "Expense Account"},
"7600 B\u00fcromaterial und Drucksorten": {"account_type": "Expense Account"},
"7630 Fachliteratur und Zeitungen ": {"account_type": "Expense Account"},
"7960 Herstellungskosten der zur Erzielung der Umsatzerl\u00f6se erbrachten Leistungen": {"account_type": "Expense Account"},
"7780 Mitgliedsbeitr\u00e4ge": {"account_type": "Expense Account"},
"7880 Skontoertr\u00e4ge auf sonstige betriebliche Aufwendungen": {"account_type": "Expense Account"},
"7990 Sonstige betrieblichen Aufwendungen": {"account_type": "Expense Account"},
"7680 Spenden und Trinkgelder": {"account_type": "Expense Account"},
"7790 Spesen des Geldverkehrs": {"account_type": "Expense Account"},
"7830 Verluste aus dem Abgang vom Anlageverm\u00f6gen, ausgenommen Finanzanlagen": {"account_type": "Expense Account"},
"7970 Vertriebskosten": {"account_type": "Expense Account"},
"7980 Verwaltungskosten": {"account_type": "Expense Account"},
"root_type": "Expense"
},
"Klasse 8: Finanz- und ausserordentliche Ertr\u00e4ge und Aufwendungen": {
"8000 Ertr\u00e4ge aus Beteiligungen": {"account_type": "Income Account"},
"8050 Ertr\u00e4ge aus anderen Wertpapieren und Ausleihungen des Finanzanlageverm\u00f6gens": {"account_type": "Income Account"},
"8100 Zinsen aus Bankguthaben": {"account_type": "Income Account"},
"8110 Zinsen aus gewaehrten Darlehen": {"account_type": "Income Account"},
"8130 Verzugszinsenertraege": {"account_type": "Income Account"},
"8220 Aufwendungen aus Beteiligungen": {"account_type": "Expense Account"},
"8260 Aufwendungen aus sonst. Fiananzanlagen und aus Wertpapieren des Umlaufverm\u00f6gens": {},
"8280 Zinsen und \u00e4hnliche Aufwendungem": {"account_type": "Expense Account"},
"8400 Au\u00dferordentliche Ertr\u00e4ge": {"account_type": "Income Account"},
"8450 Au\u00dferordentliche Aufwendungen": {"account_type": "Expense Account"},
"8500 Steuern vom Einkommen und vom Ertrag": {
"account_type": "Tax"
},
"8600 Aufl\u00f6sung unversteuerten R\u00fccklagen": {"account_type": "Income Account"},
"8700 Aufl\u00f6sung von Kapitalr\u00fccklagen": {"account_type": "Income Account"},
"8750 Aufl\u00f6sung von Gewinnr\u00fccklagen": {"account_type": "Income Account"},
"8800 Zuweisung zu unversteuerten R\u00fccklagen": {"account_type": "Expense Account"},
"8900 Zuweisung zu Gewinnr\u00fccklagen": {"account_type": "Expense Account"},
"8100 Buchwert abgegangener Beteiligungen": {"account_type": "Expense Account"},
"8130 Buchwert abgegangener Wertpapiere des Umlaufverm\u00f6gens": {"account_type": "Expense Account"},
"8120 Buchwert abgegangener sonstiger Finanzanlagen": {"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"},
"root_type": "Income"
},
"Klasse 9 Passiva: Eigenkapital, R\u00fccklagen, stille Einlagen, Abschlusskonten": {
"9000 Gezeichnetes bzw. gewidmetes Kapital": {
"Summe Eigenkapital R\u00fccklagen Abschlusskonten": {
"9000 bis 9180 Gezeichnetes bzw. gewidmetes Kapital": {
"account_type": "Equity"
},
"9200 Kapitalr\u00fccklagen": {
"9200 bis 9290 Kapitalr\u00fccklagen": {
"account_type": "Equity"
},
"9300 Gewinnr\u00fccklagen": {
"9300 bis 9380 Gewinnr\u00fccklagen": {
"account_type": "Equity"
},
"9400 Bewertungsreserven uns sonst. unversteuerte R\u00fccklagen": {
"9400 bis 9590 Bewertungsreserven uns sonst. unversteuerte R\u00fccklagen": {
"account_type": "Equity"
},
"9600 Private Entnahmen": {"account_type": "Equity"},
"9610 Privatsteuern": {"account_type": "Equity"},
"9700 Einlagen stiller Gesellschafter ": {"account_type": "Equity"},
"9900 Evidenzkonto": {"account_type": "Equity"},
"9800 Er\u00f6ffnungsbilanzkonto (EBK)": {"account_type": "Equity"},
"9880 Jahresergebnis laut Gewinn- und Verlustrechnung (G+V)": {"account_type": "Equity"},
"9850 Schlussbilanzkonto (SBK)": {"account_type": "Round Off"},
"9190 nicht eingeforderte ausstehende Einlagen und berechtigte Entnahmen von Gesellschaftern": {
"9600 bis 9690 Privat und Verrechnungskonten bei Einzelunternehmen und Personengesellschaften": {},
"9700 bis 9790 Einlagen stiller Gesellschafter ": {},
"9900 bis 9999 Evidenzkonten": {},
"Bilanzgewinn (-verlust )": {
"account_type": "Equity"
},
"Er\u00f6ffnungsbilanz": {},
"Gewinn- und Verlustrechnung": {},
"Schlussbilanz": {},
"nicht eingeforderte ausstehende Einlagen": {
"account_type": "Equity"
},
"root_type": "Equity"
},
"Summe Finanzertr\u00e4ge und Aufwendungen": {
"8000 bis 8040 Ertr\u00e4ge aus Beteiligungen": {},
"8050 bis 8090 Ertr\u00e4ge aus anderen Wertpapieren und Ausleihungen des Finanzanlageverm\u00f6gens": {},
"8100 bis 8130 Sonstige Zinsen und \u00e4hnliche Ertr\u00e4ge": {},
"8220 bis 8250 Aufwendungen aus Beteiligungen": {},
"8260 bis 8270 Aufwendungen aus sonst. Fiananzanlagen und aus Wertpapieren des Umlaufverm\u00f6gens": {},
"8280 bis 8340 Zinsen und \u00e4hnliche Aufwendungem": {},
"8400 bis 8440 Au\u00dferordentliche Ertr\u00e4ge": {},
"8450 bis 8490 Au\u00dferordentliche Aufwendungen": {},
"8500 bis 8590 Steuern vom Einkommen und vom Ertrag": {
"account_type": "Tax"
},
"8600 bis 8690 Aufl\u00f6sung unversteuerten R\u00fccklagen": {},
"8700 bis 8740 Aufl\u00f6sung von Kapitalr\u00fccklagen": {},
"8750 bis 8790 Aufl\u00f6sung von Gewinnr\u00fccklagen": {},
"8800 bis 8890 Zuweisung von unversteuerten R\u00fccklagen": {},
"Buchwert abgegangener Beteiligungen": {},
"Buchwert abgegangener Wertpapiere des Umlaufverm\u00f6gens": {},
"Buchwert abgegangener sonstiger Finanzanlagen": {},
"Erl\u00f6se aus dem Abgang von Beteiligungen": {},
"Erl\u00f6se aus dem Abgang von Wertpapieren des Umlaufverm\u00f6gens": {},
"Erl\u00f6se aus dem Abgang von sonstigen Finanzanlagen": {},
"Ertr\u00e4ge aus dem Abgang von und der Zuschreibung zu Finanzanlagen": {},
"Ertr\u00e4ge aus dem Abgang von und der Zuschreibung zu Wertpapieren des Umlaufverm\u00f6gens": {},
"Gewinabfuhr bzw. Verlust\u00fcberrechnung aus Ergebnisabf\u00fchrungsvertr\u00e4gen": {},
"nicht ausgenutzte Lieferantenskonti": {},
"root_type": "Income"
},
"Summe Fremdkapital": {
"3020 bis 3030 Steuerr\u00fcckstellungen": {},
"3040 bis 3090 Sonstige R\u00fcckstellungen": {},
"3110 bis 3170 Verbindlichkeiten gegen\u00fcber Kredidinstituten": {},
"3180 bis 3190 Verbindlichkeiten gegen\u00fcber Finanzinstituten": {},
"3380 bis 3390 Verbindlichkeiten aus der Annahme gezogener Wechsel u. d. Ausstellungen eigener Wechsel": {
"account_type": "Payable"
},
"3400 bis 3470 Verbindlichkeiten gegen\u00fc. verb. Untern., Verbindl. gegen\u00fc. Untern., mit denen eine Beteiligungsverh\u00e4lnis besteht": {},
"3600 bis 3690 Verbindlichkeiten im Rahmen der sozialen Sicherheit": {},
"3700 bis 3890 \u00dcbrige sonstige Verbindlichkeiten": {},
"3900 bis 3990 Passive Rechnungsabgrenzungsposten": {},
"Anleihen (einschlie\u00dflich konvertibler)": {},
"Erhaltene Anzahlungenauf Bestellungen": {},
"R\u00fcckstellungen f\u00fcr Abfertigung": {},
"R\u00fcckstellungen f\u00fcr Pensionen": {},
"USt. \u00a719 /art (reverse charge)": {
"account_type": "Tax"
},
"Umsatzsteuer": {},
"Umsatzsteuer Zahllast": {
"account_type": "Tax"
},
"Umsatzsteuer aus i.g. Erwerb 10%": {
"account_type": "Tax"
},
"Umsatzsteuer aus i.g. Erwerb 20%": {
"account_type": "Tax"
},
"Umsatzsteuer aus i.g. Lieferungen 10%": {
"account_type": "Tax"
},
"Umsatzsteuer aus i.g. Lieferungen 20%": {
"account_type": "Tax"
},
"Umsatzsteuer-Evidenzkonto f\u00fcr erhaltene Anzahlungen auf Bestellungen": {},
"Verbindlichkeiten aus Lieferungen u. Leistungen EU": {
"account_type": "Payable"
},
"Verbindlichkeiten aus Lieferungen u. Leistungen Inland": {
"account_type": "Payable"
},
"Verbindlichkeiten aus Lieferungen u. Leistungen sonst. Ausland": {
"account_type": "Payable"
},
"Verbindlichkeiten gegen\u00fcber Gesellschaften": {},
"Verrechnung Finanzamt": {
"account_type": "Tax"
},
"root_type": "Liability"
},
"Summe Kontoklasse 0 Anlageverm\u00f6gen": {
"44 bis 49 Sonstige Maschinen und maschinelle Anlagen": {},
"920 bis 930 Festverzinsliche Wertpapiere des Anlageverm\u00f6gens": {},
"940 bis 970 Sonstige Finanzanlagen, Wertrechte": {},
"Allgemeine Werkzeuge und Handwerkzeuge": {},
"Andere Bef\u00f6rderungsmittel": {},
"Andere Betriebs- und Gesch\u00e4ftsausstattung": {},
"Andere Erzeugungshilfsmittel": {},
"Anlagen im Bau": {},
"Anteile an Investmentfonds": {},
"Anteile an Kapitalgesellschaften ohne Beteiligungscharakter": {},
"Anteile an Personengesellschaften ohne Beteiligungscharakter": {},
"Anteile an verbundenen Unternehmen": {},
"Antriebsmaschinen": {},
"Aufwendungen f\u00fcs das Ingangssetzen u. Erweitern eines Betriebes": {},
"Ausleihungen an verbundene Unternehmen": {},
"Ausleihungen an verbundene Unternehmen, mit denen ein Beteiligungsverh\u00e4lnis besteht": {},
"Bauliche Investitionen in fremden (gepachteten) Betriebs- und Gesch\u00e4ftsgeb\u00e4uden": {},
"Bauliche Investitionen in fremden (gepachteten) Wohn- und Sozialgeb\u00e4uden": {},
"Bebaute Grundst\u00fccke (Grundwert)": {},
"Beheizungs- und Beleuchtungsanlagen": {},
"Beteiligungen an Gemeinschaftunternehmen": {},
"Beteiligungen an angeschlossenen (assoziierten) Unternehmen": {},
"Betriebs- und Gesch\u00e4ftsgeb\u00e4ude auf eigenem Grund": {},
"Betriebs- und Gesch\u00e4ftsgeb\u00e4ude auf fremdem Grund": {},
"B\u00fcromaschinen, EDV - Anlagen": {},
"Datenverarbeitungsprogramme": {},
"Energieversorgungsanlagen": {},
"Fertigungsmaschinen": {},
"Gebinde": {},
"Geleistete Anzahlungen": {},
"Genossenschaften ohne Beteiligungscharakter": {},
"Geringwertige Verm\u00f6gensgegenst\u00e4nde, soweit im Erzeugerprozess verwendet": {},
"Geringwertige Verm\u00f6gensgegenst\u00e4nde, soweit nicht im Erzeugungsprozess verwendet": {},
"Gesch\u00e4fts(Firmen)wert": {},
"Grundst\u00fcckseinrichtunten auf eigenem Grund": {},
"Grundst\u00fcckseinrichtunten auf fremdem Grund": {},
"Grundst\u00fccksgleiche Rechte": {},
"Hebezeuge und Montageanlagen": {},
"Konzessionen": {},
"Kumulierte Abschreibungen": {},
"LKW": {},
"Marken, Warenzeichen und Musterschutzrechte": {},
"Maschinenwerkzeuge": {},
"Nachrichten- und Kontrollanlagen": {},
"PKW": {},
"Pacht- und Mietrechte": {},
"Patentrechte und Lizenzen": {},
"Sonstige Ausleihungen": {},
"Sonstige Beteiligungen": {},
"Transportanlagen": {},
"Unbebaute Grundst\u00fccke": {},
"Vorrichtungen, Formen und Modelle": {},
"Wohn- und Sozialgeb\u00e4ude auf eigenem Grund": {},
"Wohn- und Sozialgeb\u00e4ude auf fremdem Grund": {},
"root_type": "Asset"
},
"Summe Personalaufwand": {
"6000 bis 6190 L\u00f6hne": {},
"6200 bis 6390 Geh\u00e4lter": {},
"6400 bis 6440 Aufwendungen f\u00fcr Abfertigungen": {},
"6450 bis 6490 Aufwendungen f\u00fcr Altersversorgung": {},
"6500 bis 6550 Gesetzlicher Sozialaufwand Arbeiter": {},
"6560 bis 6590 Gesetzlicher Sozialaufwand Angestellte": {},
"6600 bis 6650 Lohnabh\u00e4ngige Abgaben und Pflichtbeitr\u00e4gte": {},
"6660 bis 6690 Gehaltsabh\u00e4ngige Abgaben und Pflichtbeitr\u00e4gte": {},
"6700 bis 6890 Sonstige Sozialaufwendungen": {},
"Aufwandsstellenrechnung": {},
"root_type": "Expense"
},
"Summe Umlaufverm\u00f6gen": {
"2000 bis 2007 Forderungen aus Lief. und Leist. Inland": {
"account_type": "Receivable"
},
"2100 bis 2120 Forderungen aus Lief. und Leist. EU": {
"account_type": "Receivable"
},
"2150 bis 2170 Forderungen aus Lief. und Leist. Ausland": {
"account_type": "Receivable"
},
"2200 bis 2220 Forderungen gegen\u00fcber verbundenen Unternehmen": {
"account_type": "Receivable"
},
"2250 bis 2270 Forderungen gegen\u00fcber Unternehmen, mit denen ein Beteiligungsverh\u00e4ltnis besteht": {
"account_type": "Receivable"
},
"2300 bis 2460 Sonstige Forderungen und Verm\u00f6gensgegenst\u00e4nde": {
"account_type": "Receivable"
},
"2630 bis 2670 Sonstige Wertpapiere": {
"account_type": "Receivable"
},
"2750 bis 2770 Kassenbest\u00e4nde in Fremdw\u00e4hrung": {
"account_type": "Receivable"
},
"Aktive Rechnungsabrenzungsposten": {
"account_type": "Receivable"
},
"Anteile an verbundenen Unternehmen": {
"account_type": "Receivable"
},
"Bank / Guthaben bei Kreditinstituten": {
"account_type": "Receivable"
},
"Besitzwechsel ...": {
"account_type": "Receivable"
},
"Disagio": {
"account_type": "Receivable"
},
"Eigene Anteile (Wertpapiere)": {
"account_type": "Receivable"
},
"Einfuhrumsatzsteuer (bezahlt)": {},
"Eingeforderte aber noch nicht eingezahlte Einlagen": {
"account_type": "Receivable"
},
"Einzelwertberichtigungen zu Forderungen aus Lief. und Leist. Ausland": {
"account_type": "Receivable"
},
"Einzelwertberichtigungen zu Forderungen aus Lief. und Leist. EU": {
"account_type": "Receivable"
},
"Einzelwertberichtigungen zu Forderungen aus Lief. und Leist. Inland ": {
"account_type": "Receivable"
},
"Einzelwertberichtigungen zu Forderungen gegen\u00fcber Unternehmen mit denen ein Beteiligungsverh\u00e4ltnis besteht": {
"account_type": "Receivable"
},
"Einzelwertberichtigungen zu Forderungen gegen\u00fcber verbundenen Unternehmen": {
"account_type": "Receivable"
},
"Einzelwertberichtigungen zu sonstigen Forderungen und Verm\u00f6gensgegenst\u00e4nden": {
"account_type": "Receivable"
},
"Kassenbestand": {
"account_type": "Receivable"
},
"Pauschalwertberichtigungen zu Forderungen aus Lief. und Leist. Ausland": {
"account_type": "Receivable"
},
"Pauschalwertberichtigungen zu Forderungen aus Lief. und Leist. EU": {
"account_type": "Receivable"
},
"Pauschalwertberichtigungen zu Forderungen aus Lief. und Leist. Inland ": {
"account_type": "Receivable"
},
"Pauschalwertberichtigungen zu Forderungen gegen\u00fcber Unternehmen mit denen ein Beteiligungsverh\u00e4ltnis besteht": {
"account_type": "Receivable"
},
"Pauschalwertberichtigungen zu Forderungen gegen\u00fcber verbundenen Unternehmen": {
"account_type": "Receivable"
},
"Pauschalwertberichtigungen zu sonstigen Forderungen und Verm\u00f6gensgegenst\u00e4nden": {
"account_type": "Receivable"
},
"Postwertzeichen": {
"account_type": "Receivable"
},
"Schecks in Inlandsw\u00e4hrung": {
"account_type": "Receivable"
},
"Sonstige Anteile": {
"account_type": "Receivable"
},
"Stempelmarken": {
"account_type": "Receivable"
},
"Steuerabgrenzung": {
"account_type": "Receivable"
},
"Unterschiedsbetrag gem. Abschnitt XII Pensionskassengesetz": {
"account_type": "Receivable"
},
"Unterschiedsbetrag zur gebotenen Pensionsr\u00fcckstellung": {
"account_type": "Receivable"
},
"Vorsteuer": {
"account_type": "Receivable"
},
"Vorsteuer aus ig. Erwerb 10%": {
"account_type": "Tax"
},
"Vorsteuer aus ig. Erwerb 20%": {
"account_type": "Tax"
},
"Vorsteuer \u00a719/Art 19 ( reverse charge ) ": {
"account_type": "Tax"
},
"Wertberichtigungen": {
"account_type": "Receivable"
},
"root_type": "Asset"
},
"Summe Vorr\u00e4te": {
"1000 bis 1090 Bezugsverrechnung": {},
"1100 bis 1190 Rohstoffe": {},
"1200 bis 1290 Bezogene Teile": {},
"1300 bis 1340 Hilfsstoffe": {},
"1350 bis 1390 Betriebsstoffe": {},
"1400 bis 1490 Unfertige Erzeugniss": {},
"1500 bis 1590 Fertige Erzeugniss": {},
"1600 bis 1690 Waren": {},
"1700 bis 1790 Noch nicht abgerechenbare Leistungen": {},
"1900 bis 1990 Wertberichtigungen": {},
"geleistete Anzahlungen": {},
"root_type": "Asset"
},
"Summe Wareneinsatz": {
"5100 bis 5190 Verbrauch an Rohstoffen": {},
"5200 bis 5290 Verbrauch von bezogenen Fertig- und Einzelteilen": {},
"5300 bis 5390 Verbrauch von Hilfsstoffen": {},
"5400 bis 5490 Verbrauch von Betriebsstoffen": {},
"5500 bis 5590 Verbrauch von Werkzeugen und anderen Erzeugungshilfsmittel": {},
"5600 bis 5690 Verbrauch von Brenn- und Treibstoffen, Energie und Wasser": {},
"5700 bis 5790 Sonstige bezogene Herstellungsleistungen": {},
"Aufwandsstellenrechnung": {},
"Skontoertr\u00e4ge auf Materialaufwand": {},
"Skontoertr\u00e4ge auf sonstige bezogene Herstellungsleistungen": {},
"Wareneinkauf 10 %": {},
"Wareneinkauf 20 %": {},
"Wareneinkauf igErwerb 10 % VSt/10 % USt": {},
"Wareneinkauf igErwerb 20 % VSt/20 % USt": {},
"Wareneinkauf igErwerb ohne Vorsteuerabzug und 10 % USt": {},
"Wareneinkauf igErwerb ohne Vorsteuerabzug und 20 % USt": {},
"root_type": "Expense"
}
}
}
}
}

View File

@@ -3,6 +3,10 @@
frappe.ui.form.on('Accounting Dimension Filter', {
refresh: function(frm, cdt, cdn) {
if (frm.doc.accounting_dimension) {
frm.set_df_property('dimensions', 'label', frm.doc.accounting_dimension, cdn, 'dimension_value');
}
let help_content =
`<table class="table table-bordered" style="background-color: var(--scrollbar-track-color);">
<tr><td>
@@ -64,7 +68,6 @@ frappe.ui.form.on('Accounting Dimension Filter', {
frm.clear_table("dimensions");
let row = frm.add_child("dimensions");
row.accounting_dimension = frm.doc.accounting_dimension;
frm.fields_dict["dimensions"].grid.update_docfield_property("dimension_value", "label", frm.doc.accounting_dimension);
frm.refresh_field("dimensions");
frm.trigger('setup_filters');
},

View File

@@ -49,9 +49,15 @@ class AccountingPeriod(Document):
@frappe.whitelist()
def get_doctypes_for_closing(self):
docs_for_closing = []
# get period closing doctypes from all the apps
doctypes = frappe.get_hooks("period_closing_doctypes")
doctypes = [
"Sales Invoice",
"Purchase Invoice",
"Journal Entry",
"Payroll Entry",
"Bank Clearance",
"Asset",
"Stock Entry",
]
closed_doctypes = [{"document_type": doctype, "closed": 1} for doctype in doctypes]
for closed_doctype in closed_doctypes:
docs_for_closing.append(closed_doctype)

View File

@@ -18,7 +18,6 @@
"automatically_fetch_payment_terms",
"column_break_17",
"enable_common_party_accounting",
"allow_multi_currency_invoices_against_single_party_account",
"report_setting_section",
"use_custom_cash_flow",
"deferred_accounting_settings_section",
@@ -91,7 +90,7 @@
},
{
"default": "0",
"description": "Enabling ensure each Purchase Invoice has a unique value in Supplier Invoice No. field",
"description": "Enabling ensure each Sales Invoice has a unique value in Supplier Invoice No. field",
"fieldname": "check_supplier_invoice_uniqueness",
"fieldtype": "Check",
"label": "Check Supplier Invoice Number Uniqueness"
@@ -340,13 +339,6 @@
"fieldname": "report_setting_section",
"fieldtype": "Section Break",
"label": "Report Setting"
},
{
"default": "0",
"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",
"fieldtype": "Check",
"label": "Allow multi-currency invoices against single party account "
}
],
"icon": "icon-cog",
@@ -354,7 +346,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2022-11-27 21:49:52.538655",
"modified": "2022-04-08 14:45:06.796418",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",

View File

@@ -77,6 +77,6 @@ def get_party_bank_account(party_type, party):
@frappe.whitelist()
def get_bank_account_details(bank_account):
return frappe.get_cached_value(
return frappe.db.get_value(
"Bank Account", bank_account, ["account", "bank", "bank_account_no"], as_dict=1
)

View File

@@ -4,23 +4,6 @@
frappe.ui.form.on("Bank Clearance", {
setup: function(frm) {
frm.add_fetch("account", "account_currency", "account_currency");
frm.set_query("account", function() {
return {
"filters": {
"account_type": ["in",["Bank","Cash"]],
"is_group": 0,
}
};
});
frm.set_query("bank_account", function () {
return {
filters: {
'is_company_account': 1
},
};
});
},
onload: function(frm) {
@@ -29,7 +12,14 @@ frappe.ui.form.on("Bank Clearance", {
locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]: "";
frm.set_value("account", default_bank_account);
frm.set_query("account", function() {
return {
"filters": {
"account_type": ["in",["Bank","Cash"]],
"is_group": 0
}
};
});
frm.set_value("from_date", frappe.datetime.month_start());
frm.set_value("to_date", frappe.datetime.month_end());
@@ -37,14 +27,6 @@ frappe.ui.form.on("Bank Clearance", {
refresh: function(frm) {
frm.disable_save();
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) {
@@ -54,30 +36,22 @@ frappe.ui.form.on("Bank Clearance", {
callback: function(r, rt) {
frm.refresh_field("payment_entries");
frm.refresh_fields();
if (!frm.doc.payment_entries.length) {
frm.change_custom_button_type('Get Payment Entries', null, 'primary');
frm.change_custom_button_type('Update Clearance Date', null, 'default');
}
}
});
},
get_payment_entries: function(frm) {
return frappe.call({
method: "get_payment_entries",
doc: frm.doc,
callback: function(r, rt) {
frm.refresh_field("payment_entries");
frm.refresh_fields();
if (frm.doc.payment_entries.length) {
frm.add_custom_button(__('Update Clearance Date'), () =>
frm.trigger("update_clearance_date")
);
frm.change_custom_button_type('Get Payment Entries', null, 'default');
frm.change_custom_button_type('Update Clearance Date', null, 'primary');
}
$(frm.fields_dict.payment_entries.wrapper).find("[data-fieldname=amount]").each(function(i,v){
if (i !=0){
$(v).addClass("text-right")
}
})
}
});
}

View File

@@ -1,5 +1,4 @@
{
"actions": [],
"allow_copy": 1,
"creation": "2013-01-10 16:34:05",
"doctype": "DocType",
@@ -14,8 +13,11 @@
"bank_account",
"include_reconciled_entries",
"include_pos_transactions",
"get_payment_entries",
"section_break_10",
"payment_entries"
"payment_entries",
"update_clearance_date",
"total_amount"
],
"fields": [
{
@@ -74,6 +76,11 @@
"fieldtype": "Check",
"label": "Include POS Transactions"
},
{
"fieldname": "get_payment_entries",
"fieldtype": "Button",
"label": "Get Payment Entries"
},
{
"fieldname": "section_break_10",
"fieldtype": "Section Break"
@@ -84,14 +91,25 @@
"fieldtype": "Table",
"label": "Payment Entries",
"options": "Bank Clearance Detail"
},
{
"fieldname": "update_clearance_date",
"fieldtype": "Button",
"label": "Update Clearance Date"
},
{
"fieldname": "total_amount",
"fieldtype": "Currency",
"label": "Total Amount",
"options": "account_currency",
"read_only": 1
}
],
"hide_toolbar": 1,
"icon": "fa fa-check",
"idx": 1,
"issingle": 1,
"links": [],
"modified": "2022-11-28 17:24:13.008692",
"modified": "2020-04-06 16:12:06.628008",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Clearance",
@@ -108,6 +126,5 @@
"quick_entry": 1,
"read_only": 1,
"sort_field": "modified",
"sort_order": "ASC",
"states": []
"sort_order": "ASC"
}

View File

@@ -99,12 +99,12 @@ class BankClearance(Document):
.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)
.orderby(loan_disbursement.name, frappe.qb.desc)
).run(as_dict=1)
loan_repayment = frappe.qb.DocType("Loan Repayment")
query = (
loan_repayments = (
frappe.qb.from_(loan_repayment)
.select(
ConstantColumn("Loan Repayment").as_("payment_document"),
@@ -118,19 +118,13 @@ class BankClearance(Document):
)
.where(loan_repayment.docstatus == 1)
.where(loan_repayment.clearance_date.isnull())
.where(loan_repayment.repay_from_salary == 0)
.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)
.orderby(loan_repayment.posting_date)
.orderby(loan_repayment.name, frappe.qb.desc)
).run(as_dict=1)
pos_sales_invoices, pos_purchase_invoices = [], []
if self.include_pos_transactions:
@@ -179,6 +173,7 @@ class BankClearance(Document):
)
self.set("payment_entries", [])
self.total_amount = 0.0
default_currency = erpnext.get_default_currency()
for d in entries:
@@ -197,6 +192,7 @@ class BankClearance(Document):
d.pop("debit")
d.pop("account_currency")
row.update(d)
self.total_amount += flt(amount)
@frappe.whitelist()
def update_clearance_date(self):

View File

@@ -43,13 +43,20 @@ frappe.ui.form.on('Bank Guarantee', {
reference_docname: function(frm) {
if (frm.doc.reference_docname && frm.doc.reference_doctype) {
let fields_to_fetch = ["grand_total"];
let party_field = frm.doc.reference_doctype == "Sales Order" ? "customer" : "supplier";
if (frm.doc.reference_doctype == "Sales Order") {
fields_to_fetch.push("project");
}
fields_to_fetch.push(party_field);
frappe.call({
method: "erpnext.accounts.doctype.bank_guarantee.bank_guarantee.get_voucher_details",
method: "erpnext.accounts.doctype.bank_guarantee.bank_guarantee.get_vouchar_detials",
args: {
"bank_guarantee_type": frm.doc.bg_type,
"reference_name": frm.doc.reference_docname
"column_list": fields_to_fetch,
"doctype": frm.doc.reference_doctype,
"docname": frm.doc.reference_docname
},
callback: function(r) {
if (r.message) {

View File

@@ -2,8 +2,11 @@
# For license information, please see license.txt
import json
import frappe
from frappe import _
from frappe.desk.search import sanitize_searchfield
from frappe.model.document import Document
@@ -22,18 +25,14 @@ class BankGuarantee(Document):
@frappe.whitelist()
def get_voucher_details(bank_guarantee_type: str, reference_name: str):
if not isinstance(reference_name, str):
raise TypeError("reference_name must be a string")
fields_to_fetch = ["grand_total"]
if bank_guarantee_type == "Receiving":
doctype = "Sales Order"
fields_to_fetch.append("customer")
fields_to_fetch.append("project")
else:
doctype = "Purchase Order"
fields_to_fetch.append("supplier")
return frappe.db.get_value(doctype, reference_name, fields_to_fetch, as_dict=True)
def get_vouchar_detials(column_list, doctype, docname):
column_list = json.loads(column_list)
for col in column_list:
sanitize_searchfield(col)
return frappe.db.sql(
""" select {columns} from `tab{doctype}` where name=%s""".format(
columns=", ".join(column_list), doctype=doctype
),
docname,
as_dict=1,
)[0]

View File

@@ -12,9 +12,6 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
},
};
});
let no_bank_transactions_text =
`<div class="text-muted text-center">${__("No Matching Bank Transactions Found")}</div>`
set_field_options("no_bank_transactions", no_bank_transactions_text);
},
onload: function (frm) {

View File

@@ -81,7 +81,8 @@
},
{
"fieldname": "no_bank_transactions",
"fieldtype": "HTML"
"fieldtype": "HTML",
"options": "<div class=\"text-muted text-center\">No Matching Bank Transactions Found</div>"
}
],
"hide_toolbar": 1,
@@ -108,4 +109,4 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC"
}
}

View File

@@ -10,6 +10,7 @@ from frappe.model.document import Document
from frappe.query_builder.custom import ConstantColumn
from frappe.utils import flt
from erpnext import get_company_currency
from erpnext.accounts.doctype.bank_transaction.bank_transaction import get_paid_amount
from erpnext.accounts.report.bank_reconciliation_statement.bank_reconciliation_statement import (
get_amounts_not_reflected_in_system,
@@ -57,7 +58,7 @@ def get_bank_transactions(bank_account, from_date=None, to_date=None):
@frappe.whitelist()
def get_account_balance(bank_account, till_date):
# returns account balance till the specified date
account = frappe.get_cached_value("Bank Account", bank_account, "account")
account = frappe.db.get_value("Bank Account", bank_account, "account")
filters = frappe._dict(
{"account": account, "report_date": till_date, "include_pos_transactions": 1}
)
@@ -130,10 +131,8 @@ def create_journal_entry_bts(
fieldname=["name", "deposit", "withdrawal", "bank_account"],
as_dict=True,
)[0]
company_account = frappe.get_cached_value(
"Bank Account", bank_transaction.bank_account, "account"
)
account_type = frappe.get_cached_value("Account", second_account, "account_type")
company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account")
account_type = frappe.db.get_value("Account", second_account, "account_type")
if account_type in ["Receivable", "Payable"]:
if not (party_type and party):
frappe.throw(
@@ -166,7 +165,7 @@ def create_journal_entry_bts(
}
)
company = frappe.get_cached_value("Account", company_account, "company")
company = frappe.get_value("Account", company_account, "company")
journal_entry_dict = {
"voucher_type": entry_type,
@@ -221,10 +220,8 @@ def create_payment_entry_bts(
paid_amount = bank_transaction.unallocated_amount
payment_type = "Receive" if bank_transaction.deposit > 0 else "Pay"
company_account = frappe.get_cached_value(
"Bank Account", bank_transaction.bank_account, "account"
)
company = frappe.get_cached_value("Account", company_account, "company")
company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account")
company = frappe.get_value("Account", company_account, "company")
payment_entry_dict = {
"company": company,
"payment_type": payment_type,
@@ -270,7 +267,7 @@ def reconcile_vouchers(bank_transaction_name, vouchers):
# updated clear date of all the vouchers based on the bank transaction
vouchers = json.loads(vouchers)
transaction = frappe.get_doc("Bank Transaction", bank_transaction_name)
company_account = frappe.get_cached_value("Bank Account", transaction.bank_account, "account")
company_account = frappe.db.get_value("Bank Account", transaction.bank_account, "account")
if transaction.unallocated_amount == 0:
frappe.throw(_("This bank transaction is already fully reconciled"))
@@ -294,7 +291,7 @@ def reconcile_vouchers(bank_transaction_name, vouchers):
"The sum total of amounts of all selected vouchers should be less than the unallocated amount of the bank transaction"
)
)
account = frappe.get_cached_value("Bank Account", transaction.bank_account, "account")
account = frappe.db.get_value("Bank Account", transaction.bank_account, "account")
for voucher in vouchers:
gl_entry = frappe.db.get_value(
@@ -371,27 +368,6 @@ def get_queries(bank_account, company, transaction, document_types):
account_from_to = "paid_to" if transaction.deposit > 0 else "paid_from"
queries = []
# get matching queries from all the apps
for method_name in frappe.get_hooks("get_matching_queries"):
queries.extend(
frappe.get_attr(method_name)(
bank_account,
company,
transaction,
document_types,
amount_condition,
account_from_to,
)
or []
)
return queries
def get_matching_queries(
bank_account, company, transaction, document_types, amount_condition, account_from_to
):
queries = []
if "payment_entry" in document_types:
pe_amount_matching = get_pe_matching_query(amount_condition, account_from_to, transaction)
queries.extend([pe_amount_matching])
@@ -409,6 +385,10 @@ def get_matching_queries(
pi_amount_matching = get_pi_matching_query(amount_condition)
queries.extend([pi_amount_matching])
if "expense_claim" in document_types:
ec_amount_matching = get_ec_matching_query(bank_account, company, amount_condition)
queries.extend([ec_amount_matching])
return queries
@@ -487,13 +467,11 @@ def get_lr_matching_query(bank_account, amount_condition, filters):
loan_repayment.posting_date,
)
.where(loan_repayment.docstatus == 1)
.where(loan_repayment.repay_from_salary == 0)
.where(loan_repayment.clearance_date.isnull())
.where(loan_repayment.payment_account == bank_account)
)
if frappe.db.has_column("Loan Repayment", "repay_from_salary"):
query = query.where((loan_repayment.repay_from_salary == 0))
if amount_condition:
query.where(loan_repayment.amount_paid == filters.get("amount"))
else:
@@ -624,3 +602,37 @@ def get_pi_matching_query(amount_condition):
AND ifnull(clearance_date, '') = ""
AND cash_bank_account = %(bank_account)s
"""
def get_ec_matching_query(bank_account, company, amount_condition):
# get matching Expense Claim query
mode_of_payments = [
x["parent"]
for x in frappe.db.get_all(
"Mode of Payment Account", filters={"default_account": bank_account}, fields=["parent"]
)
]
mode_of_payments = "('" + "', '".join(mode_of_payments) + "' )"
company_currency = get_company_currency(company)
return f"""
SELECT
( CASE WHEN employee = %(party)s THEN 1 ELSE 0 END
+ 1 ) AS rank,
'Expense Claim' as doctype,
name,
total_sanctioned_amount as paid_amount,
'' as reference_no,
'' as reference_date,
employee as party,
'Employee' as party_type,
posting_date,
'{company_currency}' as currency
FROM
`tabExpense Claim`
WHERE
total_sanctioned_amount {amount_condition} %(amount)s
AND docstatus = 1
AND is_paid = 1
AND ifnull(clearance_date, '') = ""
AND mode_of_payment in {mode_of_payments}
"""

View File

@@ -100,7 +100,7 @@ frappe.ui.form.on("Bank Statement Import", {
if (frm.doc.status.includes("Success")) {
frm.add_custom_button(
__("Go to {0} List", [__(frm.doc.reference_doctype)]),
__("Go to {0} List", [frm.doc.reference_doctype]),
() => frappe.set_route("List", frm.doc.reference_doctype)
);
}
@@ -141,7 +141,7 @@ frappe.ui.form.on("Bank Statement Import", {
},
show_import_status(frm) {
let import_log = JSON.parse(frm.doc.statement_import_log || "[]");
let import_log = JSON.parse(frm.doc.import_log || "[]");
let successful_records = import_log.filter((log) => log.success);
let failed_records = import_log.filter((log) => !log.success);
if (successful_records.length === 0) return;
@@ -309,7 +309,7 @@ frappe.ui.form.on("Bank Statement Import", {
// method: 'frappe.core.doctype.data_import.data_import.get_preview_from_template',
show_import_preview(frm, preview_data) {
let import_log = JSON.parse(frm.doc.statement_import_log || "[]");
let import_log = JSON.parse(frm.doc.import_log || "[]");
if (
frm.import_preview &&
@@ -439,7 +439,7 @@ frappe.ui.form.on("Bank Statement Import", {
},
show_import_log(frm) {
let import_log = JSON.parse(frm.doc.statement_import_log || "[]");
let import_log = JSON.parse(frm.doc.import_log || "[]");
let logs = import_log;
frm.toggle_display("import_log", false);
frm.toggle_display("import_log_section", logs.length > 0);

View File

@@ -24,7 +24,7 @@
"section_import_preview",
"import_preview",
"import_log_section",
"statement_import_log",
"import_log",
"show_failed_logs",
"import_log_preview",
"reference_doctype",
@@ -90,6 +90,12 @@
"options": "JSON",
"read_only": 1
},
{
"fieldname": "import_log",
"fieldtype": "Code",
"label": "Import Log",
"options": "JSON"
},
{
"fieldname": "import_log_section",
"fieldtype": "Section Break",
@@ -192,17 +198,11 @@
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"fieldname": "statement_import_log",
"fieldtype": "Code",
"label": "Statement Import Log",
"options": "JSON"
}
],
"hide_toolbar": 1,
"links": [],
"modified": "2022-09-07 11:11:40.293317",
"modified": "2021-05-12 14:17:37.777246",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Statement Import",

View File

@@ -53,13 +53,15 @@ class BankStatementImport(DataImport):
if "Bank Account" not in json.dumps(preview["columns"]):
frappe.throw(_("Please add the Bank Account column"))
from frappe.utils.background_jobs import is_job_queued
from frappe.core.page.background_jobs.background_jobs import get_info
from frappe.utils.scheduler import is_scheduler_inactive
if is_scheduler_inactive() and not frappe.flags.in_test:
frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive"))
if not is_job_queued(self.name):
enqueued_jobs = [d.get("job_name") for d in get_info()]
if self.name not in enqueued_jobs:
enqueue(
start_import,
queue="default",

View File

@@ -3,21 +3,28 @@
frappe.ui.form.on("Bank Transaction", {
onload(frm) {
frm.set_query("payment_document", "payment_entries", function() {
const payment_doctypes = frm.events.get_payment_doctypes(frm);
frm.set_query("payment_document", "payment_entries", function () {
return {
filters: {
name: ["in", payment_doctypes],
name: [
"in",
[
"Payment Entry",
"Journal Entry",
"Sales Invoice",
"Purchase Invoice",
"Expense Claim",
],
],
},
};
});
},
bank_account: function(frm) {
bank_account: function (frm) {
set_bank_statement_filter(frm);
},
setup: function(frm) {
setup: function (frm) {
frm.set_query("party_type", function () {
return {
filters: {
@@ -26,16 +33,6 @@ frappe.ui.form.on("Bank Transaction", {
};
});
},
get_payment_doctypes: function() {
// get payment doctypes from all the apps
return [
"Payment Entry",
"Journal Entry",
"Sales Invoice",
"Purchase Invoice",
];
}
});
frappe.ui.form.on("Bank Transaction Payments", {

View File

@@ -58,11 +58,19 @@ class BankTransaction(StatusUpdater):
def clear_linked_payment_entries(self, for_cancel=False):
for payment_entry in self.payment_entries:
if payment_entry.payment_document == "Sales Invoice":
self.clear_sales_invoice(payment_entry, for_cancel=for_cancel)
elif payment_entry.payment_document in get_doctypes_for_bank_reconciliation():
if payment_entry.payment_document in [
"Payment Entry",
"Journal Entry",
"Purchase Invoice",
"Expense Claim",
"Loan Repayment",
"Loan Disbursement",
]:
self.clear_simple_entry(payment_entry, for_cancel=for_cancel)
elif payment_entry.payment_document == "Sales Invoice":
self.clear_sales_invoice(payment_entry, for_cancel=for_cancel)
def clear_simple_entry(self, payment_entry, for_cancel=False):
if payment_entry.payment_document == "Payment Entry":
if (
@@ -87,12 +95,6 @@ class BankTransaction(StatusUpdater):
)
@frappe.whitelist()
def get_doctypes_for_bank_reconciliation():
"""Get Bank Reconciliation doctypes from all the apps"""
return frappe.get_hooks("bank_reconciliation_doctypes")
def get_reconciled_bank_transactions(payment_entry):
reconciled_bank_transactions = frappe.get_all(
"Bank Transaction Payments",

View File

@@ -74,7 +74,7 @@ def get_header_mapping(columns, bank_account):
def get_bank_mapping(bank_account):
bank_name = frappe.get_cached_value("Bank Account", bank_account, "bank")
bank_name = frappe.db.get_value("Bank Account", bank_account, "bank")
bank = frappe.get_doc("Bank", bank_name)
mapping = {row.file_field: row.bank_transaction_field for row in bank.bank_transaction_mapping}

View File

@@ -5,7 +5,6 @@ import json
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase
from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import (
get_linked_payments,
@@ -19,21 +18,28 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal
test_dependencies = ["Item", "Cost Center"]
class TestBankTransaction(FrappeTestCase):
def setUp(self):
for dt in [
"Loan Repayment",
"Bank Transaction",
"Payment Entry",
"Payment Entry Reference",
"POS Profile",
]:
frappe.db.delete(dt)
class TestBankTransaction(unittest.TestCase):
@classmethod
def setUpClass(cls):
make_pos_profile()
add_transactions()
add_vouchers()
@classmethod
def tearDownClass(cls):
for bt in frappe.get_all("Bank Transaction"):
doc = frappe.get_doc("Bank Transaction", bt.name)
if doc.docstatus == 1:
doc.cancel()
doc.delete()
# Delete directly in DB to avoid validation errors for countries not allowing deletion
frappe.db.sql("""delete from `tabPayment Entry Reference`""")
frappe.db.sql("""delete from `tabPayment Entry`""")
# Delete POS Profile
frappe.db.sql("delete from `tabPOS Profile`")
# This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction.
def test_linked_payments(self):
bank_transaction = frappe.get_doc(
@@ -149,35 +155,6 @@ class TestBankTransaction(FrappeTestCase):
is not None
)
def test_matching_loan_repayment(self):
from erpnext.loan_management.doctype.loan.test_loan import create_loan_accounts
create_loan_accounts()
bank_account = frappe.get_doc(
{
"doctype": "Bank Account",
"account_name": "Payment Account",
"bank": "Citi Bank",
"account": "Payment Account - _TC",
}
).insert(ignore_if_duplicate=True)
bank_transaction = frappe.get_doc(
{
"doctype": "Bank Transaction",
"description": "Loan Repayment - OPSKATTUZWXXX AT776000000098709837 Herr G",
"date": "2018-10-27",
"deposit": 500,
"currency": "INR",
"bank_account": bank_account.name,
}
).submit()
repayment_entry = create_loan_and_repayment()
linked_payments = get_linked_payments(bank_transaction.name, ["loan_repayment", "exact_match"])
self.assertEqual(linked_payments[0][2], repayment_entry.name)
def create_bank_account(bank_name="Citi Bank", account_name="_Test Bank - _TC"):
try:
@@ -387,59 +364,3 @@ def add_vouchers():
)
si.insert()
si.submit()
def create_loan_and_repayment():
from erpnext.loan_management.doctype.loan.test_loan import (
create_loan,
create_loan_type,
create_repayment_entry,
make_loan_disbursement_entry,
)
from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import (
process_loan_interest_accrual_for_term_loans,
)
from erpnext.setup.doctype.employee.test_employee import make_employee
create_loan_type(
"Personal Loan",
500000,
8.4,
is_term_loan=1,
mode_of_payment="Cash",
disbursement_account="Disbursement Account - _TC",
payment_account="Payment Account - _TC",
loan_account="Loan Account - _TC",
interest_income_account="Interest Income Account - _TC",
penalty_income_account="Penalty Income Account - _TC",
)
applicant = make_employee("test_bank_reco@loan.com", company="_Test Company")
loan = create_loan(applicant, "Personal Loan", 5000, "Repay Over Number of Periods", 20)
loan = frappe.get_doc(
{
"doctype": "Loan",
"applicant_type": "Employee",
"company": "_Test Company",
"applicant": applicant,
"loan_type": "Personal Loan",
"loan_amount": 5000,
"repayment_method": "Repay Fixed Amount per Period",
"monthly_repayment_amount": 500,
"repayment_start_date": "2018-09-27",
"is_term_loan": 1,
"posting_date": "2018-09-27",
}
).insert()
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date="2018-09-27")
process_loan_interest_accrual_for_term_loans(posting_date="2018-10-27")
repayment_entry = create_repayment_entry(
loan.name,
applicant,
"2018-10-27",
500,
)
repayment_entry.submit()
return repayment_entry

View File

@@ -1,7 +1,6 @@
{
"actions": [],
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2016-05-16 11:42:29.632528",
"doctype": "DocType",
"editable_grid": 1,
@@ -10,7 +9,6 @@
"budget_against",
"company",
"cost_center",
"naming_series",
"project",
"fiscal_year",
"column_break_3",
@@ -192,26 +190,15 @@
"label": "Budget Accounts",
"options": "Budget Account",
"reqd": 1
},
{
"fieldname": "naming_series",
"fieldtype": "Data",
"hidden": 1,
"label": "Series",
"no_copy": 1,
"print_hide": 1,
"read_only": 1,
"set_only_once": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-10-10 22:14:36.361509",
"modified": "2020-10-06 15:13:54.055854",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Budget",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
@@ -233,6 +220,5 @@
],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -5,6 +5,7 @@
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.model.naming import make_autoname
from frappe.utils import add_months, flt, fmt_money, get_last_day, getdate
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
@@ -22,6 +23,11 @@ class DuplicateBudgetError(frappe.ValidationError):
class Budget(Document):
def autoname(self):
self.name = make_autoname(
self.get(frappe.scrub(self.budget_against)) + "/" + self.fiscal_year + "/.###"
)
def validate(self):
if not self.get(frappe.scrub(self.budget_against)):
frappe.throw(_("{0} is mandatory").format(self.budget_against))
@@ -59,7 +65,7 @@ class Budget(Document):
account_list = []
for d in self.get("accounts"):
if d.account:
account_details = frappe.get_cached_value(
account_details = frappe.db.get_value(
"Account", d.account, ["is_group", "company", "report_type"], as_dict=1
)
@@ -103,11 +109,8 @@ class Budget(Document):
):
self.applicable_on_booking_actual_expenses = 1
def before_naming(self):
self.naming_series = f"{{{frappe.scrub(self.budget_against)}}}./.{self.fiscal_year}/.###"
def validate_expense_against_budget(args, expense_amount=0):
def validate_expense_against_budget(args):
args = frappe._dict(args)
if args.get("company") and not args.fiscal_year:
@@ -175,13 +178,13 @@ def validate_expense_against_budget(args, expense_amount=0):
) # nosec
if budget_records:
validate_budget_records(args, budget_records, expense_amount)
validate_budget_records(args, budget_records)
def validate_budget_records(args, budget_records, expense_amount):
def validate_budget_records(args, budget_records):
for budget in budget_records:
if flt(budget.budget_amount):
amount = expense_amount or get_amount(args, budget)
amount = get_amount(args, budget)
yearly_action, monthly_action = get_actions(args, budget)
if monthly_action in ["Stop", "Warn"]:
@@ -306,7 +309,7 @@ def get_other_condition(args, budget, for_doc):
if args.get("fiscal_year"):
date_field = "schedule_date" if for_doc == "Material Request" else "transaction_date"
start_date, end_date = frappe.get_cached_value(
start_date, end_date = frappe.db.get_value(
"Fiscal Year", args.get("fiscal_year"), ["year_start_date", "year_end_date"]
)
@@ -379,7 +382,7 @@ def get_accumulated_monthly_budget(monthly_distribution, posting_date, fiscal_ye
):
distribution.setdefault(d.month, d.percentage_allocation)
dt = frappe.get_cached_value("Fiscal Year", fiscal_year, "year_start_date")
dt = frappe.db.get_value("Fiscal Year", fiscal_year, "year_start_date")
accumulated_percentage = 0.0
while dt <= getdate(posting_date):

View File

@@ -334,39 +334,6 @@ class TestBudget(unittest.TestCase):
budget.cancel()
jv.cancel()
def test_monthly_budget_against_main_cost_center(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
from erpnext.accounts.doctype.cost_center_allocation.test_cost_center_allocation import (
create_cost_center_allocation,
)
cost_centers = [
"Main Budget Cost Center 1",
"Sub Budget Cost Center 1",
"Sub Budget Cost Center 2",
]
for cc in cost_centers:
create_cost_center(cost_center_name=cc, company="_Test Company")
create_cost_center_allocation(
"_Test Company",
"Main Budget Cost Center 1 - _TC",
{"Sub Budget Cost Center 1 - _TC": 60, "Sub Budget Cost Center 2 - _TC": 40},
)
make_budget(budget_against="Cost Center", cost_center="Main Budget Cost Center 1 - _TC")
jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC",
400000,
"Main Budget Cost Center 1 - _TC",
posting_date=nowdate(),
)
self.assertRaises(BudgetError, jv.submit)
def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None):
if budget_against_field == "project":

View File

@@ -0,0 +1 @@
C Form (India specific only) - Will be deprecated.

View File

@@ -0,0 +1,43 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
//c-form js file
// -----------------------------
frappe.ui.form.on('C-Form', {
setup(frm) {
frm.fields_dict.invoices.grid.get_field("invoice_no").get_query = function(doc) {
return {
filters: {
"docstatus": 1,
"customer": doc.customer,
"company": doc.company,
"c_form_applicable": 'Yes',
"c_form_no": ''
}
};
}
frm.fields_dict.state.get_query = function() {
return {
filters: {
country: "India"
}
};
}
}
});
frappe.ui.form.on('C-Form Invoice Detail', {
invoice_no(frm, cdt, cdn) {
let d = frappe.get_doc(cdt, cdn);
if (d.invoice_no) {
frm.call('get_invoice_details', {
invoice_no: d.invoice_no
}).then(r => {
frappe.model.set_value(cdt, cdn, r.message);
});
}
}
});

View File

@@ -0,0 +1,511 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 0,
"autoname": "naming_series:",
"beta": 0,
"creation": "2013-03-07 11:55:06",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"editable_grid": 0,
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "naming_series",
"fieldtype": "Select",
"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": "Series",
"length": 0,
"no_copy": 0,
"options": "ACC-CF-.YYYY.-",
"permlevel": 0,
"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": 1,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "c_form_no",
"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": "C-Form No",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "received_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Received Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "customer",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Customer",
"length": 0,
"no_copy": 0,
"options": "Customer",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break1",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": "50%",
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0,
"width": "50%"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 1,
"label": "Company",
"length": 0,
"no_copy": 0,
"options": "Company",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "quarter",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 1,
"label": "Quarter",
"length": 0,
"no_copy": 0,
"options": "\nI\nII\nIII\nIV",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "total_amount",
"fieldtype": "Currency",
"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": "Total Amount",
"length": 0,
"no_copy": 0,
"options": "Company:company:default_currency",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "state",
"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": "State",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break0",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "invoices",
"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": "Invoices",
"length": 0,
"no_copy": 0,
"options": "C-Form Invoice Detail",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "total_invoiced_amount",
"fieldtype": "Currency",
"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": "Total Invoiced Amount",
"length": 0,
"no_copy": 0,
"options": "Company:company:default_currency",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "amended_from",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 1,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Amended From",
"length": 0,
"no_copy": 1,
"options": "C-Form",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"icon": "fa fa-file-text",
"idx": 1,
"image_view": 0,
"in_create": 0,
"is_submittable": 1,
"issingle": 0,
"istable": 0,
"max_attachments": 3,
"modified": "2018-08-21 14:44:30.558767",
"modified_by": "Administrator",
"module": "Accounts",
"name": "C-Form",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 0,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 0,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 0,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 1,
"print": 0,
"read": 1,
"report": 1,
"role": "All",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_order": "DESC",
"timeline_field": "customer",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
}

View File

@@ -0,0 +1,96 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import flt
class CForm(Document):
def validate(self):
"""Validate invoice that c-form is applicable
and no other c-form is received for that"""
for d in self.get("invoices"):
if d.invoice_no:
inv = frappe.db.sql(
"""select c_form_applicable, c_form_no from
`tabSales Invoice` where name = %s and docstatus = 1""",
d.invoice_no,
)
if inv and inv[0][0] != "Yes":
frappe.throw(_("C-form is not applicable for Invoice: {0}").format(d.invoice_no))
elif inv and inv[0][1] and inv[0][1] != self.name:
frappe.throw(
_(
"""Invoice {0} is tagged in another C-form: {1}.
If you want to change C-form no for this invoice,
please remove invoice no from the previous c-form and then try again""".format(
d.invoice_no, inv[0][1]
)
)
)
elif not inv:
frappe.throw(
_(
"Row {0}: Invoice {1} is invalid, it might be cancelled / does not exist. \
Please enter a valid Invoice".format(
d.idx, d.invoice_no
)
)
)
def on_update(self):
"""Update C-Form No on invoices"""
self.set_total_invoiced_amount()
def on_submit(self):
self.set_cform_in_sales_invoices()
def before_cancel(self):
# remove cform reference
frappe.db.sql("""update `tabSales Invoice` set c_form_no=null where c_form_no=%s""", self.name)
def set_cform_in_sales_invoices(self):
inv = [d.invoice_no for d in self.get("invoices")]
if inv:
frappe.db.sql(
"""update `tabSales Invoice` set c_form_no=%s, modified=%s where name in (%s)"""
% ("%s", "%s", ", ".join(["%s"] * len(inv))),
tuple([self.name, self.modified] + inv),
)
frappe.db.sql(
"""update `tabSales Invoice` set c_form_no = null, modified = %s
where name not in (%s) and ifnull(c_form_no, '') = %s"""
% ("%s", ", ".join(["%s"] * len(inv)), "%s"),
tuple([self.modified] + inv + [self.name]),
)
else:
frappe.throw(_("Please enter atleast 1 invoice in the table"))
def set_total_invoiced_amount(self):
total = sum(flt(d.grand_total) for d in self.get("invoices"))
frappe.db.set(self, "total_invoiced_amount", total)
@frappe.whitelist()
def get_invoice_details(self, invoice_no):
"""Pull details from invoices for referrence"""
if invoice_no:
inv = frappe.db.get_value(
"Sales Invoice",
invoice_no,
["posting_date", "territory", "base_net_total", "base_grand_total"],
as_dict=True,
)
return {
"invoice_date": inv.posting_date,
"territory": inv.territory,
"net_total": inv.base_net_total,
"grand_total": inv.base_grand_total,
}

View File

@@ -0,0 +1,10 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
# test_records = frappe.get_test_records('C-Form')
class TestCForm(unittest.TestCase):
pass

View File

@@ -0,0 +1 @@
Invoice detail for parent C-Form.

View File

@@ -0,0 +1,168 @@
{
"allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2013-02-22 01:27:38",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"editable_grid": 1,
"fields": [
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "invoice_no",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Invoice No",
"length": 0,
"no_copy": 0,
"options": "Sales Invoice",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": "160px",
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "160px"
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "invoice_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Invoice Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": "120px",
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "120px"
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"description": "",
"fieldname": "territory",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Territory",
"length": 0,
"no_copy": 0,
"options": "Territory",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": "120px",
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "120px"
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "net_total",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Net Total",
"length": 0,
"no_copy": 0,
"options": "Company:company:default_currency",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": "120px",
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "120px"
},
{
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"fieldname": "grand_total",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Grand Total",
"length": 0,
"no_copy": 0,
"options": "Company:company:default_currency",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": "120px",
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "120px"
}
],
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 1,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2016-07-11 03:27:58.768719",
"modified_by": "Administrator",
"module": "Accounts",
"name": "C-Form Invoice Detail",
"owner": "Administrator",
"permissions": [],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"track_seen": 0
}

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from frappe.model.document import Document
class CFormInvoiceDetail(Document):
pass

View File

@@ -45,14 +45,14 @@ def validate_columns(data):
@frappe.whitelist()
def validate_company(company):
parent_company, allow_account_creation_against_child_company = frappe.get_cached_value(
"Company", company, ["parent_company", "allow_account_creation_against_child_company"]
parent_company, allow_account_creation_against_child_company = frappe.db.get_value(
"Company", {"name": company}, ["parent_company", "allow_account_creation_against_child_company"]
)
if parent_company and (not allow_account_creation_against_child_company):
msg = _("{} is a child company.").format(frappe.bold(company)) + " "
msg += _("Please import accounts against parent company or enable {} in company master.").format(
frappe.bold(_("Allow Account Creation Against Child Company"))
frappe.bold("Allow Account Creation Against Child Company")
)
frappe.throw(msg, title=_("Wrong Company"))

View File

@@ -16,7 +16,7 @@ class CostCenter(NestedSet):
from erpnext.accounts.utils import get_autoname_with_number
self.name = get_autoname_with_number(
self.cost_center_number, self.cost_center_name, self.company
self.cost_center_number, self.cost_center_name, None, self.company
)
def validate(self):

View File

@@ -29,6 +29,7 @@ def test_create_test_data():
"item_name": "_Test Tesla Car",
"apply_warehouse_wise_reorder_level": 0,
"warehouse": "Stores - _TC",
"gst_hsn_code": "999800",
"valuation_rate": 5000,
"standard_rate": 5000,
"item_defaults": [

View File

@@ -19,7 +19,7 @@ class Dunning(AccountsController):
self.validate_overdue_days()
self.validate_amount()
if not self.income_account:
self.income_account = frappe.get_cached_value("Company", self.company, "default_income_account")
self.income_account = frappe.db.get_value("Company", self.company, "default_income_account")
def validate_overdue_days(self):
self.overdue_days = (getdate(self.posting_date) - getdate(self.due_date)).days or 0

View File

@@ -1,138 +1,389 @@
{
"actions": [],
"allow_import": 1,
"autoname": "ACC-ERR-.YYYY.-.#####",
"creation": "2018-04-13 18:25:55.943587",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"posting_date",
"column_break_2",
"company",
"section_break_4",
"get_entries",
"accounts",
"section_break_6",
"total_gain_loss",
"amended_from"
],
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 0,
"autoname": "ACC-ERR-.YYYY.-.#####",
"beta": 0,
"creation": "2018-04-13 18:25:55.943587",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"default": "Today",
"fieldname": "posting_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "Posting Date",
"reqd": 1
},
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Today",
"fieldname": "posting_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Posting Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"fieldname": "column_break_2",
"fieldtype": "Column Break"
},
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_2",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"fieldname": "company",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Company",
"options": "Company",
"reqd": 1
},
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Company",
"length": 0,
"no_copy": 0,
"options": "Company",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"fieldname": "section_break_4",
"fieldtype": "Section Break"
},
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_4",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"fieldname": "get_entries",
"fieldtype": "Button",
"label": "Get Entries"
},
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "get_entries",
"fieldtype": "Button",
"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": "Get Entries",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"fieldname": "accounts",
"fieldtype": "Table",
"label": "Exchange Rate Revaluation Account",
"no_copy": 1,
"options": "Exchange Rate Revaluation Account",
"reqd": 1
},
"allow_bulk_edit": 0,
"allow_in_quick_entry": 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": "Exchange Rate Revaluation Account",
"length": 0,
"no_copy": 1,
"options": "Exchange Rate Revaluation Account",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"fieldname": "section_break_6",
"fieldtype": "Section Break"
},
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_6",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"fieldname": "total_gain_loss",
"fieldtype": "Currency",
"label": "Total Gain/Loss",
"options": "Company:company:default_currency",
"read_only": 1
},
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "total_gain_loss",
"fieldtype": "Currency",
"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": "Total Gain/Loss",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Exchange Rate Revaluation",
"print_hide": 1,
"read_only": 1
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "amended_from",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Amended From",
"length": 0,
"no_copy": 1,
"options": "Exchange Rate Revaluation",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"is_submittable": 1,
"links": [],
"modified": "2022-11-17 10:28:03.911554",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Exchange Rate Revaluation",
"naming_rule": "Expression (old style)",
"owner": "Administrator",
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 1,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-08-21 16:15:34.660715",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Exchange Rate Revaluation",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"submit": 1,
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"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": 1,
"write": 1
},
},
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"share": 1,
"submit": 1,
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1
},
},
{
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"share": 1,
"submit": 1,
"amend": 1,
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"set_user_permissions": 0,
"share": 1,
"submit": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
}

View File

@@ -222,7 +222,7 @@ class ExchangeRateRevaluation(Document):
@frappe.whitelist()
def get_account_details(account, company, posting_date, party_type=None, party=None):
account_currency, account_type = frappe.get_cached_value(
account_currency, account_type = frappe.db.get_value(
"Account", account, ["account_currency", "account_type"]
)
if account_type in ["Receivable", "Payable"] and not (party_type and party):
@@ -233,10 +233,6 @@ def get_account_details(account, company, posting_date, party_type=None, party=N
balance = get_balance_on(
account, date=posting_date, party_type=party_type, party=party, in_account_currency=False
)
account_details = {
"account_currency": account_currency,
}
if balance:
balance_in_account_currency = get_balance_on(
account, date=posting_date, party_type=party_type, party=party
@@ -246,14 +242,13 @@ def get_account_details(account, company, posting_date, party_type=None, party=N
)
new_exchange_rate = get_exchange_rate(account_currency, company_currency, posting_date)
new_balance_in_base_currency = balance_in_account_currency * new_exchange_rate
account_details = account_details.update(
{
"balance_in_base_currency": balance,
"balance_in_account_currency": balance_in_account_currency,
"current_exchange_rate": current_exchange_rate,
"new_exchange_rate": new_exchange_rate,
"new_balance_in_base_currency": new_balance_in_base_currency,
}
)
account_details = {
"account_currency": account_currency,
"balance_in_base_currency": balance,
"balance_in_account_currency": balance_in_account_currency,
"current_exchange_rate": current_exchange_rate,
"new_exchange_rate": new_exchange_rate,
"new_balance_in_base_currency": new_balance_in_base_currency,
}
return account_details

View File

@@ -1,120 +1,475 @@
{
"actions": [],
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2018-04-13 18:30:06.110433",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"account",
"party_type",
"party",
"column_break_2",
"account_currency",
"balance_in_account_currency",
"balances",
"current_exchange_rate",
"balance_in_base_currency",
"column_break_9",
"new_exchange_rate",
"new_balance_in_base_currency",
"gain_loss"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "account",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Account",
"length": 0,
"no_copy": 0,
"options": "Account",
"reqd": 1
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "party_type",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Party Type",
"options": "DocType"
"length": 0,
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "party",
"fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Party",
"options": "party_type"
"length": 0,
"no_copy": 0,
"options": "party_type",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_2",
"fieldtype": "Column Break"
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "account_currency",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Account Currency",
"length": 0,
"no_copy": 0,
"options": "Currency",
"read_only": 1
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "balance_in_account_currency",
"fieldtype": "Currency",
"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": "Balance In Account Currency",
"length": 0,
"no_copy": 0,
"options": "account_currency",
"read_only": 1
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "balances",
"fieldtype": "Section Break"
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "current_exchange_rate",
"fieldtype": "Float",
"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": "Current Exchange Rate",
"read_only": 1
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "balance_in_base_currency",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Balance In Base Currency",
"options": "Company:company:default_currency",
"read_only": 1
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_9",
"fieldtype": "Column Break"
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "new_exchange_rate",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "New Exchange Rate",
"reqd": 1
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "new_balance_in_base_currency",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "New Balance In Base Currency",
"options": "Company:company:default_currency",
"read_only": 1
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "gain_loss",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Gain/Loss",
"options": "Company:company:default_currency",
"read_only": 1
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"links": [],
"modified": "2022-11-17 10:26:18.302728",
"max_attachments": 0,
"modified": "2019-06-26 18:57:51.762345",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Exchange Rate Revaluation Account",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
"track_changes": 1,
"track_seen": 0,
"track_views": 0
}

View File

@@ -9,6 +9,10 @@ from frappe.model.document import Document
from frappe.utils import add_days, add_years, cstr, getdate
class FiscalYearIncorrectDate(frappe.ValidationError):
pass
class FiscalYear(Document):
@frappe.whitelist()
def set_as_default(self):
@@ -49,18 +53,23 @@ class FiscalYear(Document):
)
def validate_dates(self):
self.validate_from_to_dates("year_start_date", "year_end_date")
if self.is_short_year:
# Fiscal Year can be shorter than one year, in some jurisdictions
# under certain circumstances. For example, in the USA and Germany.
return
if getdate(self.year_start_date) > getdate(self.year_end_date):
frappe.throw(
_("Fiscal Year Start Date should be one year earlier than Fiscal Year End Date"),
FiscalYearIncorrectDate,
)
date = getdate(self.year_start_date) + relativedelta(years=1) - relativedelta(days=1)
if getdate(self.year_end_date) != date:
frappe.throw(
_("Fiscal Year End Date should be one year after Fiscal Year Start Date"),
frappe.exceptions.InvalidDates,
FiscalYearIncorrectDate,
)
def on_update(self):
@@ -160,6 +169,5 @@ def auto_create_fiscal_year():
def get_from_and_to_date(fiscal_year):
fields = ["year_start_date", "year_end_date"]
cached_results = frappe.get_cached_value("Fiscal Year", fiscal_year, fields, as_dict=1)
return dict(from_date=cached_results.year_start_date, to_date=cached_results.year_end_date)
fields = ["year_start_date as from_date", "year_end_date as to_date"]
return frappe.db.get_value("Fiscal Year", fiscal_year, fields, as_dict=1)

View File

@@ -7,6 +7,8 @@ import unittest
import frappe
from frappe.utils import now_datetime
from erpnext.accounts.doctype.fiscal_year.fiscal_year import FiscalYearIncorrectDate
test_ignore = ["Company"]
@@ -24,7 +26,7 @@ class TestFiscalYear(unittest.TestCase):
}
)
self.assertRaises(frappe.exceptions.InvalidDates, fy.insert)
self.assertRaises(FiscalYearIncorrectDate, fy.insert)
def test_record_generator():
@@ -33,8 +35,8 @@ def test_record_generator():
"doctype": "Fiscal Year",
"year": "_Test Short Fiscal Year 2011",
"is_short_year": 1,
"year_start_date": "2011-04-01",
"year_end_date": "2011-12-31",
"year_end_date": "2011-04-01",
"year_start_date": "2011-12-31",
}
]

View File

@@ -42,7 +42,7 @@ class GLEntry(Document):
self.validate_and_set_fiscal_year()
self.pl_must_have_cost_center()
if not self.flags.from_repost and self.voucher_type != "Period Closing Voucher":
if not self.flags.from_repost:
self.check_mandatory()
self.validate_cost_center()
self.check_pl_account()
@@ -51,27 +51,23 @@ class GLEntry(Document):
def on_update(self):
adv_adj = self.flags.adv_adj
if not self.flags.from_repost and self.voucher_type != "Period Closing Voucher":
if not self.flags.from_repost:
self.validate_account_details(adv_adj)
self.validate_dimensions_for_pl_and_bs()
self.validate_allowed_dimensions()
validate_balance_type(self.account, adv_adj)
validate_frozen_account(self.account, adv_adj)
if frappe.get_cached_value("Account", self.account, "account_type") not in [
"Receivable",
"Payable",
]:
# Update outstanding amt on against voucher
if (
self.against_voucher_type in ["Journal Entry", "Sales Invoice", "Purchase Invoice", "Fees"]
and self.against_voucher
and self.flags.update_outstanding == "Yes"
and not frappe.flags.is_reverse_depr_entry
):
update_outstanding_amt(
self.account, self.party_type, self.party, self.against_voucher_type, self.against_voucher
)
# Update outstanding amt on against voucher
if (
self.against_voucher_type in ["Journal Entry", "Sales Invoice", "Purchase Invoice", "Fees"]
and self.against_voucher
and self.flags.update_outstanding == "Yes"
and not frappe.flags.is_reverse_depr_entry
):
update_outstanding_amt(
self.account, self.party_type, self.party, self.against_voucher_type, self.against_voucher
)
def check_mandatory(self):
mandatory = ["account", "voucher_type", "voucher_no", "company"]
@@ -120,7 +116,7 @@ class GLEntry(Document):
frappe.throw(msg, title=_("Missing Cost Center"))
def validate_dimensions_for_pl_and_bs(self):
account_type = frappe.get_cached_value("Account", self.account, "report_type")
account_type = frappe.db.get_value("Account", self.account, "report_type")
for dimension in get_checks_for_pl_and_bs_accounts():
if (
@@ -188,7 +184,7 @@ class GLEntry(Document):
def check_pl_account(self):
if (
self.is_opening == "Yes"
and frappe.get_cached_value("Account", self.account, "report_type") == "Profit and Loss"
and frappe.db.get_value("Account", self.account, "report_type") == "Profit and Loss"
and not self.is_cancelled
):
frappe.throw(
@@ -281,7 +277,7 @@ class GLEntry(Document):
def validate_balance_type(account, adv_adj=False):
if not adv_adj and account:
balance_must_be = frappe.get_cached_value("Account", account, "balance_must_be")
balance_must_be = frappe.db.get_value("Account", account, "balance_must_be")
if balance_must_be:
balance = frappe.db.sql(
"""select sum(debit) - sum(credit)
@@ -366,7 +362,7 @@ def update_outstanding_amt(
if against_voucher_type in ["Sales Invoice", "Purchase Invoice", "Fees"]:
ref_doc = frappe.get_doc(against_voucher_type, against_voucher)
# Didn't use db_set for optimization purpose
# Didn't use db_set for optimisation purpose
ref_doc.outstanding_amount = bal
frappe.db.set_value(against_voucher_type, against_voucher, "outstanding_amount", bal)

View File

@@ -0,0 +1,90 @@
{
"actions": [],
"creation": "2018-01-02 15:48:58.768352",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"company",
"cgst_account",
"sgst_account",
"igst_account",
"cess_account",
"utgst_account",
"is_reverse_charge_account"
],
"fields": [
{
"columns": 1,
"fieldname": "company",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Company",
"options": "Company",
"reqd": 1
},
{
"columns": 2,
"fieldname": "cgst_account",
"fieldtype": "Link",
"in_list_view": 1,
"label": "CGST Account",
"options": "Account",
"reqd": 1
},
{
"columns": 2,
"fieldname": "sgst_account",
"fieldtype": "Link",
"in_list_view": 1,
"label": "SGST Account",
"options": "Account",
"reqd": 1
},
{
"columns": 2,
"fieldname": "igst_account",
"fieldtype": "Link",
"in_list_view": 1,
"label": "IGST Account",
"options": "Account",
"reqd": 1
},
{
"columns": 2,
"fieldname": "cess_account",
"fieldtype": "Link",
"in_list_view": 1,
"label": "CESS Account",
"options": "Account"
},
{
"columns": 1,
"default": "0",
"fieldname": "is_reverse_charge_account",
"fieldtype": "Check",
"in_list_view": 1,
"label": "Is Reverse Charge Account"
},
{
"fieldname": "utgst_account",
"fieldtype": "Link",
"label": "UTGST Account",
"options": "Account"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-04-07 12:59:14.039768",
"modified_by": "Administrator",
"module": "Accounts",
"name": "GST Account",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from frappe.model.document import Document
class GSTAccount(Document):
pass

View File

@@ -21,7 +21,7 @@ class ItemTaxTemplate(Document):
check_list = []
for d in self.get("taxes"):
if d.tax_type:
account_type = frappe.get_cached_value("Account", d.tax_type, "account_type")
account_type = frappe.db.get_value("Account", d.tax_type, "account_type")
if account_type not in [
"Tax",

View File

@@ -149,6 +149,22 @@ frappe.ui.form.on("Journal Entry", {
}
});
}
else if(frm.doc.voucher_type=="Opening Entry") {
return frappe.call({
type:"GET",
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_opening_accounts",
args: {
"company": frm.doc.company
},
callback: function(r) {
frappe.model.clear_table(frm.doc, "accounts");
if(r.message) {
update_jv_details(frm.doc, r.message);
}
cur_frm.set_value("is_opening", "Yes");
}
});
}
}
},
@@ -173,8 +189,8 @@ frappe.ui.form.on("Journal Entry", {
var update_jv_details = function(doc, r) {
$.each(r, function(i, d) {
var row = frappe.model.add_child(doc, "Journal Entry Account", "accounts");
frappe.model.set_value(row.doctype, row.name, "account", d.account)
frappe.model.set_value(row.doctype, row.name, "balance", d.balance)
row.account = d.account;
row.balance = d.balance;
});
refresh_field("accounts");
}
@@ -224,6 +240,25 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
me.frm.set_query("reference_name", "accounts", function(doc, cdt, cdn) {
var jvd = frappe.get_doc(cdt, cdn);
// expense claim
if(jvd.reference_type==="Expense Claim") {
return {
filters: {
'total_sanctioned_amount': ['>', 0],
'status': ['!=', 'Paid'],
'docstatus': 1
}
};
}
if(jvd.reference_type==="Employee Advance") {
return {
filters: {
'docstatus': 1
}
};
}
// journal entry
if(jvd.reference_type==="Journal Entry") {
frappe.model.validate_missing(jvd, "account");
@@ -236,6 +271,13 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
};
}
// payroll entry
if(jvd.reference_type==="Payroll Entry") {
return {
query: "erpnext.payroll.doctype.payroll_entry.payroll_entry.get_payroll_entries_for_jv",
};
}
var out = {
filters: [
[jvd.reference_type, "docstatus", "=", 1]
@@ -253,6 +295,9 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
var party_account_field = jvd.reference_type==="Sales Invoice" ? "debit_to": "credit_to";
out.filters.push([jvd.reference_type, party_account_field, "=", jvd.account]);
if (in_list(['Debit Note', 'Credit Note'], doc.voucher_type)) {
out.filters.push([jvd.reference_type, "is_return", "=", 1]);
}
}
if(in_list(["Sales Order", "Purchase Order"], jvd.reference_type)) {
@@ -309,7 +354,8 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
}
}
get_outstanding(doctype, docname, company, child) {
get_outstanding(doctype, docname, company, child, due_date) {
var me = this;
var args = {
"doctype": doctype,
"docname": docname,

View File

@@ -137,8 +137,7 @@
"fieldname": "finance_book",
"fieldtype": "Link",
"label": "Finance Book",
"options": "Finance Book",
"read_only": 1
"options": "Finance Book"
},
{
"fieldname": "2_add_edit_gl_entries",
@@ -539,7 +538,7 @@
"idx": 176,
"is_submittable": 1,
"links": [],
"modified": "2022-06-23 22:01:32.348337",
"modified": "2022-04-06 17:18:46.865259",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry",

View File

@@ -24,6 +24,7 @@ from erpnext.accounts.utils import (
get_stock_and_account_balance,
)
from erpnext.controllers.accounts_controller import AccountsController
from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount
class StockAccountInvalidTransaction(frappe.ValidationError):
@@ -65,6 +66,7 @@ class JournalEntry(AccountsController):
self.set_against_account()
self.create_remarks()
self.set_print_format_fields()
self.validate_expense_claim()
self.validate_credit_debit_note()
self.validate_empty_accounts_table()
self.set_account_and_party_balance()
@@ -81,21 +83,27 @@ class JournalEntry(AccountsController):
self.check_credit_limit()
self.make_gl_entries()
self.update_advance_paid()
self.update_expense_claim()
self.update_inter_company_jv()
self.update_invoice_discounting()
self.update_status_for_full_and_final_statement()
def on_cancel(self):
from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
from erpnext.payroll.doctype.salary_slip.salary_slip import unlink_ref_doc_from_salary_slip
unlink_ref_doc_from_payment_entries(self)
unlink_ref_doc_from_salary_slip(self.name)
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
self.make_gl_entries(1)
self.update_advance_paid()
self.update_expense_claim()
self.unlink_advance_entry_reference()
self.unlink_asset_reference()
self.unlink_inter_company_jv()
self.unlink_asset_adjustment_entry()
self.update_invoice_discounting()
self.update_status_for_full_and_final_statement()
def get_title(self):
return self.pay_to_recd_from or self.accounts[0].account
@@ -104,13 +112,21 @@ class JournalEntry(AccountsController):
advance_paid = frappe._dict()
for d in self.get("accounts"):
if d.is_advance:
if d.reference_type in frappe.get_hooks("advance_payment_doctypes"):
if d.reference_type in ("Sales Order", "Purchase Order", "Employee Advance"):
advance_paid.setdefault(d.reference_type, []).append(d.reference_name)
for voucher_type, order_list in advance_paid.items():
for voucher_no in list(set(order_list)):
frappe.get_doc(voucher_type, voucher_no).set_total_advance_paid()
def update_status_for_full_and_final_statement(self):
for entry in self.accounts:
if entry.reference_type == "Full and Final Statement":
if self.docstatus == 1:
frappe.db.set_value("Full and Final Statement", entry.reference_name, "status", "Paid")
elif self.docstatus == 2:
frappe.db.set_value("Full and Final Statement", entry.reference_name, "status", "Unpaid")
def validate_inter_company_accounts(self):
if (
self.voucher_type == "Inter Company Journal Entry"
@@ -184,9 +200,7 @@ class JournalEntry(AccountsController):
}
)
tax_withholding_details, advance_taxes, voucher_wise_amount = get_party_tax_withholding_details(
inv, self.tax_withholding_category
)
tax_withholding_details = get_party_tax_withholding_details(inv, self.tax_withholding_category)
if not tax_withholding_details:
return
@@ -319,7 +333,7 @@ class JournalEntry(AccountsController):
def validate_party(self):
for d in self.get("accounts"):
account_type = frappe.get_cached_value("Account", d.account, "account_type")
account_type = frappe.db.get_value("Account", d.account, "account_type")
if account_type in ["Receivable", "Payable"]:
if not (d.party_type and d.party):
frappe.throw(
@@ -382,7 +396,7 @@ class JournalEntry(AccountsController):
def validate_against_jv(self):
for d in self.get("accounts"):
if d.reference_type == "Journal Entry":
account_root_type = frappe.get_cached_value("Account", d.account, "root_type")
account_root_type = frappe.db.get_value("Account", d.account, "root_type")
if account_root_type == "Asset" and flt(d.debit) > 0:
frappe.throw(
_(
@@ -402,7 +416,7 @@ class JournalEntry(AccountsController):
against_entries = frappe.db.sql(
"""select * from `tabJournal Entry Account`
where account = %s and docstatus = 1 and parent = %s
and (reference_type is null or reference_type in ('', 'Sales Order', 'Purchase Order'))
and (reference_type is null or reference_type in ("", "Sales Order", "Purchase Order"))
""",
(d.account, d.reference_name),
as_dict=True,
@@ -631,7 +645,7 @@ class JournalEntry(AccountsController):
def validate_multi_currency(self):
alternate_currency = []
for d in self.get("accounts"):
account = frappe.get_cached_value(
account = frappe.db.get_value(
"Account", d.account, ["account_currency", "account_type"], as_dict=1
)
if account:
@@ -762,7 +776,7 @@ class JournalEntry(AccountsController):
party_amount += d.debit_in_account_currency or d.credit_in_account_currency
party_account_currency = d.account_currency
elif frappe.get_cached_value("Account", d.account, "account_type") in ["Bank", "Cash"]:
elif frappe.db.get_value("Account", d.account, "account_type") in ["Bank", "Cash"]:
bank_amount += d.debit_in_account_currency or d.credit_in_account_currency
bank_account_currency = d.account_currency
@@ -786,7 +800,9 @@ class JournalEntry(AccountsController):
self.total_amount_in_words = money_in_words(amt, currency)
def build_gl_map(self):
def make_gl_entries(self, cancel=0, adv_adj=0):
from erpnext.accounts.general_ledger import make_gl_entries
gl_map = []
for d in self.get("accounts"):
if d.debit or d.credit:
@@ -822,12 +838,7 @@ class JournalEntry(AccountsController):
item=d,
)
)
return gl_map
def make_gl_entries(self, cancel=0, adv_adj=0):
from erpnext.accounts.general_ledger import make_gl_entries
gl_map = self.build_gl_map()
if self.voucher_type in ("Deferred Revenue", "Deferred Expense"):
update_outstanding = "No"
else:
@@ -921,6 +932,29 @@ class JournalEntry(AccountsController):
as_dict=True,
)
def update_expense_claim(self):
for d in self.accounts:
if d.reference_type == "Expense Claim" and d.reference_name:
doc = frappe.get_doc("Expense Claim", d.reference_name)
if self.docstatus == 2:
update_reimbursed_amount(doc, -1 * d.debit)
else:
update_reimbursed_amount(doc, d.debit)
def validate_expense_claim(self):
for d in self.accounts:
if d.reference_type == "Expense Claim":
sanctioned_amount, reimbursed_amount = frappe.db.get_value(
"Expense Claim", d.reference_name, ("total_sanctioned_amount", "total_amount_reimbursed")
)
pending_amount = flt(sanctioned_amount) - flt(reimbursed_amount)
if d.debit > pending_amount:
frappe.throw(
_(
"Row No {0}: Amount cannot be greater than Pending Amount against Expense Claim {1}. Pending Amount is {2}"
).format(d.idx, d.reference_name, pending_amount)
)
def validate_credit_debit_note(self):
if self.stock_entry:
if frappe.db.get_value("Stock Entry", self.stock_entry, "docstatus") != 1:
@@ -987,7 +1021,7 @@ def get_default_bank_cash_account(company, account_type=None, mode_of_payment=No
account = account_list[0].name
if account:
account_details = frappe.get_cached_value(
account_details = frappe.db.get_value(
"Account", account, ["account_currency", "account_type"], as_dict=1
)
@@ -1116,7 +1150,7 @@ def get_payment_entry(ref_doc, args):
"party_type": args.get("party_type"),
"party": ref_doc.get(args.get("party_type").lower()),
"cost_center": cost_center,
"account_type": frappe.get_cached_value("Account", args.get("party_account"), "account_type"),
"account_type": frappe.db.get_value("Account", args.get("party_account"), "account_type"),
"account_currency": args.get("party_account_currency")
or get_account_currency(args.get("party_account")),
"balance": get_balance_on(args.get("party_account")),
@@ -1167,6 +1201,24 @@ def get_payment_entry(ref_doc, args):
return je if args.get("journal_entry") else je.as_dict()
@frappe.whitelist()
def get_opening_accounts(company):
"""get all balance sheet accounts for opening entry"""
accounts = frappe.db.sql_list(
"""select
name from tabAccount
where
is_group=0 and report_type='Balance Sheet' and company={0} and
name not in (select distinct account from tabWarehouse where
account is not null and account != '')
order by name asc""".format(
frappe.db.escape(company)
)
)
return [{"account": a, "balance": get_balance_on(a)} for a in accounts]
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_against_jv(doctype, txt, searchfield, start, page_len, filters):
@@ -1187,7 +1239,7 @@ def get_against_jv(doctype, txt, searchfield, start, page_len, filters):
AND jv.docstatus = 1
AND jv.`{0}` LIKE %(txt)s
ORDER BY jv.name DESC
LIMIT %(limit)s offset %(offset)s
LIMIT %(offset)s, %(limit)s
""".format(
searchfield
),
@@ -1210,7 +1262,6 @@ def get_outstanding(args):
args = json.loads(args)
company_currency = erpnext.get_company_currency(args.get("company"))
due_date = None
if args.get("doctype") == "Journal Entry":
condition = " and party=%(party)s" if args.get("party") else ""
@@ -1235,12 +1286,10 @@ def get_outstanding(args):
invoice = frappe.db.get_value(
args["doctype"],
args["docname"],
["outstanding_amount", "conversion_rate", scrub(party_type), "due_date"],
["outstanding_amount", "conversion_rate", scrub(party_type)],
as_dict=1,
)
due_date = invoice.get("due_date")
exchange_rate = (
invoice.conversion_rate if (args.get("account_currency") != company_currency) else 1
)
@@ -1263,7 +1312,6 @@ def get_outstanding(args):
"exchange_rate": exchange_rate,
"party_type": party_type,
"party": invoice.get(scrub(party_type)),
"reference_due_date": due_date,
}
@@ -1283,7 +1331,7 @@ def get_party_account_and_balance(company, party_type, party, cost_center=None):
"account": account,
"balance": account_balance,
"party_balance": party_balance,
"account_currency": frappe.get_cached_value("Account", account, "account_currency"),
"account_currency": frappe.db.get_value("Account", account, "account_currency"),
}
@@ -1296,7 +1344,7 @@ def get_account_balance_and_party_type(
frappe.msgprint(_("No Permission"), raise_exception=1)
company_currency = erpnext.get_company_currency(company)
account_details = frappe.get_cached_value(
account_details = frappe.db.get_value(
"Account", account, ["account_type", "account_currency"], as_dict=1
)
@@ -1349,7 +1397,7 @@ def get_exchange_rate(
):
from erpnext.setup.utils import get_exchange_rate
account_details = frappe.get_cached_value(
account_details = frappe.db.get_value(
"Account", account, ["account_type", "root_type", "account_currency", "company"], as_dict=1
)

View File

@@ -0,0 +1,17 @@
frappe.ui.form.on("Journal Entry", {
refresh: function(frm) {
frm.set_query('company_address', function(doc) {
if(!doc.company) {
frappe.throw(__('Please set Company'));
}
return {
query: 'frappe.contacts.doctype.address.address.address_query',
filters: {
link_doctype: 'Company',
link_name: doc.company
}
};
});
}
});

View File

@@ -202,7 +202,6 @@
"fieldname": "reference_type",
"fieldtype": "Select",
"label": "Reference Type",
"no_copy": 1,
"options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim\nAsset\nLoan\nPayroll Entry\nEmployee Advance\nExchange Rate Revaluation\nInvoice Discounting\nFees\nFull and Final Statement"
},
{
@@ -210,15 +209,13 @@
"fieldtype": "Dynamic Link",
"in_list_view": 1,
"label": "Reference Name",
"no_copy": 1,
"options": "reference_type"
},
{
"depends_on": "eval:doc.reference_type&&!in_list(doc.reference_type, ['Expense Claim', 'Asset', 'Employee Loan', 'Employee Advance'])",
"fieldname": "reference_due_date",
"fieldtype": "Date",
"label": "Reference Due Date",
"no_copy": 1
"fieldtype": "Select",
"label": "Reference Due Date"
},
{
"fieldname": "project",
@@ -277,22 +274,19 @@
"fieldname": "reference_detail_no",
"fieldtype": "Data",
"hidden": 1,
"label": "Reference Detail No",
"no_copy": 1
"label": "Reference Detail No"
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2022-10-26 20:03:10.906259",
"modified": "2021-08-30 21:27:32.200299",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry Account",
"naming_rule": "Random",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -2,7 +2,7 @@
// For license information, please see license.txt
frappe.ui.form.on("Journal Entry Template", {
refresh: function(frm) {
setup: function(frm) {
frappe.model.set_default_values(frm.doc);
frm.set_query("account" ,"accounts", function(){
@@ -45,6 +45,21 @@ frappe.ui.form.on("Journal Entry Template", {
frm.trigger("clear_child");
switch(frm.doc.voucher_type){
case "Opening Entry":
frm.set_value("is_opening", "Yes");
frappe.call({
type:"GET",
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_opening_accounts",
args: {
"company": frm.doc.company
},
callback: function(r) {
if(r.message) {
add_accounts(frm.doc, r.message);
}
}
});
break;
case "Bank Entry":
case "Cash Entry":
frappe.call({

View File

@@ -4,20 +4,22 @@
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils.background_jobs import is_job_queued
from erpnext.accounts.doctype.account.account import merge_account
class LedgerMerge(Document):
def start_merge(self):
from frappe.core.page.background_jobs.background_jobs import get_info
from frappe.utils.background_jobs import enqueue
from frappe.utils.scheduler import is_scheduler_inactive
if is_scheduler_inactive() and not frappe.flags.in_test:
frappe.throw(_("Scheduler is inactive. Cannot merge accounts."), title=_("Scheduler Inactive"))
if not is_job_queued(self.name):
enqueued_jobs = [d.get("job_name") for d in get_info()]
if self.name not in enqueued_jobs:
enqueue(
start_merge,
queue="default",

View File

@@ -25,7 +25,7 @@ class ModeofPayment(Document):
def validate_accounts(self):
for entry in self.accounts:
"""Error when Company of Ledger account doesn't match with Company Selected"""
if frappe.get_cached_value("Account", entry.default_account, "company") != entry.company:
if frappe.db.get_value("Account", entry.default_account, "company") != entry.company:
frappe.throw(
_("Account {0} does not match with Company {1} in Mode of Account: {2}").format(
entry.default_account, entry.company, self.name

View File

@@ -20,14 +20,15 @@ frappe.ui.form.on('Opening Invoice Creation Tool', {
frm.dashboard.reset();
frm.doc.import_in_progress = true;
}
if (data.user != frappe.session.user) return;
if (data.count == data.total) {
setTimeout(() => {
setTimeout((title) => {
frm.doc.import_in_progress = false;
frm.clear_table("invoices");
frm.refresh_fields();
frm.page.clear_indicator();
frm.dashboard.hide_progress();
frappe.msgprint(__("Opening {0} Invoices created", [frm.doc.invoice_type]));
frm.dashboard.hide_progress(title);
frappe.msgprint(__("Opening {0} Invoice created", [frm.doc.invoice_type]));
}, 1500, data.title);
return;
}
@@ -50,6 +51,13 @@ frappe.ui.form.on('Opening Invoice Creation Tool', {
method: "make_invoices",
freeze: 1,
freeze_message: __("Creating {0} Invoice", [frm.doc.invoice_type]),
callback: function(r) {
if (r.message.length == 1) {
frappe.msgprint(__("{0} Invoice created successfully.", [frm.doc.invoice_type]));
} else if (r.message.length < 50) {
frappe.msgprint(__("{0} Invoices created successfully.", [frm.doc.invoice_type]));
}
}
});
});

View File

@@ -6,7 +6,7 @@ import frappe
from frappe import _, scrub
from frappe.model.document import Document
from frappe.utils import flt, nowdate
from frappe.utils.background_jobs import enqueue, is_job_queued
from frappe.utils.background_jobs import enqueue
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
@@ -207,12 +207,14 @@ class OpeningInvoiceCreationTool(Document):
if len(invoices) < 50:
return start_import(invoices)
else:
from frappe.core.page.background_jobs.background_jobs import get_info
from frappe.utils.scheduler import is_scheduler_inactive
if is_scheduler_inactive() and not frappe.flags.in_test:
frappe.throw(_("Scheduler is inactive. Cannot import data."), title=_("Scheduler Inactive"))
if not is_job_queued(self.name):
enqueued_jobs = [d.get("job_name") for d in get_info()]
if self.name not in enqueued_jobs:
enqueue(
start_import,
queue="default",
@@ -255,15 +257,17 @@ def start_import(invoices):
def publish(index, total, doctype):
if total < 50:
return
frappe.publish_realtime(
"opening_invoice_creation_progress",
dict(
title=_("Opening Invoice Creation In Progress"),
message=_("Creating {} out of {} {}").format(index + 1, total, doctype),
user=frappe.session.user,
count=index + 1,
total=total,
),
user=frappe.session.user,
)

View File

@@ -110,6 +110,8 @@ frappe.ui.form.on('Payment Entry', {
var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"];
} else if (frm.doc.party_type == "Supplier") {
var doctypes = ["Purchase Order", "Purchase Invoice", "Journal Entry"];
} else if (frm.doc.party_type == "Employee") {
var doctypes = ["Expense Claim", "Journal Entry"];
} else {
var doctypes = ["Journal Entry"];
}
@@ -138,12 +140,17 @@ frappe.ui.form.on('Payment Entry', {
const child = locals[cdt][cdn];
const filters = {"docstatus": 1, "company": doc.company};
const party_type_doctypes = ['Sales Invoice', 'Sales Order', 'Purchase Invoice',
'Purchase Order', 'Dunning'];
'Purchase Order', 'Expense Claim', 'Fees', 'Dunning'];
if (in_list(party_type_doctypes, child.reference_doctype)) {
filters[doc.party_type.toLowerCase()] = doc.party;
}
if(child.reference_doctype == "Expense Claim") {
filters["docstatus"] = 1;
filters["is_paid"] = 0;
}
return {
filters: filters
};
@@ -723,7 +730,7 @@ frappe.ui.form.on('Payment Entry', {
c.payment_term = d.payment_term;
c.allocated_amount = d.allocated_amount;
if(!in_list(frm.events.get_order_doctypes(frm), d.voucher_type)) {
if(!in_list(["Sales Order", "Purchase Order", "Expense Claim", "Fees"], d.voucher_type)) {
if(flt(d.outstanding_amount) > 0)
total_positive_outstanding += flt(d.outstanding_amount);
else
@@ -738,7 +745,7 @@ frappe.ui.form.on('Payment Entry', {
} else {
c.exchange_rate = 1;
}
if (in_list(frm.events.get_invoice_doctypes(frm), d.reference_doctype)){
if (in_list(['Sales Invoice', 'Purchase Invoice', "Expense Claim", "Fees"], d.reference_doctype)){
c.due_date = d.due_date;
}
});
@@ -769,14 +776,6 @@ frappe.ui.form.on('Payment Entry', {
});
},
get_order_doctypes: function(frm) {
return ["Sales Order", "Purchase Order"];
},
get_invoice_doctypes: function(frm) {
return ["Sales Invoice", "Purchase Invoice"];
},
allocate_party_amount_against_ref_docs: function(frm, paid_amount, paid_amount_change) {
var total_positive_outstanding_including_order = 0;
var total_negative_outstanding = 0;
@@ -947,6 +946,14 @@ frappe.ui.form.on('Payment Entry', {
frappe.msgprint(__("Row #{0}: Reference Document Type must be one of Purchase Order, Purchase Invoice or Journal Entry", [row.idx]));
return false;
}
if(frm.doc.party_type=="Employee" &&
!in_list(["Expense Claim", "Journal Entry"], row.reference_doctype)
) {
frappe.model.set_value(row.doctype, row.name, "against_voucher_type", null);
frappe.msgprint(__("Row #{0}: Reference Document Type must be one of Expense Claim or Journal Entry", [row.idx]));
return false;
}
}
if (row) {
@@ -1091,7 +1098,7 @@ frappe.ui.form.on('Payment Entry', {
$.each(tax_fields, function(i, fieldname) { tax[fieldname] = 0.0; });
frm.doc.paid_amount_after_tax = frm.doc.base_paid_amount;
frm.doc.paid_amount_after_tax = frm.doc.paid_amount;
});
},
@@ -1182,7 +1189,7 @@ frappe.ui.form.on('Payment Entry', {
}
cumulated_tax_fraction += tax.tax_fraction_for_current_item;
frm.doc.paid_amount_after_tax = flt(frm.doc.base_paid_amount/(1+cumulated_tax_fraction))
frm.doc.paid_amount_after_tax = flt(frm.doc.paid_amount/(1+cumulated_tax_fraction))
});
},
@@ -1214,7 +1221,6 @@ frappe.ui.form.on('Payment Entry', {
frm.doc.total_taxes_and_charges = 0.0;
frm.doc.base_total_taxes_and_charges = 0.0;
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
let actual_tax_dict = {};
// maintain actual tax rate based on idx
@@ -1235,8 +1241,8 @@ frappe.ui.form.on('Payment Entry', {
}
}
// tax accounts are only in company currency
tax.base_tax_amount = current_tax_amount;
tax.tax_amount = current_tax_amount;
tax.base_tax_amount = tax.tax_amount * frm.doc.source_exchange_rate;
current_tax_amount *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0;
if(i==0) {
@@ -1245,29 +1251,9 @@ frappe.ui.form.on('Payment Entry', {
tax.total = flt(frm.doc["taxes"][i-1].total + current_tax_amount, precision("total", tax));
}
// tac accounts are only in company currency
tax.base_total = tax.total
// calculate total taxes and base total taxes
if(frm.doc.payment_type == "Pay") {
// tax accounts only have company currency
if(tax.currency != frm.doc.paid_to_account_currency) {
//total_taxes_and_charges has the target currency. so using target conversion rate
frm.doc.total_taxes_and_charges += flt(current_tax_amount / frm.doc.target_exchange_rate);
} else {
frm.doc.total_taxes_and_charges += current_tax_amount;
}
} else if(frm.doc.payment_type == "Receive") {
if(tax.currency != frm.doc.paid_from_account_currency) {
//total_taxes_and_charges has the target currency. so using source conversion rate
frm.doc.total_taxes_and_charges += flt(current_tax_amount / frm.doc.source_exchange_rate);
} else {
frm.doc.total_taxes_and_charges += current_tax_amount;
}
}
frm.doc.base_total_taxes_and_charges += tax.base_tax_amount;
tax.base_total = tax.total * frm.doc.source_exchange_rate;
frm.doc.total_taxes_and_charges += current_tax_amount;
frm.doc.base_total_taxes_and_charges += current_tax_amount * frm.doc.source_exchange_rate;
frm.refresh_field('taxes');
frm.refresh_field('total_taxes_and_charges');

View File

@@ -6,7 +6,7 @@ import json
from functools import reduce
import frappe
from frappe import ValidationError, _, qb, scrub, throw
from frappe import ValidationError, _, scrub, throw
from frappe.utils import cint, comma_or, flt, getdate, nowdate
import erpnext
@@ -29,6 +29,7 @@ from erpnext.controllers.accounts_controller import (
get_supplier_block_status,
validate_taxes_and_charges,
)
from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount
from erpnext.setup.utils import get_exchange_rate
@@ -62,6 +63,7 @@ class PaymentEntry(AccountsController):
self.set_missing_values()
self.validate_payment_type()
self.validate_party_details()
self.validate_bank_accounts()
self.set_exchange_rate()
self.validate_mandatory()
self.validate_reference_documents()
@@ -86,6 +88,7 @@ class PaymentEntry(AccountsController):
if self.difference_amount:
frappe.throw(_("Difference Amount must be zero"))
self.make_gl_entries()
self.update_expense_claim()
self.update_outstanding_amounts()
self.update_advance_paid()
self.update_payment_schedule()
@@ -94,6 +97,7 @@ class PaymentEntry(AccountsController):
def on_cancel(self):
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
self.make_gl_entries(cancel=1)
self.update_expense_claim()
self.update_outstanding_amounts()
self.update_advance_paid()
self.delink_advance_entry_references()
@@ -180,11 +184,7 @@ class PaymentEntry(AccountsController):
frappe.throw(_("Party is mandatory"))
_party_name = "title" if self.party_type == "Shareholder" else self.party_type.lower() + "_name"
if frappe.db.has_column(self.party_type, _party_name):
self.party_name = frappe.db.get_value(self.party_type, self.party, _party_name)
else:
self.party_name = frappe.db.get_value(self.party_type, self.party, "name")
self.party_name = frappe.db.get_value(self.party_type, self.party, _party_name)
if self.party:
if not self.party_balance:
@@ -242,6 +242,23 @@ class PaymentEntry(AccountsController):
if not frappe.db.exists(self.party_type, self.party):
frappe.throw(_("Invalid {0}: {1}").format(self.party_type, self.party))
if self.party_account and self.party_type in ("Customer", "Supplier"):
self.validate_account_type(
self.party_account, [erpnext.get_party_account_type(self.party_type)]
)
def validate_bank_accounts(self):
if self.payment_type in ("Pay", "Internal Transfer"):
self.validate_account_type(self.paid_from, ["Bank", "Cash"])
if self.payment_type in ("Receive", "Internal Transfer"):
self.validate_account_type(self.paid_to, ["Bank", "Cash"])
def validate_account_type(self, account, account_types):
account_type = frappe.db.get_value("Account", account, "account_type")
# if account_type not in account_types:
# frappe.throw(_("Account Type for {0} must be {1}").format(account, comma_or(account_types)))
def set_exchange_rate(self, ref_doc=None):
self.set_source_exchange_rate(ref_doc)
self.set_target_exchange_rate(ref_doc)
@@ -279,10 +296,14 @@ class PaymentEntry(AccountsController):
frappe.throw(_("{0} is mandatory").format(self.meta.get_label(field)))
def validate_reference_documents(self):
valid_reference_doctypes = self.get_valid_reference_doctypes()
if not valid_reference_doctypes:
return
if self.party_type == "Customer":
valid_reference_doctypes = ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning")
elif self.party_type == "Supplier":
valid_reference_doctypes = ("Purchase Order", "Purchase Invoice", "Journal Entry")
elif self.party_type == "Employee":
valid_reference_doctypes = ("Expense Claim", "Journal Entry", "Employee Advance", "Gratuity")
elif self.party_type == "Shareholder":
valid_reference_doctypes = "Journal Entry"
for d in self.get("references"):
if not d.allocated_amount:
@@ -308,7 +329,7 @@ class PaymentEntry(AccountsController):
else:
self.validate_journal_entry()
if d.reference_doctype in frappe.get_hooks("invoice_doctypes"):
if d.reference_doctype in ("Sales Invoice", "Purchase Invoice", "Expense Claim", "Fees"):
if self.party_type == "Customer":
ref_party_account = (
get_party_account_based_on_invoice_discounting(d.reference_name) or ref_doc.debit_to
@@ -334,16 +355,6 @@ class PaymentEntry(AccountsController):
if ref_doc.docstatus != 1:
frappe.throw(_("{0} {1} must be submitted").format(d.reference_doctype, d.reference_name))
def get_valid_reference_doctypes(self):
if self.party_type == "Customer":
return ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning")
elif self.party_type == "Supplier":
return ("Purchase Order", "Purchase Invoice", "Journal Entry")
elif self.party_type == "Shareholder":
return ("Journal Entry",)
elif self.party_type == "Employee":
return ("Journal Entry",)
def validate_paid_invoices(self):
no_oustanding_refs = {}
@@ -351,7 +362,7 @@ class PaymentEntry(AccountsController):
if not d.allocated_amount:
continue
if d.reference_doctype in ("Sales Invoice", "Purchase Invoice"):
if d.reference_doctype in ("Sales Invoice", "Purchase Invoice", "Fees"):
outstanding_amount, is_return = frappe.get_cached_value(
d.reference_doctype, d.reference_name, ["outstanding_amount", "is_return"]
)
@@ -725,7 +736,7 @@ class PaymentEntry(AccountsController):
def validate_transaction_reference(self):
bank_account = self.paid_to if self.payment_type == "Receive" else self.paid_from
bank_account_type = frappe.get_cached_value("Account", bank_account, "account_type")
bank_account_type = frappe.db.get_value("Account", bank_account, "account_type")
if bank_account_type == "Bank":
if not self.reference_no or not self.reference_date:
@@ -774,7 +785,7 @@ class PaymentEntry(AccountsController):
self.set("remarks", "\n".join(remarks))
def build_gl_map(self):
def make_gl_entries(self, cancel=0, adv_adj=0):
if self.payment_type in ("Receive", "Pay") and not self.get("party_account_field"):
self.setup_party_account_field()
@@ -783,10 +794,7 @@ class PaymentEntry(AccountsController):
self.add_bank_gl_entries(gl_entries)
self.add_deductions_gl_entries(gl_entries)
self.add_tax_gl_entries(gl_entries)
return gl_entries
def make_gl_entries(self, cancel=0, adv_adj=0):
gl_entries = self.build_gl_map()
gl_entries = process_gl_map(gl_entries)
make_gl_entries(gl_entries, cancel=cancel, adv_adj=adv_adj)
@@ -922,13 +930,6 @@ class PaymentEntry(AccountsController):
)
if not d.included_in_paid_amount:
if get_account_currency(payment_account) != self.company_currency:
if self.payment_type == "Receive":
exchange_rate = self.target_exchange_rate
elif self.payment_type in ["Pay", "Internal Transfer"]:
exchange_rate = self.source_exchange_rate
base_tax_amount = flt((tax_amount / exchange_rate), self.precision("paid_amount"))
gl_entries.append(
self.get_gl_dict(
{
@@ -976,10 +977,23 @@ class PaymentEntry(AccountsController):
def update_advance_paid(self):
if self.payment_type in ("Receive", "Pay") and self.party:
for d in self.get("references"):
if d.allocated_amount and d.reference_doctype in frappe.get_hooks("advance_payment_doctypes"):
frappe.get_doc(
d.reference_doctype, d.reference_name, for_update=True
).set_total_advance_paid()
if d.allocated_amount and d.reference_doctype in (
"Sales Order",
"Purchase Order",
"Employee Advance",
"Gratuity",
):
frappe.get_doc(d.reference_doctype, d.reference_name).set_total_advance_paid()
def update_expense_claim(self):
if self.payment_type in ("Pay") and self.party:
for d in self.get("references"):
if d.reference_doctype == "Expense Claim" and d.reference_name:
doc = frappe.get_doc("Expense Claim", d.reference_name)
if self.docstatus == 2:
update_reimbursed_amount(doc, -1 * d.allocated_amount)
else:
update_reimbursed_amount(doc, d.allocated_amount)
def on_recurring(self, reference_doc, auto_repeat_doc):
self.reference_no = reference_doc.name
@@ -1024,7 +1038,7 @@ class PaymentEntry(AccountsController):
for fieldname in tax_fields:
tax.set(fieldname, 0.0)
self.paid_amount_after_tax = self.base_paid_amount
self.paid_amount_after_tax = self.paid_amount
def determine_exclusive_rate(self):
if not any(cint(tax.included_in_paid_amount) for tax in self.get("taxes")):
@@ -1043,7 +1057,7 @@ class PaymentEntry(AccountsController):
cumulated_tax_fraction += tax.tax_fraction_for_current_item
self.paid_amount_after_tax = flt(self.base_paid_amount / (1 + cumulated_tax_fraction))
self.paid_amount_after_tax = flt(self.paid_amount / (1 + cumulated_tax_fraction))
def calculate_taxes(self):
self.total_taxes_and_charges = 0.0
@@ -1066,7 +1080,7 @@ class PaymentEntry(AccountsController):
current_tax_amount += actual_tax_dict[tax.idx]
tax.tax_amount = current_tax_amount
tax.base_tax_amount = current_tax_amount
tax.base_tax_amount = tax.tax_amount * self.source_exchange_rate
if tax.add_deduct_tax == "Deduct":
current_tax_amount *= -1.0
@@ -1080,20 +1094,14 @@ class PaymentEntry(AccountsController):
self.get("taxes")[i - 1].total + current_tax_amount, self.precision("total", tax)
)
tax.base_total = tax.total
tax.base_total = tax.total * self.source_exchange_rate
if self.payment_type == "Pay":
if tax.currency != self.paid_to_account_currency:
self.total_taxes_and_charges += flt(current_tax_amount / self.target_exchange_rate)
else:
self.total_taxes_and_charges += current_tax_amount
elif self.payment_type == "Receive":
if tax.currency != self.paid_from_account_currency:
self.total_taxes_and_charges += flt(current_tax_amount / self.source_exchange_rate)
else:
self.total_taxes_and_charges += current_tax_amount
self.base_total_taxes_and_charges += tax.base_tax_amount
self.base_total_taxes_and_charges += flt(current_tax_amount / self.source_exchange_rate)
self.total_taxes_and_charges += flt(current_tax_amount / self.target_exchange_rate)
else:
self.base_total_taxes_and_charges += flt(current_tax_amount / self.target_exchange_rate)
self.total_taxes_and_charges += flt(current_tax_amount / self.source_exchange_rate)
if self.get("taxes"):
self.paid_amount_after_tax = self.get("taxes")[-1].base_total
@@ -1180,16 +1188,13 @@ def validate_inclusive_tax(tax, doc):
@frappe.whitelist()
def get_outstanding_reference_documents(args):
if isinstance(args, str):
args = json.loads(args)
if args.get("party_type") == "Member":
return
ple = qb.DocType("Payment Ledger Entry")
common_filter = []
posting_and_due_date = []
# confirm that Supplier is not blocked
if args.get("party_type") == "Supplier":
supplier_status = get_supplier_block_status(args["party"])
@@ -1205,19 +1210,16 @@ def get_outstanding_reference_documents(args):
party_account_currency = get_account_currency(args.get("party_account"))
company_currency = frappe.get_cached_value("Company", args.get("company"), "default_currency")
# Get positive outstanding sales /purchase invoices
# Get positive outstanding sales /purchase invoices/ Fees
condition = ""
if args.get("voucher_type") and args.get("voucher_no"):
condition = " and voucher_type={0} and voucher_no={1}".format(
frappe.db.escape(args["voucher_type"]), frappe.db.escape(args["voucher_no"])
)
common_filter.append(ple.voucher_type == args["voucher_type"])
common_filter.append(ple.voucher_no == args["voucher_no"])
# Add cost center condition
if args.get("cost_center"):
condition += " and cost_center='%s'" % args.get("cost_center")
common_filter.append(ple.cost_center == args.get("cost_center"))
date_fields_dict = {
"posting_date": ["from_posting_date", "to_posting_date"],
@@ -1229,20 +1231,16 @@ def get_outstanding_reference_documents(args):
condition += " and {0} between '{1}' and '{2}'".format(
fieldname, args.get(date_fields[0]), args.get(date_fields[1])
)
posting_and_due_date.append(ple[fieldname][args.get(date_fields[0]) : args.get(date_fields[1])])
if args.get("company"):
condition += " and company = {0}".format(frappe.db.escape(args.get("company")))
common_filter.append(ple.company == args.get("company"))
outstanding_invoices = get_outstanding_invoices(
args.get("party_type"),
args.get("party"),
args.get("party_account"),
common_filter=common_filter,
posting_date=posting_and_due_date,
min_outstanding=args.get("outstanding_amt_greater_than"),
max_outstanding=args.get("outstanding_amt_less_than"),
filters=args,
condition=condition,
)
outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices)
@@ -1250,7 +1248,7 @@ def get_outstanding_reference_documents(args):
for d in outstanding_invoices:
d["exchange_rate"] = 1
if party_account_currency != company_currency:
if d.voucher_type in frappe.get_hooks("invoice_doctypes"):
if d.voucher_type in ("Sales Invoice", "Purchase Invoice", "Expense Claim"):
d["exchange_rate"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "conversion_rate")
elif d.voucher_type == "Journal Entry":
d["exchange_rate"] = get_exchange_rate(
@@ -1303,7 +1301,7 @@ def split_invoices_based_on_payment_terms(outstanding_invoices):
d.voucher_type, d.voucher_no, "payment_terms_template"
)
if payment_term_template:
allocate_payment_based_on_payment_terms = frappe.get_cached_value(
allocate_payment_based_on_payment_terms = frappe.db.get_value(
"Payment Terms Template", payment_term_template, "allocate_payment_based_on_payment_terms"
)
if allocate_payment_based_on_payment_terms:
@@ -1446,7 +1444,7 @@ def get_negative_outstanding_invoices(
voucher_type = "Sales Invoice" if party_type == "Customer" else "Purchase Invoice"
supplier_condition = ""
if voucher_type == "Purchase Invoice":
supplier_condition = "and (release_date is null or release_date <= CURRENT_DATE)"
supplier_condition = "and (release_date is null or release_date <= CURDATE())"
if party_account_currency == company_currency:
grand_total_field = "base_grand_total"
rounded_total_field = "base_rounded_total"
@@ -1535,7 +1533,7 @@ def get_account_details(account, date, cost_center=None):
{
"account_currency": get_account_currency(account),
"account_balance": account_balance,
"account_type": frappe.get_cached_value("Account", account, "account_type"),
"account_type": frappe.db.get_value("Account", account, "account_type"),
}
)
@@ -1577,17 +1575,20 @@ def get_outstanding_on_journal_entry(name):
@frappe.whitelist()
def get_reference_details(reference_doctype, reference_name, party_account_currency):
total_amount = outstanding_amount = exchange_rate = None
total_amount = outstanding_amount = exchange_rate = bill_no = None
ref_doc = frappe.get_doc(reference_doctype, reference_name)
company_currency = ref_doc.get("company_currency") or erpnext.get_company_currency(
ref_doc.company
)
if reference_doctype == "Dunning":
total_amount = outstanding_amount = ref_doc.get("dunning_amount")
if reference_doctype == "Fees":
total_amount = ref_doc.get("grand_total")
exchange_rate = 1
outstanding_amount = ref_doc.get("outstanding_amount")
elif reference_doctype == "Dunning":
total_amount = ref_doc.get("dunning_amount")
exchange_rate = 1
outstanding_amount = ref_doc.get("dunning_amount")
elif reference_doctype == "Journal Entry" and ref_doc.docstatus == 1:
total_amount = ref_doc.get("total_amount")
if ref_doc.multi_currency:
@@ -1597,27 +1598,48 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
else:
exchange_rate = 1
outstanding_amount = get_outstanding_on_journal_entry(reference_name)
elif reference_doctype != "Journal Entry":
if ref_doc.doctype == "Expense Claim":
total_amount = flt(ref_doc.total_sanctioned_amount) + flt(ref_doc.total_taxes_and_charges)
elif ref_doc.doctype == "Employee Advance":
total_amount = ref_doc.advance_amount
exchange_rate = ref_doc.get("exchange_rate")
if party_account_currency != ref_doc.currency:
total_amount = flt(total_amount) * flt(exchange_rate)
elif ref_doc.doctype == "Gratuity":
total_amount = ref_doc.amount
if not total_amount:
if party_account_currency == company_currency:
# for handling cases that don't have multi-currency (base field)
total_amount = ref_doc.get("grand_total") or ref_doc.get("base_grand_total")
total_amount = ref_doc.base_grand_total
exchange_rate = 1
else:
total_amount = ref_doc.get("grand_total")
total_amount = ref_doc.grand_total
if not exchange_rate:
# Get the exchange rate from the original ref doc
# or get it based on the posting date of the ref doc.
exchange_rate = ref_doc.get("conversion_rate") or get_exchange_rate(
party_account_currency, company_currency, ref_doc.posting_date
)
if reference_doctype in ("Sales Invoice", "Purchase Invoice"):
outstanding_amount = ref_doc.get("outstanding_amount")
bill_no = ref_doc.get("bill_no")
elif reference_doctype == "Expense Claim":
outstanding_amount = (
flt(ref_doc.get("total_sanctioned_amount"))
+ flt(ref_doc.get("total_taxes_and_charges"))
- flt(ref_doc.get("total_amount_reimbursed"))
- flt(ref_doc.get("total_advance_amount"))
)
elif reference_doctype == "Employee Advance":
outstanding_amount = flt(ref_doc.advance_amount) - flt(ref_doc.paid_amount)
if party_account_currency != ref_doc.currency:
outstanding_amount = flt(outstanding_amount) * flt(exchange_rate)
if party_account_currency == company_currency:
exchange_rate = 1
elif reference_doctype == "Gratuity":
outstanding_amount = ref_doc.amount - flt(ref_doc.paid_amount)
else:
outstanding_amount = flt(total_amount) - flt(ref_doc.get("advance_paid"))
outstanding_amount = flt(total_amount) - flt(ref_doc.advance_paid)
else:
# Get the exchange rate based on the posting date of the ref doc.
exchange_rate = get_exchange_rate(party_account_currency, company_currency, ref_doc.posting_date)
@@ -1628,29 +1650,125 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
"total_amount": flt(total_amount),
"outstanding_amount": flt(outstanding_amount),
"exchange_rate": flt(exchange_rate),
"bill_no": ref_doc.get("bill_no"),
"bill_no": bill_no,
}
)
@frappe.whitelist()
def get_payment_entry(
dt, dn, party_amount=None, bank_account=None, bank_amount=None, party_type=None, payment_type=None
def get_amounts_based_on_reference_doctype(
reference_doctype, ref_doc, party_account_currency, company_currency, reference_name
):
total_amount = outstanding_amount = exchange_rate = None
if reference_doctype == "Fees":
total_amount = ref_doc.get("grand_total")
exchange_rate = 1
outstanding_amount = ref_doc.get("outstanding_amount")
elif reference_doctype == "Dunning":
total_amount = ref_doc.get("dunning_amount")
exchange_rate = 1
outstanding_amount = ref_doc.get("dunning_amount")
elif reference_doctype == "Journal Entry" and ref_doc.docstatus == 1:
total_amount = ref_doc.get("total_amount")
if ref_doc.multi_currency:
exchange_rate = get_exchange_rate(
party_account_currency, company_currency, ref_doc.posting_date
)
else:
exchange_rate = 1
outstanding_amount = get_outstanding_on_journal_entry(reference_name)
return total_amount, outstanding_amount, exchange_rate
def get_amounts_based_on_ref_doc(
reference_doctype, ref_doc, party_account_currency, company_currency
):
total_amount = outstanding_amount = exchange_rate = None
if ref_doc.doctype == "Expense Claim":
total_amount = flt(ref_doc.total_sanctioned_amount) + flt(ref_doc.total_taxes_and_charges)
elif ref_doc.doctype == "Employee Advance":
total_amount, exchange_rate = get_total_amount_exchange_rate_for_employee_advance(
party_account_currency, ref_doc
)
if not total_amount:
total_amount, exchange_rate = get_total_amount_exchange_rate_base_on_currency(
party_account_currency, company_currency, ref_doc
)
if not exchange_rate:
# Get the exchange rate from the original ref doc
# or get it based on the posting date of the ref doc
exchange_rate = ref_doc.get("conversion_rate") or get_exchange_rate(
party_account_currency, company_currency, ref_doc.posting_date
)
outstanding_amount, exchange_rate, bill_no = get_bill_no_and_update_amounts(
reference_doctype, ref_doc, total_amount, exchange_rate, party_account_currency, company_currency
)
return total_amount, outstanding_amount, exchange_rate, bill_no
def get_total_amount_exchange_rate_for_employee_advance(party_account_currency, ref_doc):
total_amount = ref_doc.advance_amount
exchange_rate = ref_doc.get("exchange_rate")
if party_account_currency != ref_doc.currency:
total_amount = flt(total_amount) * flt(exchange_rate)
return total_amount, exchange_rate
def get_total_amount_exchange_rate_base_on_currency(
party_account_currency, company_currency, ref_doc
):
exchange_rate = None
if party_account_currency == company_currency:
total_amount = ref_doc.base_grand_total
exchange_rate = 1
else:
total_amount = ref_doc.grand_total
return total_amount, exchange_rate
def get_bill_no_and_update_amounts(
reference_doctype, ref_doc, total_amount, exchange_rate, party_account_currency, company_currency
):
outstanding_amount = bill_no = None
if reference_doctype in ("Sales Invoice", "Purchase Invoice"):
outstanding_amount = ref_doc.get("outstanding_amount")
bill_no = ref_doc.get("bill_no")
elif reference_doctype == "Expense Claim":
outstanding_amount = (
flt(ref_doc.get("total_sanctioned_amount"))
+ flt(ref_doc.get("total_taxes_and_charges"))
- flt(ref_doc.get("total_amount_reimbursed"))
- flt(ref_doc.get("total_advance_amount"))
)
elif reference_doctype == "Employee Advance":
outstanding_amount = flt(ref_doc.advance_amount) - flt(ref_doc.paid_amount)
if party_account_currency != ref_doc.currency:
outstanding_amount = flt(outstanding_amount) * flt(exchange_rate)
if party_account_currency == company_currency:
exchange_rate = 1
else:
outstanding_amount = flt(total_amount) - flt(ref_doc.advance_paid)
return outstanding_amount, exchange_rate, bill_no
@frappe.whitelist()
def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=None):
reference_doc = None
doc = frappe.get_doc(dt, dn)
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) > 0:
frappe.throw(_("Can only make payment against unbilled {0}").format(dt))
if not party_type:
party_type = set_party_type(dt)
party_type = set_party_type(dt)
party_account = set_party_account(dt, dn, doc, party_type)
party_account_currency = set_party_account_currency(dt, party_account, doc)
if not payment_type:
payment_type = set_payment_type(dt, doc)
payment_type = set_payment_type(dt, doc)
grand_total, outstanding_amount = set_grand_total_and_outstanding_amount(
party_amount, dt, party_account_currency, doc
)
@@ -1704,9 +1822,9 @@ def get_payment_entry(
if doc.doctype == "Purchase Invoice" and doc.invoice_is_blocked():
frappe.msgprint(_("{0} is on hold till {1}").format(doc.name, doc.release_date))
else:
if doc.doctype in ("Sales Invoice", "Purchase Invoice") and frappe.get_cached_value(
if doc.doctype in ("Sales Invoice", "Purchase Invoice") and frappe.get_value(
"Payment Terms Template",
doc.payment_terms_template,
{"name": doc.payment_terms_template},
"allocate_payment_based_on_payment_terms",
):
@@ -1758,6 +1876,8 @@ def get_payment_entry(
pe.set_missing_values()
if party_account and bank:
if dt == "Employee Advance":
reference_doc = doc
pe.set_exchange_rate(ref_doc=reference_doc)
pe.set_amounts()
if discount_amount:
@@ -1792,6 +1912,8 @@ def set_party_type(dt):
party_type = "Customer"
elif dt in ("Purchase Invoice", "Purchase Order"):
party_type = "Supplier"
elif dt in ("Expense Claim", "Employee Advance", "Gratuity"):
party_type = "Employee"
return party_type
@@ -1800,6 +1922,14 @@ def set_party_account(dt, dn, doc, party_type):
party_account = get_party_account_based_on_invoice_discounting(dn) or doc.debit_to
elif dt == "Purchase Invoice":
party_account = doc.credit_to
elif dt == "Fees":
party_account = doc.receivable_account
elif dt == "Employee Advance":
party_account = doc.advance_account
elif dt == "Expense Claim":
party_account = doc.payable_account
elif dt == "Gratuity":
party_account = doc.payable_account
else:
party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company)
return party_account
@@ -1815,7 +1945,8 @@ def set_party_account_currency(dt, party_account, doc):
def set_payment_type(dt, doc):
if (
dt == "Sales Order" or (dt in ("Sales Invoice", "Dunning") and doc.outstanding_amount > 0)
dt == "Sales Order"
or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)
) or (dt == "Purchase Invoice" and doc.outstanding_amount < 0):
payment_type = "Receive"
else:
@@ -1833,15 +1964,30 @@ def set_grand_total_and_outstanding_amount(party_amount, dt, party_account_curre
else:
grand_total = doc.rounded_total or doc.grand_total
outstanding_amount = doc.outstanding_amount
elif dt in ("Expense Claim"):
grand_total = doc.total_sanctioned_amount + doc.total_taxes_and_charges
outstanding_amount = doc.grand_total - doc.total_amount_reimbursed
elif dt == "Employee Advance":
grand_total = flt(doc.advance_amount)
outstanding_amount = flt(doc.advance_amount) - flt(doc.paid_amount)
if party_account_currency != doc.currency:
grand_total = flt(doc.advance_amount) * flt(doc.exchange_rate)
outstanding_amount = (flt(doc.advance_amount) - flt(doc.paid_amount)) * flt(doc.exchange_rate)
elif dt == "Fees":
grand_total = doc.grand_total
outstanding_amount = doc.outstanding_amount
elif dt == "Dunning":
grand_total = doc.grand_total
outstanding_amount = doc.grand_total
elif dt == "Gratuity":
grand_total = doc.amount
outstanding_amount = flt(doc.amount) - flt(doc.paid_amount)
else:
if party_account_currency == doc.company_currency:
grand_total = flt(doc.get("base_rounded_total") or doc.get("base_grand_total"))
grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total)
else:
grand_total = flt(doc.get("rounded_total") or doc.get("grand_total"))
outstanding_amount = doc.get("outstanding_amount") or (grand_total - flt(doc.advance_paid))
grand_total = flt(doc.get("rounded_total") or doc.grand_total)
outstanding_amount = grand_total - flt(doc.advance_paid)
return grand_total, outstanding_amount
@@ -1857,6 +2003,8 @@ def set_paid_amount_and_received_amount(
received_amount = bank_amount
else:
received_amount = paid_amount * doc.get("conversion_rate", 1)
if dt == "Employee Advance":
received_amount = paid_amount * doc.get("exchange_rate", 1)
else:
received_amount = abs(outstanding_amount)
if bank_amount:
@@ -1864,6 +2012,8 @@ def set_paid_amount_and_received_amount(
else:
# if party account currency and bank currency is different then populate paid amount as well
paid_amount = received_amount * doc.get("conversion_rate", 1)
if dt == "Employee Advance":
paid_amount = received_amount * doc.get("exchange_rate", 1)
return paid_amount, received_amount

View File

@@ -0,0 +1,29 @@
frappe.ui.form.on("Payment Entry", {
company: function(frm) {
frappe.call({
'method': 'frappe.contacts.doctype.address.address.get_default_address',
'args': {
'doctype': 'Company',
'name': frm.doc.company
},
'callback': function(r) {
frm.set_value('company_address', r.message);
}
});
},
party: function(frm) {
if (frm.doc.party_type == "Customer" && frm.doc.party) {
frappe.call({
'method': 'frappe.contacts.doctype.address.address.get_default_address',
'args': {
'doctype': 'Customer',
'name': frm.doc.party
},
'callback': function(r) {
frm.set_value('customer_address', r.message);
}
});
}
}
});

View File

@@ -4,8 +4,6 @@
import unittest
import frappe
from frappe import qb
from frappe.tests.utils import FrappeTestCase
from frappe.utils import flt, nowdate
from erpnext.accounts.doctype.payment_entry.payment_entry import (
@@ -20,16 +18,13 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import (
create_sales_invoice,
create_sales_invoice_against_cost_center,
)
from erpnext.hr.doctype.expense_claim.test_expense_claim import make_expense_claim
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
from erpnext.setup.doctype.employee.test_employee import make_employee
test_dependencies = ["Item"]
class TestPaymentEntry(FrappeTestCase):
def tearDown(self):
frappe.db.rollback()
class TestPaymentEntry(unittest.TestCase):
def test_payment_entry_against_order(self):
so = make_sales_order()
pe = get_payment_entry("Sales Order", so.name, bank_account="_Test Cash - _TC")
@@ -298,6 +293,31 @@ class TestPaymentEntry(FrappeTestCase):
self.assertEqual(flt(outstanding_amount), 250)
self.assertEqual(status, "Unpaid")
def test_payment_entry_against_ec(self):
payable = frappe.get_cached_value("Company", "_Test Company", "default_payable_account")
ec = make_expense_claim(payable, 300, 300, "_Test Company", "Travel Expenses - _TC")
pe = get_payment_entry(
"Expense Claim", ec.name, bank_account="_Test Bank USD - _TC", bank_amount=300
)
pe.reference_no = "1"
pe.reference_date = "2016-01-01"
pe.source_exchange_rate = 1
pe.paid_to = payable
pe.insert()
pe.submit()
expected_gle = dict(
(d[0], d) for d in [[payable, 300, 0, ec.name], ["_Test Bank USD - _TC", 0, 300, None]]
)
self.validate_gl_entries(pe.name, expected_gle)
outstanding_amount = flt(
frappe.db.get_value("Expense Claim", ec.name, "total_sanctioned_amount")
) - flt(frappe.db.get_value("Expense Claim", ec.name, "total_amount_reimbursed"))
self.assertEqual(outstanding_amount, 0)
def test_payment_entry_against_si_usd_to_inr(self):
si = create_sales_invoice(
customer="_Test Customer USD",
@@ -723,46 +743,6 @@ class TestPaymentEntry(FrappeTestCase):
flt(payment_entry.total_taxes_and_charges, 2), flt(10 / payment_entry.target_exchange_rate, 2)
)
def test_gl_of_multi_currency_payment_with_taxes(self):
payment_entry = create_payment_entry(
party="_Test Supplier USD", paid_to="_Test Payable USD - _TC", save=True
)
payment_entry.append(
"taxes",
{
"account_head": "_Test Account Service Tax - _TC",
"charge_type": "Actual",
"tax_amount": 100,
"add_deduct_tax": "Add",
"description": "Test",
},
)
payment_entry.target_exchange_rate = 80
payment_entry.received_amount = 12.5
payment_entry = payment_entry.submit()
gle = qb.DocType("GL Entry")
gl_entries = (
qb.from_(gle)
.select(
gle.account,
gle.debit,
gle.credit,
gle.debit_in_account_currency,
gle.credit_in_account_currency,
)
.orderby(gle.account)
.where(gle.voucher_no == payment_entry.name)
.run()
)
expected_gl_entries = (
("_Test Account Service Tax - _TC", 100.0, 0.0, 100.0, 0.0),
("_Test Bank - _TC", 0.0, 1100.0, 0.0, 1100.0),
("_Test Payable USD - _TC", 1000.0, 0.0, 12.5, 0),
)
self.assertEqual(gl_entries, expected_gl_entries)
def test_payment_entry_against_onhold_purchase_invoice(self):
pi = make_purchase_invoice()
@@ -778,10 +758,6 @@ class TestPaymentEntry(FrappeTestCase):
self.assertTrue("is on hold" in str(err.exception).lower())
def test_payment_entry_for_employee(self):
employee = make_employee("test_payment_entry@salary.com", company="_Test Company")
create_payment_entry(party_type="Employee", party=employee, save=True)
def create_payment_entry(**args):
payment_entry = frappe.new_doc("Payment Entry")

View File

@@ -25,8 +25,7 @@
"in_list_view": 1,
"label": "Type",
"options": "DocType",
"reqd": 1,
"search_index": 1
"reqd": 1
},
{
"columns": 2,
@@ -36,8 +35,7 @@
"in_list_view": 1,
"label": "Name",
"options": "reference_doctype",
"reqd": 1,
"search_index": 1
"reqd": 1
},
{
"fieldname": "due_date",
@@ -106,7 +104,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-12-12 12:31:44.919895",
"modified": "2021-09-26 17:06:55.597389",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry Reference",
@@ -115,6 +113,5 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -11,7 +11,7 @@ class PaymentGatewayAccount(Document):
self.name = self.payment_gateway + " - " + self.currency
def validate(self):
self.currency = frappe.get_cached_value("Account", self.payment_account, "account_currency")
self.currency = frappe.db.get_value("Account", self.payment_account, "account_currency")
self.update_default_payment_gateway()
self.set_as_default_if_not_set()

View File

@@ -1,6 +1,7 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "format:PLE-{YY}-{MM}-{######}",
"creation": "2022-05-09 19:35:03.334361",
"doctype": "DocType",
"editable_grid": 1,
@@ -22,8 +23,7 @@
"amount",
"account_currency",
"amount_in_account_currency",
"delinked",
"remarks"
"delinked"
],
"fields": [
{
@@ -41,22 +41,19 @@
"fieldname": "account",
"fieldtype": "Link",
"label": "Account",
"options": "Account",
"search_index": 1
"options": "Account"
},
{
"fieldname": "party_type",
"fieldtype": "Link",
"label": "Party Type",
"options": "DocType",
"search_index": 1
"options": "DocType"
},
{
"fieldname": "party",
"fieldtype": "Dynamic Link",
"label": "Party",
"options": "party_type",
"search_index": 1
"options": "party_type"
},
{
"fieldname": "voucher_type",
@@ -118,8 +115,7 @@
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"options": "Company",
"search_index": 1
"options": "Company"
},
{
"fieldname": "cost_center",
@@ -137,20 +133,16 @@
"fieldtype": "Link",
"label": "Finance Book",
"options": "Finance Book"
},
{
"fieldname": "remarks",
"fieldtype": "Text",
"label": "Remarks"
}
],
"in_create": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2022-08-22 15:32:56.629430",
"modified": "2022-05-19 18:04:44.609115",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Ledger Entry",
"naming_rule": "Expression",
"owner": "Administrator",
"permissions": [
{

View File

@@ -6,19 +6,6 @@ import frappe
from frappe import _
from frappe.model.document import Document
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_checks_for_pl_and_bs_accounts,
)
from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import (
get_dimension_filter_map,
)
from erpnext.accounts.doctype.gl_entry.gl_entry import (
validate_balance_type,
validate_frozen_account,
)
from erpnext.accounts.utils import update_voucher_outstanding
from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
class PaymentLedgerEntry(Document):
def validate_account(self):
@@ -31,124 +18,5 @@ class PaymentLedgerEntry(Document):
if not valid_account:
frappe.throw(_("{0} account is not of type {1}").format(self.account, self.account_type))
def validate_account_details(self):
"""Account must be ledger, active and not freezed"""
ret = frappe.db.sql(
"""select is_group, docstatus, company
from tabAccount where name=%s""",
self.account,
as_dict=1,
)[0]
if ret.is_group == 1:
frappe.throw(
_(
"""{0} {1}: Account {2} is a Group Account and group accounts cannot be used in transactions"""
).format(self.voucher_type, self.voucher_no, self.account)
)
if ret.docstatus == 2:
frappe.throw(
_("{0} {1}: Account {2} is inactive").format(self.voucher_type, self.voucher_no, self.account)
)
if ret.company != self.company:
frappe.throw(
_("{0} {1}: Account {2} does not belong to Company {3}").format(
self.voucher_type, self.voucher_no, self.account, self.company
)
)
def validate_allowed_dimensions(self):
dimension_filter_map = get_dimension_filter_map()
for key, value in dimension_filter_map.items():
dimension = key[0]
account = key[1]
if self.account == account:
if value["is_mandatory"] and not self.get(dimension):
frappe.throw(
_("{0} is mandatory for account {1}").format(
frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)
),
MandatoryAccountDimensionError,
)
if value["allow_or_restrict"] == "Allow":
if self.get(dimension) and self.get(dimension) not in value["allowed_dimensions"]:
frappe.throw(
_("Invalid value {0} for {1} against account {2}").format(
frappe.bold(self.get(dimension)),
frappe.bold(frappe.unscrub(dimension)),
frappe.bold(self.account),
),
InvalidAccountDimensionError,
)
else:
if self.get(dimension) and self.get(dimension) in value["allowed_dimensions"]:
frappe.throw(
_("Invalid value {0} for {1} against account {2}").format(
frappe.bold(self.get(dimension)),
frappe.bold(frappe.unscrub(dimension)),
frappe.bold(self.account),
),
InvalidAccountDimensionError,
)
def validate_dimensions_for_pl_and_bs(self):
account_type = frappe.get_cached_value("Account", self.account, "report_type")
for dimension in get_checks_for_pl_and_bs_accounts():
if (
account_type == "Profit and Loss"
and self.company == dimension.company
and dimension.mandatory_for_pl
and not dimension.disabled
):
if not self.get(dimension.fieldname):
frappe.throw(
_("Accounting Dimension <b>{0}</b> is required for 'Profit and Loss' account {1}.").format(
dimension.label, self.account
)
)
if (
account_type == "Balance Sheet"
and self.company == dimension.company
and dimension.mandatory_for_bs
and not dimension.disabled
):
if not self.get(dimension.fieldname):
frappe.throw(
_("Accounting Dimension <b>{0}</b> is required for 'Balance Sheet' account {1}.").format(
dimension.label, self.account
)
)
def validate(self):
self.validate_account()
def on_update(self):
adv_adj = self.flags.adv_adj
if not self.flags.from_repost:
self.validate_account_details()
self.validate_dimensions_for_pl_and_bs()
self.validate_allowed_dimensions()
validate_balance_type(self.account, adv_adj)
validate_frozen_account(self.account, adv_adj)
# update outstanding amount
if (
self.against_voucher_type in ["Journal Entry", "Sales Invoice", "Purchase Invoice", "Fees"]
and self.flags.update_outstanding == "Yes"
and not frappe.flags.is_reverse_depr_entry
):
update_voucher_outstanding(
self.against_voucher_type, self.against_voucher_no, self.account, self.party_type, self.party
)
def on_doctype_update():
frappe.db.add_index("Payment Ledger Entry", ["against_voucher_no", "against_voucher_type"])
frappe.db.add_index("Payment Ledger Entry", ["voucher_no", "voucher_type"])

View File

@@ -3,13 +3,12 @@
import frappe
from frappe import qb
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.tests.utils import FrappeTestCase
from frappe.utils import nowdate
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
from erpnext.stock.doctype.item.test_item import create_item
@@ -128,25 +127,6 @@ class TestPaymentLedgerEntry(FrappeTestCase):
payment.posting_date = posting_date
return payment
def create_sales_order(
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
):
so = make_sales_order(
company=self.company,
transaction_date=posting_date,
customer=self.customer,
item_code=self.item,
cost_center=self.cost_center,
warehouse=self.warehouse,
debit_to=self.debit_to,
currency="INR",
qty=qty,
rate=100,
do_not_save=do_not_save,
do_not_submit=do_not_submit,
)
return so
def clear_old_entries(self):
doctype_list = [
"GL Entry",
@@ -426,89 +406,3 @@ class TestPaymentLedgerEntry(FrappeTestCase):
]
self.assertEqual(pl_entries_for_crnote[0], expected_values[0])
self.assertEqual(pl_entries_for_crnote[1], expected_values[1])
@change_settings(
"Accounts Settings",
{"unlink_payment_on_cancellation_of_invoice": 1, "delete_linked_ledger_entries": 1},
)
def test_multi_payment_unlink_on_invoice_cancellation(self):
transaction_date = nowdate()
amount = 100
si = self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date)
for amt in [40, 40, 20]:
# payment 1
pe = get_payment_entry(si.doctype, si.name)
pe.paid_amount = amt
pe.get("references")[0].allocated_amount = amt
pe = pe.save().submit()
si.reload()
si.cancel()
entries = frappe.db.get_list(
"Payment Ledger Entry",
filters={"against_voucher_type": si.doctype, "against_voucher_no": si.name, "delinked": 0},
)
self.assertEqual(entries, [])
# with references removed, deletion should be possible
si.delete()
self.assertRaises(frappe.DoesNotExistError, frappe.get_doc, si.doctype, si.name)
@change_settings(
"Accounts Settings",
{"unlink_payment_on_cancellation_of_invoice": 1, "delete_linked_ledger_entries": 1},
)
def test_multi_je_unlink_on_invoice_cancellation(self):
transaction_date = nowdate()
amount = 100
si = self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date)
# multiple JE's against invoice
for amt in [40, 40, 20]:
je1 = self.create_journal_entry(
self.income_account, self.debit_to, amt, posting_date=transaction_date
)
je1.get("accounts")[1].party_type = "Customer"
je1.get("accounts")[1].party = self.customer
je1.get("accounts")[1].reference_type = si.doctype
je1.get("accounts")[1].reference_name = si.name
je1 = je1.save().submit()
si.reload()
si.cancel()
entries = frappe.db.get_list(
"Payment Ledger Entry",
filters={"against_voucher_type": si.doctype, "against_voucher_no": si.name, "delinked": 0},
)
self.assertEqual(entries, [])
# with references removed, deletion should be possible
si.delete()
self.assertRaises(frappe.DoesNotExistError, frappe.get_doc, si.doctype, si.name)
@change_settings(
"Accounts Settings",
{"unlink_payment_on_cancellation_of_invoice": 1, "delete_linked_ledger_entries": 1},
)
def test_advance_payment_unlink_on_order_cancellation(self):
transaction_date = nowdate()
amount = 100
so = self.create_sales_order(qty=1, rate=amount, posting_date=transaction_date).save().submit()
pe = get_payment_entry(so.doctype, so.name).save().submit()
so.reload()
so.cancel()
entries = frappe.db.get_list(
"Payment Ledger Entry",
filters={"against_voucher_type": so.doctype, "against_voucher_no": so.name, "delinked": 0},
)
self.assertEqual(entries, [])
# with references removed, deletion should be possible
so.delete()
self.assertRaises(frappe.DoesNotExistError, frappe.get_doc, so.doctype, so.name)

View File

@@ -39,7 +39,7 @@ def get_mop_query(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql(
""" select mode_of_payment from `tabPayment Order Reference`
where parent = %(parent)s and mode_of_payment like %(txt)s
limit %(page_len)s offset %(start)s""",
limit %(start)s, %(page_len)s""",
{"parent": filters.get("parent"), "start": start, "page_len": page_len, "txt": "%%%s%%" % txt},
)
@@ -51,7 +51,7 @@ def get_supplier_query(doctype, txt, searchfield, start, page_len, filters):
""" select supplier from `tabPayment Order Reference`
where parent = %(parent)s and supplier like %(txt)s and
(payment_reference is null or payment_reference='')
limit %(page_len)s offset %(start)s""",
limit %(start)s, %(page_len)s""",
{"parent": filters.get("parent"), "start": start, "page_len": page_len, "txt": "%%%s%%" % txt},
)

View File

@@ -3,28 +3,16 @@
import frappe
from frappe import _, msgprint, qb
from frappe import _, msgprint
from frappe.model.document import Document
from frappe.query_builder.custom import ConstantColumn
from frappe.query_builder.functions import IfNull
from frappe.utils import flt, getdate, nowdate, today
import erpnext
from erpnext.accounts.utils import (
QueryPaymentLedger,
get_outstanding_invoices,
reconcile_against_document,
update_reference_in_payment_entry,
)
from erpnext.accounts.utils import get_outstanding_invoices, reconcile_against_document
from erpnext.controllers.accounts_controller import get_advance_payment_entries
class PaymentReconciliation(Document):
def __init__(self, *args, **kwargs):
super(PaymentReconciliation, self).__init__(*args, **kwargs)
self.common_filter_conditions = []
self.ple_posting_date_filter = []
@frappe.whitelist()
def get_unreconciled_entries(self):
self.get_nonreconciled_payment_entries()
@@ -120,58 +108,53 @@ class PaymentReconciliation(Document):
return list(journal_entries)
def get_dr_or_cr_notes(self):
self.build_qb_filter_conditions(get_return_invoices=True)
ple = qb.DocType("Payment Ledger Entry")
voucher_type = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
if erpnext.get_party_account_type(self.party_type) == "Receivable":
self.common_filter_conditions.append(ple.account_type == "Receivable")
else:
self.common_filter_conditions.append(ple.account_type == "Payable")
self.common_filter_conditions.append(ple.account == self.receivable_payable_account)
# get return invoices
doc = qb.DocType(voucher_type)
return_invoices = (
qb.from_(doc)
.select(ConstantColumn(voucher_type).as_("voucher_type"), doc.name.as_("voucher_no"))
.where(
(doc.docstatus == 1)
& (doc[frappe.scrub(self.party_type)] == self.party)
& (doc.is_return == 1)
& (IfNull(doc.return_against, "") == "")
)
.run(as_dict=True)
condition = self.get_conditions(get_return_invoices=True)
dr_or_cr = (
"credit_in_account_currency"
if erpnext.get_party_account_type(self.party_type) == "Receivable"
else "debit_in_account_currency"
)
outstanding_dr_or_cr = []
if return_invoices:
ple_query = QueryPaymentLedger()
return_outstanding = ple_query.get_voucher_outstandings(
vouchers=return_invoices,
common_filter=self.common_filter_conditions,
posting_date=self.ple_posting_date_filter,
min_outstanding=-(self.minimum_payment_amount) if self.minimum_payment_amount else None,
max_outstanding=-(self.maximum_payment_amount) if self.maximum_payment_amount else None,
get_payments=True,
)
reconciled_dr_or_cr = (
"debit_in_account_currency"
if dr_or_cr == "credit_in_account_currency"
else "credit_in_account_currency"
)
for inv in return_outstanding:
if inv.outstanding != 0:
outstanding_dr_or_cr.append(
frappe._dict(
{
"reference_type": inv.voucher_type,
"reference_name": inv.voucher_no,
"amount": -(inv.outstanding_in_account_currency),
"posting_date": inv.posting_date,
"currency": inv.currency,
}
)
)
return outstanding_dr_or_cr
voucher_type = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
return frappe.db.sql(
""" SELECT doc.name as reference_name, %(voucher_type)s as reference_type,
(sum(gl.{dr_or_cr}) - sum(gl.{reconciled_dr_or_cr})) as amount, doc.posting_date,
account_currency as currency
FROM `tab{doc}` doc, `tabGL Entry` gl
WHERE
(doc.name = gl.against_voucher or doc.name = gl.voucher_no)
and doc.{party_type_field} = %(party)s
and doc.is_return = 1 and ifnull(doc.return_against, "") = ""
and gl.against_voucher_type = %(voucher_type)s
and doc.docstatus = 1 and gl.party = %(party)s
and gl.party_type = %(party_type)s and gl.account = %(account)s
and gl.is_cancelled = 0 {condition}
GROUP BY doc.name
Having
amount > 0
ORDER BY doc.posting_date
""".format(
doc=voucher_type,
dr_or_cr=dr_or_cr,
reconciled_dr_or_cr=reconciled_dr_or_cr,
party_type_field=frappe.scrub(self.party_type),
condition=condition or "",
),
{
"party": self.party,
"party_type": self.party_type,
"voucher_type": voucher_type,
"account": self.receivable_payable_account,
},
as_dict=1,
)
def add_payment_entries(self, non_reconciled_payments):
self.set("payments", [])
@@ -183,16 +166,10 @@ class PaymentReconciliation(Document):
def get_invoice_entries(self):
# Fetch JVs, Sales and Purchase Invoices for 'invoices' to reconcile against
self.build_qb_filter_conditions(get_invoices=True)
condition = self.get_conditions(get_invoices=True)
non_reconciled_invoices = get_outstanding_invoices(
self.party_type,
self.party,
self.receivable_payable_account,
common_filter=self.common_filter_conditions,
posting_date=self.ple_posting_date_filter,
min_outstanding=self.minimum_invoice_amount if self.minimum_invoice_amount else None,
max_outstanding=self.maximum_invoice_amount if self.maximum_invoice_amount else None,
self.party_type, self.party, self.receivable_payable_account, condition=condition
)
if self.invoice_limit:
@@ -213,23 +190,6 @@ class PaymentReconciliation(Document):
inv.currency = entry.get("currency")
inv.outstanding_amount = flt(entry.get("outstanding_amount"))
def get_difference_amount(self, allocated_entry):
if allocated_entry.get("reference_type") != "Payment Entry":
return
dr_or_cr = (
"credit_in_account_currency"
if erpnext.get_party_account_type(self.party_type) == "Receivable"
else "debit_in_account_currency"
)
row = self.get_payment_details(allocated_entry, dr_or_cr)
doc = frappe.get_doc(allocated_entry.reference_type, allocated_entry.reference_name)
update_reference_in_payment_entry(row, doc, do_not_save=True)
return doc.difference_amount
@frappe.whitelist()
def allocate_entries(self, args):
self.validate_entries()
@@ -245,16 +205,12 @@ class PaymentReconciliation(Document):
res = self.get_allocated_entry(pay, inv, pay["amount"])
inv["outstanding_amount"] = flt(inv.get("outstanding_amount")) - flt(pay.get("amount"))
pay["amount"] = 0
res.difference_amount = self.get_difference_amount(res)
if pay.get("amount") == 0:
entries.append(res)
break
elif inv.get("outstanding_amount") == 0:
entries.append(res)
continue
else:
break
@@ -373,57 +329,89 @@ class PaymentReconciliation(Document):
if not invoices_to_reconcile:
frappe.throw(_("No records found in Allocation table"))
def build_qb_filter_conditions(self, get_invoices=False, get_return_invoices=False):
self.common_filter_conditions.clear()
self.ple_posting_date_filter.clear()
ple = qb.DocType("Payment Ledger Entry")
self.common_filter_conditions.append(ple.company == self.company)
if self.get("cost_center") and (get_invoices or get_return_invoices):
self.common_filter_conditions.append(ple.cost_center == self.cost_center)
if get_invoices:
if self.from_invoice_date:
self.ple_posting_date_filter.append(ple.posting_date.gte(self.from_invoice_date))
if self.to_invoice_date:
self.ple_posting_date_filter.append(ple.posting_date.lte(self.to_invoice_date))
elif get_return_invoices:
if self.from_payment_date:
self.ple_posting_date_filter.append(ple.posting_date.gte(self.from_payment_date))
if self.to_payment_date:
self.ple_posting_date_filter.append(ple.posting_date.lte(self.to_payment_date))
def get_conditions(self, get_payments=False):
def get_conditions(self, get_invoices=False, get_payments=False, get_return_invoices=False):
condition = " and company = '{0}' ".format(self.company)
if self.get("cost_center") and get_payments:
if self.get("cost_center") and (get_invoices or get_payments or get_return_invoices):
condition = " and cost_center = '{0}' ".format(self.cost_center)
condition += (
" and posting_date >= {0}".format(frappe.db.escape(self.from_payment_date))
if self.from_payment_date
else ""
)
condition += (
" and posting_date <= {0}".format(frappe.db.escape(self.to_payment_date))
if self.to_payment_date
else ""
)
if get_invoices:
condition += (
" and posting_date >= {0}".format(frappe.db.escape(self.from_invoice_date))
if self.from_invoice_date
else ""
)
condition += (
" and posting_date <= {0}".format(frappe.db.escape(self.to_invoice_date))
if self.to_invoice_date
else ""
)
dr_or_cr = (
"debit_in_account_currency"
if erpnext.get_party_account_type(self.party_type) == "Receivable"
else "credit_in_account_currency"
)
if self.minimum_payment_amount:
if self.minimum_invoice_amount:
condition += " and {dr_or_cr} >= {amount}".format(
dr_or_cr=dr_or_cr, amount=flt(self.minimum_invoice_amount)
)
if self.maximum_invoice_amount:
condition += " and {dr_or_cr} <= {amount}".format(
dr_or_cr=dr_or_cr, amount=flt(self.maximum_invoice_amount)
)
elif get_return_invoices:
condition = " and doc.company = '{0}' ".format(self.company)
condition += (
" and unallocated_amount >= {0}".format(flt(self.minimum_payment_amount))
if get_payments
else " and total_debit >= {0}".format(flt(self.minimum_payment_amount))
" and doc.posting_date >= {0}".format(frappe.db.escape(self.from_payment_date))
if self.from_payment_date
else ""
)
if self.maximum_payment_amount:
condition += (
" and unallocated_amount <= {0}".format(flt(self.maximum_payment_amount))
if get_payments
else " and total_debit <= {0}".format(flt(self.maximum_payment_amount))
" and doc.posting_date <= {0}".format(frappe.db.escape(self.to_payment_date))
if self.to_payment_date
else ""
)
dr_or_cr = (
"debit_in_account_currency"
if erpnext.get_party_account_type(self.party_type) == "Receivable"
else "credit_in_account_currency"
)
if self.minimum_invoice_amount:
condition += " and gl.{dr_or_cr} >= {amount}".format(
dr_or_cr=dr_or_cr, amount=flt(self.minimum_payment_amount)
)
if self.maximum_invoice_amount:
condition += " and gl.{dr_or_cr} <= {amount}".format(
dr_or_cr=dr_or_cr, amount=flt(self.maximum_payment_amount)
)
else:
condition += (
" and posting_date >= {0}".format(frappe.db.escape(self.from_payment_date))
if self.from_payment_date
else ""
)
condition += (
" and posting_date <= {0}".format(frappe.db.escape(self.to_payment_date))
if self.to_payment_date
else ""
)
if self.minimum_payment_amount:
condition += (
" and unallocated_amount >= {0}".format(flt(self.minimum_payment_amount))
if get_payments
else " and total_debit >= {0}".format(flt(self.minimum_payment_amount))
)
if self.maximum_payment_amount:
condition += (
" and unallocated_amount <= {0}".format(flt(self.maximum_payment_amount))
if get_payments
else " and total_debit <= {0}".format(flt(self.maximum_payment_amount))
)
return condition

View File

@@ -4,577 +4,93 @@
import unittest
import frappe
from frappe import qb
from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_days, nowdate
from frappe.utils import add_days, getdate
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.party import get_party_account
from erpnext.stock.doctype.item.test_item import create_item
class TestPaymentReconciliation(FrappeTestCase):
def setUp(self):
self.create_company()
self.create_item()
self.create_customer()
self.create_account()
self.clear_old_entries()
class TestPaymentReconciliation(unittest.TestCase):
@classmethod
def setUpClass(cls):
make_customer()
make_invoice_and_payment()
def tearDown(self):
frappe.db.rollback()
def test_payment_reconciliation(self):
payment_reco = frappe.get_doc("Payment Reconciliation")
payment_reco.company = "_Test Company"
payment_reco.party_type = "Customer"
payment_reco.party = "_Test Payment Reco Customer"
payment_reco.receivable_payable_account = "Debtors - _TC"
payment_reco.from_invoice_date = add_days(getdate(), -1)
payment_reco.to_invoice_date = getdate()
payment_reco.from_payment_date = add_days(getdate(), -1)
payment_reco.to_payment_date = getdate()
payment_reco.maximum_invoice_amount = 1000
payment_reco.maximum_payment_amount = 1000
payment_reco.invoice_limit = 10
payment_reco.payment_limit = 10
payment_reco.bank_cash_account = "_Test Bank - _TC"
payment_reco.cost_center = "_Test Cost Center - _TC"
payment_reco.get_unreconciled_entries()
def create_company(self):
company = None
if frappe.db.exists("Company", "_Test Payment Reconciliation"):
company = frappe.get_doc("Company", "_Test Payment Reconciliation")
else:
company = frappe.get_doc(
{
"doctype": "Company",
"company_name": "_Test Payment Reconciliation",
"country": "India",
"default_currency": "INR",
"create_chart_of_accounts_based_on": "Standard Template",
"chart_of_accounts": "Standard",
}
)
company = company.save()
self.assertEqual(len(payment_reco.get("invoices")), 1)
self.assertEqual(len(payment_reco.get("payments")), 1)
self.company = company.name
self.cost_center = company.cost_center
self.warehouse = "All Warehouses - _PR"
self.income_account = "Sales - _PR"
self.expense_account = "Cost of Goods Sold - _PR"
self.debit_to = "Debtors - _PR"
self.creditors = "Creditors - _PR"
payment_entry = payment_reco.get("payments")[0].reference_name
invoice = payment_reco.get("invoices")[0].invoice_number
# create bank account
if frappe.db.exists("Account", "HDFC - _PR"):
self.bank = "HDFC - _PR"
else:
bank_acc = frappe.get_doc(
{
"doctype": "Account",
"account_name": "HDFC",
"parent_account": "Bank Accounts - _PR",
"company": self.company,
}
)
bank_acc.save()
self.bank = bank_acc.name
def create_item(self):
item = create_item(
item_code="_Test PR Item", is_stock_item=0, company=self.company, warehouse=self.warehouse
payment_reco.allocate_entries(
{
"payments": [payment_reco.get("payments")[0].as_dict()],
"invoices": [payment_reco.get("invoices")[0].as_dict()],
}
)
self.item = item if isinstance(item, str) else item.item_code
def create_customer(self):
if frappe.db.exists("Customer", "_Test PR Customer"):
self.customer = "_Test PR Customer"
else:
customer = frappe.new_doc("Customer")
customer.customer_name = "_Test PR Customer"
customer.type = "Individual"
customer.save()
self.customer = customer.name
if frappe.db.exists("Customer", "_Test PR Customer 2"):
self.customer2 = "_Test PR Customer 2"
else:
customer = frappe.new_doc("Customer")
customer.customer_name = "_Test PR Customer 2"
customer.type = "Individual"
customer.save()
self.customer2 = customer.name
if frappe.db.exists("Customer", "_Test PR Customer 3"):
self.customer3 = "_Test PR Customer 3"
else:
customer = frappe.new_doc("Customer")
customer.customer_name = "_Test PR Customer 3"
customer.type = "Individual"
customer.default_currency = "EUR"
customer.save()
self.customer3 = customer.name
def create_account(self):
account_name = "Debtors EUR"
if not frappe.db.get_value(
"Account", filters={"account_name": account_name, "company": self.company}
):
acc = frappe.new_doc("Account")
acc.account_name = account_name
acc.parent_account = "Accounts Receivable - _PR"
acc.company = self.company
acc.account_currency = "EUR"
acc.account_type = "Receivable"
acc.insert()
else:
name = frappe.db.get_value(
"Account",
filters={"account_name": account_name, "company": self.company},
fieldname="name",
pluck=True,
)
acc = frappe.get_doc("Account", name)
self.debtors_eur = acc.name
def create_sales_invoice(
self, qty=1, rate=100, posting_date=nowdate(), do_not_save=False, do_not_submit=False
):
"""
Helper function to populate default values in sales invoice
"""
sinv = create_sales_invoice(
qty=qty,
rate=rate,
company=self.company,
customer=self.customer,
item_code=self.item,
item_name=self.item,
cost_center=self.cost_center,
warehouse=self.warehouse,
debit_to=self.debit_to,
parent_cost_center=self.cost_center,
update_stock=0,
currency="INR",
is_pos=0,
is_return=0,
return_against=None,
income_account=self.income_account,
expense_account=self.expense_account,
do_not_save=do_not_save,
do_not_submit=do_not_submit,
)
return sinv
def create_payment_entry(self, amount=100, posting_date=nowdate(), customer=None):
"""
Helper function to populate default values in payment entry
"""
payment = create_payment_entry(
company=self.company,
payment_type="Receive",
party_type="Customer",
party=customer or self.customer,
paid_from=self.debit_to,
paid_to=self.bank,
paid_amount=amount,
)
payment.posting_date = posting_date
return payment
def clear_old_entries(self):
doctype_list = [
"GL Entry",
"Payment Ledger Entry",
"Sales Invoice",
"Purchase Invoice",
"Payment Entry",
"Journal Entry",
]
for doctype in doctype_list:
qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run()
def create_payment_reconciliation(self):
pr = frappe.new_doc("Payment Reconciliation")
pr.company = self.company
pr.party_type = "Customer"
pr.party = self.customer
pr.receivable_payable_account = get_party_account(pr.party_type, pr.party, pr.company)
pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate()
return pr
def create_journal_entry(
self, acc1=None, acc2=None, amount=0, posting_date=None, cost_center=None
):
je = frappe.new_doc("Journal Entry")
je.posting_date = posting_date or nowdate()
je.company = self.company
je.user_remark = "test"
if not cost_center:
cost_center = self.cost_center
je.set(
"accounts",
[
{
"account": acc1,
"cost_center": cost_center,
"debit_in_account_currency": amount if amount > 0 else 0,
"credit_in_account_currency": abs(amount) if amount < 0 else 0,
},
{
"account": acc2,
"cost_center": cost_center,
"credit_in_account_currency": amount if amount > 0 else 0,
"debit_in_account_currency": abs(amount) if amount < 0 else 0,
},
],
)
return je
def test_filter_min_max(self):
# check filter condition minimum and maximum amount
self.create_sales_invoice(qty=1, rate=300)
self.create_sales_invoice(qty=1, rate=400)
self.create_sales_invoice(qty=1, rate=500)
self.create_payment_entry(amount=300).save().submit()
self.create_payment_entry(amount=400).save().submit()
self.create_payment_entry(amount=500).save().submit()
pr = self.create_payment_reconciliation()
pr.minimum_invoice_amount = 400
pr.maximum_invoice_amount = 500
pr.minimum_payment_amount = 300
pr.maximum_payment_amount = 600
pr.get_unreconciled_entries()
self.assertEqual(len(pr.get("invoices")), 2)
self.assertEqual(len(pr.get("payments")), 3)
pr.minimum_invoice_amount = 300
pr.maximum_invoice_amount = 600
pr.minimum_payment_amount = 400
pr.maximum_payment_amount = 500
pr.get_unreconciled_entries()
self.assertEqual(len(pr.get("invoices")), 3)
self.assertEqual(len(pr.get("payments")), 2)
pr.minimum_invoice_amount = (
pr.maximum_invoice_amount
) = pr.minimum_payment_amount = pr.maximum_payment_amount = 0
pr.get_unreconciled_entries()
self.assertEqual(len(pr.get("invoices")), 3)
self.assertEqual(len(pr.get("payments")), 3)
def test_filter_posting_date(self):
# check filter condition using transaction date
date1 = nowdate()
date2 = add_days(nowdate(), -1)
amount = 100
self.create_sales_invoice(qty=1, rate=amount, posting_date=date1)
si2 = self.create_sales_invoice(
qty=1, rate=amount, posting_date=date2, do_not_save=True, do_not_submit=True
)
si2.set_posting_time = 1
si2.posting_date = date2
si2.save().submit()
self.create_payment_entry(amount=amount, posting_date=date1).save().submit()
self.create_payment_entry(amount=amount, posting_date=date2).save().submit()
pr = self.create_payment_reconciliation()
pr.from_invoice_date = pr.to_invoice_date = date1
pr.from_payment_date = pr.to_payment_date = date1
pr.get_unreconciled_entries()
# assert only si and pe are fetched
self.assertEqual(len(pr.get("invoices")), 1)
self.assertEqual(len(pr.get("payments")), 1)
pr.from_invoice_date = date2
pr.to_invoice_date = date1
pr.from_payment_date = date2
pr.to_payment_date = date1
pr.get_unreconciled_entries()
# assert only si and pe are fetched
self.assertEqual(len(pr.get("invoices")), 2)
self.assertEqual(len(pr.get("payments")), 2)
def test_filter_posting_date_case2(self):
"""
Posting date should not affect outstanding amount calculation
"""
from_date = add_days(nowdate(), -30)
to_date = nowdate()
self.create_payment_entry(amount=25, posting_date=from_date).submit()
self.create_sales_invoice(rate=25, qty=1, posting_date=to_date)
pr = self.create_payment_reconciliation()
pr.from_invoice_date = pr.from_payment_date = from_date
pr.to_invoice_date = pr.to_payment_date = to_date
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 1)
self.assertEqual(len(pr.payments), 1)
invoices = [x.as_dict() for x in pr.invoices]
payments = [x.as_dict() for x in pr.payments]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.reconcile()
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 0)
self.assertEqual(len(pr.payments), 0)
pr.from_invoice_date = pr.from_payment_date = to_date
pr.to_invoice_date = pr.to_payment_date = to_date
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 0)
def test_filter_invoice_limit(self):
# check filter condition - invoice limit
transaction_date = nowdate()
rate = 100
invoices = []
payments = []
for i in range(5):
invoices.append(self.create_sales_invoice(qty=1, rate=rate, posting_date=transaction_date))
pe = self.create_payment_entry(amount=rate, posting_date=transaction_date).save().submit()
payments.append(pe)
pr = self.create_payment_reconciliation()
pr.from_invoice_date = pr.to_invoice_date = transaction_date
pr.from_payment_date = pr.to_payment_date = transaction_date
pr.invoice_limit = 2
pr.payment_limit = 3
pr.get_unreconciled_entries()
self.assertEqual(len(pr.get("invoices")), 2)
self.assertEqual(len(pr.get("payments")), 3)
def test_payment_against_invoice(self):
si = self.create_sales_invoice(qty=1, rate=200)
pe = self.create_payment_entry(amount=55).save().submit()
# second payment entry
self.create_payment_entry(amount=35).save().submit()
pr = self.create_payment_reconciliation()
# reconcile multiple payments against invoice
pr.get_unreconciled_entries()
invoices = [x.as_dict() for x in pr.get("invoices")]
payments = [x.as_dict() for x in pr.get("payments")]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.reconcile()
si.reload()
self.assertEqual(si.status, "Partly Paid")
# check PR tool output post reconciliation
self.assertEqual(len(pr.get("invoices")), 1)
self.assertEqual(pr.get("invoices")[0].get("outstanding_amount"), 110)
self.assertEqual(pr.get("payments"), [])
# cancel one PE
pe.reload()
pe.cancel()
pr.get_unreconciled_entries()
# check PR tool output
self.assertEqual(len(pr.get("invoices")), 1)
self.assertEqual(len(pr.get("payments")), 0)
self.assertEqual(pr.get("invoices")[0].get("outstanding_amount"), 165)
def test_payment_against_journal(self):
transaction_date = nowdate()
sales = "Sales - _PR"
amount = 921
# debit debtors account to record an invoice
je = self.create_journal_entry(self.debit_to, sales, amount, transaction_date)
je.accounts[0].party_type = "Customer"
je.accounts[0].party = self.customer
je.save()
je.submit()
self.create_payment_entry(amount=amount, posting_date=transaction_date).save().submit()
pr = self.create_payment_reconciliation()
pr.minimum_invoice_amount = pr.maximum_invoice_amount = amount
pr.from_invoice_date = pr.to_invoice_date = transaction_date
pr.from_payment_date = pr.to_payment_date = transaction_date
pr.get_unreconciled_entries()
invoices = [x.as_dict() for x in pr.get("invoices")]
payments = [x.as_dict() for x in pr.get("payments")]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.reconcile()
# check PR tool output
self.assertEqual(len(pr.get("invoices")), 0)
self.assertEqual(len(pr.get("payments")), 0)
def test_journal_against_invoice(self):
transaction_date = nowdate()
amount = 100
si = self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date)
# credit debtors account to record a payment
je = self.create_journal_entry(self.bank, self.debit_to, amount, transaction_date)
je.accounts[1].party_type = "Customer"
je.accounts[1].party = self.customer
je.save()
je.submit()
pr = self.create_payment_reconciliation()
pr.get_unreconciled_entries()
invoices = [x.as_dict() for x in pr.get("invoices")]
payments = [x.as_dict() for x in pr.get("payments")]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.reconcile()
# assert outstanding
si.reload()
self.assertEqual(si.status, "Paid")
self.assertEqual(si.outstanding_amount, 0)
# check PR tool output
self.assertEqual(len(pr.get("invoices")), 0)
self.assertEqual(len(pr.get("payments")), 0)
def test_journal_against_journal(self):
transaction_date = nowdate()
sales = "Sales - _PR"
amount = 100
# debit debtors account to simulate a invoice
je1 = self.create_journal_entry(self.debit_to, sales, amount, transaction_date)
je1.accounts[0].party_type = "Customer"
je1.accounts[0].party = self.customer
je1.save()
je1.submit()
# credit debtors account to simulate a payment
je2 = self.create_journal_entry(self.bank, self.debit_to, amount, transaction_date)
je2.accounts[1].party_type = "Customer"
je2.accounts[1].party = self.customer
je2.save()
je2.submit()
pr = self.create_payment_reconciliation()
pr.get_unreconciled_entries()
invoices = [x.as_dict() for x in pr.get("invoices")]
payments = [x.as_dict() for x in pr.get("payments")]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.reconcile()
self.assertEqual(pr.get("invoices"), [])
self.assertEqual(pr.get("payments"), [])
def test_cr_note_against_invoice(self):
transaction_date = nowdate()
amount = 100
si = self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date)
cr_note = self.create_sales_invoice(
qty=-1, rate=amount, posting_date=transaction_date, do_not_save=True, do_not_submit=True
)
cr_note.is_return = 1
cr_note = cr_note.save().submit()
pr = self.create_payment_reconciliation()
pr.get_unreconciled_entries()
invoices = [x.as_dict() for x in pr.get("invoices")]
payments = [x.as_dict() for x in pr.get("payments")]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.reconcile()
pr.get_unreconciled_entries()
# check reconciliation tool output
# reconciled invoice and credit note shouldn't show up in selection
self.assertEqual(pr.get("invoices"), [])
self.assertEqual(pr.get("payments"), [])
# assert outstanding
si.reload()
self.assertEqual(si.status, "Paid")
self.assertEqual(si.outstanding_amount, 0)
def test_cr_note_partial_against_invoice(self):
transaction_date = nowdate()
amount = 100
allocated_amount = 80
si = self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date)
cr_note = self.create_sales_invoice(
qty=-1, rate=amount, posting_date=transaction_date, do_not_save=True, do_not_submit=True
)
cr_note.is_return = 1
cr_note = cr_note.save().submit()
pr = self.create_payment_reconciliation()
pr.get_unreconciled_entries()
invoices = [x.as_dict() for x in pr.get("invoices")]
payments = [x.as_dict() for x in pr.get("payments")]
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
pr.allocation[0].allocated_amount = allocated_amount
pr.reconcile()
# assert outstanding
si.reload()
self.assertEqual(si.status, "Partly Paid")
self.assertEqual(si.outstanding_amount, 20)
pr.get_unreconciled_entries()
# check reconciliation tool output
self.assertEqual(len(pr.get("invoices")), 1)
self.assertEqual(len(pr.get("payments")), 1)
self.assertEqual(pr.get("invoices")[0].outstanding_amount, 20)
self.assertEqual(pr.get("payments")[0].amount, 20)
def test_pr_output_foreign_currency_and_amount(self):
# test for currency and amount invoices and payments
transaction_date = nowdate()
# In EUR
amount = 100
exchange_rate = 80
si = self.create_sales_invoice(
qty=1, rate=amount, posting_date=transaction_date, do_not_save=True, do_not_submit=True
)
si.customer = self.customer3
si.currency = "EUR"
si.conversion_rate = exchange_rate
si.debit_to = self.debtors_eur
si = si.save().submit()
cr_note = self.create_sales_invoice(
qty=-1, rate=amount, posting_date=transaction_date, do_not_save=True, do_not_submit=True
)
cr_note.customer = self.customer3
cr_note.is_return = 1
cr_note.currency = "EUR"
cr_note.conversion_rate = exchange_rate
cr_note.debit_to = self.debtors_eur
cr_note = cr_note.save().submit()
pr = self.create_payment_reconciliation()
pr.party = self.customer3
pr.receivable_payable_account = self.debtors_eur
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 1)
self.assertEqual(len(pr.payments), 1)
self.assertEqual(pr.invoices[0].amount, amount)
self.assertEqual(pr.invoices[0].currency, "EUR")
self.assertEqual(pr.payments[0].amount, amount)
self.assertEqual(pr.payments[0].currency, "EUR")
cr_note.cancel()
pay = self.create_payment_entry(
amount=amount, posting_date=transaction_date, customer=self.customer3
)
pay.paid_from = self.debtors_eur
pay.paid_from_account_currency = "EUR"
pay.source_exchange_rate = exchange_rate
pay.received_amount = exchange_rate * amount
pay = pay.save().submit()
pr.get_unreconciled_entries()
self.assertEqual(len(pr.invoices), 1)
self.assertEqual(len(pr.payments), 1)
self.assertEqual(pr.payments[0].amount, amount)
self.assertEqual(pr.payments[0].currency, "EUR")
payment_reco.reconcile()
payment_entry_doc = frappe.get_doc("Payment Entry", payment_entry)
self.assertEqual(payment_entry_doc.get("references")[0].reference_name, invoice)
def make_customer():
if not frappe.db.get_value("Customer", "_Test Payment Reco Customer"):
frappe.get_doc(
{
"doctype": "Customer",
"customer_name": "_Test Payment Reco Customer",
"customer_type": "Individual",
"customer_group": "_Test Customer Group",
"territory": "_Test Territory",
}
).insert()
def make_invoice_and_payment():
si = create_sales_invoice(
customer="_Test Payment Reco Customer", qty=1, rate=690, do_not_save=True
)
si.cost_center = "_Test Cost Center - _TC"
si.save()
si.submit()
pe = frappe.get_doc(
{
"doctype": "Payment Entry",
"payment_type": "Receive",
"party_type": "Customer",
"party": "_Test Payment Reco Customer",
"company": "_Test Company",
"paid_from_account_currency": "INR",
"paid_to_account_currency": "INR",
"source_exchange_rate": 1,
"target_exchange_rate": 1,
"reference_no": "1",
"reference_date": getdate(),
"received_amount": 690,
"paid_amount": 690,
"paid_from": "Debtors - _TC",
"paid_to": "_Test Bank - _TC",
"cost_center": "_Test Cost Center - _TC",
}
)
pe.insert()
pe.submit()

View File

@@ -186,10 +186,8 @@
{
"fetch_from": "bank_account.bank",
"fieldname": "bank",
"fieldtype": "Link",
"label": "Bank",
"options": "Bank",
"read_only": 1
"fieldtype": "Read Only",
"label": "Bank"
},
{
"fetch_from": "bank_account.bank_account_no",
@@ -368,11 +366,10 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-09-30 16:19:43.680025",
"modified": "2020-09-18 12:24:14.178853",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Request",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
@@ -404,6 +401,5 @@
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
"sort_order": "DESC"
}

View File

@@ -6,10 +6,10 @@ import json
import frappe
from frappe import _
from frappe.integrations.utils import get_payment_gateway_controller
from frappe.model.document import Document
from frappe.utils import flt, get_url, nowdate
from frappe.utils.background_jobs import enqueue
from payments.utils import get_payment_gateway_controller
from erpnext.accounts.doctype.payment_entry.payment_entry import (
get_company_defaults,
@@ -53,7 +53,7 @@ class PaymentRequest(Document):
def validate_currency(self):
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
if self.payment_account and ref_doc.currency != frappe.get_cached_value(
if self.payment_account and ref_doc.currency != frappe.db.get_value(
"Account", self.payment_account, "account_currency"
):
frappe.throw(_("Transaction currency must be same as Payment Gateway currency"))

View File

@@ -39,7 +39,6 @@
{
"columns": 2,
"fetch_from": "payment_term.description",
"fetch_if_empty": 1,
"fieldname": "description",
"fieldtype": "Small Text",
"in_list_view": 1,
@@ -160,7 +159,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-09-16 13:57:06.382859",
"modified": "2021-04-28 05:41:35.084233",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Schedule",
@@ -169,6 +168,5 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -10,11 +10,10 @@
"fiscal_year",
"amended_from",
"company",
"cost_center_wise_pnl",
"column_break1",
"closing_account_head",
"remarks",
"gle_processing_status",
"error_message"
"remarks"
],
"fields": [
{
@@ -87,26 +86,17 @@
"reqd": 1
},
{
"depends_on": "eval:doc.docstatus!=0",
"fieldname": "gle_processing_status",
"fieldtype": "Select",
"label": "GL Entry Processing Status",
"options": "In Progress\nCompleted\nFailed",
"read_only": 1
},
{
"depends_on": "eval:doc.gle_processing_status=='Failed'",
"fieldname": "error_message",
"fieldtype": "Text",
"label": "Error Message",
"read_only": 1
"default": "0",
"fieldname": "cost_center_wise_pnl",
"fieldtype": "Check",
"label": "Book Cost Center Wise Profit/Loss"
}
],
"icon": "fa fa-file-text",
"idx": 1,
"is_submittable": 1,
"links": [],
"modified": "2022-07-20 14:51:04.714154",
"modified": "2021-05-20 15:27:37.210458",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Period Closing Voucher",

View File

@@ -8,6 +8,7 @@ from frappe.utils import flt
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
get_dimensions,
)
from erpnext.accounts.utils import get_account_currency
from erpnext.controllers.accounts_controller import AccountsController
@@ -19,31 +20,16 @@ class PeriodClosingVoucher(AccountsController):
self.validate_posting_date()
def on_submit(self):
self.db_set("gle_processing_status", "In Progress")
self.make_gl_entries()
def on_cancel(self):
self.db_set("gle_processing_status", "In Progress")
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
gle_count = frappe.db.count(
"GL Entry",
{"voucher_type": "Period Closing Voucher", "voucher_no": self.name, "is_cancelled": 0},
)
if gle_count > 5000:
frappe.enqueue(
make_reverse_gl_entries,
voucher_type="Period Closing Voucher",
voucher_no=self.name,
queue="long",
)
frappe.msgprint(
_("The GL Entries will be cancelled in the background, it can take a few minutes."), alert=True
)
else:
make_reverse_gl_entries(voucher_type="Period Closing Voucher", voucher_no=self.name)
from erpnext.accounts.general_ledger import make_reverse_gl_entries
make_reverse_gl_entries(voucher_type="Period Closing Voucher", voucher_no=self.name)
def validate_account_head(self):
closing_account_type = frappe.get_cached_value("Account", self.closing_account_head, "root_type")
closing_account_type = frappe.db.get_value("Account", self.closing_account_head, "root_type")
if closing_account_type not in ["Liability", "Equity"]:
frappe.throw(
@@ -68,8 +54,8 @@ class PeriodClosingVoucher(AccountsController):
pce = frappe.db.sql(
"""select name from `tabPeriod Closing Voucher`
where posting_date > %s and fiscal_year = %s and docstatus = 1 and company = %s""",
(self.posting_date, self.fiscal_year, self.company),
where posting_date > %s and fiscal_year = %s and docstatus = 1""",
(self.posting_date, self.fiscal_year),
)
if pce and pce[0][0]:
frappe.throw(
@@ -81,80 +67,90 @@ class PeriodClosingVoucher(AccountsController):
def make_gl_entries(self):
gl_entries = self.get_gl_entries()
if gl_entries:
if len(gl_entries) > 5000:
frappe.enqueue(process_gl_entries, gl_entries=gl_entries, queue="long")
frappe.msgprint(
_("The GL Entries will be processed in the background, it can take a few minutes."),
alert=True,
)
else:
process_gl_entries(gl_entries)
from erpnext.accounts.general_ledger import make_gl_entries
make_gl_entries(gl_entries)
def get_gl_entries(self):
gl_entries = []
pl_accounts = self.get_pl_balances()
# pl account
for acc in self.get_pl_balances_based_on_dimensions(group_by_account=True):
for acc in pl_accounts:
if flt(acc.bal_in_company_currency):
gl_entries.append(self.get_gle_for_pl_account(acc))
gl_entries.append(
self.get_gl_dict(
{
"account": acc.account,
"cost_center": acc.cost_center,
"finance_book": acc.finance_book,
"account_currency": acc.account_currency,
"debit_in_account_currency": abs(flt(acc.bal_in_account_currency))
if flt(acc.bal_in_account_currency) < 0
else 0,
"debit": abs(flt(acc.bal_in_company_currency))
if flt(acc.bal_in_company_currency) < 0
else 0,
"credit_in_account_currency": abs(flt(acc.bal_in_account_currency))
if flt(acc.bal_in_account_currency) > 0
else 0,
"credit": abs(flt(acc.bal_in_company_currency))
if flt(acc.bal_in_company_currency) > 0
else 0,
},
item=acc,
)
)
# closing liability account
for acc in self.get_pl_balances_based_on_dimensions(group_by_account=False):
if flt(acc.bal_in_company_currency):
gl_entries.append(self.get_gle_for_closing_account(acc))
if gl_entries:
gle_for_net_pl_bal = self.get_pnl_gl_entry(pl_accounts)
gl_entries += gle_for_net_pl_bal
return gl_entries
def get_gle_for_pl_account(self, acc):
gl_entry = self.get_gl_dict(
{
"account": acc.account,
"cost_center": acc.cost_center,
"finance_book": acc.finance_book,
"account_currency": acc.account_currency,
"debit_in_account_currency": abs(flt(acc.bal_in_account_currency))
if flt(acc.bal_in_account_currency) < 0
else 0,
"debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) < 0 else 0,
"credit_in_account_currency": abs(flt(acc.bal_in_account_currency))
if flt(acc.bal_in_account_currency) > 0
else 0,
"credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0,
},
item=acc,
)
self.update_default_dimensions(gl_entry, acc)
return gl_entry
def get_pnl_gl_entry(self, pl_accounts):
company_cost_center = frappe.db.get_value("Company", self.company, "cost_center")
gl_entries = []
def get_gle_for_closing_account(self, acc):
gl_entry = self.get_gl_dict(
{
"account": self.closing_account_head,
"cost_center": acc.cost_center,
"finance_book": acc.finance_book,
"account_currency": acc.account_currency,
"debit_in_account_currency": abs(flt(acc.bal_in_account_currency))
if flt(acc.bal_in_account_currency) > 0
else 0,
"debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0,
"credit_in_account_currency": abs(flt(acc.bal_in_account_currency))
if flt(acc.bal_in_account_currency) < 0
else 0,
"credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) < 0 else 0,
},
item=acc,
)
self.update_default_dimensions(gl_entry, acc)
return gl_entry
for acc in pl_accounts:
if flt(acc.bal_in_company_currency):
cost_center = acc.cost_center if self.cost_center_wise_pnl else company_cost_center
gl_entry = self.get_gl_dict(
{
"account": self.closing_account_head,
"cost_center": cost_center,
"finance_book": acc.finance_book,
"account_currency": acc.account_currency,
"debit_in_account_currency": abs(flt(acc.bal_in_account_currency))
if flt(acc.bal_in_account_currency) > 0
else 0,
"debit": abs(flt(acc.bal_in_company_currency))
if flt(acc.bal_in_company_currency) > 0
else 0,
"credit_in_account_currency": abs(flt(acc.bal_in_account_currency))
if flt(acc.bal_in_account_currency) < 0
else 0,
"credit": abs(flt(acc.bal_in_company_currency))
if flt(acc.bal_in_company_currency) < 0
else 0,
},
item=acc,
)
def update_default_dimensions(self, gl_entry, acc):
self.update_default_dimensions(gl_entry)
gl_entries.append(gl_entry)
return gl_entries
def update_default_dimensions(self, gl_entry):
if not self.accounting_dimensions:
self.accounting_dimensions = get_accounting_dimensions()
_, default_dimensions = get_dimensions()
for dimension in self.accounting_dimensions:
gl_entry.update({dimension: acc.get(dimension)})
gl_entry.update({dimension: default_dimensions.get(self.company, {}).get(dimension)})
def get_pl_balances_based_on_dimensions(self, group_by_account=False):
def get_pl_balances(self):
"""Get balance for dimension-wise pl accounts"""
dimension_fields = ["t1.cost_center", "t1.finance_book"]
@@ -163,56 +159,20 @@ class PeriodClosingVoucher(AccountsController):
for dimension in self.accounting_dimensions:
dimension_fields.append("t1.{0}".format(dimension))
if group_by_account:
dimension_fields.append("t1.account")
return frappe.db.sql(
"""
select
t2.account_currency,
{dimension_fields},
t1.account, t2.account_currency, {dimension_fields},
sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as bal_in_account_currency,
sum(t1.debit) - sum(t1.credit) as bal_in_company_currency
from `tabGL Entry` t1, `tabAccount` t2
where
t1.is_cancelled = 0
and t1.account = t2.name
and t2.report_type = 'Profit and Loss'
and t2.docstatus < 2
and t2.company = %s
and t1.posting_date between %s and %s
group by {dimension_fields}
where t1.is_cancelled = 0 and t1.account = t2.name and t2.report_type = 'Profit and Loss'
and t2.docstatus < 2 and t2.company = %s
and t1.posting_date between %s and %s
group by t1.account, {dimension_fields}
""".format(
dimension_fields=", ".join(dimension_fields)
),
(self.company, self.get("year_start_date"), self.posting_date),
as_dict=1,
)
def process_gl_entries(gl_entries):
from erpnext.accounts.general_ledger import make_gl_entries
try:
make_gl_entries(gl_entries, merge_entries=False)
frappe.db.set_value(
"Period Closing Voucher", gl_entries[0].get("voucher_no"), "gle_processing_status", "Completed"
)
except Exception as e:
frappe.db.rollback()
frappe.log_error(e)
frappe.db.set_value(
"Period Closing Voucher", gl_entries[0].get("voucher_no"), "gle_processing_status", "Failed"
)
def make_reverse_gl_entries(voucher_type, voucher_no):
from erpnext.accounts.general_ledger import make_reverse_gl_entries
try:
make_reverse_gl_entries(voucher_type=voucher_type, voucher_no=voucher_no)
frappe.db.set_value("Period Closing Voucher", voucher_no, "gle_processing_status", "Completed")
except Exception as e:
frappe.db.rollback()
frappe.log_error(e)
frappe.db.set_value("Period Closing Voucher", voucher_no, "gle_processing_status", "Failed")

View File

@@ -49,7 +49,7 @@ class TestPeriodClosingVoucher(unittest.TestCase):
expected_gle = (
("Cost of Goods Sold - TPC", 0.0, 600.0),
(surplus_account, 200.0, 0.0),
(surplus_account, 600.0, 400.0),
("Sales - TPC", 400.0, 0.0),
)
@@ -59,8 +59,7 @@ class TestPeriodClosingVoucher(unittest.TestCase):
""",
(pcv.name),
)
pcv.reload()
self.assertEqual(pcv.gle_processing_status, "Completed")
self.assertEqual(pcv_gle, expected_gle)
def test_cost_center_wise_posting(self):
@@ -94,6 +93,7 @@ class TestPeriodClosingVoucher(unittest.TestCase):
)
pcv = self.make_period_closing_voucher(submit=False)
pcv.cost_center_wise_pnl = 1
pcv.save()
pcv.submit()
surplus_account = pcv.closing_account_head
@@ -114,17 +114,7 @@ class TestPeriodClosingVoucher(unittest.TestCase):
(pcv.name),
)
self.assertSequenceEqual(pcv_gle, expected_gle)
pcv.reload()
pcv.cancel()
self.assertFalse(
frappe.db.get_value(
"GL Entry",
{"voucher_type": "Period Closing Voucher", "voucher_no": pcv.name, "is_cancelled": 0},
)
)
self.assertEqual(pcv_gle, expected_gle)
def test_period_closing_with_finance_book_entries(self):
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
@@ -175,7 +165,7 @@ class TestPeriodClosingVoucher(unittest.TestCase):
(pcv.name),
)
self.assertSequenceEqual(pcv_gle, expected_gle)
self.assertEqual(pcv_gle, expected_gle)
def make_period_closing_voucher(self, submit=True):
surplus_account = create_account()

View File

@@ -25,7 +25,7 @@ frappe.ui.form.on('POS Closing Entry', {
frappe.realtime.on('closing_process_complete', async function(data) {
await frm.reload_doc();
if (frm.doc.status == 'Failed' && frm.doc.error_message) {
if (frm.doc.status == 'Failed' && frm.doc.error_message && data.user == frappe.session.user) {
frappe.msgprint({
title: __('POS Closing Failed'),
message: frm.doc.error_message,
@@ -36,15 +36,6 @@ frappe.ui.form.on('POS Closing Entry', {
});
set_html_data(frm);
if (frm.doc.docstatus == 1) {
if (!frm.doc.posting_date) {
frm.set_value("posting_date", frappe.datetime.nowdate());
}
if (!frm.doc.posting_time) {
frm.set_value("posting_time", frappe.datetime.now_time());
}
}
},
refresh: function(frm) {

View File

@@ -11,7 +11,6 @@
"period_end_date",
"column_break_3",
"posting_date",
"posting_time",
"pos_opening_entry",
"status",
"section_break_5",
@@ -52,6 +51,7 @@
"fieldtype": "Datetime",
"in_list_view": 1,
"label": "Period End Date",
"read_only": 1,
"reqd": 1
},
{
@@ -219,13 +219,6 @@
"fieldtype": "Small Text",
"label": "Error",
"read_only": 1
},
{
"fieldname": "posting_time",
"fieldtype": "Time",
"label": "Posting Time",
"no_copy": 1,
"reqd": 1
}
],
"is_submittable": 1,
@@ -235,11 +228,10 @@
"link_fieldname": "pos_closing_entry"
}
],
"modified": "2022-08-01 11:37:14.991228",
"modified": "2021-10-20 16:19:25.340565",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Closing Entry",
"naming_rule": "Expression (old style)",
"owner": "Administrator",
"permissions": [
{
@@ -286,6 +278,5 @@
],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@@ -15,9 +15,6 @@ from erpnext.controllers.status_updater import StatusUpdater
class POSClosingEntry(StatusUpdater):
def validate(self):
self.posting_date = self.posting_date or frappe.utils.nowdate()
self.posting_time = self.posting_time or frappe.utils.nowtime()
if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))

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