mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-29 13:58:37 +00:00
Compare commits
286 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4b0790003 | ||
|
|
55f553fb09 | ||
|
|
cff6e72838 | ||
|
|
6cca18e168 | ||
|
|
2e5c531bbf | ||
|
|
80e5a47ff6 | ||
|
|
4fa93b05c6 | ||
|
|
ab757cbd82 | ||
|
|
cf93714a7c | ||
|
|
82f05bf11a | ||
|
|
542c80433d | ||
|
|
1fe6d4ef1f | ||
|
|
ba95df2a74 | ||
|
|
dc69f47aab | ||
|
|
0575005c87 | ||
|
|
6f2e639182 | ||
|
|
ed0d0d8e8e | ||
|
|
4c35942c2d | ||
|
|
73e9b38cda | ||
|
|
e867fe77a4 | ||
|
|
6948acbb69 | ||
|
|
5938af9c3f | ||
|
|
c575c88ab9 | ||
|
|
2b47f5815e | ||
|
|
026f9f5a69 | ||
|
|
b849f6c4d6 | ||
|
|
8f24292155 | ||
|
|
bf948243a6 | ||
|
|
690b52622d | ||
|
|
14600fa190 | ||
|
|
3d661709fa | ||
|
|
25301881c9 | ||
|
|
bc7ab2f787 | ||
|
|
602f0769c4 | ||
|
|
f3295a9f59 | ||
|
|
2f4d8e1e94 | ||
|
|
bd9e0e8b5a | ||
|
|
62bc75a917 | ||
|
|
51f7e6e648 | ||
|
|
6a6a3ae3c3 | ||
|
|
ae494b8ee8 | ||
|
|
c2976ef643 | ||
|
|
844ec58f6b | ||
|
|
bafb79db50 | ||
|
|
1d108ec224 | ||
|
|
57cf3c28f8 | ||
|
|
c5e30f5336 | ||
|
|
4cfecdc66b | ||
|
|
ca4f86d5af | ||
|
|
d6168ca979 | ||
|
|
78a2333109 | ||
|
|
ceea8be483 | ||
|
|
bd82ae523d | ||
|
|
8ca3d6b7f3 | ||
|
|
388bbca492 | ||
|
|
1641630d70 | ||
|
|
61456ec45d | ||
|
|
dd25c83c30 | ||
|
|
ff0b51ecdb | ||
|
|
fcafff7ebe | ||
|
|
e1d31aad4d | ||
|
|
22a19e4b6e | ||
|
|
b0f5c02d74 | ||
|
|
0805a8e19c | ||
|
|
5985e02574 | ||
|
|
8a607db493 | ||
|
|
cdd5f992f6 | ||
|
|
9171f0cd9d | ||
|
|
ea66f18032 | ||
|
|
313ad7ae89 | ||
|
|
bcf7d87b61 | ||
|
|
a5f398474a | ||
|
|
373b868c1d | ||
|
|
665898a579 | ||
|
|
2f632d031a | ||
|
|
ab3671a13b | ||
|
|
30cbc682b4 | ||
|
|
b877fa60db | ||
|
|
c7a97cfb31 | ||
|
|
413a8a41f0 | ||
|
|
adb6918834 | ||
|
|
619898350a | ||
|
|
68d35b08f6 | ||
|
|
94caf7f5a8 | ||
|
|
9ec3087af8 | ||
|
|
8e48c4ee3e | ||
|
|
d10e5e666b | ||
|
|
ed8217d309 | ||
|
|
c3acdcf3ac | ||
|
|
dd5101056d | ||
|
|
d420eeb884 | ||
|
|
43d6cc087e | ||
|
|
e7f57542ab | ||
|
|
42c93a1f00 | ||
|
|
b86571d9d4 | ||
|
|
b167bffd22 | ||
|
|
2f2b45bd6d | ||
|
|
495a8a9ce1 | ||
|
|
0147754273 | ||
|
|
1d9c28ec5e | ||
|
|
49f28b0dbb | ||
|
|
cf29156139 | ||
|
|
63e26e39d4 | ||
|
|
a3a052bb51 | ||
|
|
ea807f8d9a | ||
|
|
4aaa1a15d7 | ||
|
|
d43cf0eca4 | ||
|
|
a5a41f7347 | ||
|
|
026c6085cc | ||
|
|
64406a631f | ||
|
|
2c21404813 | ||
|
|
2b72d143ca | ||
|
|
078383f086 | ||
|
|
6dc7a192ab | ||
|
|
a76285b349 | ||
|
|
f4f886c7d1 | ||
|
|
7925fc7130 | ||
|
|
0569899499 | ||
|
|
44f509f989 | ||
|
|
f3af0b2d2e | ||
|
|
59b3277696 | ||
|
|
a299092337 | ||
|
|
db809cb066 | ||
|
|
39e38bf083 | ||
|
|
8c041eb424 | ||
|
|
7c092a6b5f | ||
|
|
ebf3c0173e | ||
|
|
296f312e7f | ||
|
|
16943ac248 | ||
|
|
573183cff5 | ||
|
|
e55a264e57 | ||
|
|
79f9785d15 | ||
|
|
a178e6693c | ||
|
|
6459c28316 | ||
|
|
3e711e888d | ||
|
|
04b1d459eb | ||
|
|
3c563b8cea | ||
|
|
4fa3f96121 | ||
|
|
681d48a7f9 | ||
|
|
881476b4fb | ||
|
|
5d77e3ce05 | ||
|
|
0f9a6ee70a | ||
|
|
862c5145c1 | ||
|
|
e0fbf0c042 | ||
|
|
e443e6c02a | ||
|
|
b7b864e68c | ||
|
|
29ac533af4 | ||
|
|
2deb1edec1 | ||
|
|
78318964c7 | ||
|
|
279f51e636 | ||
|
|
5102d0c3f7 | ||
|
|
03e458390b | ||
|
|
d80b0aa157 | ||
|
|
f360bdcbf6 | ||
|
|
4fa412fe3f | ||
|
|
937e1fb024 | ||
|
|
dfd4ef178e | ||
|
|
47c6d9099b | ||
|
|
13f3ebf915 | ||
|
|
2a4bbf34b4 | ||
|
|
ae4a8c8788 | ||
|
|
3d1942571d | ||
|
|
2e2c23aa0f | ||
|
|
c330f47680 | ||
|
|
a974091678 | ||
|
|
87c0417e22 | ||
|
|
dbae36ece3 | ||
|
|
cec3cdec66 | ||
|
|
c3eab84e37 | ||
|
|
c0c693d8b0 | ||
|
|
a485e4e802 | ||
|
|
8de1d8663f | ||
|
|
6f50ad685e | ||
|
|
a880bdec5e | ||
|
|
5a9bd3bac6 | ||
|
|
646440fd55 | ||
|
|
684290233f | ||
|
|
29ea5cfc21 | ||
|
|
eb1081664a | ||
|
|
5b27642880 | ||
|
|
973611a356 | ||
|
|
4f9c28cd22 | ||
|
|
e16c14863b | ||
|
|
e1a5a7006f | ||
|
|
93940f30b7 | ||
|
|
189954eaf1 | ||
|
|
33ee01174b | ||
|
|
87ba196473 | ||
|
|
017729d545 | ||
|
|
2ef2057f44 | ||
|
|
04fdaaffbd | ||
|
|
fc051d143c | ||
|
|
851b8871b2 | ||
|
|
3ed42e180c | ||
|
|
3ca4f24d21 | ||
|
|
21d560cd19 | ||
|
|
4a7d75b5cc | ||
|
|
8f13b484a9 | ||
|
|
8ecca2a1cf | ||
|
|
fafb46eebd | ||
|
|
d53b197896 | ||
|
|
3dd3935e76 | ||
|
|
5c388a132f | ||
|
|
b0234489ca | ||
|
|
21336f1a2c | ||
|
|
888118e3e1 | ||
|
|
d9008779de | ||
|
|
c66c4e3aab | ||
|
|
2240aeb307 | ||
|
|
42d09448ee | ||
|
|
8049582652 | ||
|
|
1d5415f39d | ||
|
|
69780da099 | ||
|
|
8ddfc34c30 | ||
|
|
fb823b53d1 | ||
|
|
23dacbe9b2 | ||
|
|
0138595000 | ||
|
|
f45e8b9c16 | ||
|
|
e44783a3c5 | ||
|
|
85ad34672c | ||
|
|
71207a7dae | ||
|
|
71e14b3dbb | ||
|
|
41f1c11e85 | ||
|
|
4be554d8b4 | ||
|
|
3194e3a020 | ||
|
|
cacb0f6fde | ||
|
|
200ddbf66c | ||
|
|
20f2bef55f | ||
|
|
e68b08817e | ||
|
|
2561cf2072 | ||
|
|
abbbfe6240 | ||
|
|
4e1b2c6f8d | ||
|
|
c669dba691 | ||
|
|
8c183741bd | ||
|
|
2bbea63de1 | ||
|
|
baf014fc61 | ||
|
|
95e3dc9b81 | ||
|
|
703e4f4f5d | ||
|
|
bf2ebce6f4 | ||
|
|
65d24ea9ea | ||
|
|
6c9c3426f8 | ||
|
|
146d41ee81 | ||
|
|
7da461b862 | ||
|
|
8b57ecd8ef | ||
|
|
7d010adcd4 | ||
|
|
4af0a9b192 | ||
|
|
3bac2a88bd | ||
|
|
50a8907e8c | ||
|
|
42e25d4cdf | ||
|
|
4c1befab8f | ||
|
|
d928a5c3aa | ||
|
|
3bf7115cdc | ||
|
|
5541d68477 | ||
|
|
070df97663 | ||
|
|
58a6bbcf6d | ||
|
|
6650373c9f | ||
|
|
3c790c12f2 | ||
|
|
b875de6fb7 | ||
|
|
58e304f64b | ||
|
|
46171738c0 | ||
|
|
e7abdc34d0 | ||
|
|
fc103ab4ce | ||
|
|
ce2bf5fb1c | ||
|
|
59ab13c34f | ||
|
|
7ffa6b4fbd | ||
|
|
19203bb87d | ||
|
|
9792099cea | ||
|
|
c11d950fc5 | ||
|
|
53ec2a9268 | ||
|
|
a0fc8e252c | ||
|
|
632b67cbc8 | ||
|
|
b261242792 | ||
|
|
01ac54d65d | ||
|
|
0e57f4dd3c | ||
|
|
7339e447bb | ||
|
|
55a8be5cad | ||
|
|
3f62e854e5 | ||
|
|
697fcef98b | ||
|
|
e2c4e16d72 | ||
|
|
3355dc2a41 | ||
|
|
77b0c5f722 | ||
|
|
138133e11d | ||
|
|
582ed2c7f1 | ||
|
|
d934dda410 | ||
|
|
9d1fac19e5 | ||
|
|
bdb5cc8ad4 |
13
.github/workflows/patch.yml
vendored
13
.github/workflows/patch.yml
vendored
@@ -43,9 +43,11 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Setup Python
|
||||
uses: "gabrielfalcao/pyenv-action@v9"
|
||||
uses: "actions/setup-python@v4"
|
||||
with:
|
||||
versions: 3.10:latest, 3.7:latest
|
||||
python-version: |
|
||||
3.7
|
||||
3.10
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v2
|
||||
@@ -92,7 +94,6 @@ jobs:
|
||||
- name: Install
|
||||
run: |
|
||||
pip install frappe-bench
|
||||
pyenv global $(pyenv versions | grep '3.10')
|
||||
bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
|
||||
env:
|
||||
DB: mariadb
|
||||
@@ -107,7 +108,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"
|
||||
@@ -120,7 +120,7 @@ jobs:
|
||||
git -C "apps/erpnext" checkout -q -f $branch_name
|
||||
|
||||
rm -rf ~/frappe-bench/env
|
||||
bench setup env
|
||||
bench setup env --python python3.7
|
||||
bench pip install -e ./apps/payments
|
||||
bench pip install -e ./apps/erpnext
|
||||
|
||||
@@ -132,9 +132,8 @@ jobs:
|
||||
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 -v setup env --python python3.10
|
||||
bench pip install -e ./apps/payments
|
||||
bench pip install -e ./apps/erpnext
|
||||
|
||||
|
||||
38
.github/workflows/release_notes.yml
vendored
Normal file
38
.github/workflows/release_notes.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
# This action:
|
||||
#
|
||||
# 1. Generates release notes using github API.
|
||||
# 2. Strips unnecessary info like chore/style etc from notes.
|
||||
# 3. Updates release info.
|
||||
|
||||
# This action needs to be maintained on all branches that do releases.
|
||||
|
||||
name: 'Release Notes'
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag_name:
|
||||
description: 'Tag of release like v13.0.0'
|
||||
required: true
|
||||
type: string
|
||||
release:
|
||||
types: [released]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
regen-notes:
|
||||
name: 'Regenerate release notes'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Update notes
|
||||
run: |
|
||||
NEW_NOTES=$(gh api --method POST -H "Accept: application/vnd.github+json" /repos/frappe/erpnext/releases/generate-notes -f tag_name=$RELEASE_TAG | jq -r '.body' | sed -E '/^\* (chore|ci|test|docs|style)/d' )
|
||||
RELEASE_ID=$(gh api -H "Accept: application/vnd.github+json" /repos/frappe/erpnext/releases/tags/$RELEASE_TAG | jq -r '.id')
|
||||
gh api --method PATCH -H "Accept: application/vnd.github+json" /repos/frappe/erpnext/releases/$RELEASE_ID -f body="$NEW_NOTES"
|
||||
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
RELEASE_TAG: ${{ github.event.inputs.tag_name || github.event.release.tag_name }}
|
||||
@@ -3,7 +3,7 @@ import inspect
|
||||
|
||||
import frappe
|
||||
|
||||
__version__ = "14.27.4"
|
||||
__version__ = "14.32.0"
|
||||
|
||||
|
||||
def get_default_company(user=None):
|
||||
|
||||
@@ -4,18 +4,19 @@
|
||||
"creation": "2020-07-17 11:25:34.593061",
|
||||
"docstatus": 0,
|
||||
"doctype": "Dashboard Chart",
|
||||
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"frappe.sys_defaults.fiscal_year\",\"to_fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}",
|
||||
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"erpnext.utils.get_fiscal_year()\",\"to_fiscal_year\":\"erpnext.utils.get_fiscal_year()\"}",
|
||||
"filters_json": "{\"period\":\"Monthly\",\"budget_against\":\"Cost Center\",\"show_cumulative\":0}",
|
||||
"idx": 0,
|
||||
"is_public": 1,
|
||||
"is_standard": 1,
|
||||
"modified": "2020-07-22 12:24:49.144210",
|
||||
"modified": "2023-07-19 13:13:13.307073",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Budget Variance",
|
||||
"number_of_groups": 0,
|
||||
"owner": "Administrator",
|
||||
"report_name": "Budget Variance Report",
|
||||
"roles": [],
|
||||
"timeseries": 0,
|
||||
"type": "Bar",
|
||||
"use_report_chart": 1,
|
||||
|
||||
@@ -4,18 +4,19 @@
|
||||
"creation": "2020-07-17 11:25:34.448572",
|
||||
"docstatus": 0,
|
||||
"doctype": "Dashboard Chart",
|
||||
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"frappe.sys_defaults.fiscal_year\",\"to_fiscal_year\":\"frappe.sys_defaults.fiscal_year\"}",
|
||||
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"erpnext.utils.get_fiscal_year()\",\"to_fiscal_year\":\"erpnext.utils.get_fiscal_year()\"}",
|
||||
"filters_json": "{\"filter_based_on\":\"Fiscal Year\",\"period_start_date\":\"2020-04-01\",\"period_end_date\":\"2021-03-31\",\"periodicity\":\"Yearly\",\"include_default_book_entries\":1}",
|
||||
"idx": 0,
|
||||
"is_public": 1,
|
||||
"is_standard": 1,
|
||||
"modified": "2020-07-22 12:33:48.888943",
|
||||
"modified": "2023-07-19 13:08:56.470390",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Profit and Loss",
|
||||
"number_of_groups": 0,
|
||||
"owner": "Administrator",
|
||||
"report_name": "Profit and Loss Statement",
|
||||
"roles": [],
|
||||
"timeseries": 0,
|
||||
"type": "Bar",
|
||||
"use_report_chart": 1,
|
||||
|
||||
@@ -136,7 +136,7 @@ def convert_deferred_revenue_to_income(
|
||||
send_mail(deferred_process)
|
||||
|
||||
|
||||
def get_booking_dates(doc, item, posting_date=None):
|
||||
def get_booking_dates(doc, item, posting_date=None, prev_posting_date=None):
|
||||
if not posting_date:
|
||||
posting_date = add_days(today(), -1)
|
||||
|
||||
@@ -146,39 +146,42 @@ def get_booking_dates(doc, item, posting_date=None):
|
||||
"deferred_revenue_account" if doc.doctype == "Sales Invoice" else "deferred_expense_account"
|
||||
)
|
||||
|
||||
prev_gl_entry = frappe.db.sql(
|
||||
"""
|
||||
select name, posting_date from `tabGL Entry` where company=%s and account=%s and
|
||||
voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
|
||||
and is_cancelled = 0
|
||||
order by posting_date desc limit 1
|
||||
""",
|
||||
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
|
||||
as_dict=True,
|
||||
)
|
||||
if not prev_posting_date:
|
||||
prev_gl_entry = frappe.db.sql(
|
||||
"""
|
||||
select name, posting_date from `tabGL Entry` where company=%s and account=%s and
|
||||
voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
|
||||
and is_cancelled = 0
|
||||
order by posting_date desc limit 1
|
||||
""",
|
||||
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
prev_gl_via_je = frappe.db.sql(
|
||||
"""
|
||||
SELECT p.name, p.posting_date FROM `tabJournal Entry` p, `tabJournal Entry Account` c
|
||||
WHERE p.name = c.parent and p.company=%s and c.account=%s
|
||||
and c.reference_type=%s and c.reference_name=%s
|
||||
and c.reference_detail_no=%s and c.docstatus < 2 order by posting_date desc limit 1
|
||||
""",
|
||||
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
|
||||
as_dict=True,
|
||||
)
|
||||
prev_gl_via_je = frappe.db.sql(
|
||||
"""
|
||||
SELECT p.name, p.posting_date FROM `tabJournal Entry` p, `tabJournal Entry Account` c
|
||||
WHERE p.name = c.parent and p.company=%s and c.account=%s
|
||||
and c.reference_type=%s and c.reference_name=%s
|
||||
and c.reference_detail_no=%s and c.docstatus < 2 order by posting_date desc limit 1
|
||||
""",
|
||||
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
if prev_gl_via_je:
|
||||
if (not prev_gl_entry) or (
|
||||
prev_gl_entry and prev_gl_entry[0].posting_date < prev_gl_via_je[0].posting_date
|
||||
):
|
||||
prev_gl_entry = prev_gl_via_je
|
||||
if prev_gl_via_je:
|
||||
if (not prev_gl_entry) or (
|
||||
prev_gl_entry and prev_gl_entry[0].posting_date < prev_gl_via_je[0].posting_date
|
||||
):
|
||||
prev_gl_entry = prev_gl_via_je
|
||||
|
||||
if prev_gl_entry:
|
||||
start_date = getdate(add_days(prev_gl_entry[0].posting_date, 1))
|
||||
else:
|
||||
start_date = item.service_start_date
|
||||
|
||||
if prev_gl_entry:
|
||||
start_date = getdate(add_days(prev_gl_entry[0].posting_date, 1))
|
||||
else:
|
||||
start_date = item.service_start_date
|
||||
|
||||
start_date = getdate(add_days(prev_posting_date, 1))
|
||||
end_date = get_last_day(start_date)
|
||||
if end_date >= item.service_end_date:
|
||||
end_date = item.service_end_date
|
||||
@@ -341,9 +344,15 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
|
||||
accounts_frozen_upto = frappe.get_cached_value("Accounts Settings", "None", "acc_frozen_upto")
|
||||
|
||||
def _book_deferred_revenue_or_expense(
|
||||
item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on
|
||||
item,
|
||||
via_journal_entry,
|
||||
submit_journal_entry,
|
||||
book_deferred_entries_based_on,
|
||||
prev_posting_date=None,
|
||||
):
|
||||
start_date, end_date, last_gl_entry = get_booking_dates(doc, item, posting_date=posting_date)
|
||||
start_date, end_date, last_gl_entry = get_booking_dates(
|
||||
doc, item, posting_date=posting_date, prev_posting_date=prev_posting_date
|
||||
)
|
||||
if not (start_date and end_date):
|
||||
return
|
||||
|
||||
@@ -377,9 +386,12 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
|
||||
if not amount:
|
||||
return
|
||||
|
||||
gl_posting_date = end_date
|
||||
prev_posting_date = None
|
||||
# check if books nor frozen till endate:
|
||||
if accounts_frozen_upto and getdate(end_date) <= getdate(accounts_frozen_upto):
|
||||
end_date = get_last_day(add_days(accounts_frozen_upto, 1))
|
||||
gl_posting_date = get_last_day(add_days(accounts_frozen_upto, 1))
|
||||
prev_posting_date = end_date
|
||||
|
||||
if via_journal_entry:
|
||||
book_revenue_via_journal_entry(
|
||||
@@ -388,7 +400,7 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
|
||||
debit_account,
|
||||
amount,
|
||||
base_amount,
|
||||
end_date,
|
||||
gl_posting_date,
|
||||
project,
|
||||
account_currency,
|
||||
item.cost_center,
|
||||
@@ -404,7 +416,7 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
|
||||
against,
|
||||
amount,
|
||||
base_amount,
|
||||
end_date,
|
||||
gl_posting_date,
|
||||
project,
|
||||
account_currency,
|
||||
item.cost_center,
|
||||
@@ -418,7 +430,11 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
|
||||
|
||||
if getdate(end_date) < getdate(posting_date) and not last_gl_entry:
|
||||
_book_deferred_revenue_or_expense(
|
||||
item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on
|
||||
item,
|
||||
via_journal_entry,
|
||||
submit_journal_entry,
|
||||
book_deferred_entries_based_on,
|
||||
prev_posting_date,
|
||||
)
|
||||
|
||||
via_journal_entry = cint(
|
||||
|
||||
@@ -2,75 +2,13 @@
|
||||
"country_code": "nl",
|
||||
"name": "Netherlands - Grootboekschema",
|
||||
"tree": {
|
||||
"FABRIKAGEREKENINGEN": {
|
||||
"is_group": 1,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"FINANCIELE REKENINGEN, KORTLOPENDE VORDERINGEN EN SCHULDEN": {
|
||||
"Bank": {
|
||||
"RABO Bank": {
|
||||
"account_type": "Bank"
|
||||
},
|
||||
"account_type": "Bank"
|
||||
},
|
||||
"KORTLOPENDE SCHULDEN": {
|
||||
"Af te dragen Btw-verlegd": {
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"Afdracht loonheffing": {},
|
||||
"Btw af te dragen hoog": {
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"Btw af te dragen laag": {
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"Btw af te dragen overig": {
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"Btw oude jaren": {
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"Btw te vorderen hoog": {
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"Btw te vorderen laag": {
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"Btw te vorderen overig": {
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"Btw-afdracht": {
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"Crediteuren": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Dividend": {},
|
||||
"Dividendbelasting": {},
|
||||
"Energiekosten 1": {},
|
||||
"Investeringsaftrek": {},
|
||||
"Loonheffing": {},
|
||||
"Overige te betalen posten": {},
|
||||
"Pensioenpremies 1": {},
|
||||
"Premie WIR": {},
|
||||
"Rekening-courant inkoopvereniging": {},
|
||||
"Rente": {},
|
||||
"Sociale lasten 1": {},
|
||||
"Stock Recieved niet gefactureerd": {
|
||||
"account_type": "Stock Received But Not Billed"
|
||||
},
|
||||
"Tanti\u00e8mes 1": {},
|
||||
"Te vorderen Btw-verlegd": {
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"Telefoon/telefax 1": {},
|
||||
"Termijnen onderh. werk": {},
|
||||
"Vakantiedagen": {},
|
||||
"Vakantiegeld 1": {},
|
||||
"Vakantiezegels": {},
|
||||
"Vennootschapsbelasting": {},
|
||||
"Vooruit ontvangen bedr.": {}
|
||||
},
|
||||
},
|
||||
"LIQUIDE MIDDELEN": {
|
||||
"ABN-AMRO bank": {},
|
||||
"Bankbetaalkaarten": {},
|
||||
@@ -91,6 +29,110 @@
|
||||
},
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"TUSSENREKENINGEN": {
|
||||
"Betaalwijze cadeaubonnen": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Betaalwijze chipknip": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Betaalwijze contant": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Betaalwijze pin": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Inkopen Nederland hoog": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Inkopen Nederland laag": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Inkopen Nederland onbelast": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Inkopen Nederland overig": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Inkopen Nederland verlegd": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Inkopen binnen EU hoog": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Inkopen binnen EU laag": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Inkopen binnen EU overig": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Inkopen buiten EU hoog": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Inkopen buiten EU laag": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Inkopen buiten EU overig": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Kassa 1": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Kassa 2": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Netto lonen": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Tegenrekening Inkopen": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Tussenrek. autom. betalingen": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Tussenrek. autom. loonbetalingen": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Tussenrek. cadeaubonbetalingen": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Tussenrekening balans": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Tussenrekening chipknip": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Tussenrekening correcties": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Tussenrekening pin": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Vraagposten": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"VOORRAAD GRONDSTOFFEN, HULPMATERIALEN EN HANDELSGOEDEREN": {
|
||||
"Emballage": {},
|
||||
"Gereed product 1": {},
|
||||
"Gereed product 2": {},
|
||||
"Goederen 1": {},
|
||||
"Goederen 2": {},
|
||||
"Goederen in consignatie": {},
|
||||
"Goederen onderweg": {},
|
||||
"Grondstoffen 1": {},
|
||||
"Grondstoffen 2": {},
|
||||
"Halffabrikaten 1": {},
|
||||
"Halffabrikaten 2": {},
|
||||
"Hulpstoffen 1": {},
|
||||
"Hulpstoffen 2": {},
|
||||
"Kantoorbenodigdheden": {},
|
||||
"Onderhanden werk": {},
|
||||
"Verpakkingsmateriaal": {},
|
||||
"Zegels": {},
|
||||
"root_type": "Asset"
|
||||
},
|
||||
"root_type": "Asset"
|
||||
},
|
||||
"VORDERINGEN": {
|
||||
"Debiteuren": {
|
||||
"account_type": "Receivable"
|
||||
@@ -104,278 +146,299 @@
|
||||
"Voorziening dubieuze debiteuren": {}
|
||||
},
|
||||
"root_type": "Asset"
|
||||
},
|
||||
"INDIRECTE KOSTEN": {
|
||||
},
|
||||
"KORTLOPENDE SCHULDEN": {
|
||||
"Af te dragen Btw-verlegd": {
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"Afdracht loonheffing": {},
|
||||
"Btw af te dragen hoog": {
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"Btw af te dragen laag": {
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"Btw af te dragen overig": {
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"Btw oude jaren": {
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"Btw te vorderen hoog": {
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"Btw te vorderen laag": {
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"Btw te vorderen overig": {
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"Btw-afdracht": {
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"Crediteuren": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Dividend": {},
|
||||
"Dividendbelasting": {},
|
||||
"Energiekosten 1": {},
|
||||
"Investeringsaftrek": {},
|
||||
"Loonheffing": {},
|
||||
"Overige te betalen posten": {},
|
||||
"Pensioenpremies 1": {},
|
||||
"Premie WIR": {},
|
||||
"Rekening-courant inkoopvereniging": {},
|
||||
"Rente": {},
|
||||
"Sociale lasten 1": {},
|
||||
"Stock Recieved niet gefactureerd": {
|
||||
"account_type": "Stock Received But Not Billed"
|
||||
},
|
||||
"Tanti\u00e8mes 1": {},
|
||||
"Te vorderen Btw-verlegd": {
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"Telefoon/telefax 1": {},
|
||||
"Termijnen onderh. werk": {},
|
||||
"Vakantiedagen": {},
|
||||
"Vakantiegeld 1": {},
|
||||
"Vakantiezegels": {},
|
||||
"Vennootschapsbelasting": {},
|
||||
"Vooruit ontvangen bedr.": {},
|
||||
"is_group": 1,
|
||||
"root_type": "Liability"
|
||||
},
|
||||
"FABRIKAGEREKENINGEN": {
|
||||
"is_group": 1,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"KOSTENREKENINGEN": {
|
||||
"AFSCHRIJVINGEN": {
|
||||
"Aanhangwagens": {},
|
||||
"Aankoopkosten": {},
|
||||
"Aanloopkosten": {},
|
||||
"Auteursrechten": {},
|
||||
"Bedrijfsgebouwen": {},
|
||||
"Bedrijfsinventaris": {
|
||||
"root_type": "Expense",
|
||||
"INDIRECTE KOSTEN": {
|
||||
"is_group": 1,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"KOSTENREKENINGEN": {
|
||||
"AFSCHRIJVINGEN": {
|
||||
"Aanhangwagens": {},
|
||||
"Aankoopkosten": {},
|
||||
"Aanloopkosten": {},
|
||||
"Auteursrechten": {},
|
||||
"Bedrijfsgebouwen": {},
|
||||
"Bedrijfsinventaris": {
|
||||
"account_type": "Depreciation"
|
||||
},
|
||||
"Drankvergunningen": {},
|
||||
"Fabrieksinventaris": {
|
||||
"account_type": "Depreciation"
|
||||
},
|
||||
"Gebouwen": {},
|
||||
"Gereedschappen": {},
|
||||
"Goodwill": {},
|
||||
"Grondverbetering": {},
|
||||
"Heftrucks": {},
|
||||
"Kantine-inventaris": {},
|
||||
"Kantoorinventaris": {
|
||||
"account_type": "Depreciation"
|
||||
},
|
||||
"Kantoormachines": {},
|
||||
"Licenties": {},
|
||||
"Machines 1": {},
|
||||
"Magazijninventaris": {},
|
||||
"Octrooien": {},
|
||||
"Ontwikkelingskosten": {},
|
||||
"Pachtersinvestering": {},
|
||||
"Parkeerplaats": {},
|
||||
"Personenauto's": {
|
||||
"account_type": "Depreciation"
|
||||
},
|
||||
"Rijwielen en bromfietsen": {},
|
||||
"Tonnagevergunningen": {},
|
||||
"Verbouwingen": {},
|
||||
"Vergunningen": {},
|
||||
"Voorraadverschillen": {},
|
||||
"Vrachtauto's": {},
|
||||
"Winkels": {},
|
||||
"Woon-winkelhuis": {},
|
||||
"account_type": "Depreciation"
|
||||
},
|
||||
"Drankvergunningen": {},
|
||||
"Fabrieksinventaris": {
|
||||
"account_type": "Depreciation"
|
||||
"ALGEMENE KOSTEN": {
|
||||
"Accountantskosten": {},
|
||||
"Advieskosten": {},
|
||||
"Assuranties 1": {},
|
||||
"Bankkosten": {},
|
||||
"Juridische kosten": {},
|
||||
"Overige algemene kosten": {},
|
||||
"Toev. Ass. eigen risico": {}
|
||||
},
|
||||
"Gebouwen": {},
|
||||
"Gereedschappen": {},
|
||||
"Goodwill": {},
|
||||
"Grondverbetering": {},
|
||||
"Heftrucks": {},
|
||||
"Kantine-inventaris": {},
|
||||
"Kantoorinventaris": {
|
||||
"account_type": "Depreciation"
|
||||
"BEDRIJFSKOSTEN": {
|
||||
"Assuranties 2": {},
|
||||
"Energie (krachtstroom)": {},
|
||||
"Gereedschappen 1": {},
|
||||
"Hulpmaterialen 1": {},
|
||||
"Huur inventaris": {},
|
||||
"Huur machines": {},
|
||||
"Leasing invent.operational": {},
|
||||
"Leasing mach. operational": {},
|
||||
"Onderhoud inventaris": {},
|
||||
"Onderhoud machines": {},
|
||||
"Ophalen/vervoer afval": {},
|
||||
"Overige bedrijfskosten": {}
|
||||
},
|
||||
"Kantoormachines": {},
|
||||
"Licenties": {},
|
||||
"Machines 1": {},
|
||||
"Magazijninventaris": {},
|
||||
"Octrooien": {},
|
||||
"Ontwikkelingskosten": {},
|
||||
"Pachtersinvestering": {},
|
||||
"Parkeerplaats": {},
|
||||
"Personenauto's": {
|
||||
"account_type": "Depreciation"
|
||||
"FINANCIERINGSKOSTEN 1": {
|
||||
"Overige rentebaten": {},
|
||||
"Overige rentelasten": {},
|
||||
"Rente bankkrediet": {},
|
||||
"Rente huurkoopcontracten": {},
|
||||
"Rente hypotheek": {},
|
||||
"Rente leasecontracten": {},
|
||||
"Rente lening o/g": {},
|
||||
"Rente lening u/g": {}
|
||||
},
|
||||
"Rijwielen en bromfietsen": {},
|
||||
"Tonnagevergunningen": {},
|
||||
"Verbouwingen": {},
|
||||
"Vergunningen": {},
|
||||
"Voorraadverschillen": {},
|
||||
"Vrachtauto's": {},
|
||||
"Winkels": {},
|
||||
"Woon-winkelhuis": {},
|
||||
"account_type": "Depreciation"
|
||||
},
|
||||
"ALGEMENE KOSTEN": {
|
||||
"Accountantskosten": {},
|
||||
"Advieskosten": {},
|
||||
"Assuranties 1": {},
|
||||
"Bankkosten": {},
|
||||
"Juridische kosten": {},
|
||||
"Overige algemene kosten": {},
|
||||
"Toev. Ass. eigen risico": {}
|
||||
},
|
||||
"BEDRIJFSKOSTEN": {
|
||||
"Assuranties 2": {},
|
||||
"Energie (krachtstroom)": {},
|
||||
"Gereedschappen 1": {},
|
||||
"Hulpmaterialen 1": {},
|
||||
"Huur inventaris": {},
|
||||
"Huur machines": {},
|
||||
"Leasing invent.operational": {},
|
||||
"Leasing mach. operational": {},
|
||||
"Onderhoud inventaris": {},
|
||||
"Onderhoud machines": {},
|
||||
"Ophalen/vervoer afval": {},
|
||||
"Overige bedrijfskosten": {}
|
||||
},
|
||||
"FINANCIERINGSKOSTEN 1": {
|
||||
"Overige rentebaten": {},
|
||||
"Overige rentelasten": {},
|
||||
"Rente bankkrediet": {},
|
||||
"Rente huurkoopcontracten": {},
|
||||
"Rente hypotheek": {},
|
||||
"Rente leasecontracten": {},
|
||||
"Rente lening o/g": {},
|
||||
"Rente lening u/g": {}
|
||||
},
|
||||
"HUISVESTINGSKOSTEN": {
|
||||
"Assurantie onroerend goed": {},
|
||||
"Belastingen onr. Goed": {},
|
||||
"Energiekosten": {},
|
||||
"Groot onderhoud onr. Goed": {},
|
||||
"Huur": {},
|
||||
"Huurwaarde woongedeelte": {},
|
||||
"Onderhoud onroerend goed": {},
|
||||
"Ontvangen huren": {},
|
||||
"Overige huisvestingskosten": {},
|
||||
"Pacht": {},
|
||||
"Schoonmaakkosten": {},
|
||||
"Toevoeging egalisatieres. Groot onderhoud": {}
|
||||
},
|
||||
"KANTOORKOSTEN": {
|
||||
"Administratiekosten": {},
|
||||
"Contributies/abonnementen": {},
|
||||
"Huur kantoorapparatuur": {},
|
||||
"Internetaansluiting": {},
|
||||
"Kantoorbenodigdh./drukw.": {},
|
||||
"Onderhoud kantoorinvent.": {},
|
||||
"Overige kantoorkosten": {},
|
||||
"Porti": {},
|
||||
"Telefoon/telefax": {}
|
||||
},
|
||||
"OVERIGE BATEN EN LASTEN": {
|
||||
"Betaalde schadevergoed.": {},
|
||||
"Boekverlies vaste activa": {},
|
||||
"Boekwinst van vaste activa": {},
|
||||
"K.O. regeling OB": {},
|
||||
"Kasverschillen": {},
|
||||
"Kosten loonbelasting": {},
|
||||
"Kosten omzetbelasting": {},
|
||||
"Nadelige koersverschillen": {},
|
||||
"Naheffing bedrijfsver.": {},
|
||||
"Ontvangen schadevergoed.": {},
|
||||
"Overige baten": {},
|
||||
"Overige lasten": {},
|
||||
"Voordelige koersverschil.": {}
|
||||
},
|
||||
"PERSONEELSKOSTEN": {
|
||||
"Autokostenvergoeding": {},
|
||||
"Bedrijfskleding": {},
|
||||
"Belastingvrije uitkeringen": {},
|
||||
"Bijzondere beloningen": {},
|
||||
"Congressen, seminars en symposia": {},
|
||||
"Gereedschapsgeld": {},
|
||||
"Geschenken personeel": {},
|
||||
"Gratificaties": {},
|
||||
"Inhouding pensioenpremies": {},
|
||||
"Inhouding sociale lasten": {},
|
||||
"Kantinekosten": {},
|
||||
"Lonen en salarissen": {},
|
||||
"Loonwerk": {},
|
||||
"Managementvergoedingen": {},
|
||||
"Opleidingskosten": {},
|
||||
"Oprenting stamrechtverpl.": {},
|
||||
"Overhevelingstoeslag": {},
|
||||
"Overige kostenverg.": {},
|
||||
"Overige personeelskosten": {},
|
||||
"Overige uitkeringen": {},
|
||||
"Pensioenpremies": {},
|
||||
"Provisie 1": {},
|
||||
"Reiskosten": {},
|
||||
"Rijwielvergoeding": {},
|
||||
"Sociale lasten": {},
|
||||
"Tanti\u00e8mes": {},
|
||||
"Thuiswerkers": {},
|
||||
"Toev. Backservice pens.verpl.": {},
|
||||
"Toevoeging pensioenverpl.": {},
|
||||
"Uitkering ziekengeld": {},
|
||||
"Uitzendkrachten": {},
|
||||
"Vakantiebonnen": {},
|
||||
"Vakantiegeld": {},
|
||||
"Vergoeding studiekosten": {},
|
||||
"Wervingskosten personeel": {}
|
||||
},
|
||||
"VERKOOPKOSTEN": {
|
||||
"Advertenties": {},
|
||||
"Afschrijving dubieuze deb.": {},
|
||||
"Beurskosten": {},
|
||||
"Etalagekosten": {},
|
||||
"Exportkosten": {},
|
||||
"Kascorrecties": {},
|
||||
"Overige verkoopkosten": {},
|
||||
"Provisie": {},
|
||||
"Reclame": {},
|
||||
"Reis en verblijfkosten": {},
|
||||
"Relatiegeschenken": {},
|
||||
"Representatiekosten": {},
|
||||
"Uitgaande vrachten": {},
|
||||
"Veilingkosten": {},
|
||||
"Verpakkingsmateriaal 1": {},
|
||||
"Websitekosten": {}
|
||||
},
|
||||
"VERVOERSKOSTEN": {
|
||||
"Assuranties auto's": {},
|
||||
"Brandstoffen": {},
|
||||
"Leasing auto's": {},
|
||||
"Onderhoud personenauto's": {},
|
||||
"Onderhoud vrachtauto's": {},
|
||||
"Overige vervoerskosten": {},
|
||||
"Priv\u00e9-gebruik auto's": {},
|
||||
"Wegenbelasting": {}
|
||||
},
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"TUSSENREKENINGEN": {
|
||||
"Betaalwijze cadeaubonnen": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Betaalwijze chipknip": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Betaalwijze contant": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Betaalwijze pin": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Inkopen Nederland hoog": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Inkopen Nederland laag": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Inkopen Nederland onbelast": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Inkopen Nederland overig": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Inkopen Nederland verlegd": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Inkopen binnen EU hoog": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Inkopen binnen EU laag": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Inkopen binnen EU overig": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Inkopen buiten EU hoog": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Inkopen buiten EU laag": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Inkopen buiten EU overig": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Kassa 1": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Kassa 2": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Netto lonen": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Tegenrekening Inkopen": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Tussenrek. autom. betalingen": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Tussenrek. autom. loonbetalingen": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Tussenrek. cadeaubonbetalingen": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Tussenrekening balans": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Tussenrekening chipknip": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Tussenrekening correcties": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Tussenrekening pin": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Vraagposten": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"root_type": "Asset"
|
||||
"HUISVESTINGSKOSTEN": {
|
||||
"Assurantie onroerend goed": {},
|
||||
"Belastingen onr. Goed": {},
|
||||
"Energiekosten": {},
|
||||
"Groot onderhoud onr. Goed": {},
|
||||
"Huur": {},
|
||||
"Huurwaarde woongedeelte": {},
|
||||
"Onderhoud onroerend goed": {},
|
||||
"Ontvangen huren": {},
|
||||
"Overige huisvestingskosten": {},
|
||||
"Pacht": {},
|
||||
"Schoonmaakkosten": {},
|
||||
"Toevoeging egalisatieres. Groot onderhoud": {}
|
||||
},
|
||||
"KANTOORKOSTEN": {
|
||||
"Administratiekosten": {},
|
||||
"Contributies/abonnementen": {},
|
||||
"Huur kantoorapparatuur": {},
|
||||
"Internetaansluiting": {},
|
||||
"Kantoorbenodigdh./drukw.": {},
|
||||
"Onderhoud kantoorinvent.": {},
|
||||
"Overige kantoorkosten": {},
|
||||
"Porti": {},
|
||||
"Telefoon/telefax": {}
|
||||
},
|
||||
"OVERIGE BATEN EN LASTEN": {
|
||||
"Betaalde schadevergoed.": {},
|
||||
"Boekverlies vaste activa": {},
|
||||
"Boekwinst van vaste activa": {},
|
||||
"K.O. regeling OB": {},
|
||||
"Kasverschillen": {},
|
||||
"Kosten loonbelasting": {},
|
||||
"Kosten omzetbelasting": {},
|
||||
"Nadelige koersverschillen": {},
|
||||
"Naheffing bedrijfsver.": {},
|
||||
"Ontvangen schadevergoed.": {},
|
||||
"Overige baten": {},
|
||||
"Overige lasten": {},
|
||||
"Voordelige koersverschil.": {}
|
||||
},
|
||||
"PERSONEELSKOSTEN": {
|
||||
"Autokostenvergoeding": {},
|
||||
"Bedrijfskleding": {},
|
||||
"Belastingvrije uitkeringen": {},
|
||||
"Bijzondere beloningen": {},
|
||||
"Congressen, seminars en symposia": {},
|
||||
"Gereedschapsgeld": {},
|
||||
"Geschenken personeel": {},
|
||||
"Gratificaties": {},
|
||||
"Inhouding pensioenpremies": {},
|
||||
"Inhouding sociale lasten": {},
|
||||
"Kantinekosten": {},
|
||||
"Lonen en salarissen": {},
|
||||
"Loonwerk": {},
|
||||
"Managementvergoedingen": {},
|
||||
"Opleidingskosten": {},
|
||||
"Oprenting stamrechtverpl.": {},
|
||||
"Overhevelingstoeslag": {},
|
||||
"Overige kostenverg.": {},
|
||||
"Overige personeelskosten": {},
|
||||
"Overige uitkeringen": {},
|
||||
"Pensioenpremies": {},
|
||||
"Provisie 1": {},
|
||||
"Reiskosten": {},
|
||||
"Rijwielvergoeding": {},
|
||||
"Sociale lasten": {},
|
||||
"Tanti\u00e8mes": {},
|
||||
"Thuiswerkers": {},
|
||||
"Toev. Backservice pens.verpl.": {},
|
||||
"Toevoeging pensioenverpl.": {},
|
||||
"Uitkering ziekengeld": {},
|
||||
"Uitzendkrachten": {},
|
||||
"Vakantiebonnen": {},
|
||||
"Vakantiegeld": {},
|
||||
"Vergoeding studiekosten": {},
|
||||
"Wervingskosten personeel": {}
|
||||
},
|
||||
"VERKOOPKOSTEN": {
|
||||
"Advertenties": {},
|
||||
"Afschrijving dubieuze deb.": {},
|
||||
"Beurskosten": {},
|
||||
"Etalagekosten": {},
|
||||
"Exportkosten": {},
|
||||
"Kascorrecties": {},
|
||||
"Overige verkoopkosten": {},
|
||||
"Provisie": {},
|
||||
"Reclame": {},
|
||||
"Reis en verblijfkosten": {},
|
||||
"Relatiegeschenken": {},
|
||||
"Representatiekosten": {},
|
||||
"Uitgaande vrachten": {},
|
||||
"Veilingkosten": {},
|
||||
"Verpakkingsmateriaal 1": {},
|
||||
"Websitekosten": {}
|
||||
},
|
||||
"VERVOERSKOSTEN": {
|
||||
"Assuranties auto's": {},
|
||||
"Brandstoffen": {},
|
||||
"Leasing auto's": {},
|
||||
"Onderhoud personenauto's": {},
|
||||
"Onderhoud vrachtauto's": {},
|
||||
"Overige vervoerskosten": {},
|
||||
"Priv\u00e9-gebruik auto's": {},
|
||||
"Wegenbelasting": {}
|
||||
},
|
||||
"VOORRAAD GEREED PRODUCT EN ONDERHANDEN WERK": {
|
||||
"Betalingskort. crediteuren": {},
|
||||
"Garantiekosten": {},
|
||||
"Hulpmaterialen": {},
|
||||
"Inkomende vrachten": {
|
||||
"account_type": "Expenses Included In Valuation"
|
||||
},
|
||||
"Inkoop import buiten EU hoog": {},
|
||||
"Inkoop import buiten EU laag": {},
|
||||
"Inkoop import buiten EU overig": {},
|
||||
"Inkoopbonussen": {},
|
||||
"Inkoopkosten": {},
|
||||
"Inkoopprovisie": {},
|
||||
"Inkopen BTW verlegd": {},
|
||||
"Inkopen EU hoog tarief": {},
|
||||
"Inkopen EU laag tarief": {},
|
||||
"Inkopen EU overig": {},
|
||||
"Inkopen hoog": {},
|
||||
"Inkopen laag": {},
|
||||
"Inkopen nul": {},
|
||||
"Inkopen overig": {},
|
||||
"Invoerkosten": {},
|
||||
"Kosten inkoopvereniging": {},
|
||||
"Kostprijs omzet grondstoffen": {
|
||||
"account_type": "Cost of Goods Sold"
|
||||
},
|
||||
"Kostprijs omzet handelsgoederen": {},
|
||||
"Onttrekking uitgev.garantie": {},
|
||||
"Priv\u00e9-gebruik goederen": {},
|
||||
"Stock aanpassing": {
|
||||
"account_type": "Stock Adjustment"
|
||||
},
|
||||
"Tegenrekening inkoop": {},
|
||||
"Toev. Voorz. incour. grondst.": {},
|
||||
"Toevoeging garantieverpl.": {},
|
||||
"Toevoeging voorz. incour. handelsgoed.": {},
|
||||
"Uitbesteed werk": {},
|
||||
"Voorz. Incourourant grondst.": {},
|
||||
"Voorz.incour. handelsgoed.": {},
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"root_type": "Expense"
|
||||
}
|
||||
},
|
||||
"VASTE ACTIVA, EIGEN VERMOGEN, LANGLOPEND VREEMD VERMOGEN EN VOORZIENINGEN": {
|
||||
"EIGEN VERMOGEN": {
|
||||
@@ -602,7 +665,7 @@
|
||||
"account_type": "Equity"
|
||||
}
|
||||
},
|
||||
"root_type": "Asset"
|
||||
"root_type": "Equity"
|
||||
},
|
||||
"VERKOOPRESULTATEN": {
|
||||
"Diensten fabric. 0% niet-EU": {},
|
||||
@@ -627,67 +690,6 @@
|
||||
"Verleende Kredietbep. fabricage": {},
|
||||
"Verleende Kredietbep. handel": {},
|
||||
"root_type": "Income"
|
||||
},
|
||||
"VOORRAAD GEREED PRODUCT EN ONDERHANDEN WERK": {
|
||||
"Betalingskort. crediteuren": {},
|
||||
"Garantiekosten": {},
|
||||
"Hulpmaterialen": {},
|
||||
"Inkomende vrachten": {
|
||||
"account_type": "Expenses Included In Valuation"
|
||||
},
|
||||
"Inkoop import buiten EU hoog": {},
|
||||
"Inkoop import buiten EU laag": {},
|
||||
"Inkoop import buiten EU overig": {},
|
||||
"Inkoopbonussen": {},
|
||||
"Inkoopkosten": {},
|
||||
"Inkoopprovisie": {},
|
||||
"Inkopen BTW verlegd": {},
|
||||
"Inkopen EU hoog tarief": {},
|
||||
"Inkopen EU laag tarief": {},
|
||||
"Inkopen EU overig": {},
|
||||
"Inkopen hoog": {},
|
||||
"Inkopen laag": {},
|
||||
"Inkopen nul": {},
|
||||
"Inkopen overig": {},
|
||||
"Invoerkosten": {},
|
||||
"Kosten inkoopvereniging": {},
|
||||
"Kostprijs omzet grondstoffen": {
|
||||
"account_type": "Cost of Goods Sold"
|
||||
},
|
||||
"Kostprijs omzet handelsgoederen": {},
|
||||
"Onttrekking uitgev.garantie": {},
|
||||
"Priv\u00e9-gebruik goederen": {},
|
||||
"Stock aanpassing": {
|
||||
"account_type": "Stock Adjustment"
|
||||
},
|
||||
"Tegenrekening inkoop": {},
|
||||
"Toev. Voorz. incour. grondst.": {},
|
||||
"Toevoeging garantieverpl.": {},
|
||||
"Toevoeging voorz. incour. handelsgoed.": {},
|
||||
"Uitbesteed werk": {},
|
||||
"Voorz. Incourourant grondst.": {},
|
||||
"Voorz.incour. handelsgoed.": {},
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"VOORRAAD GRONDSTOFFEN, HULPMATERIALEN EN HANDELSGOEDEREN": {
|
||||
"Emballage": {},
|
||||
"Gereed product 1": {},
|
||||
"Gereed product 2": {},
|
||||
"Goederen 1": {},
|
||||
"Goederen 2": {},
|
||||
"Goederen in consignatie": {},
|
||||
"Goederen onderweg": {},
|
||||
"Grondstoffen 1": {},
|
||||
"Grondstoffen 2": {},
|
||||
"Halffabrikaten 1": {},
|
||||
"Halffabrikaten 2": {},
|
||||
"Hulpstoffen 1": {},
|
||||
"Hulpstoffen 2": {},
|
||||
"Kantoorbenodigdheden": {},
|
||||
"Onderhanden werk": {},
|
||||
"Verpakkingsmateriaal": {},
|
||||
"Zegels": {},
|
||||
"root_type": "Asset"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Account Closing Balance", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@@ -0,0 +1,164 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2023-02-21 15:20:59.586811",
|
||||
"default_view": "List",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"closing_date",
|
||||
"account",
|
||||
"cost_center",
|
||||
"debit",
|
||||
"credit",
|
||||
"account_currency",
|
||||
"debit_in_account_currency",
|
||||
"credit_in_account_currency",
|
||||
"project",
|
||||
"company",
|
||||
"finance_book",
|
||||
"period_closing_voucher",
|
||||
"is_period_closing_voucher_entry"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "closing_date",
|
||||
"fieldtype": "Date",
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Closing Date",
|
||||
"oldfieldname": "posting_date",
|
||||
"oldfieldtype": "Date",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "account",
|
||||
"fieldtype": "Link",
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Account",
|
||||
"oldfieldname": "account",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Account",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Cost Center",
|
||||
"oldfieldname": "cost_center",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Cost Center"
|
||||
},
|
||||
{
|
||||
"fieldname": "debit",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Debit Amount",
|
||||
"oldfieldname": "debit",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "credit",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Credit Amount",
|
||||
"oldfieldname": "credit",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "account_currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Account Currency",
|
||||
"options": "Currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "debit_in_account_currency",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Debit Amount in Account Currency",
|
||||
"options": "account_currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "credit_in_account_currency",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Credit Amount in Account Currency",
|
||||
"options": "account_currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Company",
|
||||
"oldfieldname": "company",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Company",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "finance_book",
|
||||
"fieldtype": "Link",
|
||||
"label": "Finance Book",
|
||||
"options": "Finance Book"
|
||||
},
|
||||
{
|
||||
"fieldname": "period_closing_voucher",
|
||||
"fieldtype": "Link",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Period Closing Voucher",
|
||||
"options": "Period Closing Voucher",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_period_closing_voucher_entry",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Period Closing Voucher Entry"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-list",
|
||||
"in_create": 1,
|
||||
"links": [],
|
||||
"modified": "2023-03-06 08:56:36.393237",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Account Closing Balance",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User"
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager"
|
||||
},
|
||||
{
|
||||
"export": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Auditor"
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cint, cstr
|
||||
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
get_accounting_dimensions,
|
||||
)
|
||||
|
||||
|
||||
class AccountClosingBalance(Document):
|
||||
pass
|
||||
|
||||
|
||||
def make_closing_entries(closing_entries, voucher_name, company, closing_date):
|
||||
accounting_dimensions = get_accounting_dimensions()
|
||||
|
||||
previous_closing_entries = get_previous_closing_entries(
|
||||
company, closing_date, accounting_dimensions
|
||||
)
|
||||
combined_entries = closing_entries + previous_closing_entries
|
||||
|
||||
merged_entries = aggregate_with_last_account_closing_balance(
|
||||
combined_entries, accounting_dimensions
|
||||
)
|
||||
|
||||
for key, value in merged_entries.items():
|
||||
cle = frappe.new_doc("Account Closing Balance")
|
||||
cle.update(value)
|
||||
cle.update(value["dimensions"])
|
||||
cle.update(
|
||||
{
|
||||
"period_closing_voucher": voucher_name,
|
||||
"closing_date": closing_date,
|
||||
}
|
||||
)
|
||||
cle.flags.ignore_permissions = True
|
||||
cle.submit()
|
||||
|
||||
|
||||
def aggregate_with_last_account_closing_balance(entries, accounting_dimensions):
|
||||
merged_entries = {}
|
||||
for entry in entries:
|
||||
key, key_values = generate_key(entry, accounting_dimensions)
|
||||
merged_entries.setdefault(
|
||||
key,
|
||||
{
|
||||
"debit": 0,
|
||||
"credit": 0,
|
||||
"debit_in_account_currency": 0,
|
||||
"credit_in_account_currency": 0,
|
||||
},
|
||||
)
|
||||
|
||||
merged_entries[key]["dimensions"] = key_values
|
||||
merged_entries[key]["debit"] += entry.get("debit")
|
||||
merged_entries[key]["credit"] += entry.get("credit")
|
||||
merged_entries[key]["debit_in_account_currency"] += entry.get("debit_in_account_currency")
|
||||
merged_entries[key]["credit_in_account_currency"] += entry.get("credit_in_account_currency")
|
||||
|
||||
return merged_entries
|
||||
|
||||
|
||||
def generate_key(entry, accounting_dimensions):
|
||||
key = [
|
||||
cstr(entry.get("account")),
|
||||
cstr(entry.get("account_currency")),
|
||||
cstr(entry.get("cost_center")),
|
||||
cstr(entry.get("project")),
|
||||
cstr(entry.get("finance_book")),
|
||||
cint(entry.get("is_period_closing_voucher_entry")),
|
||||
]
|
||||
|
||||
key_values = {
|
||||
"company": cstr(entry.get("company")),
|
||||
"account": cstr(entry.get("account")),
|
||||
"account_currency": cstr(entry.get("account_currency")),
|
||||
"cost_center": cstr(entry.get("cost_center")),
|
||||
"project": cstr(entry.get("project")),
|
||||
"finance_book": cstr(entry.get("finance_book")),
|
||||
"is_period_closing_voucher_entry": cint(entry.get("is_period_closing_voucher_entry")),
|
||||
}
|
||||
for dimension in accounting_dimensions:
|
||||
key.append(cstr(entry.get(dimension)))
|
||||
key_values[dimension] = cstr(entry.get(dimension))
|
||||
|
||||
return tuple(key), key_values
|
||||
|
||||
|
||||
def get_previous_closing_entries(company, closing_date, accounting_dimensions):
|
||||
entries = []
|
||||
last_period_closing_voucher = frappe.db.get_all(
|
||||
"Period Closing Voucher",
|
||||
filters={"docstatus": 1, "company": company, "posting_date": ("<", closing_date)},
|
||||
fields=["name"],
|
||||
order_by="posting_date desc",
|
||||
limit=1,
|
||||
)
|
||||
|
||||
if last_period_closing_voucher:
|
||||
account_closing_balance = frappe.qb.DocType("Account Closing Balance")
|
||||
query = frappe.qb.from_(account_closing_balance).select(
|
||||
account_closing_balance.company,
|
||||
account_closing_balance.account,
|
||||
account_closing_balance.account_currency,
|
||||
account_closing_balance.debit,
|
||||
account_closing_balance.credit,
|
||||
account_closing_balance.debit_in_account_currency,
|
||||
account_closing_balance.credit_in_account_currency,
|
||||
account_closing_balance.cost_center,
|
||||
account_closing_balance.project,
|
||||
account_closing_balance.finance_book,
|
||||
account_closing_balance.is_period_closing_voucher_entry,
|
||||
)
|
||||
|
||||
for dimension in accounting_dimensions:
|
||||
query = query.select(account_closing_balance[dimension])
|
||||
|
||||
query = query.where(
|
||||
account_closing_balance.period_closing_voucher == last_period_closing_voucher[0].name
|
||||
)
|
||||
entries = query.run(as_dict=1)
|
||||
|
||||
return entries
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
|
||||
class TestAccountClosingBalance(FrappeTestCase):
|
||||
pass
|
||||
@@ -271,6 +271,12 @@ def get_dimensions(with_cost_center_and_project=False):
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
if isinstance(with_cost_center_and_project, str):
|
||||
if with_cost_center_and_project.lower().strip() == "true":
|
||||
with_cost_center_and_project = True
|
||||
else:
|
||||
with_cost_center_and_project = False
|
||||
|
||||
if with_cost_center_and_project:
|
||||
dimension_filters.extend(
|
||||
[
|
||||
|
||||
@@ -20,5 +20,11 @@ frappe.ui.form.on('Accounting Period', {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
frm.set_query("document_type", "closed_documents", () => {
|
||||
return {
|
||||
query: "erpnext.controllers.queries.get_doctypes_for_closing",
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -11,6 +11,10 @@ class OverlapError(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
class ClosedAccountingPeriod(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
class AccountingPeriod(Document):
|
||||
def validate(self):
|
||||
self.validate_overlap()
|
||||
@@ -65,3 +69,42 @@ class AccountingPeriod(Document):
|
||||
"closed_documents",
|
||||
{"document_type": doctype_for_closing.document_type, "closed": doctype_for_closing.closed},
|
||||
)
|
||||
|
||||
|
||||
def validate_accounting_period_on_doc_save(doc, method=None):
|
||||
if doc.doctype == "Bank Clearance":
|
||||
return
|
||||
elif doc.doctype == "Asset":
|
||||
if doc.is_existing_asset:
|
||||
return
|
||||
else:
|
||||
date = doc.available_for_use_date
|
||||
elif doc.doctype == "Asset Repair":
|
||||
date = doc.completion_date
|
||||
else:
|
||||
date = doc.posting_date
|
||||
|
||||
ap = frappe.qb.DocType("Accounting Period")
|
||||
cd = frappe.qb.DocType("Closed Document")
|
||||
|
||||
accounting_period = (
|
||||
frappe.qb.from_(ap)
|
||||
.from_(cd)
|
||||
.select(ap.name)
|
||||
.where(
|
||||
(ap.name == cd.parent)
|
||||
& (ap.company == doc.company)
|
||||
& (cd.closed == 1)
|
||||
& (cd.document_type == doc.doctype)
|
||||
& (date >= ap.start_date)
|
||||
& (date <= ap.end_date)
|
||||
)
|
||||
).run(as_dict=1)
|
||||
|
||||
if accounting_period:
|
||||
frappe.throw(
|
||||
_("You cannot create a {0} within the closed Accounting Period {1}").format(
|
||||
doc.doctype, frappe.bold(accounting_period[0]["name"])
|
||||
),
|
||||
ClosedAccountingPeriod,
|
||||
)
|
||||
|
||||
@@ -6,9 +6,11 @@ import unittest
|
||||
import frappe
|
||||
from frappe.utils import add_months, nowdate
|
||||
|
||||
from erpnext.accounts.doctype.accounting_period.accounting_period import OverlapError
|
||||
from erpnext.accounts.doctype.accounting_period.accounting_period import (
|
||||
ClosedAccountingPeriod,
|
||||
OverlapError,
|
||||
)
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.accounts.general_ledger import ClosedAccountingPeriod
|
||||
|
||||
test_dependencies = ["Item"]
|
||||
|
||||
@@ -33,9 +35,9 @@ class TestAccountingPeriod(unittest.TestCase):
|
||||
ap1.save()
|
||||
|
||||
doc = create_sales_invoice(
|
||||
do_not_submit=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC"
|
||||
do_not_save=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC"
|
||||
)
|
||||
self.assertRaises(ClosedAccountingPeriod, doc.submit)
|
||||
self.assertRaises(ClosedAccountingPeriod, doc.save)
|
||||
|
||||
def tearDown(self):
|
||||
for d in frappe.get_all("Accounting Period"):
|
||||
|
||||
@@ -62,7 +62,10 @@
|
||||
"acc_frozen_upto",
|
||||
"column_break_25",
|
||||
"frozen_accounts_modifier",
|
||||
"report_settings_sb"
|
||||
"report_settings_sb",
|
||||
"banking_tab",
|
||||
"enable_party_matching",
|
||||
"enable_fuzzy_matching"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -385,6 +388,26 @@
|
||||
"fieldname": "show_taxes_as_table_in_print",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Taxes as Table in Print"
|
||||
},
|
||||
{
|
||||
"fieldname": "banking_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Banking"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Auto match and set the Party in Bank Transactions",
|
||||
"fieldname": "enable_party_matching",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Automatic Party Matching"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "enable_party_matching",
|
||||
"description": "Approximately match the description/party name against parties",
|
||||
"fieldname": "enable_fuzzy_matching",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Fuzzy Matching"
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
@@ -392,7 +415,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-06-13 18:47:46.430291",
|
||||
"modified": "2023-06-15 18:47:46.430291",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
|
||||
@@ -19,7 +19,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
|
||||
|
||||
onload: function (frm) {
|
||||
// Set default filter dates
|
||||
today = frappe.datetime.get_today()
|
||||
let today = frappe.datetime.get_today()
|
||||
frm.doc.bank_statement_from_date = frappe.datetime.add_months(today, -1);
|
||||
frm.doc.bank_statement_to_date = today;
|
||||
frm.trigger('bank_account');
|
||||
|
||||
@@ -10,6 +10,7 @@ from frappe.model.document import Document
|
||||
from frappe.query_builder.custom import ConstantColumn
|
||||
from frappe.utils import cint, flt
|
||||
|
||||
from erpnext import get_default_cost_center
|
||||
from erpnext.accounts.doctype.bank_transaction.bank_transaction import get_total_allocated_amount
|
||||
from erpnext.accounts.report.bank_reconciliation_statement.bank_reconciliation_statement import (
|
||||
get_amounts_not_reflected_in_system,
|
||||
@@ -140,6 +141,9 @@ def create_journal_entry_bts(
|
||||
second_account
|
||||
)
|
||||
)
|
||||
|
||||
company = frappe.get_value("Account", company_account, "company")
|
||||
|
||||
accounts = []
|
||||
# Multi Currency?
|
||||
accounts.append(
|
||||
@@ -149,6 +153,7 @@ def create_journal_entry_bts(
|
||||
"debit_in_account_currency": bank_transaction.withdrawal,
|
||||
"party_type": party_type,
|
||||
"party": party,
|
||||
"cost_center": get_default_cost_center(company),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -158,11 +163,10 @@ def create_journal_entry_bts(
|
||||
"bank_account": bank_transaction.bank_account,
|
||||
"credit_in_account_currency": bank_transaction.withdrawal,
|
||||
"debit_in_account_currency": bank_transaction.deposit,
|
||||
"cost_center": get_default_cost_center(company),
|
||||
}
|
||||
)
|
||||
|
||||
company = frappe.get_value("Account", company_account, "company")
|
||||
|
||||
journal_entry_dict = {
|
||||
"voucher_type": entry_type,
|
||||
"company": company,
|
||||
|
||||
178
erpnext/accounts/doctype/bank_transaction/auto_match_party.py
Normal file
178
erpnext/accounts/doctype/bank_transaction/auto_match_party.py
Normal file
@@ -0,0 +1,178 @@
|
||||
from typing import Tuple, Union
|
||||
|
||||
import frappe
|
||||
from frappe.utils import flt
|
||||
from rapidfuzz import fuzz, process
|
||||
|
||||
|
||||
class AutoMatchParty:
|
||||
"""
|
||||
Matches by Account/IBAN and then by Party Name/Description sequentially.
|
||||
Returns when a result is obtained.
|
||||
|
||||
Result (if present) is of the form: (Party Type, Party,)
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs) -> None:
|
||||
self.__dict__.update(kwargs)
|
||||
|
||||
def get(self, key):
|
||||
return self.__dict__.get(key, None)
|
||||
|
||||
def match(self) -> Union[Tuple, None]:
|
||||
result = None
|
||||
result = AutoMatchbyAccountIBAN(
|
||||
bank_party_account_number=self.bank_party_account_number,
|
||||
bank_party_iban=self.bank_party_iban,
|
||||
deposit=self.deposit,
|
||||
).match()
|
||||
|
||||
fuzzy_matching_enabled = frappe.db.get_single_value("Accounts Settings", "enable_fuzzy_matching")
|
||||
if not result and fuzzy_matching_enabled:
|
||||
result = AutoMatchbyPartyNameDescription(
|
||||
bank_party_name=self.bank_party_name, description=self.description, deposit=self.deposit
|
||||
).match()
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class AutoMatchbyAccountIBAN:
|
||||
def __init__(self, **kwargs) -> None:
|
||||
self.__dict__.update(kwargs)
|
||||
|
||||
def get(self, key):
|
||||
return self.__dict__.get(key, None)
|
||||
|
||||
def match(self):
|
||||
if not (self.bank_party_account_number or self.bank_party_iban):
|
||||
return None
|
||||
|
||||
result = self.match_account_in_party()
|
||||
return result
|
||||
|
||||
def match_account_in_party(self) -> Union[Tuple, None]:
|
||||
"""Check if there is a IBAN/Account No. match in Customer/Supplier/Employee"""
|
||||
result = None
|
||||
parties = get_parties_in_order(self.deposit)
|
||||
or_filters = self.get_or_filters()
|
||||
|
||||
for party in parties:
|
||||
party_result = frappe.db.get_all(
|
||||
"Bank Account", or_filters=or_filters, pluck="party", limit_page_length=1
|
||||
)
|
||||
|
||||
if party == "Employee" and not party_result:
|
||||
# Search in Bank Accounts first for Employee, and then Employee record
|
||||
if "bank_account_no" in or_filters:
|
||||
or_filters["bank_ac_no"] = or_filters.pop("bank_account_no")
|
||||
|
||||
party_result = frappe.db.get_all(
|
||||
party, or_filters=or_filters, pluck="name", limit_page_length=1
|
||||
)
|
||||
|
||||
if party_result:
|
||||
result = (
|
||||
party,
|
||||
party_result[0],
|
||||
)
|
||||
break
|
||||
|
||||
return result
|
||||
|
||||
def get_or_filters(self) -> dict:
|
||||
or_filters = {}
|
||||
if self.bank_party_account_number:
|
||||
or_filters["bank_account_no"] = self.bank_party_account_number
|
||||
|
||||
if self.bank_party_iban:
|
||||
or_filters["iban"] = self.bank_party_iban
|
||||
|
||||
return or_filters
|
||||
|
||||
|
||||
class AutoMatchbyPartyNameDescription:
|
||||
def __init__(self, **kwargs) -> None:
|
||||
self.__dict__.update(kwargs)
|
||||
|
||||
def get(self, key):
|
||||
return self.__dict__.get(key, None)
|
||||
|
||||
def match(self) -> Union[Tuple, None]:
|
||||
# fuzzy search by customer/supplier & employee
|
||||
if not (self.bank_party_name or self.description):
|
||||
return None
|
||||
|
||||
result = self.match_party_name_desc_in_party()
|
||||
return result
|
||||
|
||||
def match_party_name_desc_in_party(self) -> Union[Tuple, None]:
|
||||
"""Fuzzy search party name and/or description against parties in the system"""
|
||||
result = None
|
||||
parties = get_parties_in_order(self.deposit)
|
||||
|
||||
for party in parties:
|
||||
filters = {"status": "Active"} if party == "Employee" else {"disabled": 0}
|
||||
names = frappe.get_all(party, filters=filters, pluck=party.lower() + "_name")
|
||||
|
||||
for field in ["bank_party_name", "description"]:
|
||||
if not self.get(field):
|
||||
continue
|
||||
|
||||
result, skip = self.fuzzy_search_and_return_result(party, names, field)
|
||||
if result or skip:
|
||||
break
|
||||
|
||||
if result or skip:
|
||||
# Skip If: It was hard to distinguish between close matches and so match is None
|
||||
# OR if the right match was found
|
||||
break
|
||||
|
||||
return result
|
||||
|
||||
def fuzzy_search_and_return_result(self, party, names, field) -> Union[Tuple, None]:
|
||||
skip = False
|
||||
result = process.extract(query=self.get(field), choices=names, scorer=fuzz.token_set_ratio)
|
||||
party_name, skip = self.process_fuzzy_result(result)
|
||||
|
||||
if not party_name:
|
||||
return None, skip
|
||||
|
||||
return (
|
||||
party,
|
||||
party_name,
|
||||
), skip
|
||||
|
||||
def process_fuzzy_result(self, result: Union[list, None]):
|
||||
"""
|
||||
If there are multiple valid close matches return None as result may be faulty.
|
||||
Return the result only if one accurate match stands out.
|
||||
|
||||
Returns: Result, Skip (whether or not to discontinue matching)
|
||||
"""
|
||||
PARTY, SCORE, CUTOFF = 0, 1, 80
|
||||
|
||||
if not result or not len(result):
|
||||
return None, False
|
||||
|
||||
first_result = result[0]
|
||||
if len(result) == 1:
|
||||
return (first_result[PARTY] if first_result[SCORE] > CUTOFF else None), True
|
||||
|
||||
second_result = result[1]
|
||||
if first_result[SCORE] > CUTOFF:
|
||||
# If multiple matches with the same score, return None but discontinue matching
|
||||
# Matches were found but were too close to distinguish between
|
||||
if first_result[SCORE] == second_result[SCORE]:
|
||||
return None, True
|
||||
|
||||
return first_result[PARTY], True
|
||||
else:
|
||||
return None, False
|
||||
|
||||
|
||||
def get_parties_in_order(deposit: float) -> list:
|
||||
parties = ["Supplier", "Employee", "Customer"] # most -> least likely to receive
|
||||
if flt(deposit) > 0:
|
||||
parties = ["Customer", "Supplier", "Employee"] # most -> least likely to pay
|
||||
|
||||
return parties
|
||||
@@ -33,7 +33,11 @@
|
||||
"unallocated_amount",
|
||||
"party_section",
|
||||
"party_type",
|
||||
"party"
|
||||
"party",
|
||||
"column_break_3czf",
|
||||
"bank_party_name",
|
||||
"bank_party_account_number",
|
||||
"bank_party_iban"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -63,7 +67,7 @@
|
||||
"fieldtype": "Select",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Status",
|
||||
"options": "\nPending\nSettled\nUnreconciled\nReconciled"
|
||||
"options": "\nPending\nSettled\nUnreconciled\nReconciled\nCancelled"
|
||||
},
|
||||
{
|
||||
"fieldname": "bank_account",
|
||||
@@ -202,11 +206,30 @@
|
||||
"fieldtype": "Data",
|
||||
"label": "Transaction Type",
|
||||
"length": 50
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3czf",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "bank_party_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Party Name/Account Holder (Bank Statement)"
|
||||
},
|
||||
{
|
||||
"fieldname": "bank_party_iban",
|
||||
"fieldtype": "Data",
|
||||
"label": "Party IBAN (Bank Statement)"
|
||||
},
|
||||
{
|
||||
"fieldname": "bank_party_account_number",
|
||||
"fieldtype": "Data",
|
||||
"label": "Party Account No. (Bank Statement)"
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-05-29 18:36:50.475964",
|
||||
"modified": "2023-06-06 13:58:12.821411",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bank Transaction",
|
||||
@@ -260,4 +283,4 @@
|
||||
"states": [],
|
||||
"title_field": "bank_account",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,9 @@ class BankTransaction(StatusUpdater):
|
||||
self.clear_linked_payment_entries()
|
||||
self.set_status()
|
||||
|
||||
if frappe.db.get_single_value("Accounts Settings", "enable_party_matching"):
|
||||
self.auto_set_party()
|
||||
|
||||
_saving_flag = False
|
||||
|
||||
# nosemgrep: frappe-semgrep-rules.rules.frappe-modifying-but-not-comitting
|
||||
@@ -146,6 +149,26 @@ class BankTransaction(StatusUpdater):
|
||||
payment_entry.payment_document, payment_entry.payment_entry, clearance_date, self
|
||||
)
|
||||
|
||||
def auto_set_party(self):
|
||||
from erpnext.accounts.doctype.bank_transaction.auto_match_party import AutoMatchParty
|
||||
|
||||
if self.party_type and self.party:
|
||||
return
|
||||
|
||||
result = AutoMatchParty(
|
||||
bank_party_account_number=self.bank_party_account_number,
|
||||
bank_party_iban=self.bank_party_iban,
|
||||
bank_party_name=self.bank_party_name,
|
||||
description=self.description,
|
||||
deposit=self.deposit,
|
||||
).match()
|
||||
|
||||
if result:
|
||||
party_type, party = result
|
||||
frappe.db.set_value(
|
||||
"Bank Transaction", self.name, field={"party_type": party_type, "party": party}
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_doctypes_for_bank_reconciliation():
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import nowdate
|
||||
|
||||
from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import create_bank_account
|
||||
|
||||
|
||||
class TestAutoMatchParty(FrappeTestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
create_bank_account()
|
||||
frappe.db.set_single_value("Accounts Settings", "enable_party_matching", 1)
|
||||
frappe.db.set_single_value("Accounts Settings", "enable_fuzzy_matching", 1)
|
||||
return super().setUpClass()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
frappe.db.set_single_value("Accounts Settings", "enable_party_matching", 0)
|
||||
frappe.db.set_single_value("Accounts Settings", "enable_fuzzy_matching", 0)
|
||||
|
||||
def test_match_by_account_number(self):
|
||||
create_supplier_for_match(account_no="000000003716541159")
|
||||
doc = create_bank_transaction(
|
||||
withdrawal=1200,
|
||||
transaction_id="562213b0ca1bf838dab8f2c6a39bbc3b",
|
||||
account_no="000000003716541159",
|
||||
iban="DE02000000003716541159",
|
||||
)
|
||||
|
||||
self.assertEqual(doc.party_type, "Supplier")
|
||||
self.assertEqual(doc.party, "John Doe & Co.")
|
||||
|
||||
def test_match_by_iban(self):
|
||||
create_supplier_for_match(iban="DE02000000003716541159")
|
||||
doc = create_bank_transaction(
|
||||
withdrawal=1200,
|
||||
transaction_id="c5455a224602afaa51592a9d9250600d",
|
||||
account_no="000000003716541159",
|
||||
iban="DE02000000003716541159",
|
||||
)
|
||||
|
||||
self.assertEqual(doc.party_type, "Supplier")
|
||||
self.assertEqual(doc.party, "John Doe & Co.")
|
||||
|
||||
def test_match_by_party_name(self):
|
||||
create_supplier_for_match(supplier_name="Jackson Ella W.")
|
||||
doc = create_bank_transaction(
|
||||
withdrawal=1200,
|
||||
transaction_id="1f6f661f347ff7b1ea588665f473adb1",
|
||||
party_name="Ella Jackson",
|
||||
iban="DE04000000003716545346",
|
||||
)
|
||||
self.assertEqual(doc.party_type, "Supplier")
|
||||
self.assertEqual(doc.party, "Jackson Ella W.")
|
||||
|
||||
def test_match_by_description(self):
|
||||
create_supplier_for_match(supplier_name="Microsoft")
|
||||
doc = create_bank_transaction(
|
||||
description="Auftraggeber: microsoft payments Buchungstext: msft ..e3006b5hdy. ref. j375979555927627/5536",
|
||||
withdrawal=1200,
|
||||
transaction_id="8df880a2d09c3bed3fea358ca5168c5a",
|
||||
party_name="",
|
||||
)
|
||||
self.assertEqual(doc.party_type, "Supplier")
|
||||
self.assertEqual(doc.party, "Microsoft")
|
||||
|
||||
def test_skip_match_if_multiple_close_results(self):
|
||||
create_supplier_for_match(supplier_name="Adithya Medical & General Stores")
|
||||
create_supplier_for_match(supplier_name="Adithya Medical And General Stores")
|
||||
|
||||
doc = create_bank_transaction(
|
||||
description="Paracetamol Consignment, SINV-0009",
|
||||
withdrawal=24.85,
|
||||
transaction_id="3a1da4ee2dc5a980138d56ef3460cbd9",
|
||||
party_name="Adithya Medical & General",
|
||||
)
|
||||
|
||||
# Mapping is skipped as both Supplier names have the same match score
|
||||
self.assertEqual(doc.party_type, None)
|
||||
self.assertEqual(doc.party, None)
|
||||
|
||||
|
||||
def create_supplier_for_match(supplier_name="John Doe & Co.", iban=None, account_no=None):
|
||||
if frappe.db.exists("Supplier", {"supplier_name": supplier_name}):
|
||||
# Update related Bank Account details
|
||||
if not (iban or account_no):
|
||||
return
|
||||
|
||||
frappe.db.set_value(
|
||||
dt="Bank Account",
|
||||
dn={"party": supplier_name},
|
||||
field={"iban": iban, "bank_account_no": account_no},
|
||||
)
|
||||
return
|
||||
|
||||
# Create Supplier and Bank Account for the same
|
||||
supplier = frappe.new_doc("Supplier")
|
||||
supplier.supplier_name = supplier_name
|
||||
supplier.supplier_group = "Services"
|
||||
supplier.supplier_type = "Company"
|
||||
supplier.insert()
|
||||
|
||||
if not frappe.db.exists("Bank", "TestBank"):
|
||||
bank = frappe.new_doc("Bank")
|
||||
bank.bank_name = "TestBank"
|
||||
bank.insert(ignore_if_duplicate=True)
|
||||
|
||||
if not frappe.db.exists("Bank Account", supplier.name + " - " + "TestBank"):
|
||||
bank_account = frappe.new_doc("Bank Account")
|
||||
bank_account.account_name = supplier.name
|
||||
bank_account.bank = "TestBank"
|
||||
bank_account.iban = iban
|
||||
bank_account.bank_account_no = account_no
|
||||
bank_account.party_type = "Supplier"
|
||||
bank_account.party = supplier.name
|
||||
bank_account.insert()
|
||||
|
||||
|
||||
def create_bank_transaction(
|
||||
description=None,
|
||||
withdrawal=0,
|
||||
deposit=0,
|
||||
transaction_id=None,
|
||||
party_name=None,
|
||||
account_no=None,
|
||||
iban=None,
|
||||
):
|
||||
doc = frappe.new_doc("Bank Transaction")
|
||||
doc.update(
|
||||
{
|
||||
"doctype": "Bank Transaction",
|
||||
"description": description or "1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G",
|
||||
"date": nowdate(),
|
||||
"withdrawal": withdrawal,
|
||||
"deposit": deposit,
|
||||
"currency": "INR",
|
||||
"bank_account": "Checking Account - Citi Bank",
|
||||
"transaction_id": transaction_id,
|
||||
"bank_party_name": party_name,
|
||||
"bank_party_account_number": account_no,
|
||||
"bank_party_iban": iban,
|
||||
}
|
||||
)
|
||||
doc.insert()
|
||||
doc.submit()
|
||||
doc.reload()
|
||||
|
||||
return doc
|
||||
@@ -37,7 +37,7 @@ frappe.ui.form.on('Exchange Rate Revaluation', {
|
||||
|
||||
validate_rounding_loss: function(frm) {
|
||||
let allowance = frm.doc.rounding_loss_allowance;
|
||||
if (!(allowance > 0 && allowance < 1)) {
|
||||
if (!(allowance >= 0 && allowance < 1)) {
|
||||
frappe.throw(__("Rounding Loss Allowance should be between 0 and 1"));
|
||||
}
|
||||
},
|
||||
|
||||
@@ -100,15 +100,16 @@
|
||||
},
|
||||
{
|
||||
"default": "0.05",
|
||||
"description": "Only values between 0 and 1 are allowed. \nEx: If allowance is set at 0.07, accounts that have balance of 0.07 in either of the currencies will be considered as zero balance account",
|
||||
"description": "Only values between [0,1) are allowed. Like {0.00, 0.04, 0.09, ...}\nEx: If allowance is set at 0.07, accounts that have balance of 0.07 in either of the currencies will be considered as zero balance account",
|
||||
"fieldname": "rounding_loss_allowance",
|
||||
"fieldtype": "Float",
|
||||
"label": "Rounding Loss Allowance"
|
||||
"label": "Rounding Loss Allowance",
|
||||
"precision": "9"
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-06-12 21:02:09.818208",
|
||||
"modified": "2023-06-20 07:29:06.972434",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Exchange Rate Revaluation",
|
||||
|
||||
@@ -22,7 +22,7 @@ class ExchangeRateRevaluation(Document):
|
||||
self.set_total_gain_loss()
|
||||
|
||||
def validate_rounding_loss_allowance(self):
|
||||
if not (self.rounding_loss_allowance > 0 and self.rounding_loss_allowance < 1):
|
||||
if not (self.rounding_loss_allowance >= 0 and self.rounding_loss_allowance < 1):
|
||||
frappe.throw(_("Rounding Loss Allowance should be between 0 and 1"))
|
||||
|
||||
def set_total_gain_loss(self):
|
||||
@@ -93,6 +93,12 @@ class ExchangeRateRevaluation(Document):
|
||||
|
||||
return True
|
||||
|
||||
def fetch_and_calculate_accounts_data(self):
|
||||
accounts = self.get_accounts_data()
|
||||
if accounts:
|
||||
for acc in accounts:
|
||||
self.append("accounts", acc)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_accounts_data(self):
|
||||
self.validate_mandatory()
|
||||
@@ -186,7 +192,7 @@ class ExchangeRateRevaluation(Document):
|
||||
# round off balance based on currency precision
|
||||
# and consider debit-credit difference allowance
|
||||
currency_precision = get_currency_precision()
|
||||
rounding_loss_allowance = rounding_loss_allowance or 0.05
|
||||
rounding_loss_allowance = float(rounding_loss_allowance) or 0.05
|
||||
for acc in account_details:
|
||||
acc.balance_in_account_currency = flt(acc.balance_in_account_currency, currency_precision)
|
||||
if abs(acc.balance_in_account_currency) <= rounding_loss_allowance:
|
||||
@@ -252,8 +258,8 @@ class ExchangeRateRevaluation(Document):
|
||||
new_balance_in_base_currency = 0
|
||||
new_balance_in_account_currency = 0
|
||||
|
||||
current_exchange_rate = calculate_exchange_rate_using_last_gle(
|
||||
company, d.account, d.party_type, d.party
|
||||
current_exchange_rate = (
|
||||
calculate_exchange_rate_using_last_gle(company, d.account, d.party_type, d.party) or 0.0
|
||||
)
|
||||
|
||||
gain_loss = new_balance_in_account_currency - (
|
||||
@@ -373,6 +379,24 @@ class ExchangeRateRevaluation(Document):
|
||||
"credit": 0,
|
||||
}
|
||||
)
|
||||
|
||||
journal_entry_accounts.append(journal_account)
|
||||
|
||||
journal_entry_accounts.append(
|
||||
{
|
||||
"account": unrealized_exchange_gain_loss_account,
|
||||
"balance": get_balance_on(unrealized_exchange_gain_loss_account),
|
||||
"debit": 0,
|
||||
"credit": 0,
|
||||
"debit_in_account_currency": abs(d.gain_loss) if d.gain_loss < 0 else 0,
|
||||
"credit_in_account_currency": abs(d.gain_loss) if d.gain_loss > 0 else 0,
|
||||
"cost_center": erpnext.get_default_cost_center(self.company),
|
||||
"exchange_rate": 1,
|
||||
"reference_type": "Exchange Rate Revaluation",
|
||||
"reference_name": self.name,
|
||||
}
|
||||
)
|
||||
|
||||
elif d.get("balance_in_base_currency") and not d.get("new_balance_in_base_currency"):
|
||||
# Base currency has balance
|
||||
dr_or_cr = "credit" if d.get("balance_in_base_currency") > 0 else "debit"
|
||||
@@ -388,22 +412,22 @@ class ExchangeRateRevaluation(Document):
|
||||
}
|
||||
)
|
||||
|
||||
journal_entry_accounts.append(journal_account)
|
||||
journal_entry_accounts.append(journal_account)
|
||||
|
||||
journal_entry_accounts.append(
|
||||
{
|
||||
"account": unrealized_exchange_gain_loss_account,
|
||||
"balance": get_balance_on(unrealized_exchange_gain_loss_account),
|
||||
"debit": abs(self.gain_loss_booked) if self.gain_loss_booked < 0 else 0,
|
||||
"credit": abs(self.gain_loss_booked) if self.gain_loss_booked > 0 else 0,
|
||||
"debit_in_account_currency": abs(self.gain_loss_booked) if self.gain_loss_booked < 0 else 0,
|
||||
"credit_in_account_currency": self.gain_loss_booked if self.gain_loss_booked > 0 else 0,
|
||||
"cost_center": erpnext.get_default_cost_center(self.company),
|
||||
"exchange_rate": 1,
|
||||
"reference_type": "Exchange Rate Revaluation",
|
||||
"reference_name": self.name,
|
||||
}
|
||||
)
|
||||
journal_entry_accounts.append(
|
||||
{
|
||||
"account": unrealized_exchange_gain_loss_account,
|
||||
"balance": get_balance_on(unrealized_exchange_gain_loss_account),
|
||||
"debit": abs(d.gain_loss) if d.gain_loss < 0 else 0,
|
||||
"credit": abs(d.gain_loss) if d.gain_loss > 0 else 0,
|
||||
"debit_in_account_currency": 0,
|
||||
"credit_in_account_currency": 0,
|
||||
"cost_center": erpnext.get_default_cost_center(self.company),
|
||||
"exchange_rate": 1,
|
||||
"reference_type": "Exchange Rate Revaluation",
|
||||
"reference_name": self.name,
|
||||
}
|
||||
)
|
||||
|
||||
journal_entry.set("accounts", journal_entry_accounts)
|
||||
journal_entry.set_total_debit_credit()
|
||||
@@ -552,7 +576,7 @@ def calculate_exchange_rate_using_last_gle(company, account, party_type, party):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_account_details(
|
||||
company, posting_date, account, party_type=None, party=None, rounding_loss_allowance=None
|
||||
company, posting_date, account, party_type=None, party=None, rounding_loss_allowance: float = None
|
||||
):
|
||||
if not (company and posting_date):
|
||||
frappe.throw(_("Company and Posting Date is mandatory"))
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
"fieldname": "current_exchange_rate",
|
||||
"fieldtype": "Float",
|
||||
"label": "Current Exchange Rate",
|
||||
"precision": "9",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@@ -92,6 +93,7 @@
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "New Exchange Rate",
|
||||
"precision": "9",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@@ -147,7 +149,7 @@
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-12-29 19:38:52.915295",
|
||||
"modified": "2023-06-22 12:39:56.446722",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Exchange Rate Revaluation Account",
|
||||
|
||||
@@ -8,17 +8,6 @@ frappe.ui.form.on('Fiscal Year', {
|
||||
frappe.datetime.add_days(frappe.defaults.get_default("year_end_date"), 1));
|
||||
}
|
||||
},
|
||||
refresh: function (frm) {
|
||||
if (!frm.doc.__islocal && (frm.doc.name != frappe.sys_defaults.fiscal_year)) {
|
||||
frm.add_custom_button(__("Set as Default"), () => frm.events.set_as_default(frm));
|
||||
frm.set_intro(__("To set this Fiscal Year as Default, click on 'Set as Default'"));
|
||||
} else {
|
||||
frm.set_intro("");
|
||||
}
|
||||
},
|
||||
set_as_default: function(frm) {
|
||||
return frm.call('set_as_default');
|
||||
},
|
||||
year_start_date: function(frm) {
|
||||
if (!frm.doc.is_short_year) {
|
||||
let year_end_date =
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import frappe
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from frappe import _, msgprint
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import add_days, add_years, cstr, getdate
|
||||
|
||||
@@ -14,22 +14,6 @@ class FiscalYearIncorrectDate(frappe.ValidationError):
|
||||
|
||||
|
||||
class FiscalYear(Document):
|
||||
@frappe.whitelist()
|
||||
def set_as_default(self):
|
||||
frappe.db.set_value("Global Defaults", None, "current_fiscal_year", self.name)
|
||||
global_defaults = frappe.get_doc("Global Defaults")
|
||||
global_defaults.check_permission("write")
|
||||
global_defaults.on_update()
|
||||
|
||||
# clear cache
|
||||
frappe.clear_cache()
|
||||
|
||||
msgprint(
|
||||
_(
|
||||
"{0} is now the default Fiscal Year. Please refresh your browser for the change to take effect."
|
||||
).format(self.name)
|
||||
)
|
||||
|
||||
def validate(self):
|
||||
self.validate_dates()
|
||||
self.validate_overlap()
|
||||
@@ -77,13 +61,6 @@ class FiscalYear(Document):
|
||||
frappe.cache().delete_value("fiscal_years")
|
||||
|
||||
def on_trash(self):
|
||||
global_defaults = frappe.get_doc("Global Defaults")
|
||||
if global_defaults.current_fiscal_year == self.name:
|
||||
frappe.throw(
|
||||
_(
|
||||
"You cannot delete Fiscal Year {0}. Fiscal Year {0} is set as default in Global Settings"
|
||||
).format(self.name)
|
||||
)
|
||||
frappe.cache().delete_value("fiscal_years")
|
||||
|
||||
def validate_overlap(self):
|
||||
|
||||
@@ -326,12 +326,10 @@ class JournalEntry(AccountsController):
|
||||
d.db_update()
|
||||
|
||||
def unlink_asset_reference(self):
|
||||
if self.voucher_type != "Depreciation Entry":
|
||||
return
|
||||
|
||||
for d in self.get("accounts"):
|
||||
if (
|
||||
d.reference_type == "Asset"
|
||||
self.voucher_type == "Depreciation Entry"
|
||||
and d.reference_type == "Asset"
|
||||
and d.reference_name
|
||||
and d.account_type == "Depreciation"
|
||||
and d.debit
|
||||
@@ -358,6 +356,15 @@ class JournalEntry(AccountsController):
|
||||
else:
|
||||
asset.db_set("value_after_depreciation", asset.value_after_depreciation + d.debit)
|
||||
asset.set_status()
|
||||
elif self.voucher_type == "Journal Entry" and d.reference_type == "Asset" and d.reference_name:
|
||||
journal_entry_for_scrap = frappe.db.get_value(
|
||||
"Asset", d.reference_name, "journal_entry_for_scrap"
|
||||
)
|
||||
|
||||
if journal_entry_for_scrap == self.name:
|
||||
frappe.throw(
|
||||
_("Journal Entry for Asset scrapping cannot be cancelled. Please restore the Asset.")
|
||||
)
|
||||
|
||||
def unlink_inter_company_jv(self):
|
||||
if (
|
||||
@@ -389,6 +396,15 @@ class JournalEntry(AccountsController):
|
||||
d.idx, d.account
|
||||
)
|
||||
)
|
||||
elif (
|
||||
d.party_type
|
||||
and frappe.db.get_value("Party Type", d.party_type, "account_type") != account_type
|
||||
):
|
||||
frappe.throw(
|
||||
_("Row {0}: Account {1} and Party Type {2} have different account types").format(
|
||||
d.idx, d.account, d.party_type
|
||||
)
|
||||
)
|
||||
|
||||
def check_credit_limit(self):
|
||||
customers = list(
|
||||
|
||||
@@ -122,13 +122,10 @@ frappe.ui.form.on('Payment Entry', {
|
||||
frm.set_query('payment_term', 'references', function(frm, cdt, cdn) {
|
||||
const child = locals[cdt][cdn];
|
||||
if (in_list(['Purchase Invoice', 'Sales Invoice'], child.reference_doctype) && child.reference_name) {
|
||||
let payment_term_list = frappe.get_list('Payment Schedule', {'parent': child.reference_name});
|
||||
|
||||
payment_term_list = payment_term_list.map(pt => pt.payment_term);
|
||||
|
||||
return {
|
||||
query: "erpnext.controllers.queries.get_payment_terms_for_references",
|
||||
filters: {
|
||||
'name': ['in', payment_term_list]
|
||||
'reference': child.reference_name
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -613,7 +610,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
frm.events.set_unallocated_amount(frm);
|
||||
},
|
||||
|
||||
get_outstanding_invoice: function(frm) {
|
||||
get_outstanding_invoices_or_orders: function(frm, get_outstanding_invoices, get_orders_to_be_billed) {
|
||||
const today = frappe.datetime.get_today();
|
||||
const fields = [
|
||||
{fieldtype:"Section Break", label: __("Posting Date")},
|
||||
@@ -643,12 +640,29 @@ frappe.ui.form.on('Payment Entry', {
|
||||
{fieldtype:"Check", label: __("Allocate Payment Amount"), fieldname:"allocate_payment_amount", default:1},
|
||||
];
|
||||
|
||||
let btn_text = "";
|
||||
|
||||
if (get_outstanding_invoices) {
|
||||
btn_text = "Get Outstanding Invoices";
|
||||
}
|
||||
else if (get_orders_to_be_billed) {
|
||||
btn_text = "Get Outstanding Orders";
|
||||
}
|
||||
|
||||
frappe.prompt(fields, function(filters){
|
||||
frappe.flags.allocate_payment_amount = true;
|
||||
frm.events.validate_filters_data(frm, filters);
|
||||
frm.doc.cost_center = filters.cost_center;
|
||||
frm.events.get_outstanding_documents(frm, filters);
|
||||
}, __("Filters"), __("Get Outstanding Documents"));
|
||||
frm.events.get_outstanding_documents(frm, filters, get_outstanding_invoices, get_orders_to_be_billed);
|
||||
}, __("Filters"), __(btn_text));
|
||||
},
|
||||
|
||||
get_outstanding_invoices: function(frm) {
|
||||
frm.events.get_outstanding_invoices_or_orders(frm, true, false);
|
||||
},
|
||||
|
||||
get_outstanding_orders: function(frm) {
|
||||
frm.events.get_outstanding_invoices_or_orders(frm, false, true);
|
||||
},
|
||||
|
||||
validate_filters_data: function(frm, filters) {
|
||||
@@ -674,7 +688,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
}
|
||||
},
|
||||
|
||||
get_outstanding_documents: function(frm, filters) {
|
||||
get_outstanding_documents: function(frm, filters, get_outstanding_invoices, get_orders_to_be_billed) {
|
||||
frm.clear_table("references");
|
||||
|
||||
if(!frm.doc.party) {
|
||||
@@ -698,6 +712,13 @@ frappe.ui.form.on('Payment Entry', {
|
||||
args[key] = filters[key];
|
||||
}
|
||||
|
||||
if (get_outstanding_invoices) {
|
||||
args["get_outstanding_invoices"] = true;
|
||||
}
|
||||
else if (get_orders_to_be_billed) {
|
||||
args["get_orders_to_be_billed"] = true;
|
||||
}
|
||||
|
||||
frappe.flags.allocate_payment_amount = filters['allocate_payment_amount'];
|
||||
|
||||
return frappe.call({
|
||||
|
||||
@@ -48,7 +48,8 @@
|
||||
"base_received_amount",
|
||||
"base_received_amount_after_tax",
|
||||
"section_break_14",
|
||||
"get_outstanding_invoice",
|
||||
"get_outstanding_invoices",
|
||||
"get_outstanding_orders",
|
||||
"references",
|
||||
"section_break_34",
|
||||
"total_allocated_amount",
|
||||
@@ -355,12 +356,6 @@
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Reference"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.docstatus==0",
|
||||
"fieldname": "get_outstanding_invoice",
|
||||
"fieldtype": "Button",
|
||||
"label": "Get Outstanding Invoice"
|
||||
},
|
||||
{
|
||||
"fieldname": "references",
|
||||
"fieldtype": "Table",
|
||||
@@ -728,12 +723,24 @@
|
||||
"fieldname": "section_break_60",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_border": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.docstatus==0",
|
||||
"fieldname": "get_outstanding_invoices",
|
||||
"fieldtype": "Button",
|
||||
"label": "Get Outstanding Invoices"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.docstatus==0",
|
||||
"fieldname": "get_outstanding_orders",
|
||||
"fieldtype": "Button",
|
||||
"label": "Get Outstanding Orders"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-02-14 04:52:30.478523",
|
||||
"modified": "2023-06-19 11:38:04.387219",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Entry",
|
||||
|
||||
@@ -8,6 +8,7 @@ from functools import reduce
|
||||
import frappe
|
||||
from frappe import ValidationError, _, qb, scrub, throw
|
||||
from frappe.utils import cint, comma_or, flt, getdate, nowdate
|
||||
from frappe.utils.data import comma_and, fmt_money
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.doctype.bank_account.bank_account import (
|
||||
@@ -151,6 +152,33 @@ class PaymentEntry(AccountsController):
|
||||
if self.payment_type == "Internal Transfer":
|
||||
return
|
||||
|
||||
if self.party_type in ("Customer", "Supplier"):
|
||||
self.validate_allocated_amount_with_latest_data()
|
||||
else:
|
||||
fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.")
|
||||
for d in self.get("references"):
|
||||
if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(d.outstanding_amount):
|
||||
frappe.throw(fail_message.format(d.idx))
|
||||
|
||||
# Check for negative outstanding invoices as well
|
||||
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(d.outstanding_amount):
|
||||
frappe.throw(fail_message.format(d.idx))
|
||||
|
||||
def term_based_allocation_enabled_for_reference(
|
||||
self, reference_doctype: str, reference_name: str
|
||||
) -> bool:
|
||||
if (
|
||||
reference_doctype
|
||||
and reference_doctype in ["Sales Invoice", "Sales Order", "Purchase Order", "Purchase Invoice"]
|
||||
and reference_name
|
||||
):
|
||||
if template := frappe.db.get_value(reference_doctype, reference_name, "payment_terms_template"):
|
||||
return frappe.db.get_value(
|
||||
"Payment Terms Template", template, "allocate_payment_based_on_payment_terms"
|
||||
)
|
||||
return False
|
||||
|
||||
def validate_allocated_amount_with_latest_data(self):
|
||||
latest_references = get_outstanding_reference_documents(
|
||||
{
|
||||
"posting_date": self.posting_date,
|
||||
@@ -159,6 +187,8 @@ class PaymentEntry(AccountsController):
|
||||
"payment_type": self.payment_type,
|
||||
"party": self.party,
|
||||
"party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to,
|
||||
"get_outstanding_invoices": True,
|
||||
"get_orders_to_be_billed": True,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -166,39 +196,62 @@ class PaymentEntry(AccountsController):
|
||||
latest_lookup = {}
|
||||
for d in latest_references:
|
||||
d = frappe._dict(d)
|
||||
latest_lookup.update({(d.voucher_type, d.voucher_no): d})
|
||||
latest_lookup.setdefault((d.voucher_type, d.voucher_no), frappe._dict())[d.payment_term] = d
|
||||
|
||||
for d in self.get("references").copy():
|
||||
latest = latest_lookup.get((d.reference_doctype, d.reference_name))
|
||||
for idx, d in enumerate(self.get("references"), start=1):
|
||||
latest = latest_lookup.get((d.reference_doctype, d.reference_name)) or frappe._dict()
|
||||
|
||||
# If term based allocation is enabled, throw
|
||||
if (
|
||||
d.payment_term is None or d.payment_term == ""
|
||||
) and self.term_based_allocation_enabled_for_reference(
|
||||
d.reference_doctype, d.reference_name
|
||||
):
|
||||
frappe.throw(
|
||||
_(
|
||||
"{0} has Payment Term based allocation enabled. Select a Payment Term for Row #{1} in Payment References section"
|
||||
).format(frappe.bold(d.reference_name), frappe.bold(idx))
|
||||
)
|
||||
|
||||
# if no payment template is used by invoice and has a custom term(no `payment_term`), then invoice outstanding will be in 'None' key
|
||||
latest = latest.get(d.payment_term) or latest.get(None)
|
||||
|
||||
# The reference has already been fully paid
|
||||
if not latest:
|
||||
frappe.throw(
|
||||
_("{0} {1} has already been fully paid.").format(d.reference_doctype, d.reference_name)
|
||||
_("{0} {1} has already been fully paid.").format(_(d.reference_doctype), d.reference_name)
|
||||
)
|
||||
# The reference has already been partly paid
|
||||
elif (
|
||||
latest.outstanding_amount < latest.invoice_amount
|
||||
and d.outstanding_amount != latest.outstanding_amount
|
||||
):
|
||||
elif latest.outstanding_amount < latest.invoice_amount and flt(
|
||||
d.outstanding_amount, d.precision("outstanding_amount")
|
||||
) != flt(latest.outstanding_amount, d.precision("outstanding_amount")):
|
||||
|
||||
frappe.throw(
|
||||
_(
|
||||
"{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' button to get the latest outstanding amount."
|
||||
).format(d.reference_doctype, d.reference_name)
|
||||
"{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts."
|
||||
).format(_(d.reference_doctype), d.reference_name)
|
||||
)
|
||||
|
||||
d.outstanding_amount = latest.outstanding_amount
|
||||
|
||||
fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.")
|
||||
|
||||
if (flt(d.allocated_amount)) > 0:
|
||||
if flt(d.allocated_amount) > flt(d.outstanding_amount):
|
||||
frappe.throw(fail_message.format(d.idx))
|
||||
if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount):
|
||||
frappe.throw(fail_message.format(d.idx))
|
||||
|
||||
if d.payment_term and (
|
||||
(flt(d.allocated_amount)) > 0
|
||||
and flt(d.allocated_amount) > flt(latest.payment_term_outstanding)
|
||||
):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row #{0}: Allocated amount:{1} is greater than outstanding amount:{2} for Payment Term {3}"
|
||||
).format(
|
||||
d.idx, d.allocated_amount, latest.payment_term_outstanding, d.payment_term
|
||||
)
|
||||
)
|
||||
|
||||
# Check for negative outstanding invoices as well
|
||||
if flt(d.allocated_amount) < 0:
|
||||
if flt(d.allocated_amount) < flt(d.outstanding_amount):
|
||||
frappe.throw(fail_message.format(d.idx))
|
||||
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount):
|
||||
frappe.throw(fail_message.format(d.idx))
|
||||
|
||||
def delink_advance_entry_references(self):
|
||||
for reference in self.references:
|
||||
@@ -290,7 +343,7 @@ class PaymentEntry(AccountsController):
|
||||
def validate_party_details(self):
|
||||
if self.party:
|
||||
if not frappe.db.exists(self.party_type, self.party):
|
||||
frappe.throw(_("Invalid {0}: {1}").format(self.party_type, self.party))
|
||||
frappe.throw(_("{0} {1} does not exist").format(_(self.party_type), self.party))
|
||||
|
||||
def set_exchange_rate(self, ref_doc=None):
|
||||
self.set_source_exchange_rate(ref_doc)
|
||||
@@ -339,7 +392,9 @@ class PaymentEntry(AccountsController):
|
||||
continue
|
||||
if d.reference_doctype not in valid_reference_doctypes:
|
||||
frappe.throw(
|
||||
_("Reference Doctype must be one of {0}").format(comma_or(valid_reference_doctypes))
|
||||
_("Reference Doctype must be one of {0}").format(
|
||||
comma_or((_(d) for d in valid_reference_doctypes))
|
||||
)
|
||||
)
|
||||
|
||||
elif d.reference_name:
|
||||
@@ -352,7 +407,7 @@ class PaymentEntry(AccountsController):
|
||||
if self.party != ref_doc.get(scrub(self.party_type)):
|
||||
frappe.throw(
|
||||
_("{0} {1} is not associated with {2} {3}").format(
|
||||
d.reference_doctype, d.reference_name, self.party_type, self.party
|
||||
_(d.reference_doctype), d.reference_name, _(self.party_type), self.party
|
||||
)
|
||||
)
|
||||
else:
|
||||
@@ -371,18 +426,18 @@ class PaymentEntry(AccountsController):
|
||||
if ref_party_account != self.party_account:
|
||||
frappe.throw(
|
||||
_("{0} {1} is associated with {2}, but Party Account is {3}").format(
|
||||
d.reference_doctype, d.reference_name, ref_party_account, self.party_account
|
||||
_(d.reference_doctype), d.reference_name, ref_party_account, self.party_account
|
||||
)
|
||||
)
|
||||
|
||||
if ref_doc.doctype == "Purchase Invoice" and ref_doc.get("on_hold"):
|
||||
frappe.throw(
|
||||
_("{0} {1} is on hold").format(d.reference_doctype, d.reference_name),
|
||||
title=_("Invalid Invoice"),
|
||||
_("{0} {1} is on hold").format(_(d.reference_doctype), d.reference_name),
|
||||
title=_("Invalid Purchase Invoice"),
|
||||
)
|
||||
|
||||
if ref_doc.docstatus != 1:
|
||||
frappe.throw(_("{0} {1} must be submitted").format(d.reference_doctype, d.reference_name))
|
||||
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":
|
||||
@@ -408,14 +463,13 @@ class PaymentEntry(AccountsController):
|
||||
if outstanding_amount <= 0 and not is_return:
|
||||
no_oustanding_refs.setdefault(d.reference_doctype, []).append(d)
|
||||
|
||||
for k, v in no_oustanding_refs.items():
|
||||
for reference_doctype, references in no_oustanding_refs.items():
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"{} - {} now has {} as it had no outstanding amount left before submitting the Payment Entry."
|
||||
"References {0} of type {1} had no outstanding amount left before submitting the Payment Entry. Now they have a negative outstanding amount."
|
||||
).format(
|
||||
_(k),
|
||||
frappe.bold(", ".join(d.reference_name for d in v)),
|
||||
frappe.bold(_("negative outstanding amount")),
|
||||
frappe.bold(comma_and([d.reference_name for d in references])),
|
||||
_(reference_doctype),
|
||||
)
|
||||
+ "<br><br>"
|
||||
+ _("If this is undesirable please cancel the corresponding Payment Entry."),
|
||||
@@ -450,7 +504,7 @@ class PaymentEntry(AccountsController):
|
||||
if not valid:
|
||||
frappe.throw(
|
||||
_("Against Journal Entry {0} does not have any unmatched {1} entry").format(
|
||||
d.reference_name, dr_or_cr
|
||||
d.reference_name, _(dr_or_cr)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -517,7 +571,7 @@ class PaymentEntry(AccountsController):
|
||||
if allocated_amount > outstanding:
|
||||
frappe.throw(
|
||||
_("Row #{0}: Cannot allocate more than {1} against payment term {2}").format(
|
||||
idx, outstanding, key[0]
|
||||
idx, fmt_money(outstanding), key[0]
|
||||
)
|
||||
)
|
||||
|
||||
@@ -821,7 +875,7 @@ class PaymentEntry(AccountsController):
|
||||
elif paid_amount - additional_charges > total_negative_outstanding:
|
||||
frappe.throw(
|
||||
_("Paid Amount cannot be greater than total negative outstanding amount {0}").format(
|
||||
total_negative_outstanding
|
||||
fmt_money(total_negative_outstanding)
|
||||
),
|
||||
InvalidPaymentEntry,
|
||||
)
|
||||
@@ -1297,6 +1351,9 @@ def get_outstanding_reference_documents(args):
|
||||
if args.get("party_type") == "Member":
|
||||
return
|
||||
|
||||
if not args.get("get_outstanding_invoices") and not args.get("get_orders_to_be_billed"):
|
||||
args["get_outstanding_invoices"] = True
|
||||
|
||||
ple = qb.DocType("Payment Ledger Entry")
|
||||
common_filter = []
|
||||
accounting_dimensions_filter = []
|
||||
@@ -1347,69 +1404,103 @@ def get_outstanding_reference_documents(args):
|
||||
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"),
|
||||
accounting_dimensions=accounting_dimensions_filter,
|
||||
)
|
||||
|
||||
outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices)
|
||||
|
||||
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"):
|
||||
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(
|
||||
party_account_currency, company_currency, d.posting_date
|
||||
)
|
||||
if d.voucher_type in ("Purchase Invoice"):
|
||||
d["bill_no"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "bill_no")
|
||||
|
||||
# Get all SO / PO which are not fully billed or against which full advance not paid
|
||||
orders_to_be_billed = []
|
||||
orders_to_be_billed = get_orders_to_be_billed(
|
||||
args.get("posting_date"),
|
||||
args.get("party_type"),
|
||||
args.get("party"),
|
||||
args.get("company"),
|
||||
party_account_currency,
|
||||
company_currency,
|
||||
filters=args,
|
||||
)
|
||||
|
||||
# Get negative outstanding sales /purchase invoices
|
||||
outstanding_invoices = []
|
||||
negative_outstanding_invoices = []
|
||||
if args.get("party_type") != "Employee" and not args.get("voucher_no"):
|
||||
negative_outstanding_invoices = get_negative_outstanding_invoices(
|
||||
|
||||
if args.get("get_outstanding_invoices"):
|
||||
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"),
|
||||
accounting_dimensions=accounting_dimensions_filter,
|
||||
)
|
||||
|
||||
outstanding_invoices = split_invoices_based_on_payment_terms(
|
||||
outstanding_invoices, args.get("company")
|
||||
)
|
||||
|
||||
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"):
|
||||
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(
|
||||
party_account_currency, company_currency, d.posting_date
|
||||
)
|
||||
if d.voucher_type in ("Purchase Invoice"):
|
||||
d["bill_no"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "bill_no")
|
||||
|
||||
# Get negative outstanding sales /purchase invoices
|
||||
if args.get("party_type") != "Employee" and not args.get("voucher_no"):
|
||||
negative_outstanding_invoices = get_negative_outstanding_invoices(
|
||||
args.get("party_type"),
|
||||
args.get("party"),
|
||||
args.get("party_account"),
|
||||
party_account_currency,
|
||||
company_currency,
|
||||
condition=condition,
|
||||
)
|
||||
|
||||
# Get all SO / PO which are not fully billed or against which full advance not paid
|
||||
orders_to_be_billed = []
|
||||
if args.get("get_orders_to_be_billed"):
|
||||
orders_to_be_billed = get_orders_to_be_billed(
|
||||
args.get("posting_date"),
|
||||
args.get("party_type"),
|
||||
args.get("party"),
|
||||
args.get("company"),
|
||||
party_account_currency,
|
||||
company_currency,
|
||||
condition=condition,
|
||||
filters=args,
|
||||
)
|
||||
|
||||
data = negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed
|
||||
|
||||
if not data:
|
||||
if args.get("get_outstanding_invoices") and args.get("get_orders_to_be_billed"):
|
||||
ref_document_type = "invoices or orders"
|
||||
elif args.get("get_outstanding_invoices"):
|
||||
ref_document_type = "invoices"
|
||||
elif args.get("get_orders_to_be_billed"):
|
||||
ref_document_type = "orders"
|
||||
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"No outstanding invoices found for the {0} {1} which qualify the filters you have specified."
|
||||
).format(_(args.get("party_type")).lower(), frappe.bold(args.get("party")))
|
||||
"No outstanding {0} found for the {1} {2} which qualify the filters you have specified."
|
||||
).format(
|
||||
_(ref_document_type), _(args.get("party_type")).lower(), frappe.bold(args.get("party"))
|
||||
)
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def split_invoices_based_on_payment_terms(outstanding_invoices):
|
||||
def split_invoices_based_on_payment_terms(outstanding_invoices, company):
|
||||
invoice_ref_based_on_payment_terms = {}
|
||||
|
||||
company_currency = (
|
||||
frappe.db.get_value("Company", company, "default_currency") if company else None
|
||||
)
|
||||
exc_rates = frappe._dict()
|
||||
for doctype in ["Sales Invoice", "Purchase Invoice"]:
|
||||
invoices = [x.voucher_no for x in outstanding_invoices if x.voucher_type == doctype]
|
||||
for x in frappe.db.get_all(
|
||||
doctype,
|
||||
filters={"name": ["in", invoices]},
|
||||
fields=["name", "currency", "conversion_rate", "party_account_currency"],
|
||||
):
|
||||
exc_rates[x.name] = frappe._dict(
|
||||
conversion_rate=x.conversion_rate,
|
||||
currency=x.currency,
|
||||
party_account_currency=x.party_account_currency,
|
||||
company_currency=company_currency,
|
||||
)
|
||||
|
||||
for idx, d in enumerate(outstanding_invoices):
|
||||
if d.voucher_type in ["Sales Invoice", "Purchase Invoice"]:
|
||||
payment_term_template = frappe.db.get_value(
|
||||
@@ -1426,6 +1517,14 @@ def split_invoices_based_on_payment_terms(outstanding_invoices):
|
||||
|
||||
for payment_term in payment_schedule:
|
||||
if payment_term.outstanding > 0.1:
|
||||
doc_details = exc_rates.get(payment_term.parent, None)
|
||||
is_multi_currency_acc = (doc_details.currency != doc_details.company_currency) and (
|
||||
doc_details.party_account_currency != doc_details.company_currency
|
||||
)
|
||||
payment_term_outstanding = flt(payment_term.outstanding)
|
||||
if not is_multi_currency_acc:
|
||||
payment_term_outstanding = doc_details.conversion_rate * flt(payment_term.outstanding)
|
||||
|
||||
invoice_ref_based_on_payment_terms.setdefault(idx, [])
|
||||
invoice_ref_based_on_payment_terms[idx].append(
|
||||
frappe._dict(
|
||||
@@ -1437,6 +1536,10 @@ def split_invoices_based_on_payment_terms(outstanding_invoices):
|
||||
"posting_date": d.posting_date,
|
||||
"invoice_amount": flt(d.invoice_amount),
|
||||
"outstanding_amount": flt(d.outstanding_amount),
|
||||
"payment_term_outstanding": payment_term_outstanding,
|
||||
"allocated_amount": payment_term_outstanding
|
||||
if payment_term_outstanding
|
||||
else d.outstanding_amount,
|
||||
"payment_amount": payment_term.payment_amount,
|
||||
"payment_term": payment_term.payment_term,
|
||||
}
|
||||
@@ -1476,60 +1579,59 @@ def get_orders_to_be_billed(
|
||||
cost_center=None,
|
||||
filters=None,
|
||||
):
|
||||
voucher_type = None
|
||||
if party_type == "Customer":
|
||||
voucher_type = "Sales Order"
|
||||
elif party_type == "Supplier":
|
||||
voucher_type = "Purchase Order"
|
||||
elif party_type == "Employee":
|
||||
voucher_type = None
|
||||
|
||||
if not voucher_type:
|
||||
return []
|
||||
|
||||
# Add cost center condition
|
||||
if voucher_type:
|
||||
doc = frappe.get_doc({"doctype": voucher_type})
|
||||
condition = ""
|
||||
if doc and hasattr(doc, "cost_center") and doc.cost_center:
|
||||
condition = " and cost_center='%s'" % cost_center
|
||||
doc = frappe.get_doc({"doctype": voucher_type})
|
||||
condition = ""
|
||||
if doc and hasattr(doc, "cost_center") and doc.cost_center:
|
||||
condition = " and cost_center='%s'" % cost_center
|
||||
|
||||
orders = []
|
||||
if voucher_type:
|
||||
if party_account_currency == company_currency:
|
||||
grand_total_field = "base_grand_total"
|
||||
rounded_total_field = "base_rounded_total"
|
||||
else:
|
||||
grand_total_field = "grand_total"
|
||||
rounded_total_field = "rounded_total"
|
||||
if party_account_currency == company_currency:
|
||||
grand_total_field = "base_grand_total"
|
||||
rounded_total_field = "base_rounded_total"
|
||||
else:
|
||||
grand_total_field = "grand_total"
|
||||
rounded_total_field = "rounded_total"
|
||||
|
||||
orders = frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
name as voucher_no,
|
||||
if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) as invoice_amount,
|
||||
(if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) - advance_paid) as outstanding_amount,
|
||||
transaction_date as posting_date
|
||||
from
|
||||
`tab{voucher_type}`
|
||||
where
|
||||
{party_type} = %s
|
||||
and docstatus = 1
|
||||
and company = %s
|
||||
and ifnull(status, "") != "Closed"
|
||||
and if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) > advance_paid
|
||||
and abs(100 - per_billed) > 0.01
|
||||
{condition}
|
||||
order by
|
||||
transaction_date, name
|
||||
""".format(
|
||||
**{
|
||||
"rounded_total_field": rounded_total_field,
|
||||
"grand_total_field": grand_total_field,
|
||||
"voucher_type": voucher_type,
|
||||
"party_type": scrub(party_type),
|
||||
"condition": condition,
|
||||
}
|
||||
),
|
||||
(party, company),
|
||||
as_dict=True,
|
||||
)
|
||||
orders = frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
name as voucher_no,
|
||||
if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) as invoice_amount,
|
||||
(if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) - advance_paid) as outstanding_amount,
|
||||
transaction_date as posting_date
|
||||
from
|
||||
`tab{voucher_type}`
|
||||
where
|
||||
{party_type} = %s
|
||||
and docstatus = 1
|
||||
and company = %s
|
||||
and ifnull(status, "") != "Closed"
|
||||
and if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) > advance_paid
|
||||
and abs(100 - per_billed) > 0.01
|
||||
{condition}
|
||||
order by
|
||||
transaction_date, name
|
||||
""".format(
|
||||
**{
|
||||
"rounded_total_field": rounded_total_field,
|
||||
"grand_total_field": grand_total_field,
|
||||
"voucher_type": voucher_type,
|
||||
"party_type": scrub(party_type),
|
||||
"condition": condition,
|
||||
}
|
||||
),
|
||||
(party, company),
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
order_list = []
|
||||
for d in orders:
|
||||
@@ -1562,6 +1664,8 @@ def get_negative_outstanding_invoices(
|
||||
cost_center=None,
|
||||
condition=None,
|
||||
):
|
||||
if party_type not in ["Customer", "Supplier"]:
|
||||
return []
|
||||
voucher_type = "Sales Invoice" if party_type == "Customer" else "Purchase Invoice"
|
||||
supplier_condition = ""
|
||||
if voucher_type == "Purchase Invoice":
|
||||
@@ -1610,7 +1714,7 @@ def get_negative_outstanding_invoices(
|
||||
def get_party_details(company, party_type, party, date, cost_center=None):
|
||||
bank_account = ""
|
||||
if not frappe.db.exists(party_type, party):
|
||||
frappe.throw(_("Invalid {0}: {1}").format(party_type, party))
|
||||
frappe.throw(_("{0} {1} does not exist").format(_(party_type), party))
|
||||
|
||||
party_account = get_party_account(party_type, party, company)
|
||||
|
||||
@@ -1711,7 +1815,7 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
||||
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.get("base_grand_total") or ref_doc.get("grand_total")
|
||||
exchange_rate = 1
|
||||
else:
|
||||
total_amount = ref_doc.get("grand_total")
|
||||
@@ -1759,7 +1863,7 @@ def get_payment_entry(
|
||||
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) >= (
|
||||
100.0 + over_billing_allowance
|
||||
):
|
||||
frappe.throw(_("Can only make payment against unbilled {0}").format(dt))
|
||||
frappe.throw(_("Can only make payment against unbilled {0}").format(_(dt)))
|
||||
|
||||
if not party_type:
|
||||
party_type = set_party_type(dt)
|
||||
@@ -2210,6 +2314,7 @@ def get_reference_as_per_payment_terms(
|
||||
"due_date": doc.get("due_date"),
|
||||
"total_amount": grand_total,
|
||||
"outstanding_amount": outstanding_amount,
|
||||
"payment_term_outstanding": payment_term_outstanding,
|
||||
"payment_term": payment_term.payment_term,
|
||||
"allocated_amount": payment_term_outstanding,
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ from frappe.utils import flt, nowdate
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import (
|
||||
InvalidPaymentEntry,
|
||||
get_payment_entry,
|
||||
get_reference_details,
|
||||
)
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import (
|
||||
make_purchase_invoice,
|
||||
@@ -1037,6 +1038,124 @@ class TestPaymentEntry(FrappeTestCase):
|
||||
|
||||
self.assertRaises(frappe.ValidationError, pe_draft.submit)
|
||||
|
||||
def test_details_update_on_reference_table(self):
|
||||
so = make_sales_order(
|
||||
customer="_Test Customer USD", currency="USD", qty=1, rate=100, do_not_submit=True
|
||||
)
|
||||
so.conversion_rate = 50
|
||||
so.submit()
|
||||
pe = get_payment_entry("Sales Order", so.name)
|
||||
pe.references.clear()
|
||||
pe.paid_from = "Debtors - _TC"
|
||||
pe.paid_from_account_currency = "INR"
|
||||
pe.source_exchange_rate = 50
|
||||
pe.save()
|
||||
|
||||
ref_details = get_reference_details(so.doctype, so.name, pe.paid_from_account_currency)
|
||||
expected_response = {
|
||||
"total_amount": 5000.0,
|
||||
"outstanding_amount": 5000.0,
|
||||
"exchange_rate": 1.0,
|
||||
"due_date": None,
|
||||
"bill_no": None,
|
||||
}
|
||||
self.assertDictEqual(ref_details, expected_response)
|
||||
|
||||
@change_settings(
|
||||
"Accounts Settings",
|
||||
{
|
||||
"unlink_payment_on_cancellation_of_invoice": 1,
|
||||
"delete_linked_ledger_entries": 1,
|
||||
"allow_multi_currency_invoices_against_single_party_account": 1,
|
||||
},
|
||||
)
|
||||
def test_overallocation_validation_on_payment_terms(self):
|
||||
"""
|
||||
Validate Allocation on Payment Entry based on Payment Schedule. Upon overallocation, validation error must be thrown.
|
||||
|
||||
"""
|
||||
customer = create_customer()
|
||||
create_payment_terms_template()
|
||||
|
||||
# Validate allocation on base/company currency
|
||||
si1 = create_sales_invoice(do_not_save=1, qty=1, rate=200)
|
||||
si1.payment_terms_template = "Test Receivable Template"
|
||||
si1.save().submit()
|
||||
|
||||
si1.reload()
|
||||
pe = get_payment_entry(si1.doctype, si1.name).save()
|
||||
# Allocated amount should be according to the payment schedule
|
||||
for idx, schedule in enumerate(si1.payment_schedule):
|
||||
with self.subTest(idx=idx):
|
||||
self.assertEqual(flt(schedule.payment_amount), flt(pe.references[idx].allocated_amount))
|
||||
pe.save()
|
||||
|
||||
# Overallocation validation should trigger
|
||||
pe.paid_amount = 400
|
||||
pe.references[0].allocated_amount = 200
|
||||
pe.references[1].allocated_amount = 200
|
||||
self.assertRaises(frappe.ValidationError, pe.save)
|
||||
pe.delete()
|
||||
si1.cancel()
|
||||
si1.delete()
|
||||
|
||||
# Validate allocation on foreign currency
|
||||
si2 = create_sales_invoice(
|
||||
customer="_Test Customer USD",
|
||||
debit_to="_Test Receivable USD - _TC",
|
||||
currency="USD",
|
||||
conversion_rate=80,
|
||||
do_not_save=1,
|
||||
)
|
||||
si2.payment_terms_template = "Test Receivable Template"
|
||||
si2.save().submit()
|
||||
|
||||
si2.reload()
|
||||
pe = get_payment_entry(si2.doctype, si2.name).save()
|
||||
# Allocated amount should be according to the payment schedule
|
||||
for idx, schedule in enumerate(si2.payment_schedule):
|
||||
with self.subTest(idx=idx):
|
||||
self.assertEqual(flt(schedule.payment_amount), flt(pe.references[idx].allocated_amount))
|
||||
pe.save()
|
||||
|
||||
# Overallocation validation should trigger
|
||||
pe.paid_amount = 200
|
||||
pe.references[0].allocated_amount = 100
|
||||
pe.references[1].allocated_amount = 100
|
||||
self.assertRaises(frappe.ValidationError, pe.save)
|
||||
pe.delete()
|
||||
si2.cancel()
|
||||
si2.delete()
|
||||
|
||||
# Validate allocation in base/company currency on a foreign currency document
|
||||
# when invoice is made is foreign currency, but posted to base/company currency debtors account
|
||||
si3 = create_sales_invoice(
|
||||
customer=customer,
|
||||
currency="USD",
|
||||
conversion_rate=80,
|
||||
do_not_save=1,
|
||||
)
|
||||
si3.payment_terms_template = "Test Receivable Template"
|
||||
si3.save().submit()
|
||||
|
||||
si3.reload()
|
||||
pe = get_payment_entry(si3.doctype, si3.name).save()
|
||||
# Allocated amount should be equal to payment term outstanding
|
||||
self.assertEqual(len(pe.references), 2)
|
||||
for idx, ref in enumerate(pe.references):
|
||||
with self.subTest(idx=idx):
|
||||
self.assertEqual(ref.payment_term_outstanding, ref.allocated_amount)
|
||||
pe.save()
|
||||
|
||||
# Overallocation validation should trigger
|
||||
pe.paid_amount = 16000
|
||||
pe.references[0].allocated_amount = 8000
|
||||
pe.references[1].allocated_amount = 8000
|
||||
self.assertRaises(frappe.ValidationError, pe.save)
|
||||
pe.delete()
|
||||
si3.cancel()
|
||||
si3.delete()
|
||||
|
||||
|
||||
def create_payment_entry(**args):
|
||||
payment_entry = frappe.new_doc("Payment Entry")
|
||||
@@ -1126,3 +1245,17 @@ def create_payment_terms_template_with_discount(
|
||||
def create_payment_term(name):
|
||||
if not frappe.db.exists("Payment Term", name):
|
||||
frappe.get_doc({"doctype": "Payment Term", "payment_term_name": name}).insert()
|
||||
|
||||
|
||||
def create_customer(name="_Test Customer 2 USD", currency="USD"):
|
||||
customer = None
|
||||
if frappe.db.exists("Customer", name):
|
||||
customer = name
|
||||
else:
|
||||
customer = frappe.new_doc("Customer")
|
||||
customer.customer_name = name
|
||||
customer.default_currency = currency
|
||||
customer.type = "Individual"
|
||||
customer.save()
|
||||
customer = customer.name
|
||||
return customer
|
||||
|
||||
@@ -85,25 +85,29 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
|
||||
|
||||
// check for any running reconciliation jobs
|
||||
if (this.frm.doc.receivable_payable_account) {
|
||||
frappe.db.get_single_value("Accounts Settings", "auto_reconcile_payments").then((enabled) => {
|
||||
if(enabled) {
|
||||
this.frm.call({
|
||||
'method': "erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation.is_any_doc_running",
|
||||
"args": {
|
||||
for_filter: {
|
||||
company: this.frm.doc.company,
|
||||
party_type: this.frm.doc.party_type,
|
||||
party: this.frm.doc.party,
|
||||
receivable_payable_account: this.frm.doc.receivable_payable_account
|
||||
this.frm.call({
|
||||
doc: this.frm.doc,
|
||||
method: 'is_auto_process_enabled',
|
||||
callback: (r) => {
|
||||
if (r.message) {
|
||||
this.frm.call({
|
||||
'method': "erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation.is_any_doc_running",
|
||||
"args": {
|
||||
for_filter: {
|
||||
company: this.frm.doc.company,
|
||||
party_type: this.frm.doc.party_type,
|
||||
party: this.frm.doc.party,
|
||||
receivable_payable_account: this.frm.doc.receivable_payable_account
|
||||
}
|
||||
}
|
||||
}
|
||||
}).then(r => {
|
||||
if (r.message) {
|
||||
let doc_link = frappe.utils.get_form_link("Process Payment Reconciliation", r.message, true);
|
||||
let msg = __("Payment Reconciliation Job: {0} is running for this party. Can't reconcile now.", [doc_link]);
|
||||
this.frm.dashboard.add_comment(msg, "yellow");
|
||||
}
|
||||
});
|
||||
}).then(r => {
|
||||
if (r.message) {
|
||||
let doc_link = frappe.utils.get_form_link("Process Payment Reconciliation", r.message, true);
|
||||
let msg = __("Payment Reconciliation Job: {0} is running for this party. Can't reconcile now.", [doc_link]);
|
||||
this.frm.dashboard.add_comment(msg, "yellow");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -252,6 +252,10 @@ class PaymentReconciliation(Document):
|
||||
|
||||
return difference_amount
|
||||
|
||||
@frappe.whitelist()
|
||||
def is_auto_process_enabled(self):
|
||||
return frappe.db.get_single_value("Accounts Settings", "auto_reconcile_payments")
|
||||
|
||||
@frappe.whitelist()
|
||||
def calculate_difference_on_allocation_change(self, payment_entry, invoice, allocated_amount):
|
||||
invoice_exchange_map = self.get_invoice_exchange_map(invoice, payment_entry)
|
||||
@@ -343,7 +347,10 @@ class PaymentReconciliation(Document):
|
||||
payment_details = self.get_payment_details(row, dr_or_cr)
|
||||
reconciled_entry.append(payment_details)
|
||||
|
||||
if payment_details.difference_amount:
|
||||
if payment_details.difference_amount and row.reference_type not in [
|
||||
"Sales Invoice",
|
||||
"Purchase Invoice",
|
||||
]:
|
||||
self.make_difference_entry(payment_details)
|
||||
|
||||
if entry_list:
|
||||
@@ -429,6 +436,8 @@ class PaymentReconciliation(Document):
|
||||
journal_entry.save()
|
||||
journal_entry.submit()
|
||||
|
||||
return journal_entry
|
||||
|
||||
def get_payment_details(self, row, dr_or_cr):
|
||||
return frappe._dict(
|
||||
{
|
||||
@@ -594,6 +603,16 @@ class PaymentReconciliation(Document):
|
||||
|
||||
|
||||
def reconcile_dr_cr_note(dr_cr_notes, company):
|
||||
def get_difference_row(inv):
|
||||
if inv.difference_amount != 0 and inv.difference_account:
|
||||
difference_row = {
|
||||
"account": inv.difference_account,
|
||||
inv.dr_or_cr: abs(inv.difference_amount) if inv.difference_amount > 0 else 0,
|
||||
reconcile_dr_or_cr: abs(inv.difference_amount) if inv.difference_amount < 0 else 0,
|
||||
"cost_center": erpnext.get_default_cost_center(company),
|
||||
}
|
||||
return difference_row
|
||||
|
||||
for inv in dr_cr_notes:
|
||||
voucher_type = "Credit Note" if inv.voucher_type == "Sales Invoice" else "Debit Note"
|
||||
|
||||
@@ -638,5 +657,9 @@ def reconcile_dr_cr_note(dr_cr_notes, company):
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
if difference_entry := get_difference_row(inv):
|
||||
jv.append("accounts", difference_entry)
|
||||
|
||||
jv.flags.ignore_mandatory = True
|
||||
jv.submit()
|
||||
|
||||
@@ -11,10 +11,13 @@ from frappe.utils import add_days, flt, nowdate
|
||||
from erpnext import get_default_cost_center
|
||||
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.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.accounts.party import get_party_account
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
|
||||
test_dependencies = ["Item"]
|
||||
|
||||
|
||||
class TestPaymentReconciliation(FrappeTestCase):
|
||||
def setUp(self):
|
||||
@@ -163,7 +166,9 @@ class TestPaymentReconciliation(FrappeTestCase):
|
||||
def create_payment_reconciliation(self):
|
||||
pr = frappe.new_doc("Payment Reconciliation")
|
||||
pr.company = self.company
|
||||
pr.party_type = "Customer"
|
||||
pr.party_type = (
|
||||
self.party_type if hasattr(self, "party_type") and self.party_type else "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()
|
||||
@@ -890,6 +895,42 @@ class TestPaymentReconciliation(FrappeTestCase):
|
||||
self.assertEqual(pr.allocation[0].allocated_amount, 85)
|
||||
self.assertEqual(pr.allocation[0].difference_amount, 0)
|
||||
|
||||
def test_reconciliation_purchase_invoice_against_return(self):
|
||||
pi = make_purchase_invoice(
|
||||
supplier="_Test Supplier USD", currency="USD", conversion_rate=50
|
||||
).submit()
|
||||
|
||||
pi_return = frappe.get_doc(pi.as_dict())
|
||||
pi_return.name = None
|
||||
pi_return.docstatus = 0
|
||||
pi_return.is_return = 1
|
||||
pi_return.conversion_rate = 80
|
||||
pi_return.items[0].qty = -pi_return.items[0].qty
|
||||
pi_return.submit()
|
||||
|
||||
self.company = "_Test Company"
|
||||
self.party_type = "Supplier"
|
||||
self.customer = "_Test Supplier USD"
|
||||
|
||||
pr = self.create_payment_reconciliation()
|
||||
pr.get_unreconciled_entries()
|
||||
|
||||
invoices = []
|
||||
payments = []
|
||||
for invoice in pr.invoices:
|
||||
if invoice.invoice_number == pi.name:
|
||||
invoices.append(invoice.as_dict())
|
||||
break
|
||||
for payment in pr.payments:
|
||||
if payment.reference_name == pi_return.name:
|
||||
payments.append(payment.as_dict())
|
||||
break
|
||||
|
||||
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||
|
||||
# Should not raise frappe.exceptions.ValidationError: Total Debit must be equal to Total Credit.
|
||||
pr.reconcile()
|
||||
|
||||
|
||||
def make_customer(customer_name, currency=None):
|
||||
if not frappe.db.exists("Customer", customer_name):
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Payment Terms Template', {
|
||||
setup: function(frm) {
|
||||
refresh: function(frm) {
|
||||
frm.fields_dict.terms.grid.toggle_reqd("payment_term", frm.doc.allocate_payment_based_on_payment_terms);
|
||||
},
|
||||
|
||||
allocate_payment_based_on_payment_terms: function(frm) {
|
||||
frm.fields_dict.terms.grid.toggle_reqd("payment_term", frm.doc.allocate_payment_based_on_payment_terms);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@ from frappe.utils import flt
|
||||
class PaymentTermsTemplate(Document):
|
||||
def validate(self):
|
||||
self.validate_invoice_portion()
|
||||
self.check_duplicate_terms()
|
||||
self.validate_terms()
|
||||
|
||||
def validate_invoice_portion(self):
|
||||
total_portion = 0
|
||||
@@ -23,9 +23,12 @@ class PaymentTermsTemplate(Document):
|
||||
_("Combined invoice portion must equal 100%"), raise_exception=1, indicator="red"
|
||||
)
|
||||
|
||||
def check_duplicate_terms(self):
|
||||
def validate_terms(self):
|
||||
terms = []
|
||||
for term in self.terms:
|
||||
if self.allocate_payment_based_on_payment_terms and not term.payment_term:
|
||||
frappe.throw(_("Row {0}: Payment Term is mandatory").format(term.idx))
|
||||
|
||||
term_info = (term.payment_term, term.credit_days, term.credit_months, term.due_date_based_on)
|
||||
if term_info in terms:
|
||||
frappe.msgprint(
|
||||
|
||||
@@ -4,12 +4,13 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import add_days, flt
|
||||
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
get_accounting_dimensions,
|
||||
)
|
||||
from erpnext.accounts.utils import get_account_currency
|
||||
from erpnext.accounts.utils import get_account_currency, get_fiscal_year, validate_fiscal_year
|
||||
from erpnext.controllers.accounts_controller import AccountsController
|
||||
|
||||
|
||||
@@ -20,9 +21,17 @@ class PeriodClosingVoucher(AccountsController):
|
||||
|
||||
def on_submit(self):
|
||||
self.db_set("gle_processing_status", "In Progress")
|
||||
self.make_gl_entries()
|
||||
get_opening_entries = False
|
||||
|
||||
if not frappe.db.exists(
|
||||
"Period Closing Voucher", {"company": self.company, "docstatus": 1, "name": ("!=", self.name)}
|
||||
):
|
||||
get_opening_entries = True
|
||||
|
||||
self.make_gl_entries(get_opening_entries=get_opening_entries)
|
||||
|
||||
def on_cancel(self):
|
||||
self.validate_future_closing_vouchers()
|
||||
self.db_set("gle_processing_status", "In Progress")
|
||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
|
||||
gle_count = frappe.db.count(
|
||||
@@ -43,8 +52,27 @@ class PeriodClosingVoucher(AccountsController):
|
||||
else:
|
||||
make_reverse_gl_entries(voucher_type="Period Closing Voucher", voucher_no=self.name)
|
||||
|
||||
self.delete_closing_entries()
|
||||
|
||||
def validate_future_closing_vouchers(self):
|
||||
if frappe.db.exists(
|
||||
"Period Closing Voucher",
|
||||
{"posting_date": (">", self.posting_date), "docstatus": 1, "company": self.company},
|
||||
):
|
||||
frappe.throw(
|
||||
_(
|
||||
"You can not cancel this Period Closing Voucher, please cancel the future Period Closing Vouchers first"
|
||||
)
|
||||
)
|
||||
|
||||
def delete_closing_entries(self):
|
||||
closing_balance = frappe.qb.DocType("Account Closing Balance")
|
||||
frappe.qb.from_(closing_balance).delete().where(
|
||||
closing_balance.period_closing_voucher == self.name
|
||||
).run()
|
||||
|
||||
def validate_account_head(self):
|
||||
closing_account_type = frappe.db.get_value("Account", self.closing_account_head, "root_type")
|
||||
closing_account_type = frappe.get_cached_value("Account", self.closing_account_head, "root_type")
|
||||
|
||||
if closing_account_type not in ["Liability", "Equity"]:
|
||||
frappe.throw(
|
||||
@@ -57,8 +85,6 @@ class PeriodClosingVoucher(AccountsController):
|
||||
frappe.throw(_("Currency of the Closing Account must be {0}").format(company_currency))
|
||||
|
||||
def validate_posting_date(self):
|
||||
from erpnext.accounts.utils import get_fiscal_year, validate_fiscal_year
|
||||
|
||||
validate_fiscal_year(
|
||||
self.posting_date, self.fiscal_year, self.company, label=_("Posting Date"), doc=self
|
||||
)
|
||||
@@ -67,6 +93,8 @@ class PeriodClosingVoucher(AccountsController):
|
||||
self.posting_date, self.fiscal_year, company=self.company
|
||||
)[1]
|
||||
|
||||
self.check_if_previous_year_closed()
|
||||
|
||||
pce = frappe.db.sql(
|
||||
"""select name from `tabPeriod Closing Voucher`
|
||||
where posting_date > %s and fiscal_year = %s and docstatus = 1 and company = %s""",
|
||||
@@ -79,28 +107,65 @@ class PeriodClosingVoucher(AccountsController):
|
||||
)
|
||||
)
|
||||
|
||||
def make_gl_entries(self):
|
||||
def check_if_previous_year_closed(self):
|
||||
last_year_closing = add_days(self.year_start_date, -1)
|
||||
|
||||
previous_fiscal_year = get_fiscal_year(last_year_closing, company=self.company, boolean=True)
|
||||
|
||||
if previous_fiscal_year and not frappe.db.exists(
|
||||
"GL Entry", {"posting_date": ("<=", last_year_closing), "company": self.company}
|
||||
):
|
||||
return
|
||||
|
||||
if previous_fiscal_year and not frappe.db.exists(
|
||||
"Period Closing Voucher",
|
||||
{"posting_date": ("<=", last_year_closing), "docstatus": 1, "company": self.company},
|
||||
):
|
||||
frappe.throw(_("Previous Year is not closed, please close it first"))
|
||||
|
||||
def make_gl_entries(self, get_opening_entries=False):
|
||||
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)
|
||||
closing_entries = self.get_grouped_gl_entries(get_opening_entries=get_opening_entries)
|
||||
if len(gl_entries) > 5000:
|
||||
frappe.enqueue(
|
||||
process_gl_entries,
|
||||
gl_entries=gl_entries,
|
||||
closing_entries=closing_entries,
|
||||
voucher_name=self.name,
|
||||
company=self.company,
|
||||
closing_date=self.posting_date,
|
||||
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, closing_entries, self.name, self.company, self.posting_date)
|
||||
|
||||
def get_grouped_gl_entries(self, get_opening_entries=False):
|
||||
closing_entries = []
|
||||
for acc in self.get_balances_based_on_dimensions(
|
||||
group_by_account=True, for_aggregation=True, get_opening_entries=get_opening_entries
|
||||
):
|
||||
closing_entries.append(self.get_closing_entries(acc))
|
||||
|
||||
return closing_entries
|
||||
|
||||
def get_gl_entries(self):
|
||||
gl_entries = []
|
||||
|
||||
# pl account
|
||||
for acc in self.get_pl_balances_based_on_dimensions(group_by_account=True):
|
||||
for acc in self.get_balances_based_on_dimensions(
|
||||
group_by_account=True, report_type="Profit and Loss"
|
||||
):
|
||||
if flt(acc.bal_in_company_currency):
|
||||
gl_entries.append(self.get_gle_for_pl_account(acc))
|
||||
|
||||
# closing liability account
|
||||
for acc in self.get_pl_balances_based_on_dimensions(group_by_account=False):
|
||||
for acc in self.get_balances_based_on_dimensions(
|
||||
group_by_account=False, report_type="Profit and Loss"
|
||||
):
|
||||
if flt(acc.bal_in_company_currency):
|
||||
gl_entries.append(self.get_gle_for_closing_account(acc))
|
||||
|
||||
@@ -109,6 +174,8 @@ class PeriodClosingVoucher(AccountsController):
|
||||
def get_gle_for_pl_account(self, acc):
|
||||
gl_entry = self.get_gl_dict(
|
||||
{
|
||||
"company": self.company,
|
||||
"closing_date": self.posting_date,
|
||||
"account": acc.account,
|
||||
"cost_center": acc.cost_center,
|
||||
"finance_book": acc.finance_book,
|
||||
@@ -121,6 +188,7 @@ class PeriodClosingVoucher(AccountsController):
|
||||
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,
|
||||
"is_period_closing_voucher_entry": 1,
|
||||
},
|
||||
item=acc,
|
||||
)
|
||||
@@ -130,6 +198,8 @@ class PeriodClosingVoucher(AccountsController):
|
||||
def get_gle_for_closing_account(self, acc):
|
||||
gl_entry = self.get_gl_dict(
|
||||
{
|
||||
"company": self.company,
|
||||
"closing_date": self.posting_date,
|
||||
"account": self.closing_account_head,
|
||||
"cost_center": acc.cost_center,
|
||||
"finance_book": acc.finance_book,
|
||||
@@ -142,12 +212,36 @@ class PeriodClosingVoucher(AccountsController):
|
||||
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,
|
||||
"is_period_closing_voucher_entry": 1,
|
||||
},
|
||||
item=acc,
|
||||
)
|
||||
self.update_default_dimensions(gl_entry, acc)
|
||||
return gl_entry
|
||||
|
||||
def get_closing_entries(self, acc):
|
||||
closing_entry = self.get_gl_dict(
|
||||
{
|
||||
"company": self.company,
|
||||
"closing_date": self.posting_date,
|
||||
"period_closing_voucher": self.name,
|
||||
"account": acc.account,
|
||||
"cost_center": acc.cost_center,
|
||||
"finance_book": acc.finance_book,
|
||||
"account_currency": acc.account_currency,
|
||||
"debit_in_account_currency": flt(acc.debit_in_account_currency),
|
||||
"debit": flt(acc.debit),
|
||||
"credit_in_account_currency": flt(acc.credit_in_account_currency),
|
||||
"credit": flt(acc.credit),
|
||||
},
|
||||
item=acc,
|
||||
)
|
||||
|
||||
for dimension in self.accounting_dimensions:
|
||||
closing_entry.update({dimension: acc.get(dimension)})
|
||||
|
||||
return closing_entry
|
||||
|
||||
def update_default_dimensions(self, gl_entry, acc):
|
||||
if not self.accounting_dimensions:
|
||||
self.accounting_dimensions = get_accounting_dimensions()
|
||||
@@ -155,53 +249,95 @@ class PeriodClosingVoucher(AccountsController):
|
||||
for dimension in self.accounting_dimensions:
|
||||
gl_entry.update({dimension: acc.get(dimension)})
|
||||
|
||||
def get_pl_balances_based_on_dimensions(self, group_by_account=False):
|
||||
def get_balances_based_on_dimensions(
|
||||
self, group_by_account=False, report_type=None, for_aggregation=False, get_opening_entries=False
|
||||
):
|
||||
"""Get balance for dimension-wise pl accounts"""
|
||||
|
||||
dimension_fields = ["t1.cost_center", "t1.finance_book"]
|
||||
qb_dimension_fields = ["cost_center", "finance_book", "project"]
|
||||
|
||||
self.accounting_dimensions = get_accounting_dimensions()
|
||||
for dimension in self.accounting_dimensions:
|
||||
dimension_fields.append("t1.{0}".format(dimension))
|
||||
qb_dimension_fields.append(dimension)
|
||||
|
||||
if group_by_account:
|
||||
dimension_fields.append("t1.account")
|
||||
qb_dimension_fields.append("account")
|
||||
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
t1.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
|
||||
where
|
||||
t1.is_cancelled = 0
|
||||
and t1.account in (select name from `tabAccount` where report_type = 'Profit and Loss' and docstatus < 2 and company = %s)
|
||||
and t1.posting_date between %s and %s
|
||||
group by {dimension_fields}
|
||||
""".format(
|
||||
dimension_fields=", ".join(dimension_fields),
|
||||
),
|
||||
(self.company, self.get("year_start_date"), self.posting_date),
|
||||
as_dict=1,
|
||||
account_filters = {
|
||||
"company": self.company,
|
||||
"is_group": 0,
|
||||
}
|
||||
|
||||
if report_type:
|
||||
account_filters.update({"report_type": report_type})
|
||||
|
||||
accounts = frappe.get_all("Account", filters=account_filters, pluck="name")
|
||||
|
||||
gl_entry = frappe.qb.DocType("GL Entry")
|
||||
query = frappe.qb.from_(gl_entry).select(gl_entry.account, gl_entry.account_currency)
|
||||
|
||||
if not for_aggregation:
|
||||
query = query.select(
|
||||
(Sum(gl_entry.debit_in_account_currency) - Sum(gl_entry.credit_in_account_currency)).as_(
|
||||
"bal_in_account_currency"
|
||||
),
|
||||
(Sum(gl_entry.debit) - Sum(gl_entry.credit)).as_("bal_in_company_currency"),
|
||||
)
|
||||
else:
|
||||
query = query.select(
|
||||
(Sum(gl_entry.debit_in_account_currency)).as_("debit_in_account_currency"),
|
||||
(Sum(gl_entry.credit_in_account_currency)).as_("credit_in_account_currency"),
|
||||
(Sum(gl_entry.debit)).as_("debit"),
|
||||
(Sum(gl_entry.credit)).as_("credit"),
|
||||
)
|
||||
|
||||
for dimension in qb_dimension_fields:
|
||||
query = query.select(gl_entry[dimension])
|
||||
|
||||
query = query.where(
|
||||
(gl_entry.company == self.company)
|
||||
& (gl_entry.is_cancelled == 0)
|
||||
& (gl_entry.account.isin(accounts))
|
||||
)
|
||||
|
||||
if get_opening_entries:
|
||||
query = query.where(
|
||||
gl_entry.posting_date.between(self.get("year_start_date"), self.posting_date)
|
||||
| gl_entry.is_opening
|
||||
== "Yes"
|
||||
)
|
||||
else:
|
||||
query = query.where(
|
||||
gl_entry.posting_date.between(self.get("year_start_date"), self.posting_date)
|
||||
& gl_entry.is_opening
|
||||
== "No"
|
||||
)
|
||||
|
||||
def process_gl_entries(gl_entries):
|
||||
if for_aggregation:
|
||||
query = query.where(gl_entry.voucher_type != "Period Closing Voucher")
|
||||
|
||||
for dimension in qb_dimension_fields:
|
||||
query = query.groupby(gl_entry[dimension])
|
||||
|
||||
return query.run(as_dict=1)
|
||||
|
||||
|
||||
def process_gl_entries(gl_entries, closing_entries, voucher_name, company, closing_date):
|
||||
from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import (
|
||||
make_closing_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"
|
||||
)
|
||||
if gl_entries:
|
||||
make_gl_entries(gl_entries, merge_entries=False)
|
||||
|
||||
make_closing_entries(gl_entries + closing_entries, voucher_name, company, closing_date)
|
||||
frappe.db.set_value("Period Closing Voucher", voucher_name, "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"
|
||||
)
|
||||
frappe.db.set_value("Period Closing Voucher", voucher_name, "gle_processing_status", "Failed")
|
||||
|
||||
|
||||
def make_reverse_gl_entries(voucher_type, voucher_no):
|
||||
|
||||
@@ -16,16 +16,17 @@ from erpnext.accounts.utils import get_fiscal_year, now
|
||||
class TestPeriodClosingVoucher(unittest.TestCase):
|
||||
def test_closing_entry(self):
|
||||
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
|
||||
frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'")
|
||||
|
||||
company = create_company()
|
||||
cost_center = create_cost_center("Test Cost Center 1")
|
||||
|
||||
jv1 = make_journal_entry(
|
||||
posting_date="2021-03-15",
|
||||
amount=400,
|
||||
account1="Cash - TPC",
|
||||
account2="Sales - TPC",
|
||||
cost_center=cost_center,
|
||||
posting_date=now(),
|
||||
save=False,
|
||||
)
|
||||
jv1.company = company
|
||||
@@ -33,18 +34,18 @@ class TestPeriodClosingVoucher(unittest.TestCase):
|
||||
jv1.submit()
|
||||
|
||||
jv2 = make_journal_entry(
|
||||
posting_date="2021-03-15",
|
||||
amount=600,
|
||||
account1="Cost of Goods Sold - TPC",
|
||||
account2="Cash - TPC",
|
||||
cost_center=cost_center,
|
||||
posting_date=now(),
|
||||
save=False,
|
||||
)
|
||||
jv2.company = company
|
||||
jv2.save()
|
||||
jv2.submit()
|
||||
|
||||
pcv = self.make_period_closing_voucher()
|
||||
pcv = self.make_period_closing_voucher(posting_date="2021-03-31")
|
||||
surplus_account = pcv.closing_account_head
|
||||
|
||||
expected_gle = (
|
||||
@@ -65,6 +66,7 @@ class TestPeriodClosingVoucher(unittest.TestCase):
|
||||
|
||||
def test_cost_center_wise_posting(self):
|
||||
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
|
||||
frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'")
|
||||
|
||||
company = create_company()
|
||||
surplus_account = create_account()
|
||||
@@ -81,6 +83,7 @@ class TestPeriodClosingVoucher(unittest.TestCase):
|
||||
debit_to="Debtors - TPC",
|
||||
currency="USD",
|
||||
customer="_Test Customer USD",
|
||||
posting_date="2021-03-15",
|
||||
)
|
||||
create_sales_invoice(
|
||||
company=company,
|
||||
@@ -91,9 +94,10 @@ class TestPeriodClosingVoucher(unittest.TestCase):
|
||||
debit_to="Debtors - TPC",
|
||||
currency="USD",
|
||||
customer="_Test Customer USD",
|
||||
posting_date="2021-03-15",
|
||||
)
|
||||
|
||||
pcv = self.make_period_closing_voucher(submit=False)
|
||||
pcv = self.make_period_closing_voucher(posting_date="2021-03-31", submit=False)
|
||||
pcv.save()
|
||||
pcv.submit()
|
||||
surplus_account = pcv.closing_account_head
|
||||
@@ -128,12 +132,13 @@ class TestPeriodClosingVoucher(unittest.TestCase):
|
||||
|
||||
def test_period_closing_with_finance_book_entries(self):
|
||||
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
|
||||
frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'")
|
||||
|
||||
company = create_company()
|
||||
surplus_account = create_account()
|
||||
cost_center = create_cost_center("Test Cost Center 1")
|
||||
|
||||
si = create_sales_invoice(
|
||||
create_sales_invoice(
|
||||
company=company,
|
||||
income_account="Sales - TPC",
|
||||
expense_account="Cost of Goods Sold - TPC",
|
||||
@@ -142,6 +147,7 @@ class TestPeriodClosingVoucher(unittest.TestCase):
|
||||
debit_to="Debtors - TPC",
|
||||
currency="USD",
|
||||
customer="_Test Customer USD",
|
||||
posting_date="2021-03-15",
|
||||
)
|
||||
|
||||
jv = make_journal_entry(
|
||||
@@ -149,14 +155,14 @@ class TestPeriodClosingVoucher(unittest.TestCase):
|
||||
account2="Sales - TPC",
|
||||
amount=400,
|
||||
cost_center=cost_center,
|
||||
posting_date=now(),
|
||||
posting_date="2021-03-15",
|
||||
)
|
||||
jv.company = company
|
||||
jv.finance_book = create_finance_book().name
|
||||
jv.save()
|
||||
jv.submit()
|
||||
|
||||
pcv = self.make_period_closing_voucher()
|
||||
pcv = self.make_period_closing_voucher(posting_date="2021-03-31")
|
||||
surplus_account = pcv.closing_account_head
|
||||
|
||||
expected_gle = (
|
||||
@@ -194,14 +200,130 @@ class TestPeriodClosingVoucher(unittest.TestCase):
|
||||
repost_doc.posting_date = add_months(today(), 13)
|
||||
repost_doc.save()
|
||||
|
||||
def make_period_closing_voucher(self, submit=True):
|
||||
def test_gl_entries_restrictions(self):
|
||||
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
|
||||
frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'")
|
||||
|
||||
company = create_company()
|
||||
cost_center = create_cost_center("Test Cost Center 1")
|
||||
|
||||
self.make_period_closing_voucher(posting_date="2021-03-31")
|
||||
|
||||
jv1 = make_journal_entry(
|
||||
posting_date="2021-03-15",
|
||||
amount=400,
|
||||
account1="Cash - TPC",
|
||||
account2="Sales - TPC",
|
||||
cost_center=cost_center,
|
||||
save=False,
|
||||
)
|
||||
jv1.company = company
|
||||
jv1.save()
|
||||
|
||||
self.assertRaises(frappe.ValidationError, jv1.submit)
|
||||
|
||||
def test_closing_balance_with_dimensions(self):
|
||||
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
|
||||
frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'")
|
||||
frappe.db.sql("delete from `tabAccount Closing Balance` where company='Test PCV Company'")
|
||||
|
||||
company = create_company()
|
||||
cost_center1 = create_cost_center("Test Cost Center 1")
|
||||
cost_center2 = create_cost_center("Test Cost Center 2")
|
||||
|
||||
jv1 = make_journal_entry(
|
||||
posting_date="2021-03-15",
|
||||
amount=400,
|
||||
account1="Cash - TPC",
|
||||
account2="Sales - TPC",
|
||||
cost_center=cost_center1,
|
||||
save=False,
|
||||
)
|
||||
jv1.company = company
|
||||
jv1.save()
|
||||
jv1.submit()
|
||||
|
||||
jv2 = make_journal_entry(
|
||||
posting_date="2021-03-15",
|
||||
amount=200,
|
||||
account1="Cash - TPC",
|
||||
account2="Sales - TPC",
|
||||
cost_center=cost_center2,
|
||||
save=False,
|
||||
)
|
||||
jv2.company = company
|
||||
jv2.save()
|
||||
jv2.submit()
|
||||
|
||||
pcv1 = self.make_period_closing_voucher(posting_date="2021-03-31")
|
||||
|
||||
closing_balance = frappe.db.get_value(
|
||||
"Account Closing Balance",
|
||||
{
|
||||
"account": "Sales - TPC",
|
||||
"cost_center": cost_center1,
|
||||
"period_closing_voucher": pcv1.name,
|
||||
"is_period_closing_voucher_entry": 0,
|
||||
},
|
||||
["credit", "credit_in_account_currency"],
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
self.assertEqual(closing_balance.credit, 400)
|
||||
self.assertEqual(closing_balance.credit_in_account_currency, 400)
|
||||
|
||||
jv3 = make_journal_entry(
|
||||
posting_date="2022-03-15",
|
||||
amount=300,
|
||||
account1="Cash - TPC",
|
||||
account2="Sales - TPC",
|
||||
cost_center=cost_center2,
|
||||
save=False,
|
||||
)
|
||||
|
||||
jv3.company = company
|
||||
jv3.save()
|
||||
jv3.submit()
|
||||
|
||||
pcv2 = self.make_period_closing_voucher(posting_date="2022-03-31")
|
||||
|
||||
cc1_closing_balance = frappe.db.get_value(
|
||||
"Account Closing Balance",
|
||||
{
|
||||
"account": "Sales - TPC",
|
||||
"cost_center": cost_center1,
|
||||
"period_closing_voucher": pcv2.name,
|
||||
"is_period_closing_voucher_entry": 0,
|
||||
},
|
||||
["credit", "credit_in_account_currency"],
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
cc2_closing_balance = frappe.db.get_value(
|
||||
"Account Closing Balance",
|
||||
{
|
||||
"account": "Sales - TPC",
|
||||
"cost_center": cost_center2,
|
||||
"period_closing_voucher": pcv2.name,
|
||||
"is_period_closing_voucher_entry": 0,
|
||||
},
|
||||
["credit", "credit_in_account_currency"],
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
self.assertEqual(cc1_closing_balance.credit, 400)
|
||||
self.assertEqual(cc1_closing_balance.credit_in_account_currency, 400)
|
||||
self.assertEqual(cc2_closing_balance.credit, 500)
|
||||
self.assertEqual(cc2_closing_balance.credit_in_account_currency, 500)
|
||||
|
||||
def make_period_closing_voucher(self, posting_date=None, submit=True):
|
||||
surplus_account = create_account()
|
||||
cost_center = create_cost_center("Test Cost Center 1")
|
||||
pcv = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Period Closing Voucher",
|
||||
"transaction_date": today(),
|
||||
"posting_date": today(),
|
||||
"transaction_date": posting_date or today(),
|
||||
"posting_date": posting_date or today(),
|
||||
"company": "Test PCV Company",
|
||||
"fiscal_year": get_fiscal_year(today(), company="Test PCV Company")[0],
|
||||
"cost_center": cost_center,
|
||||
|
||||
@@ -123,22 +123,29 @@ frappe.ui.form.on('POS Closing Entry', {
|
||||
row.expected_amount = row.opening_amount;
|
||||
}
|
||||
|
||||
const pos_inv_promises = frm.doc.pos_transactions.map(
|
||||
row => frappe.db.get_doc("POS Invoice", row.pos_invoice)
|
||||
);
|
||||
|
||||
const pos_invoices = await Promise.all(pos_inv_promises);
|
||||
|
||||
for (let doc of pos_invoices) {
|
||||
frm.doc.grand_total += flt(doc.grand_total);
|
||||
frm.doc.net_total += flt(doc.net_total);
|
||||
frm.doc.total_quantity += flt(doc.total_qty);
|
||||
refresh_payments(doc, frm);
|
||||
refresh_taxes(doc, frm);
|
||||
refresh_fields(frm);
|
||||
set_html_data(frm);
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
frappe.call({
|
||||
method: 'erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry.get_pos_invoices',
|
||||
args: {
|
||||
start: frappe.datetime.get_datetime_as_string(frm.doc.period_start_date),
|
||||
end: frappe.datetime.get_datetime_as_string(frm.doc.period_end_date),
|
||||
pos_profile: frm.doc.pos_profile,
|
||||
user: frm.doc.user
|
||||
},
|
||||
callback: (r) => {
|
||||
let pos_invoices = r.message;
|
||||
for (let doc of pos_invoices) {
|
||||
frm.doc.grand_total += flt(doc.grand_total);
|
||||
frm.doc.net_total += flt(doc.net_total);
|
||||
frm.doc.total_quantity += flt(doc.total_qty);
|
||||
refresh_payments(doc, frm);
|
||||
refresh_taxes(doc, frm);
|
||||
refresh_fields(frm);
|
||||
set_html_data(frm);
|
||||
}
|
||||
}
|
||||
})
|
||||
])
|
||||
frappe.dom.unfreeze();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -16,8 +16,10 @@ from erpnext.stock.doctype.item.test_item import create_item
|
||||
class TestProcessDeferredAccounting(unittest.TestCase):
|
||||
def test_creation_of_ledger_entry_on_submit(self):
|
||||
"""test creation of gl entries on submission of document"""
|
||||
change_acc_settings(acc_frozen_upto="2023-05-31", book_deferred_entries_based_on="Months")
|
||||
|
||||
deferred_account = create_account(
|
||||
account_name="Deferred Revenue",
|
||||
account_name="Deferred Revenue for Accounts Frozen",
|
||||
parent_account="Current Liabilities - _TC",
|
||||
company="_Test Company",
|
||||
)
|
||||
@@ -29,11 +31,11 @@ class TestProcessDeferredAccounting(unittest.TestCase):
|
||||
item.save()
|
||||
|
||||
si = create_sales_invoice(
|
||||
item=item.name, update_stock=0, posting_date="2019-01-10", do_not_submit=True
|
||||
item=item.name, rate=3000, update_stock=0, posting_date="2023-07-01", do_not_submit=True
|
||||
)
|
||||
si.items[0].enable_deferred_revenue = 1
|
||||
si.items[0].service_start_date = "2019-01-10"
|
||||
si.items[0].service_end_date = "2019-03-15"
|
||||
si.items[0].service_start_date = "2023-05-01"
|
||||
si.items[0].service_end_date = "2023-07-31"
|
||||
si.items[0].deferred_revenue_account = deferred_account
|
||||
si.save()
|
||||
si.submit()
|
||||
@@ -41,9 +43,9 @@ class TestProcessDeferredAccounting(unittest.TestCase):
|
||||
process_deferred_accounting = doc = frappe.get_doc(
|
||||
dict(
|
||||
doctype="Process Deferred Accounting",
|
||||
posting_date="2019-01-01",
|
||||
start_date="2019-01-01",
|
||||
end_date="2019-01-31",
|
||||
posting_date="2023-07-01",
|
||||
start_date="2023-05-01",
|
||||
end_date="2023-06-30",
|
||||
type="Income",
|
||||
)
|
||||
)
|
||||
@@ -52,11 +54,16 @@ class TestProcessDeferredAccounting(unittest.TestCase):
|
||||
process_deferred_accounting.submit()
|
||||
|
||||
expected_gle = [
|
||||
[deferred_account, 33.85, 0.0, "2019-01-31"],
|
||||
["Sales - _TC", 0.0, 33.85, "2019-01-31"],
|
||||
["Debtors - _TC", 3000, 0.0, "2023-07-01"],
|
||||
[deferred_account, 0.0, 3000, "2023-07-01"],
|
||||
["Sales - _TC", 0.0, 1000, "2023-06-30"],
|
||||
[deferred_account, 1000, 0.0, "2023-06-30"],
|
||||
["Sales - _TC", 0.0, 1000, "2023-06-30"],
|
||||
[deferred_account, 1000, 0.0, "2023-06-30"],
|
||||
]
|
||||
|
||||
check_gl_entries(self, si.name, expected_gle, "2019-01-10")
|
||||
check_gl_entries(self, si.name, expected_gle, "2023-07-01")
|
||||
change_acc_settings()
|
||||
|
||||
def test_pda_submission_and_cancellation(self):
|
||||
pda = frappe.get_doc(
|
||||
@@ -70,3 +77,10 @@ class TestProcessDeferredAccounting(unittest.TestCase):
|
||||
)
|
||||
pda.submit()
|
||||
pda.cancel()
|
||||
|
||||
|
||||
def change_acc_settings(acc_frozen_upto="", book_deferred_entries_based_on="Days"):
|
||||
acc_settings = frappe.get_doc("Accounts Settings", "Accounts Settings")
|
||||
acc_settings.acc_frozen_upto = acc_frozen_upto
|
||||
acc_settings.book_deferred_entries_based_on = book_deferred_entries_based_on
|
||||
acc_settings.save()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="page-break">
|
||||
<div id="header-html" class="hidden-pdf">
|
||||
{% if letter_head %}
|
||||
{% if letter_head.content %}
|
||||
<div class="letter-head text-center">{{ letter_head.content }}</div>
|
||||
<hr style="height:2px;border-width:0;color:black;background-color:black;">
|
||||
{% endif %}
|
||||
|
||||
@@ -63,6 +63,20 @@ frappe.ui.form.on('Process Statement Of Accounts', {
|
||||
frm.set_value('to_date', frappe.datetime.get_today());
|
||||
}
|
||||
},
|
||||
report: function(frm){
|
||||
let filters = {
|
||||
'company': frm.doc.company,
|
||||
}
|
||||
if(frm.doc.report == 'Accounts Receivable'){
|
||||
filters['account_type'] = 'Receivable';
|
||||
}
|
||||
frm.set_query("account", function() {
|
||||
return {
|
||||
filters: filters
|
||||
};
|
||||
});
|
||||
|
||||
},
|
||||
customer_collection: function(frm){
|
||||
frm.set_value('collection_name', '');
|
||||
if(frm.doc.customer_collection){
|
||||
|
||||
@@ -6,17 +6,24 @@
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"report",
|
||||
"section_break_11",
|
||||
"from_date",
|
||||
"posting_date",
|
||||
"company",
|
||||
"account",
|
||||
"group_by",
|
||||
"cost_center",
|
||||
"territory",
|
||||
"column_break_14",
|
||||
"to_date",
|
||||
"finance_book",
|
||||
"currency",
|
||||
"project",
|
||||
"payment_terms_template",
|
||||
"sales_partner",
|
||||
"sales_person",
|
||||
"based_on_payment_terms",
|
||||
"section_break_3",
|
||||
"customer_collection",
|
||||
"collection_name",
|
||||
@@ -65,14 +72,14 @@
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.enable_auto_email == 0;",
|
||||
"depends_on": "eval:(doc.enable_auto_email == 0 && doc.report == 'General Ledger');",
|
||||
"fieldname": "from_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "From Date",
|
||||
"mandatory_depends_on": "eval:doc.frequency == '';"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.enable_auto_email == 0;",
|
||||
"depends_on": "eval:(doc.enable_auto_email == 0 && doc.report == 'General Ledger');",
|
||||
"fieldname": "to_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "To Date",
|
||||
@@ -85,6 +92,7 @@
|
||||
"options": "PSOA Cost Center"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: (doc.report == 'General Ledger');",
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Table MultiSelect",
|
||||
"label": "Project",
|
||||
@@ -102,7 +110,7 @@
|
||||
{
|
||||
"fieldname": "section_break_11",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "General Ledger Filters"
|
||||
"label": "Report Filters"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_14",
|
||||
@@ -162,12 +170,14 @@
|
||||
},
|
||||
{
|
||||
"default": "Group by Voucher (Consolidated)",
|
||||
"depends_on": "eval:(doc.report == 'General Ledger');",
|
||||
"fieldname": "group_by",
|
||||
"fieldtype": "Select",
|
||||
"label": "Group By",
|
||||
"options": "\nGroup by Voucher\nGroup by Voucher (Consolidated)"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: (doc.report == 'General Ledger');",
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Currency",
|
||||
@@ -295,6 +305,7 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval: (doc.report == 'General Ledger');",
|
||||
"fieldname": "show_net_values_in_party_account",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Net Values in Party Account"
|
||||
@@ -308,10 +319,59 @@
|
||||
{
|
||||
"fieldname": "column_break_ocfq",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "report",
|
||||
"fieldtype": "Select",
|
||||
"label": "Report",
|
||||
"options": "General Ledger\nAccounts Receivable",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "Today",
|
||||
"depends_on": "eval:(doc.report == 'Accounts Receivable');",
|
||||
"fieldname": "posting_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Posting Date"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: (doc.report == 'Accounts Receivable');",
|
||||
"fieldname": "payment_terms_template",
|
||||
"fieldtype": "Link",
|
||||
"label": "Payment Terms Template",
|
||||
"options": "Payment Terms Template"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: (doc.report == 'Accounts Receivable');",
|
||||
"fieldname": "sales_partner",
|
||||
"fieldtype": "Link",
|
||||
"label": "Sales Partner",
|
||||
"options": "Sales Partner"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: (doc.report == 'Accounts Receivable');",
|
||||
"fieldname": "sales_person",
|
||||
"fieldtype": "Link",
|
||||
"label": "Sales Person",
|
||||
"options": "Sales Person"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: (doc.report == 'Accounts Receivable');",
|
||||
"fieldname": "territory",
|
||||
"fieldtype": "Link",
|
||||
"label": "Territory",
|
||||
"options": "Territory"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:(doc.report == 'Accounts Receivable');",
|
||||
"fieldname": "based_on_payment_terms",
|
||||
"fieldtype": "Check",
|
||||
"label": "Based On Payment Terms"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2023-04-26 12:46:43.645455",
|
||||
"modified": "2023-06-23 10:13:15.051950",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Process Statement Of Accounts",
|
||||
|
||||
@@ -14,6 +14,7 @@ from frappe.www.printview import get_print_style
|
||||
|
||||
from erpnext import get_company_currency
|
||||
from erpnext.accounts.party import get_party_account_currency
|
||||
from erpnext.accounts.report.accounts_receivable.accounts_receivable import execute as get_ar_soa
|
||||
from erpnext.accounts.report.accounts_receivable_summary.accounts_receivable_summary import (
|
||||
execute as get_ageing,
|
||||
)
|
||||
@@ -42,29 +43,10 @@ class ProcessStatementOfAccounts(Document):
|
||||
def get_report_pdf(doc, consolidated=True):
|
||||
statement_dict = {}
|
||||
ageing = ""
|
||||
base_template_path = "frappe/www/printview.html"
|
||||
template_path = (
|
||||
"erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html"
|
||||
)
|
||||
|
||||
for entry in doc.customers:
|
||||
if doc.include_ageing:
|
||||
ageing_filters = frappe._dict(
|
||||
{
|
||||
"company": doc.company,
|
||||
"report_date": doc.to_date,
|
||||
"ageing_based_on": doc.ageing_based_on,
|
||||
"range1": 30,
|
||||
"range2": 60,
|
||||
"range3": 90,
|
||||
"range4": 120,
|
||||
"customer": entry.customer,
|
||||
}
|
||||
)
|
||||
col1, ageing = get_ageing(ageing_filters)
|
||||
|
||||
if ageing:
|
||||
ageing[0]["ageing_based_on"] = doc.ageing_based_on
|
||||
ageing = set_ageing(doc, entry)
|
||||
|
||||
tax_id = frappe.get_doc("Customer", entry.customer).tax_id
|
||||
presentation_currency = (
|
||||
@@ -72,59 +54,25 @@ def get_report_pdf(doc, consolidated=True):
|
||||
or doc.currency
|
||||
or get_company_currency(doc.company)
|
||||
)
|
||||
if doc.letter_head:
|
||||
from frappe.www.printview import get_letter_head
|
||||
|
||||
letter_head = get_letter_head(doc, 0)
|
||||
filters = get_common_filters(doc)
|
||||
|
||||
filters = frappe._dict(
|
||||
{
|
||||
"from_date": doc.from_date,
|
||||
"to_date": doc.to_date,
|
||||
"company": doc.company,
|
||||
"finance_book": doc.finance_book if doc.finance_book else None,
|
||||
"account": [doc.account] if doc.account else None,
|
||||
"party_type": "Customer",
|
||||
"party": [entry.customer],
|
||||
"party_name": [entry.customer_name] if entry.customer_name else None,
|
||||
"presentation_currency": presentation_currency,
|
||||
"group_by": doc.group_by,
|
||||
"currency": doc.currency,
|
||||
"cost_center": [cc.cost_center_name for cc in doc.cost_center],
|
||||
"project": [p.project_name for p in doc.project],
|
||||
"show_opening_entries": 0,
|
||||
"include_default_book_entries": 0,
|
||||
"tax_id": tax_id if tax_id else None,
|
||||
}
|
||||
)
|
||||
col, res = get_soa(filters)
|
||||
if doc.report == "General Ledger":
|
||||
filters.update(get_gl_filters(doc, entry, tax_id, presentation_currency))
|
||||
else:
|
||||
filters.update(get_ar_filters(doc, entry))
|
||||
|
||||
for x in [0, -2, -1]:
|
||||
res[x]["account"] = res[x]["account"].replace("'", "")
|
||||
if doc.report == "General Ledger":
|
||||
col, res = get_soa(filters)
|
||||
for x in [0, -2, -1]:
|
||||
res[x]["account"] = res[x]["account"].replace("'", "")
|
||||
if len(res) == 3:
|
||||
continue
|
||||
else:
|
||||
ar_res = get_ar_soa(filters)
|
||||
col, res = ar_res[0], ar_res[1]
|
||||
|
||||
if len(res) == 3:
|
||||
continue
|
||||
|
||||
html = frappe.render_template(
|
||||
template_path,
|
||||
{
|
||||
"filters": filters,
|
||||
"data": res,
|
||||
"ageing": ageing[0] if (doc.include_ageing and ageing) else None,
|
||||
"letter_head": letter_head if doc.letter_head else None,
|
||||
"terms_and_conditions": frappe.db.get_value(
|
||||
"Terms and Conditions", doc.terms_and_conditions, "terms"
|
||||
)
|
||||
if doc.terms_and_conditions
|
||||
else None,
|
||||
},
|
||||
)
|
||||
|
||||
html = frappe.render_template(
|
||||
base_template_path,
|
||||
{"body": html, "css": get_print_style(), "title": "Statement For " + entry.customer},
|
||||
)
|
||||
statement_dict[entry.customer] = html
|
||||
statement_dict[entry.customer] = get_html(doc, filters, entry, col, res, ageing)
|
||||
|
||||
if not bool(statement_dict):
|
||||
return False
|
||||
@@ -137,6 +85,110 @@ def get_report_pdf(doc, consolidated=True):
|
||||
return statement_dict
|
||||
|
||||
|
||||
def set_ageing(doc, entry):
|
||||
ageing_filters = frappe._dict(
|
||||
{
|
||||
"company": doc.company,
|
||||
"report_date": doc.to_date,
|
||||
"ageing_based_on": doc.ageing_based_on,
|
||||
"range1": 30,
|
||||
"range2": 60,
|
||||
"range3": 90,
|
||||
"range4": 120,
|
||||
"customer": entry.customer,
|
||||
}
|
||||
)
|
||||
col1, ageing = get_ageing(ageing_filters)
|
||||
|
||||
if ageing:
|
||||
ageing[0]["ageing_based_on"] = doc.ageing_based_on
|
||||
|
||||
return ageing
|
||||
|
||||
|
||||
def get_common_filters(doc):
|
||||
return frappe._dict(
|
||||
{
|
||||
"company": doc.company,
|
||||
"finance_book": doc.finance_book if doc.finance_book else None,
|
||||
"account": [doc.account] if doc.account else None,
|
||||
"cost_center": [cc.cost_center_name for cc in doc.cost_center],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def get_gl_filters(doc, entry, tax_id, presentation_currency):
|
||||
return {
|
||||
"from_date": doc.from_date,
|
||||
"to_date": doc.to_date,
|
||||
"party_type": "Customer",
|
||||
"party": [entry.customer],
|
||||
"party_name": [entry.customer_name] if entry.customer_name else None,
|
||||
"presentation_currency": presentation_currency,
|
||||
"group_by": doc.group_by,
|
||||
"currency": doc.currency,
|
||||
"project": [p.project_name for p in doc.project],
|
||||
"show_opening_entries": 0,
|
||||
"include_default_book_entries": 0,
|
||||
"tax_id": tax_id if tax_id else None,
|
||||
"show_net_values_in_party_account": doc.show_net_values_in_party_account,
|
||||
}
|
||||
|
||||
|
||||
def get_ar_filters(doc, entry):
|
||||
return {
|
||||
"report_date": doc.posting_date if doc.posting_date else None,
|
||||
"customer": entry.customer,
|
||||
"payment_terms_template": doc.payment_terms_template if doc.payment_terms_template else None,
|
||||
"sales_partner": doc.sales_partner if doc.sales_partner else None,
|
||||
"sales_person": doc.sales_person if doc.sales_person else None,
|
||||
"territory": doc.territory if doc.territory else None,
|
||||
"based_on_payment_terms": doc.based_on_payment_terms,
|
||||
"report_name": "Accounts Receivable",
|
||||
"ageing_based_on": doc.ageing_based_on,
|
||||
"range1": 30,
|
||||
"range2": 60,
|
||||
"range3": 90,
|
||||
"range4": 120,
|
||||
}
|
||||
|
||||
|
||||
def get_html(doc, filters, entry, col, res, ageing):
|
||||
base_template_path = "frappe/www/printview.html"
|
||||
template_path = (
|
||||
"erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html"
|
||||
if doc.report == "General Ledger"
|
||||
else "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html"
|
||||
)
|
||||
|
||||
if doc.letter_head:
|
||||
from frappe.www.printview import get_letter_head
|
||||
|
||||
letter_head = get_letter_head(doc, 0)
|
||||
|
||||
html = frappe.render_template(
|
||||
template_path,
|
||||
{
|
||||
"filters": filters,
|
||||
"data": res,
|
||||
"report": {"report_name": doc.report, "columns": col},
|
||||
"ageing": ageing[0] if (doc.include_ageing and ageing) else None,
|
||||
"letter_head": letter_head if doc.letter_head else None,
|
||||
"terms_and_conditions": frappe.db.get_value(
|
||||
"Terms and Conditions", doc.terms_and_conditions, "terms"
|
||||
)
|
||||
if doc.terms_and_conditions
|
||||
else None,
|
||||
},
|
||||
)
|
||||
|
||||
html = frappe.render_template(
|
||||
base_template_path,
|
||||
{"body": html, "css": get_print_style(), "title": "Statement For " + entry.customer},
|
||||
)
|
||||
return html
|
||||
|
||||
|
||||
def get_customers_based_on_territory_or_customer_group(customer_collection, collection_name):
|
||||
fields_dict = {
|
||||
"Customer Group": "customer_group",
|
||||
|
||||
@@ -0,0 +1,344 @@
|
||||
<style>
|
||||
.print-format {
|
||||
padding: 4mm;
|
||||
font-size: 8.0pt !important;
|
||||
}
|
||||
.print-format td {
|
||||
vertical-align:middle !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<h2 class="text-center" style="margin-top:0">{{ _(report.report_name) }}</h2>
|
||||
<h4 class="text-center">
|
||||
{{ filters.customer }}
|
||||
</h4>
|
||||
<h6 class="text-center">
|
||||
{% if (filters.tax_id) %}
|
||||
{{ _("Tax Id: ") }}{{ filters.tax_id }}
|
||||
{% endif %}
|
||||
</h6>
|
||||
<h5 class="text-center">
|
||||
{{ _(filters.ageing_based_on) }}
|
||||
{{ _("Until") }}
|
||||
{{ frappe.format(filters.report_date, 'Date') }}
|
||||
</h5>
|
||||
|
||||
<div class="clearfix">
|
||||
<div class="pull-left">
|
||||
{% if(filters.payment_terms) %}
|
||||
<strong>{{ _("Payment Terms") }}:</strong> {{ filters.payment_terms }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
{% if(filters.credit_limit) %}
|
||||
<strong>{{ _("Credit Limit") }}:</strong> {{ frappe.utils.fmt_money(filters.credit_limit) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if(filters.show_future_payments) %}
|
||||
{% set balance_row = data.slice(-1).pop() %}
|
||||
{% for i in report.columns %}
|
||||
{% if i.fieldname == 'age' %}
|
||||
{% set elem = i %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% set start = report.columns.findIndex(elem) %}
|
||||
{% set range1 = report.columns[start].label %}
|
||||
{% set range2 = report.columns[start+1].label %}
|
||||
{% set range3 = report.columns[start+2].label %}
|
||||
{% set range4 = report.columns[start+3].label %}
|
||||
{% set range5 = report.columns[start+4].label %}
|
||||
{% set range6 = report.columns[start+5].label %}
|
||||
|
||||
{% if(balance_row) %}
|
||||
<table class="table table-bordered table-condensed">
|
||||
<caption class="text-right">(Amount in {{ data[0]["currency"] ~ "" }})</caption>
|
||||
<colgroup>
|
||||
<col style="width: 30mm;">
|
||||
<col style="width: 18mm;">
|
||||
<col style="width: 18mm;">
|
||||
<col style="width: 18mm;">
|
||||
<col style="width: 18mm;">
|
||||
<col style="width: 18mm;">
|
||||
<col style="width: 18mm;">
|
||||
<col style="width: 18mm;">
|
||||
</colgroup>
|
||||
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ _(" ") }}</th>
|
||||
<th>{{ _(range1) }}</th>
|
||||
<th>{{ _(range2) }}</th>
|
||||
<th>{{ _(range3) }}</th>
|
||||
<th>{{ _(range4) }}</th>
|
||||
<th>{{ _(range5) }}</th>
|
||||
<th>{{ _(range6) }}</th>
|
||||
<th>{{ _("Total") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ _("Total Outstanding") }}</td>
|
||||
<td class="text-right">
|
||||
{{ format_number(balance_row["age"], null, 2) }}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{{ frappe.utils.fmt_money(balance_row["range1"], data[data.length-1]["currency"]) }}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{{ frappe.utils.fmt_money(balance_row["range2"], data[data.length-1]["currency"]) }}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{{ frappe.utils.fmt_money(balance_row["range3"], data[data.length-1]["currency"]) }}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{{ frappe.utils.fmt_money(balance_row["range4"], data[data.length-1]["currency"]) }}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{{ frappe.utils.fmt_money(balance_row["range5"], data[data.length-1]["currency"]) }}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{{ frappe.utils.fmt_money(flt(balance_row["outstanding"]), data[data.length-1]["currency"]) }}
|
||||
</td>
|
||||
</tr>
|
||||
<td>{{ _("Future Payments") }}</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td class="text-right">
|
||||
{{ frappe.utils.fmt_money(flt(balance_row[("future_amount")]), data[data.length-1]["currency"]) }}
|
||||
</td>
|
||||
<tr class="cvs-footer">
|
||||
<th class="text-left">{{ _("Cheques Required") }}</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th class="text-right">
|
||||
{{ frappe.utils.fmt_money(flt(balance_row["outstanding"] - balance_row[("future_amount")]), data[data.length-1]["currency"]) }}</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
{% if(report.report_name == "Accounts Receivable" or report.report_name == "Accounts Payable") %}
|
||||
<th style="width: 10%">{{ _("Date") }}</th>
|
||||
<th style="width: 4%">{{ _("Age (Days)") }}</th>
|
||||
|
||||
{% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %}
|
||||
<th style="width: 14%">{{ _("Reference") }}</th>
|
||||
<th style="width: 10%">{{ _("Sales Person") }}</th>
|
||||
{% else %}
|
||||
<th style="width: 24%">{{ _("Reference") }}</th>
|
||||
{% endif %}
|
||||
{% if not(filters.show_future_payments) %}
|
||||
<th style="width: 20%">
|
||||
{% if (filters.customer or filters.supplier or filters.customer_name) %}
|
||||
{{ _("Remarks") }}
|
||||
{% else %}
|
||||
{{ _("Party") }}
|
||||
{% endif %}
|
||||
</th>
|
||||
{% endif %}
|
||||
<th style="width: 10%; text-align: right">{{ _("Invoiced Amount") }}</th>
|
||||
{% if not(filters.show_future_payments) %}
|
||||
<th style="width: 10%; text-align: right">{{ _("Paid Amount") }}</th>
|
||||
<th style="width: 10%; text-align: right">
|
||||
{% if report.report_name == "Accounts Receivable" %}
|
||||
{{ _('Credit Note') }}
|
||||
{% else %}
|
||||
{{ _('Debit Note') }}
|
||||
{% endif %}
|
||||
</th>
|
||||
{% endif %}
|
||||
<th style="width: 10%; text-align: right">{{ _("Outstanding Amount") }}</th>
|
||||
{% if(filters.show_future_payments) %}
|
||||
{% if(report.report_name == "Accounts Receivable") %}
|
||||
<th style="width: 12%">{{ _("Customer LPO No.") }}</th>
|
||||
{% endif %}
|
||||
<th style="width: 10%">{{ _("Future Payment Ref") }}</th>
|
||||
<th style="width: 10%">{{ _("Future Payment Amount") }}</th>
|
||||
<th style="width: 10%">{{ _("Remaining Balance") }}</th>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<th style="width: 40%">
|
||||
{% if (filters.customer or filters.supplier or filters.customer_name) %}
|
||||
{{ _("Remarks")}}
|
||||
{% else %}
|
||||
{{ _("Party") }}
|
||||
{% endif %}
|
||||
</th>
|
||||
<th style="width: 15%">{{ _("Total Invoiced Amount") }}</th>
|
||||
<th style="width: 15%">{{ _("Total Paid Amount") }}</th>
|
||||
<th style="width: 15%">
|
||||
{% if report.report_name == "Accounts Receivable Summary" %}
|
||||
{{ _('Credit Note Amount') }}
|
||||
{% else %}
|
||||
{{ _('Debit Note Amount') }}
|
||||
{% endif %}
|
||||
</th>
|
||||
<th style="width: 15%">{{ _("Total Outstanding Amount") }}</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for i in range(data|length) %}
|
||||
<tr>
|
||||
{% if(report.report_name == "Accounts Receivable" or report.report_name == "Accounts Payable") %}
|
||||
{% if(data[i]["party"]) %}
|
||||
<td>{{ (data[i]["posting_date"]) }}</td>
|
||||
<td style="text-align: right">{{ data[i]["age"] }}</td>
|
||||
<td>
|
||||
{% if not(filters.show_future_payments) %}
|
||||
{{ data[i]["voucher_type"] }}
|
||||
<br>
|
||||
{% endif %}
|
||||
{{ data[i]["voucher_no"] }}
|
||||
</td>
|
||||
|
||||
{% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %}
|
||||
<td>{{ data[i]["sales_person"] }}</td>
|
||||
{% endif %}
|
||||
|
||||
{% if not (filters.show_future_payments) %}
|
||||
<td>
|
||||
{% if(not(filters.customer or filters.supplier or filters.customer_name)) %}
|
||||
{{ data[i]["party"] }}
|
||||
{% if(data[i]["customer_name"] and data[i]["customer_name"] != data[i]["party"]) %}
|
||||
<br> {{ data[i]["customer_name"] }}
|
||||
{% elif(data[i]["supplier_name"] != data[i]["party"]) %}
|
||||
<br> {{ data[i]["supplier_name"] }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<div>
|
||||
{% if data[i]["remarks"] %}
|
||||
{{ _("Remarks") }}:
|
||||
{{ data[i]["remarks"] }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
{% endif %}
|
||||
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(data[i]["invoiced"], currency=data[i]["currency"]) }}</td>
|
||||
|
||||
{% if not(filters.show_future_payments) %}
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }}</td>
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }}</td>
|
||||
{% endif %}
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(data[i]["outstanding"], currency=data[i]["currency"]) }}</td>
|
||||
|
||||
{% if(filters.show_future_payments) %}
|
||||
{% if(report.report_name == "Accounts Receivable") %}
|
||||
<td style="text-align: right">
|
||||
{{ data[i]["po_no"] }}</td>
|
||||
{% endif %}
|
||||
<td style="text-align: right">{{ data[i]["future_ref"] }}</td>
|
||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["future_amount"], currency=data[i]["currency"]) }}</td>
|
||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["remaining_balance"], currency=data[i]["currency"]) }}</td>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<td></td>
|
||||
{% if not(filters.show_future_payments) %}
|
||||
<td></td>
|
||||
{% endif %}
|
||||
{% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %}
|
||||
<td></td>
|
||||
{% endif %}
|
||||
<td></td>
|
||||
<td style="text-align: right"><b>{{ _("Total") }}</b></td>
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(data[i]["invoiced"], data[i]["currency"]) }}</td>
|
||||
|
||||
{% if not(filters.show_future_payments) %}
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }}</td>
|
||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }} </td>
|
||||
{% endif %}
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(data[i]["outstanding"], currency=data[i]["currency"]) }}</td>
|
||||
|
||||
{% if(filters.show_future_payments) %}
|
||||
{% if(report.report_name == "Accounts Receivable") %}
|
||||
<td style="text-align: right">
|
||||
{{ data[i]["po_no"] }}</td>
|
||||
{% endif %}
|
||||
<td style="text-align: right">{{ data[i]["future_ref"] }}</td>
|
||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["future_amount"], currency=data[i]["currency"]) }}</td>
|
||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["remaining_balance"], currency=data[i]["currency"]) }}</td>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if(data[i]["party"] or " ") %}
|
||||
{% if not(data[i]["is_total_row"]) %}
|
||||
<td>
|
||||
{% if(not(filters.customer | filters.supplier)) %}
|
||||
{{ data[i]["party"] }}
|
||||
{% if(data[i]["customer_name"] and data[i]["customer_name"] != data[i]["party"]) %}
|
||||
<br> {{ data[i]["customer_name"] }}
|
||||
{% elif(data[i]["supplier_name"] != data[i]["party"]) %}
|
||||
<br> {{ data[i]["supplier_name"] }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<br>{{ _("Remarks") }}:
|
||||
{{ data[i]["remarks"] }}
|
||||
</td>
|
||||
{% else %}
|
||||
<td><b>{{ _("Total") }}</b></td>
|
||||
{% endif %}
|
||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["invoiced"], currency=data[i]["currency"]) }}</td>
|
||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }}</td>
|
||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }}</td>
|
||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["outstanding"], currency=data[i]["currency"]) }}</td>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="invoiced"), currency=data[0]["currency"]) }}</b></td>
|
||||
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="paid"), currency=data[0]["currency"]) }}</b></td>
|
||||
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="credit_note"), currency=data[0]["currency"]) }}</b></td>
|
||||
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="outstanding"), currency=data[0]["currency"]) }}</b></td>
|
||||
</tbody>
|
||||
</table>
|
||||
<br>
|
||||
{% if ageing %}
|
||||
<h4 class="text-center">{{ _("Ageing Report based on ") }} {{ ageing.ageing_based_on }}
|
||||
{{ _("up to " ) }} {{ frappe.format(filters.report_date, 'Date')}}
|
||||
</h4>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 25%">30 Days</th>
|
||||
<th style="width: 25%">60 Days</th>
|
||||
<th style="width: 25%">90 Days</th>
|
||||
<th style="width: 25%">120 Days</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range1, currency=data[0]["currency"]) }}</td>
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range2, currency=data[0]["currency"]) }}</td>
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range3, currency=data[0]["currency"]) }}</td>
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range4, currency=data[0]["currency"]) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
<p class="text-right text-muted">{{ _("Printed On ") }}{{ frappe.utils.now() }}</p>
|
||||
@@ -547,6 +547,7 @@
|
||||
"depends_on": "update_stock",
|
||||
"fieldname": "rejected_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Rejected Warehouse",
|
||||
"no_copy": 1,
|
||||
"options": "Warehouse",
|
||||
@@ -1573,7 +1574,7 @@
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-04-29 12:57:50.832598",
|
||||
"modified": "2023-07-04 17:23:59.145031",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
||||
@@ -637,13 +637,6 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
||||
gle_filters={"account": "Stock In Hand - TCP1"},
|
||||
)
|
||||
|
||||
# assert loss booked in COGS
|
||||
self.assertGLEs(
|
||||
return_pi,
|
||||
[{"credit": 0, "debit": 200}],
|
||||
gle_filters={"account": "Cost of Goods Sold - TCP1"},
|
||||
)
|
||||
|
||||
def test_return_with_lcv(self):
|
||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||
from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import (
|
||||
@@ -1662,6 +1655,21 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
||||
|
||||
self.assertTrue(return_pi.docstatus == 1)
|
||||
|
||||
def test_gl_entries_for_standalone_debit_note(self):
|
||||
make_purchase_invoice(qty=5, rate=500, update_stock=True)
|
||||
|
||||
returned_inv = make_purchase_invoice(qty=-5, rate=5, update_stock=True, is_return=True)
|
||||
|
||||
# override the rate with valuation rate
|
||||
sle = frappe.get_all(
|
||||
"Stock Ledger Entry",
|
||||
fields=["stock_value_difference", "actual_qty"],
|
||||
filters={"voucher_no": returned_inv.name},
|
||||
)[0]
|
||||
|
||||
rate = flt(sle.stock_value_difference) / flt(sle.actual_qty)
|
||||
self.assertAlmostEqual(returned_inv.items[0].rate, rate)
|
||||
|
||||
|
||||
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
|
||||
gl_entries = frappe.db.sql(
|
||||
|
||||
@@ -176,6 +176,7 @@
|
||||
"fieldname": "received_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Received Qty",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@@ -420,6 +421,7 @@
|
||||
{
|
||||
"fieldname": "rejected_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Rejected Warehouse",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
@@ -880,7 +882,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-11-29 13:01:20.438217",
|
||||
"modified": "2023-07-04 17:22:21.501152",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
@@ -890,4 +892,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -670,19 +670,6 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
}
|
||||
}
|
||||
|
||||
// expense account
|
||||
frm.fields_dict['items'].grid.get_field('expense_account').get_query = function(doc) {
|
||||
if (erpnext.is_perpetual_inventory_enabled(doc.company)) {
|
||||
return {
|
||||
filters: {
|
||||
'report_type': 'Profit and Loss',
|
||||
'company': doc.company,
|
||||
"is_group": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// discount account
|
||||
frm.fields_dict['items'].grid.get_field('discount_account').get_query = function(doc) {
|
||||
return {
|
||||
@@ -900,6 +887,8 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
frm.events.append_time_log(frm, timesheet, 1.0);
|
||||
}
|
||||
});
|
||||
frm.refresh_field("timesheets");
|
||||
frm.trigger("calculate_timesheet_totals");
|
||||
},
|
||||
|
||||
async get_exchange_rate(frm, from_currency, to_currency) {
|
||||
@@ -939,9 +928,6 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
row.billing_amount = flt(time_log.billing_amount) * flt(exchange_rate);
|
||||
row.timesheet_detail = time_log.name;
|
||||
row.project_name = time_log.project_name;
|
||||
|
||||
frm.refresh_field("timesheets");
|
||||
frm.trigger("calculate_timesheet_totals");
|
||||
},
|
||||
|
||||
calculate_timesheet_totals: function(frm) {
|
||||
|
||||
@@ -320,6 +320,7 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval: !doc.is_debit_note",
|
||||
"fieldname": "is_return",
|
||||
"fieldtype": "Check",
|
||||
"hide_days": 1,
|
||||
@@ -1959,6 +1960,7 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval: !doc.is_return",
|
||||
"description": "Issue a debit note with 0 qty against an existing Sales Invoice",
|
||||
"fieldname": "is_debit_note",
|
||||
"fieldtype": "Check",
|
||||
@@ -2153,7 +2155,7 @@
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2023-04-28 14:15:59.901154",
|
||||
"modified": "2023-06-19 16:02:05.309332",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
||||
@@ -1012,10 +1012,16 @@ class SalesInvoice(SellingController):
|
||||
|
||||
def check_prev_docstatus(self):
|
||||
for d in self.get("items"):
|
||||
if d.sales_order and frappe.db.get_value("Sales Order", d.sales_order, "docstatus") != 1:
|
||||
if (
|
||||
d.sales_order
|
||||
and frappe.db.get_value("Sales Order", d.sales_order, "docstatus", cache=True) != 1
|
||||
):
|
||||
frappe.throw(_("Sales Order {0} is not submitted").format(d.sales_order))
|
||||
|
||||
if d.delivery_note and frappe.db.get_value("Delivery Note", d.delivery_note, "docstatus") != 1:
|
||||
if (
|
||||
d.delivery_note
|
||||
and frappe.db.get_value("Delivery Note", d.delivery_note, "docstatus", cache=True) != 1
|
||||
):
|
||||
throw(_("Delivery Note {0} is not submitted").format(d.delivery_note))
|
||||
|
||||
def make_gl_entries(self, gl_entries=None, from_repost=False):
|
||||
|
||||
@@ -1900,16 +1900,22 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
si = self.create_si_to_test_tax_breakup()
|
||||
|
||||
itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(si)
|
||||
itemised_tax_data = get_itemised_tax_breakup_data(si)
|
||||
|
||||
expected_itemised_tax = {
|
||||
"_Test Item": {"Service Tax": {"tax_rate": 10.0, "tax_amount": 1000.0}},
|
||||
"_Test Item 2": {"Service Tax": {"tax_rate": 10.0, "tax_amount": 500.0}},
|
||||
}
|
||||
expected_itemised_taxable_amount = {"_Test Item": 10000.0, "_Test Item 2": 5000.0}
|
||||
expected_itemised_tax = [
|
||||
{
|
||||
"item": "_Test Item",
|
||||
"taxable_amount": 10000.0,
|
||||
"Service Tax": {"tax_rate": 10.0, "tax_amount": 1000.0},
|
||||
},
|
||||
{
|
||||
"item": "_Test Item 2",
|
||||
"taxable_amount": 5000.0,
|
||||
"Service Tax": {"tax_rate": 10.0, "tax_amount": 500.0},
|
||||
},
|
||||
]
|
||||
|
||||
self.assertEqual(itemised_tax, expected_itemised_tax)
|
||||
self.assertEqual(itemised_taxable_amount, expected_itemised_taxable_amount)
|
||||
self.assertEqual(itemised_tax_data, expected_itemised_tax)
|
||||
|
||||
frappe.flags.country = None
|
||||
|
||||
|
||||
@@ -603,7 +603,8 @@
|
||||
"in_list_view": 1,
|
||||
"label": "Batch No",
|
||||
"options": "Batch",
|
||||
"print_hide": 1
|
||||
"print_hide": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "col_break5",
|
||||
@@ -890,7 +891,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-10-17 12:51:44.825398",
|
||||
"modified": "2023-07-25 11:58:10.723833",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Item",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2017-12-25 16:50:53.878430",
|
||||
"doctype": "DocType",
|
||||
@@ -111,11 +112,12 @@
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"modified": "2019-11-17 23:24:11.395882",
|
||||
"links": [],
|
||||
"modified": "2023-04-10 22:02:20.406087",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Shareholder",
|
||||
"name_case": "Title Case",
|
||||
"naming_rule": "By \"Naming Series\" field",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@@ -158,6 +160,7 @@
|
||||
"search_fields": "folio_no",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "title",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -3,8 +3,10 @@
|
||||
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe import _, qb
|
||||
from frappe.model.document import Document
|
||||
from frappe.query_builder import Criterion
|
||||
from frappe.query_builder.functions import Abs, Sum
|
||||
from frappe.utils import cint, flt, getdate
|
||||
|
||||
|
||||
@@ -346,26 +348,33 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
|
||||
def get_advance_vouchers(
|
||||
parties, company=None, from_date=None, to_date=None, party_type="Supplier"
|
||||
):
|
||||
# for advance vouchers, debit and credit is reversed
|
||||
dr_or_cr = "debit" if party_type == "Supplier" else "credit"
|
||||
"""
|
||||
Use Payment Ledger to fetch unallocated Advance Payments
|
||||
"""
|
||||
|
||||
filters = {
|
||||
dr_or_cr: [">", 0],
|
||||
"is_opening": "No",
|
||||
"is_cancelled": 0,
|
||||
"party_type": party_type,
|
||||
"party": ["in", parties],
|
||||
}
|
||||
ple = qb.DocType("Payment Ledger Entry")
|
||||
|
||||
if party_type == "Customer":
|
||||
filters.update({"against_voucher": ["is", "not set"]})
|
||||
conditions = []
|
||||
|
||||
conditions.append(ple.amount.lt(0))
|
||||
conditions.append(ple.delinked == 0)
|
||||
conditions.append(ple.party_type == party_type)
|
||||
conditions.append(ple.party.isin(parties))
|
||||
conditions.append(ple.voucher_no == ple.against_voucher_no)
|
||||
|
||||
if company:
|
||||
filters["company"] = company
|
||||
if from_date and to_date:
|
||||
filters["posting_date"] = ["between", (from_date, to_date)]
|
||||
conditions.append(ple.company == company)
|
||||
|
||||
return frappe.get_all("GL Entry", filters=filters, distinct=1, pluck="voucher_no") or [""]
|
||||
if from_date and to_date:
|
||||
conditions.append(ple.posting_date[from_date:to_date])
|
||||
|
||||
advances = (
|
||||
qb.from_(ple).select(ple.voucher_no).distinct().where(Criterion.all(conditions)).run(as_list=1)
|
||||
)
|
||||
if advances:
|
||||
advances = [x[0] for x in advances]
|
||||
|
||||
return advances
|
||||
|
||||
|
||||
def get_taxes_deducted_on_advances_allocated(inv, tax_details):
|
||||
@@ -499,6 +508,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
|
||||
|
||||
def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers):
|
||||
tcs_amount = 0
|
||||
ple = qb.DocType("Payment Ledger Entry")
|
||||
|
||||
# sum of debit entries made from sales invoices
|
||||
invoiced_amt = (
|
||||
@@ -516,18 +526,20 @@ def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers):
|
||||
)
|
||||
|
||||
# sum of credit entries made from PE / JV with unset 'against voucher'
|
||||
|
||||
conditions = []
|
||||
conditions.append(ple.amount.lt(0))
|
||||
conditions.append(ple.delinked == 0)
|
||||
conditions.append(ple.party.isin(parties))
|
||||
conditions.append(ple.voucher_no == ple.against_voucher_no)
|
||||
conditions.append(ple.company == inv.company)
|
||||
|
||||
advances = (
|
||||
qb.from_(ple).select(Abs(Sum(ple.amount))).where(Criterion.all(conditions)).run(as_list=1)
|
||||
)
|
||||
|
||||
advance_amt = (
|
||||
frappe.db.get_value(
|
||||
"GL Entry",
|
||||
{
|
||||
"is_cancelled": 0,
|
||||
"party": ["in", parties],
|
||||
"company": inv.company,
|
||||
"voucher_no": ["in", adv_vouchers],
|
||||
},
|
||||
"sum(credit)",
|
||||
)
|
||||
or 0.0
|
||||
qb.from_(ple).select(Abs(Sum(ple.amount))).where(Criterion.all(conditions)).run()[0][0] or 0.0
|
||||
)
|
||||
|
||||
# sum of credit entries made from sales invoice
|
||||
@@ -573,7 +585,9 @@ def get_tds_amount_from_ldc(ldc, parties, tax_details, posting_date, net_total):
|
||||
"supplier": ("in", parties),
|
||||
"apply_tds": 1,
|
||||
"docstatus": 1,
|
||||
"tax_withholding_category": ldc.tax_withholding_category,
|
||||
"posting_date": ("between", (ldc.valid_from, ldc.valid_upto)),
|
||||
"company": ldc.company,
|
||||
},
|
||||
"sum(tax_withholding_net_total)",
|
||||
)
|
||||
@@ -603,7 +617,7 @@ def is_valid_certificate(
|
||||
):
|
||||
valid = False
|
||||
|
||||
available_amount = flt(certificate_limit) - flt(deducted_amount) - flt(current_amount)
|
||||
available_amount = flt(certificate_limit) - flt(deducted_amount)
|
||||
|
||||
if (getdate(valid_from) <= getdate(posting_date) <= getdate(valid_upto)) and available_amount > 0:
|
||||
valid = True
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import change_settings
|
||||
from frappe.utils import today
|
||||
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
@@ -152,6 +153,64 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
||||
for d in reversed(invoices):
|
||||
d.cancel()
|
||||
|
||||
@change_settings(
|
||||
"Accounts Settings",
|
||||
{"unlink_payment_on_cancellation_of_invoice": 1},
|
||||
)
|
||||
def test_tcs_on_unallocated_advance_payments(self):
|
||||
frappe.db.set_value(
|
||||
"Customer", "Test TCS Customer", "tax_withholding_category", "Cumulative Threshold TCS"
|
||||
)
|
||||
|
||||
vouchers = []
|
||||
|
||||
# create advance payment
|
||||
pe = create_payment_entry(
|
||||
payment_type="Receive", party_type="Customer", party="Test TCS Customer", paid_amount=20000
|
||||
)
|
||||
pe.paid_from = "Debtors - _TC"
|
||||
pe.paid_to = "Cash - _TC"
|
||||
pe.submit()
|
||||
vouchers.append(pe)
|
||||
|
||||
# create invoice
|
||||
si1 = create_sales_invoice(customer="Test TCS Customer", rate=5000)
|
||||
si1.submit()
|
||||
vouchers.append(si1)
|
||||
|
||||
# reconcile
|
||||
pr = frappe.get_doc("Payment Reconciliation")
|
||||
pr.company = "_Test Company"
|
||||
pr.party_type = "Customer"
|
||||
pr.party = "Test TCS Customer"
|
||||
pr.receivable_payable_account = "Debtors - _TC"
|
||||
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()
|
||||
|
||||
# make another invoice
|
||||
# sum of unallocated amount from payment entry and this sales invoice will breach cumulative threashold
|
||||
# TDS should be calculated
|
||||
si2 = create_sales_invoice(customer="Test TCS Customer", rate=15000)
|
||||
si2.submit()
|
||||
vouchers.append(si2)
|
||||
|
||||
si3 = create_sales_invoice(customer="Test TCS Customer", rate=10000)
|
||||
si3.submit()
|
||||
vouchers.append(si3)
|
||||
|
||||
# assert tax collection on total invoice amount created until now
|
||||
tcs_charged = sum([d.base_tax_amount for d in si2.taxes if d.account_head == "TCS - _TC"])
|
||||
tcs_charged += sum([d.base_tax_amount for d in si3.taxes if d.account_head == "TCS - _TC"])
|
||||
self.assertEqual(tcs_charged, 1500)
|
||||
|
||||
# cancel invoice and payments to avoid clashing
|
||||
for d in reversed(vouchers):
|
||||
d.reload()
|
||||
d.cancel()
|
||||
|
||||
def test_tds_calculation_on_net_total(self):
|
||||
frappe.db.set_value(
|
||||
"Supplier", "Test TDS Supplier4", "tax_withholding_category", "Cumulative Threshold TDS"
|
||||
|
||||
@@ -13,14 +13,11 @@ import erpnext
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
get_accounting_dimensions,
|
||||
)
|
||||
from erpnext.accounts.doctype.accounting_period.accounting_period import ClosedAccountingPeriod
|
||||
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
|
||||
from erpnext.accounts.utils import create_payment_ledger_entry
|
||||
|
||||
|
||||
class ClosedAccountingPeriod(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
def make_gl_entries(
|
||||
gl_map,
|
||||
cancel=False,
|
||||
@@ -300,6 +297,9 @@ def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
|
||||
|
||||
if gl_map:
|
||||
check_freezing_date(gl_map[0]["posting_date"], adv_adj)
|
||||
is_opening = any(d.get("is_opening") == "Yes" for d in gl_map)
|
||||
if gl_map[0]["voucher_type"] != "Period Closing Voucher":
|
||||
validate_against_pcv(is_opening, gl_map[0]["posting_date"], gl_map[0]["company"])
|
||||
|
||||
for entry in gl_map:
|
||||
make_entry(entry, adv_adj, update_outstanding, from_repost)
|
||||
@@ -521,6 +521,9 @@ def make_reverse_gl_entries(
|
||||
)
|
||||
validate_accounting_period(gl_entries)
|
||||
check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
|
||||
|
||||
is_opening = any(d.get("is_opening") == "Yes" for d in gl_entries)
|
||||
validate_against_pcv(is_opening, gl_entries[0]["posting_date"], gl_entries[0]["company"])
|
||||
set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"])
|
||||
|
||||
for entry in gl_entries:
|
||||
@@ -568,6 +571,28 @@ def check_freezing_date(posting_date, adv_adj=False):
|
||||
)
|
||||
|
||||
|
||||
def validate_against_pcv(is_opening, posting_date, company):
|
||||
if is_opening and frappe.db.exists(
|
||||
"Period Closing Voucher", {"docstatus": 1, "company": company}
|
||||
):
|
||||
frappe.throw(
|
||||
_("Opening Entry can not be created after Period Closing Voucher is created."),
|
||||
title=_("Invalid Opening Entry"),
|
||||
)
|
||||
|
||||
last_pcv_date = frappe.db.get_value(
|
||||
"Period Closing Voucher", {"docstatus": 1, "company": company}, "max(posting_date)"
|
||||
)
|
||||
|
||||
if last_pcv_date and getdate(posting_date) <= getdate(last_pcv_date):
|
||||
message = _("Books have been closed till the period ending on {0}").format(
|
||||
formatdate(last_pcv_date)
|
||||
)
|
||||
message += "</br >"
|
||||
message += _("You cannot create/amend any accounting entries till this date.")
|
||||
frappe.throw(message, title=_("Period Closed"))
|
||||
|
||||
|
||||
def set_as_cancel(voucher_type, voucher_no):
|
||||
"""
|
||||
Set is_cancelled=1 in all original gl entries for the voucher
|
||||
|
||||
@@ -33,6 +33,7 @@ import erpnext
|
||||
from erpnext import get_company_currency
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
from erpnext.exceptions import InvalidAccountCurrency, PartyDisabled, PartyFrozen
|
||||
from erpnext.utilities.regional import temporary_flag
|
||||
|
||||
PURCHASE_TRANSACTION_TYPES = {"Purchase Order", "Purchase Receipt", "Purchase Invoice"}
|
||||
SALES_TRANSACTION_TYPES = {
|
||||
@@ -261,9 +262,8 @@ def set_address_details(
|
||||
)
|
||||
|
||||
if doctype in TRANSACTION_TYPES:
|
||||
# required to set correct region
|
||||
frappe.flags.company = company
|
||||
get_regional_address_details(party_details, doctype, company)
|
||||
with temporary_flag("company", company):
|
||||
get_regional_address_details(party_details, doctype, company)
|
||||
|
||||
return party_address, shipping_address
|
||||
|
||||
|
||||
@@ -284,4 +284,4 @@
|
||||
{% } %}
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="text-right text-muted">{{ __("Printed On ") }}{%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}</p>
|
||||
<p class="text-right text-muted">{{ __("Printed On ") }}{%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}</p>
|
||||
@@ -25,6 +25,8 @@ def execute(filters=None):
|
||||
company=filters.company,
|
||||
)
|
||||
|
||||
filters.period_start_date = period_list[0]["year_start_date"]
|
||||
|
||||
currency = filters.presentation_currency or frappe.get_cached_value(
|
||||
"Company", filters.company, "default_currency"
|
||||
)
|
||||
@@ -96,7 +98,7 @@ def execute(filters=None):
|
||||
chart = get_chart_data(filters, columns, asset, liability, equity)
|
||||
|
||||
report_summary = get_report_summary(
|
||||
period_list, asset, liability, equity, provisional_profit_loss, total_credit, currency, filters
|
||||
period_list, asset, liability, equity, provisional_profit_loss, currency, filters
|
||||
)
|
||||
|
||||
return columns, data, message, chart, report_summary
|
||||
@@ -174,7 +176,6 @@ def get_report_summary(
|
||||
liability,
|
||||
equity,
|
||||
provisional_profit_loss,
|
||||
total_credit,
|
||||
currency,
|
||||
filters,
|
||||
consolidated=False,
|
||||
|
||||
@@ -152,5 +152,5 @@ def get_entries(filters):
|
||||
|
||||
return sorted(
|
||||
journal_entries + payment_entries + loan_disbursements + loan_repayments,
|
||||
key=lambda k: k[2] or getdate(nowdate()),
|
||||
key=lambda k: k[2].strftime("%H%M%S") or getdate(nowdate()),
|
||||
)
|
||||
|
||||
@@ -49,7 +49,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
"label": __("Start Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||
"reqd": 1,
|
||||
on_change: () => {
|
||||
frappe.model.with_doc("Fiscal Year", frappe.query_report.get_filter_value('from_fiscal_year'), function(r) {
|
||||
@@ -65,7 +65,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
"label": __("End Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||
"reqd": 1,
|
||||
on_change: () => {
|
||||
frappe.model.with_doc("Fiscal Year", frappe.query_report.get_filter_value('to_fiscal_year'), function(r) {
|
||||
@@ -139,7 +139,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
return value;
|
||||
},
|
||||
onload: function() {
|
||||
let fiscal_year = frappe.defaults.get_user_default("fiscal_year")
|
||||
let fiscal_year = erpnext.utils.get_fiscal_year(frappe.datetime.get_today());
|
||||
|
||||
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
|
||||
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
||||
|
||||
@@ -118,7 +118,6 @@ def get_balance_sheet_data(fiscal_year, companies, columns, filters):
|
||||
liability,
|
||||
equity,
|
||||
provisional_profit_loss,
|
||||
total_credit,
|
||||
company_currency,
|
||||
filters,
|
||||
True,
|
||||
@@ -656,7 +655,7 @@ def set_gl_entries_by_account(
|
||||
if filters and filters.get("presentation_currency") != d.default_currency:
|
||||
currency_info["company"] = d.name
|
||||
currency_info["company_currency"] = d.default_currency
|
||||
convert_to_presentation_currency(gl_entries, currency_info, filters.get("company"))
|
||||
convert_to_presentation_currency(gl_entries, currency_info)
|
||||
|
||||
for entry in gl_entries:
|
||||
if entry.account_number:
|
||||
|
||||
@@ -48,7 +48,7 @@ function get_filters() {
|
||||
"label": __("Start Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@@ -56,7 +56,7 @@ function get_filters() {
|
||||
"label": __("End Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@@ -100,7 +100,7 @@ frappe.query_reports["Deferred Revenue and Expense"] = {
|
||||
return default_formatter(value, row, column, data);
|
||||
},
|
||||
onload: function(report){
|
||||
let fiscal_year = frappe.defaults.get_user_default("fiscal_year");
|
||||
let fiscal_year = erpnext.utils.get_fiscal_year(frappe.datetime.get_today());
|
||||
|
||||
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
|
||||
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
import frappe
|
||||
from frappe import _, qb
|
||||
from frappe.query_builder import Column, functions
|
||||
from frappe.utils import add_days, date_diff, flt, get_first_day, get_last_day, rounded
|
||||
from frappe.utils import add_days, date_diff, flt, get_first_day, get_last_day, getdate, rounded
|
||||
|
||||
from erpnext.accounts.report.financial_statements import get_period_list
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
|
||||
|
||||
class Deferred_Item(object):
|
||||
@@ -226,7 +227,7 @@ class Deferred_Revenue_and_Expense_Report(object):
|
||||
|
||||
# If no filters are provided, get user defaults
|
||||
if not filters:
|
||||
fiscal_year = frappe.get_doc("Fiscal Year", frappe.defaults.get_user_default("fiscal_year"))
|
||||
fiscal_year = frappe.get_doc("Fiscal Year", get_fiscal_year(date=getdate()))
|
||||
self.filters = frappe._dict(
|
||||
{
|
||||
"company": frappe.defaults.get_user_default("Company"),
|
||||
|
||||
@@ -10,6 +10,7 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal
|
||||
from erpnext.accounts.report.deferred_revenue_and_expense.deferred_revenue_and_expense import (
|
||||
Deferred_Revenue_and_Expense_Report,
|
||||
)
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
|
||||
@@ -116,7 +117,7 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
||||
pda.submit()
|
||||
|
||||
# execute report
|
||||
fiscal_year = frappe.get_doc("Fiscal Year", frappe.defaults.get_user_default("fiscal_year"))
|
||||
fiscal_year = frappe.get_doc("Fiscal Year", get_fiscal_year(date="2021-05-01"))
|
||||
self.filters = frappe._dict(
|
||||
{
|
||||
"company": frappe.defaults.get_user_default("Company"),
|
||||
@@ -209,7 +210,7 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
||||
pda.submit()
|
||||
|
||||
# execute report
|
||||
fiscal_year = frappe.get_doc("Fiscal Year", frappe.defaults.get_user_default("fiscal_year"))
|
||||
fiscal_year = frappe.get_doc("Fiscal Year", get_fiscal_year(date="2021-05-01"))
|
||||
self.filters = frappe._dict(
|
||||
{
|
||||
"company": frappe.defaults.get_user_default("Company"),
|
||||
@@ -297,7 +298,7 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
||||
pda.submit()
|
||||
|
||||
# execute report
|
||||
fiscal_year = frappe.get_doc("Fiscal Year", frappe.defaults.get_user_default("fiscal_year"))
|
||||
fiscal_year = frappe.get_doc("Fiscal Year", get_fiscal_year(date="2021-05-01"))
|
||||
self.filters = frappe._dict(
|
||||
{
|
||||
"company": frappe.defaults.get_user_default("Company"),
|
||||
|
||||
@@ -18,7 +18,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
"label": __("Fiscal Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||
"reqd": 1,
|
||||
"on_change": function(query_report) {
|
||||
var fiscal_year = query_report.get_values().fiscal_year;
|
||||
|
||||
@@ -416,52 +416,53 @@ def set_gl_entries_by_account(
|
||||
filters,
|
||||
gl_entries_by_account,
|
||||
ignore_closing_entries=False,
|
||||
ignore_opening_entries=False,
|
||||
):
|
||||
"""Returns a dict like { "account": [gl entries], ... }"""
|
||||
gl_entries = []
|
||||
|
||||
additional_conditions = get_additional_conditions(from_date, ignore_closing_entries, filters)
|
||||
|
||||
accounts = frappe.db.sql_list(
|
||||
"""select name from `tabAccount`
|
||||
where lft >= %s and rgt <= %s and company = %s""",
|
||||
(root_lft, root_rgt, company),
|
||||
accounts_list = frappe.db.get_all(
|
||||
"Account",
|
||||
filters={"company": company, "is_group": 0, "lft": (">=", root_lft), "rgt": ("<=", root_rgt)},
|
||||
pluck="name",
|
||||
)
|
||||
|
||||
if accounts:
|
||||
additional_conditions += " and account in ({})".format(
|
||||
", ".join(frappe.db.escape(d) for d in accounts)
|
||||
)
|
||||
if accounts_list:
|
||||
# For balance sheet
|
||||
if not from_date:
|
||||
from_date = filters["period_start_date"]
|
||||
last_period_closing_voucher = frappe.db.get_all(
|
||||
"Period Closing Voucher",
|
||||
filters={"docstatus": 1, "company": filters.company, "posting_date": ("<", from_date)},
|
||||
fields=["posting_date", "name"],
|
||||
order_by="posting_date desc",
|
||||
limit=1,
|
||||
)
|
||||
if last_period_closing_voucher:
|
||||
gl_entries += get_accounting_entries(
|
||||
"Account Closing Balance",
|
||||
from_date,
|
||||
to_date,
|
||||
accounts_list,
|
||||
filters,
|
||||
ignore_closing_entries,
|
||||
last_period_closing_voucher[0].name,
|
||||
)
|
||||
from_date = add_days(last_period_closing_voucher[0].posting_date, 1)
|
||||
ignore_opening_entries = True
|
||||
|
||||
gl_filters = {
|
||||
"company": company,
|
||||
"from_date": from_date,
|
||||
"to_date": to_date,
|
||||
"finance_book": cstr(filters.get("finance_book")),
|
||||
}
|
||||
|
||||
if filters.get("include_default_book_entries"):
|
||||
gl_filters["company_fb"] = frappe.db.get_value("Company", company, "default_finance_book")
|
||||
|
||||
for key, value in filters.items():
|
||||
if value:
|
||||
gl_filters.update({key: value})
|
||||
|
||||
gl_entries = frappe.db.sql(
|
||||
"""
|
||||
select posting_date, account, debit, credit, is_opening, fiscal_year,
|
||||
debit_in_account_currency, credit_in_account_currency, account_currency from `tabGL Entry`
|
||||
where company=%(company)s
|
||||
{additional_conditions}
|
||||
and posting_date <= %(to_date)s
|
||||
and is_cancelled = 0""".format(
|
||||
additional_conditions=additional_conditions
|
||||
),
|
||||
gl_filters,
|
||||
as_dict=True,
|
||||
gl_entries += get_accounting_entries(
|
||||
"GL Entry",
|
||||
from_date,
|
||||
to_date,
|
||||
accounts_list,
|
||||
filters,
|
||||
ignore_closing_entries,
|
||||
ignore_opening_entries=ignore_opening_entries,
|
||||
)
|
||||
|
||||
if filters and filters.get("presentation_currency"):
|
||||
convert_to_presentation_currency(gl_entries, get_currency(filters), filters.get("company"))
|
||||
convert_to_presentation_currency(gl_entries, get_currency(filters))
|
||||
|
||||
for entry in gl_entries:
|
||||
gl_entries_by_account.setdefault(entry.account, []).append(entry)
|
||||
@@ -469,49 +470,90 @@ def set_gl_entries_by_account(
|
||||
return gl_entries_by_account
|
||||
|
||||
|
||||
def get_additional_conditions(from_date, ignore_closing_entries, filters):
|
||||
additional_conditions = []
|
||||
def get_accounting_entries(
|
||||
doctype,
|
||||
from_date,
|
||||
to_date,
|
||||
accounts,
|
||||
filters,
|
||||
ignore_closing_entries,
|
||||
period_closing_voucher=None,
|
||||
ignore_opening_entries=False,
|
||||
):
|
||||
gl_entry = frappe.qb.DocType(doctype)
|
||||
query = (
|
||||
frappe.qb.from_(gl_entry)
|
||||
.select(
|
||||
gl_entry.account,
|
||||
gl_entry.debit,
|
||||
gl_entry.credit,
|
||||
gl_entry.debit_in_account_currency,
|
||||
gl_entry.credit_in_account_currency,
|
||||
gl_entry.account_currency,
|
||||
)
|
||||
.where(gl_entry.company == filters.company)
|
||||
)
|
||||
|
||||
if doctype == "GL Entry":
|
||||
query = query.select(gl_entry.posting_date, gl_entry.is_opening, gl_entry.fiscal_year)
|
||||
query = query.where(gl_entry.is_cancelled == 0)
|
||||
query = query.where(gl_entry.posting_date <= to_date)
|
||||
|
||||
if ignore_opening_entries:
|
||||
query = query.where(gl_entry.is_opening == "No")
|
||||
else:
|
||||
query = query.select(gl_entry.closing_date.as_("posting_date"))
|
||||
query = query.where(gl_entry.period_closing_voucher == period_closing_voucher)
|
||||
|
||||
query = apply_additional_conditions(doctype, query, from_date, ignore_closing_entries, filters)
|
||||
query = query.where(gl_entry.account.isin(accounts))
|
||||
|
||||
entries = query.run(as_dict=True)
|
||||
|
||||
return entries
|
||||
|
||||
|
||||
def apply_additional_conditions(doctype, query, from_date, ignore_closing_entries, filters):
|
||||
gl_entry = frappe.qb.DocType(doctype)
|
||||
accounting_dimensions = get_accounting_dimensions(as_list=False)
|
||||
|
||||
if ignore_closing_entries:
|
||||
additional_conditions.append("ifnull(voucher_type, '')!='Period Closing Voucher'")
|
||||
if doctype == "GL Entry":
|
||||
query = query.where(gl_entry.voucher_type != "Period Closing Voucher")
|
||||
else:
|
||||
query = query.where(gl_entry.is_period_closing_voucher_entry == 0)
|
||||
|
||||
if from_date:
|
||||
additional_conditions.append("posting_date >= %(from_date)s")
|
||||
if from_date and doctype == "GL Entry":
|
||||
query = query.where(gl_entry.posting_date >= from_date)
|
||||
|
||||
if filters:
|
||||
if filters.get("project"):
|
||||
if not isinstance(filters.get("project"), list):
|
||||
filters.project = frappe.parse_json(filters.get("project"))
|
||||
|
||||
additional_conditions.append("project in %(project)s")
|
||||
query = query.where(gl_entry.project.isin(filters.project))
|
||||
|
||||
if filters.get("cost_center"):
|
||||
filters.cost_center = get_cost_centers_with_children(filters.cost_center)
|
||||
additional_conditions.append("cost_center in %(cost_center)s")
|
||||
query = query.where(gl_entry.cost_center.isin(filters.cost_center))
|
||||
|
||||
if filters.get("include_default_book_entries"):
|
||||
if filters.get("finance_book"):
|
||||
if filters.get("company_fb") and cstr(filters.get("finance_book")) != cstr(
|
||||
filters.get("company_fb")
|
||||
):
|
||||
frappe.throw(
|
||||
_("To use a different finance book, please uncheck 'Include Default Book Entries'")
|
||||
)
|
||||
else:
|
||||
additional_conditions.append(
|
||||
"(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)"
|
||||
)
|
||||
else:
|
||||
additional_conditions.append("(finance_book in (%(company_fb)s, '') OR finance_book IS NULL)")
|
||||
else:
|
||||
if filters.get("finance_book"):
|
||||
additional_conditions.append(
|
||||
"(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)"
|
||||
company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book")
|
||||
|
||||
if filters.finance_book and company_fb and cstr(filters.finance_book) != cstr(company_fb):
|
||||
frappe.throw(
|
||||
_("To use a different finance book, please uncheck 'Include Default Book Entries'")
|
||||
)
|
||||
else:
|
||||
additional_conditions.append("(finance_book in ('') OR finance_book IS NULL)")
|
||||
|
||||
query = query.where(
|
||||
(gl_entry.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""]))
|
||||
| (gl_entry.finance_book.isnull())
|
||||
)
|
||||
else:
|
||||
query = query.where(
|
||||
(gl_entry.finance_book.isin([cstr(filters.finance_book), ""]))
|
||||
| (gl_entry.finance_book.isnull())
|
||||
)
|
||||
|
||||
if accounting_dimensions:
|
||||
for dimension in accounting_dimensions:
|
||||
@@ -520,11 +562,10 @@ def get_additional_conditions(from_date, ignore_closing_entries, filters):
|
||||
filters[dimension.fieldname] = get_dimension_with_children(
|
||||
dimension.document_type, filters.get(dimension.fieldname)
|
||||
)
|
||||
additional_conditions.append("{0} in %({0})s".format(dimension.fieldname))
|
||||
else:
|
||||
additional_conditions.append("{0} in %({0})s".format(dimension.fieldname))
|
||||
|
||||
return " and {}".format(" and ".join(additional_conditions)) if additional_conditions else ""
|
||||
query = query.where(gl_entry[dimension.fieldname].isin(filters[dimension.fieldname]))
|
||||
|
||||
return query
|
||||
|
||||
|
||||
def get_cost_centers_with_children(cost_centers):
|
||||
|
||||
@@ -204,7 +204,7 @@ def get_gl_entries(filters, accounting_dimensions):
|
||||
)
|
||||
|
||||
if filters.get("presentation_currency"):
|
||||
return convert_to_presentation_currency(gl_entries, currency_map, filters.get("company"))
|
||||
return convert_to_presentation_currency(gl_entries, currency_map)
|
||||
else:
|
||||
return gl_entries
|
||||
|
||||
|
||||
@@ -12,14 +12,6 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
erpnext.financial_statements);
|
||||
|
||||
frappe.query_reports["Gross and Net Profit Report"]["filters"].push(
|
||||
{
|
||||
"fieldname": "project",
|
||||
"label": __("Project"),
|
||||
"fieldtype": "MultiSelectList",
|
||||
get_data: function(txt) {
|
||||
return frappe.db.get_link_options('Project', txt);
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "accumulated_values",
|
||||
"label": __("Accumulated Values"),
|
||||
|
||||
@@ -15,21 +15,21 @@ from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register i
|
||||
get_group_by_conditions,
|
||||
get_tax_accounts,
|
||||
)
|
||||
from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import get_item_details
|
||||
from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
return _execute(filters)
|
||||
|
||||
|
||||
def _execute(filters=None, additional_table_columns=None, additional_query_columns=None):
|
||||
def _execute(filters=None, additional_table_columns=None):
|
||||
if not filters:
|
||||
filters = {}
|
||||
columns = get_columns(additional_table_columns, filters)
|
||||
|
||||
company_currency = erpnext.get_company_currency(filters.company)
|
||||
|
||||
item_list = get_items(filters, additional_query_columns)
|
||||
item_list = get_items(filters, get_query_columns(additional_table_columns))
|
||||
aii_account_map = get_aii_accounts()
|
||||
if item_list:
|
||||
itemised_tax, tax_columns = get_tax_accounts(
|
||||
@@ -40,6 +40,16 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
||||
tax_doctype="Purchase Taxes and Charges",
|
||||
)
|
||||
|
||||
scrubbed_tax_fields = {}
|
||||
|
||||
for tax in tax_columns:
|
||||
scrubbed_tax_fields.update(
|
||||
{
|
||||
tax + " Rate": frappe.scrub(tax + " Rate"),
|
||||
tax + " Amount": frappe.scrub(tax + " Amount"),
|
||||
}
|
||||
)
|
||||
|
||||
po_pr_map = get_purchase_receipts_against_purchase_order(item_list)
|
||||
|
||||
data = []
|
||||
@@ -50,11 +60,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
||||
if filters.get("group_by"):
|
||||
grand_total = get_grand_total(filters, "Purchase Invoice")
|
||||
|
||||
item_details = get_item_details()
|
||||
|
||||
for d in item_list:
|
||||
item_record = item_details.get(d.item_code)
|
||||
|
||||
purchase_receipt = None
|
||||
if d.purchase_receipt:
|
||||
purchase_receipt = d.purchase_receipt
|
||||
@@ -67,42 +73,34 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
||||
|
||||
row = {
|
||||
"item_code": d.item_code,
|
||||
"item_name": item_record.item_name if item_record else d.item_name,
|
||||
"item_group": item_record.item_group if item_record else d.item_group,
|
||||
"item_name": d.pi_item_name if d.pi_item_name else d.i_item_name,
|
||||
"item_group": d.pi_item_group if d.pi_item_group else d.i_item_group,
|
||||
"description": d.description,
|
||||
"invoice": d.parent,
|
||||
"posting_date": d.posting_date,
|
||||
"supplier": d.supplier,
|
||||
"supplier_name": d.supplier_name,
|
||||
**get_values_for_columns(additional_table_columns, d),
|
||||
"credit_to": d.credit_to,
|
||||
"mode_of_payment": d.mode_of_payment,
|
||||
"project": d.project,
|
||||
"company": d.company,
|
||||
"purchase_order": d.purchase_order,
|
||||
"purchase_receipt": purchase_receipt,
|
||||
"expense_account": expense_account,
|
||||
"stock_qty": d.stock_qty,
|
||||
"stock_uom": d.stock_uom,
|
||||
"rate": d.base_net_amount / d.stock_qty if d.stock_qty else d.base_net_amount,
|
||||
"amount": d.base_net_amount,
|
||||
}
|
||||
|
||||
if additional_query_columns:
|
||||
for col in additional_query_columns:
|
||||
row.update({col: d.get(col)})
|
||||
|
||||
row.update(
|
||||
{
|
||||
"credit_to": d.credit_to,
|
||||
"mode_of_payment": d.mode_of_payment,
|
||||
"project": d.project,
|
||||
"company": d.company,
|
||||
"purchase_order": d.purchase_order,
|
||||
"purchase_receipt": d.purchase_receipt,
|
||||
"expense_account": expense_account,
|
||||
"stock_qty": d.stock_qty,
|
||||
"stock_uom": d.stock_uom,
|
||||
"rate": d.base_net_amount / d.stock_qty if d.stock_qty else d.base_net_amount,
|
||||
"amount": d.base_net_amount,
|
||||
}
|
||||
)
|
||||
|
||||
total_tax = 0
|
||||
for tax in tax_columns:
|
||||
item_tax = itemised_tax.get(d.name, {}).get(tax, {})
|
||||
row.update(
|
||||
{
|
||||
frappe.scrub(tax + " Rate"): item_tax.get("tax_rate", 0),
|
||||
frappe.scrub(tax + " Amount"): item_tax.get("tax_amount", 0),
|
||||
scrubbed_tax_fields[tax + " Rate"]: item_tax.get("tax_rate", 0),
|
||||
scrubbed_tax_fields[tax + " Amount"]: item_tax.get("tax_amount", 0),
|
||||
}
|
||||
)
|
||||
total_tax += flt(item_tax.get("tax_amount"))
|
||||
@@ -241,7 +239,7 @@ def get_columns(additional_table_columns, filters):
|
||||
},
|
||||
{
|
||||
"label": _("Purchase Receipt"),
|
||||
"fieldname": "Purchase Receipt",
|
||||
"fieldname": "purchase_receipt",
|
||||
"fieldtype": "Link",
|
||||
"options": "Purchase Receipt",
|
||||
"width": 100,
|
||||
@@ -312,11 +310,6 @@ def get_conditions(filters):
|
||||
def get_items(filters, additional_query_columns):
|
||||
conditions = get_conditions(filters)
|
||||
|
||||
if additional_query_columns:
|
||||
additional_query_columns = ", " + ", ".join(additional_query_columns)
|
||||
else:
|
||||
additional_query_columns = ""
|
||||
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
@@ -325,19 +318,20 @@ def get_items(filters, additional_query_columns):
|
||||
`tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total,
|
||||
`tabPurchase Invoice`.unrealized_profit_loss_account,
|
||||
`tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description,
|
||||
`tabPurchase Invoice Item`.`item_name`, `tabPurchase Invoice Item`.`item_group`,
|
||||
`tabPurchase Invoice Item`.`item_name` as pi_item_name, `tabPurchase Invoice Item`.`item_group` as pi_item_group,
|
||||
`tabItem`.`item_name` as i_item_name, `tabItem`.`item_group` as i_item_group,
|
||||
`tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`,
|
||||
`tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`,
|
||||
`tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`,
|
||||
`tabPurchase Invoice Item`.`stock_uom`, `tabPurchase Invoice Item`.`base_net_amount`,
|
||||
`tabPurchase Invoice`.`supplier_name`, `tabPurchase Invoice`.`mode_of_payment` {0}
|
||||
from `tabPurchase Invoice`, `tabPurchase Invoice Item`
|
||||
from `tabPurchase Invoice`, `tabPurchase Invoice Item`, `tabItem`
|
||||
where `tabPurchase Invoice`.name = `tabPurchase Invoice Item`.`parent` and
|
||||
`tabPurchase Invoice`.docstatus = 1 %s
|
||||
`tabItem`.name = `tabPurchase Invoice Item`.`item_code` and
|
||||
`tabPurchase Invoice`.docstatus = 1 {1}
|
||||
""".format(
|
||||
additional_query_columns
|
||||
)
|
||||
% (conditions),
|
||||
additional_query_columns, conditions
|
||||
),
|
||||
filters,
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
@@ -9,9 +9,9 @@ from frappe.utils import cstr, flt
|
||||
from frappe.utils.xlsxutils import handle_html
|
||||
|
||||
from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments
|
||||
from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns
|
||||
from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import (
|
||||
get_customer_details,
|
||||
get_item_details,
|
||||
)
|
||||
|
||||
|
||||
@@ -19,22 +19,27 @@ def execute(filters=None):
|
||||
return _execute(filters)
|
||||
|
||||
|
||||
def _execute(
|
||||
filters=None,
|
||||
additional_table_columns=None,
|
||||
additional_query_columns=None,
|
||||
additional_conditions=None,
|
||||
):
|
||||
def _execute(filters=None, additional_table_columns=None, additional_conditions=None):
|
||||
if not filters:
|
||||
filters = {}
|
||||
columns = get_columns(additional_table_columns, filters)
|
||||
|
||||
company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency")
|
||||
|
||||
item_list = get_items(filters, additional_query_columns, additional_conditions)
|
||||
item_list = get_items(filters, get_query_columns(additional_table_columns), additional_conditions)
|
||||
if item_list:
|
||||
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency)
|
||||
|
||||
scrubbed_tax_fields = {}
|
||||
|
||||
for tax in tax_columns:
|
||||
scrubbed_tax_fields.update(
|
||||
{
|
||||
tax + " Rate": frappe.scrub(tax + " Rate"),
|
||||
tax + " Amount": frappe.scrub(tax + " Amount"),
|
||||
}
|
||||
)
|
||||
|
||||
mode_of_payments = get_mode_of_payments(set(d.parent for d in item_list))
|
||||
so_dn_map = get_delivery_notes_against_sales_order(item_list)
|
||||
|
||||
@@ -47,11 +52,9 @@ def _execute(
|
||||
grand_total = get_grand_total(filters, "Sales Invoice")
|
||||
|
||||
customer_details = get_customer_details()
|
||||
item_details = get_item_details()
|
||||
|
||||
for d in item_list:
|
||||
customer_record = customer_details.get(d.customer)
|
||||
item_record = item_details.get(d.item_code)
|
||||
|
||||
delivery_note = None
|
||||
if d.delivery_note:
|
||||
@@ -64,38 +67,30 @@ def _execute(
|
||||
|
||||
row = {
|
||||
"item_code": d.item_code,
|
||||
"item_name": item_record.item_name if item_record else d.item_name,
|
||||
"item_group": item_record.item_group if item_record else d.item_group,
|
||||
"item_name": d.si_item_name if d.si_item_name else d.i_item_name,
|
||||
"item_group": d.si_item_group if d.si_item_group else d.i_item_group,
|
||||
"description": d.description,
|
||||
"invoice": d.parent,
|
||||
"posting_date": d.posting_date,
|
||||
"customer": d.customer,
|
||||
"customer_name": customer_record.customer_name,
|
||||
"customer_group": customer_record.customer_group,
|
||||
**get_values_for_columns(additional_table_columns, d),
|
||||
"debit_to": d.debit_to,
|
||||
"mode_of_payment": ", ".join(mode_of_payments.get(d.parent, [])),
|
||||
"territory": d.territory,
|
||||
"project": d.project,
|
||||
"company": d.company,
|
||||
"sales_order": d.sales_order,
|
||||
"delivery_note": d.delivery_note,
|
||||
"income_account": d.unrealized_profit_loss_account
|
||||
if d.is_internal_customer == 1
|
||||
else d.income_account,
|
||||
"cost_center": d.cost_center,
|
||||
"stock_qty": d.stock_qty,
|
||||
"stock_uom": d.stock_uom,
|
||||
}
|
||||
|
||||
if additional_query_columns:
|
||||
for col in additional_query_columns:
|
||||
row.update({col: d.get(col)})
|
||||
|
||||
row.update(
|
||||
{
|
||||
"debit_to": d.debit_to,
|
||||
"mode_of_payment": ", ".join(mode_of_payments.get(d.parent, [])),
|
||||
"territory": d.territory,
|
||||
"project": d.project,
|
||||
"company": d.company,
|
||||
"sales_order": d.sales_order,
|
||||
"delivery_note": d.delivery_note,
|
||||
"income_account": d.unrealized_profit_loss_account
|
||||
if d.is_internal_customer == 1
|
||||
else d.income_account,
|
||||
"cost_center": d.cost_center,
|
||||
"stock_qty": d.stock_qty,
|
||||
"stock_uom": d.stock_uom,
|
||||
}
|
||||
)
|
||||
|
||||
if d.stock_uom != d.uom and d.stock_qty:
|
||||
row.update({"rate": (d.base_net_rate * d.qty) / d.stock_qty, "amount": d.base_net_amount})
|
||||
else:
|
||||
@@ -107,8 +102,8 @@ def _execute(
|
||||
item_tax = itemised_tax.get(d.name, {}).get(tax, {})
|
||||
row.update(
|
||||
{
|
||||
frappe.scrub(tax + " Rate"): item_tax.get("tax_rate", 0),
|
||||
frappe.scrub(tax + " Amount"): item_tax.get("tax_amount", 0),
|
||||
scrubbed_tax_fields[tax + " Rate"]: item_tax.get("tax_rate", 0),
|
||||
scrubbed_tax_fields[tax + " Amount"]: item_tax.get("tax_amount", 0),
|
||||
}
|
||||
)
|
||||
if item_tax.get("is_other_charges"):
|
||||
@@ -387,11 +382,6 @@ def get_group_by_conditions(filters, doctype):
|
||||
def get_items(filters, additional_query_columns, additional_conditions=None):
|
||||
conditions = get_conditions(filters, additional_conditions)
|
||||
|
||||
if additional_query_columns:
|
||||
additional_query_columns = ", " + ", ".join(additional_query_columns)
|
||||
else:
|
||||
additional_query_columns = ""
|
||||
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
@@ -404,17 +394,20 @@ def get_items(filters, additional_query_columns, additional_conditions=None):
|
||||
`tabSales Invoice Item`.project,
|
||||
`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description,
|
||||
`tabSales Invoice Item`.`item_name`, `tabSales Invoice Item`.`item_group`,
|
||||
`tabSales Invoice Item`.`item_name` as si_item_name, `tabSales Invoice Item`.`item_group` as si_item_group,
|
||||
`tabItem`.`item_name` as i_item_name, `tabItem`.`item_group` as i_item_group,
|
||||
`tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note,
|
||||
`tabSales Invoice Item`.income_account, `tabSales Invoice Item`.cost_center,
|
||||
`tabSales Invoice Item`.stock_qty, `tabSales Invoice Item`.stock_uom,
|
||||
`tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount,
|
||||
`tabSales Invoice`.customer_name, `tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail,
|
||||
`tabSales Invoice`.update_stock, `tabSales Invoice Item`.uom, `tabSales Invoice Item`.qty {0}
|
||||
from `tabSales Invoice`, `tabSales Invoice Item`
|
||||
where `tabSales Invoice`.name = `tabSales Invoice Item`.parent
|
||||
and `tabSales Invoice`.docstatus = 1 {1}
|
||||
from `tabSales Invoice`, `tabSales Invoice Item`, `tabItem`
|
||||
where `tabSales Invoice`.name = `tabSales Invoice Item`.parent and
|
||||
`tabItem`.name = `tabSales Invoice Item`.`item_code` and
|
||||
`tabSales Invoice`.docstatus = 1 {1}
|
||||
""".format(
|
||||
additional_query_columns or "", conditions
|
||||
additional_query_columns, conditions
|
||||
),
|
||||
filters,
|
||||
as_dict=1,
|
||||
|
||||
@@ -9,14 +9,6 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
erpnext.utils.add_dimensions('Profit and Loss Statement', 10);
|
||||
|
||||
frappe.query_reports["Profit and Loss Statement"]["filters"].push(
|
||||
{
|
||||
"fieldname": "project",
|
||||
"label": __("Project"),
|
||||
"fieldtype": "MultiSelectList",
|
||||
get_data: function(txt) {
|
||||
return frappe.db.get_link_options('Project', txt);
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "include_default_book_entries",
|
||||
"label": __("Include Default Book Entries"),
|
||||
|
||||
@@ -16,16 +16,37 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
"fieldname": "based_on",
|
||||
"label": __("Based On"),
|
||||
"fieldtype": "Select",
|
||||
"options": ["Cost Center", "Project"],
|
||||
"options": ["Cost Center", "Project", "Accounting Dimension"],
|
||||
"default": "Cost Center",
|
||||
"reqd": 1
|
||||
"reqd": 1,
|
||||
"on_change": function(query_report){
|
||||
let based_on = query_report.get_values().based_on;
|
||||
if(based_on!='Accounting Dimension'){
|
||||
frappe.query_report.set_filter_value({
|
||||
accounting_dimension: ''
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "accounting_dimension",
|
||||
"label": __("Accounting Dimension"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Accounting Dimension",
|
||||
"get_query": () =>{
|
||||
return {
|
||||
filters: {
|
||||
"disabled": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "fiscal_year",
|
||||
"label": __("Fiscal Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||
"reqd": 1,
|
||||
"on_change": function(query_report) {
|
||||
var fiscal_year = query_report.get_values().fiscal_year;
|
||||
|
||||
@@ -6,6 +6,7 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import cstr, flt
|
||||
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions
|
||||
from erpnext.accounts.report.financial_statements import (
|
||||
filter_accounts,
|
||||
filter_out_zero_value_rows,
|
||||
@@ -16,10 +17,12 @@ value_fields = ("income", "expense", "gross_profit_loss")
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
if not filters.get("based_on"):
|
||||
filters["based_on"] = "Cost Center"
|
||||
if filters.get("based_on") == "Accounting Dimension" and not filters.get("accounting_dimension"):
|
||||
frappe.throw(_("Select Accounting Dimension."))
|
||||
|
||||
based_on = filters.based_on.replace(" ", "_").lower()
|
||||
based_on = (
|
||||
filters.based_on if filters.based_on != "Accounting Dimension" else filters.accounting_dimension
|
||||
)
|
||||
validate_filters(filters)
|
||||
accounts = get_accounts_data(based_on, filters.get("company"))
|
||||
data = get_data(accounts, filters, based_on)
|
||||
@@ -28,14 +31,14 @@ def execute(filters=None):
|
||||
|
||||
|
||||
def get_accounts_data(based_on, company):
|
||||
if based_on == "cost_center":
|
||||
if based_on == "Cost Center":
|
||||
return frappe.db.sql(
|
||||
"""select name, parent_cost_center as parent_account, cost_center_name as account_name, lft, rgt
|
||||
from `tabCost Center` where company=%s order by name""",
|
||||
company,
|
||||
as_dict=True,
|
||||
)
|
||||
elif based_on == "project":
|
||||
elif based_on == "Project":
|
||||
return frappe.get_all("Project", fields=["name"], filters={"company": company}, order_by="name")
|
||||
else:
|
||||
filters = {}
|
||||
@@ -56,11 +59,17 @@ def get_data(accounts, filters, based_on):
|
||||
|
||||
gl_entries_by_account = {}
|
||||
|
||||
accounting_dimensions = get_dimensions(with_cost_center_and_project=True)[0]
|
||||
fieldname = ""
|
||||
for dimension in accounting_dimensions:
|
||||
if dimension["document_type"] == based_on:
|
||||
fieldname = dimension["fieldname"]
|
||||
|
||||
set_gl_entries_by_account(
|
||||
filters.get("company"),
|
||||
filters.get("from_date"),
|
||||
filters.get("to_date"),
|
||||
based_on,
|
||||
fieldname,
|
||||
gl_entries_by_account,
|
||||
ignore_closing_entries=not flt(filters.get("with_period_closing_entry")),
|
||||
)
|
||||
|
||||
@@ -10,17 +10,18 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
get_accounting_dimensions,
|
||||
get_dimension_with_children,
|
||||
)
|
||||
from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
return _execute(filters)
|
||||
|
||||
|
||||
def _execute(filters=None, additional_table_columns=None, additional_query_columns=None):
|
||||
def _execute(filters=None, additional_table_columns=None):
|
||||
if not filters:
|
||||
filters = {}
|
||||
|
||||
invoice_list = get_invoices(filters, additional_query_columns)
|
||||
invoice_list = get_invoices(filters, get_query_columns(additional_table_columns))
|
||||
columns, expense_accounts, tax_accounts, unrealized_profit_loss_accounts = get_columns(
|
||||
invoice_list, additional_table_columns
|
||||
)
|
||||
@@ -47,13 +48,12 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
||||
purchase_receipt = list(set(invoice_po_pr_map.get(inv.name, {}).get("purchase_receipt", [])))
|
||||
project = list(set(invoice_po_pr_map.get(inv.name, {}).get("project", [])))
|
||||
|
||||
row = [inv.name, inv.posting_date, inv.supplier, inv.supplier_name]
|
||||
|
||||
if additional_query_columns:
|
||||
for col in additional_query_columns:
|
||||
row.append(inv.get(col))
|
||||
|
||||
row += [
|
||||
row = [
|
||||
inv.name,
|
||||
inv.posting_date,
|
||||
inv.supplier,
|
||||
inv.supplier_name,
|
||||
*get_values_for_columns(additional_table_columns, inv).values(),
|
||||
supplier_details.get(inv.supplier), # supplier_group
|
||||
inv.tax_id,
|
||||
inv.credit_to,
|
||||
@@ -244,9 +244,6 @@ def get_conditions(filters):
|
||||
|
||||
|
||||
def get_invoices(filters, additional_query_columns):
|
||||
if additional_query_columns:
|
||||
additional_query_columns = ", " + ", ".join(additional_query_columns)
|
||||
|
||||
conditions = get_conditions(filters)
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
@@ -255,11 +252,10 @@ def get_invoices(filters, additional_query_columns):
|
||||
remarks, base_net_total, base_grand_total, outstanding_amount,
|
||||
mode_of_payment {0}
|
||||
from `tabPurchase Invoice`
|
||||
where docstatus = 1 %s
|
||||
where docstatus = 1 {1}
|
||||
order by posting_date desc, name desc""".format(
|
||||
additional_query_columns or ""
|
||||
)
|
||||
% conditions,
|
||||
additional_query_columns, conditions
|
||||
),
|
||||
filters,
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
@@ -11,17 +11,18 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
get_accounting_dimensions,
|
||||
get_dimension_with_children,
|
||||
)
|
||||
from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
return _execute(filters)
|
||||
|
||||
|
||||
def _execute(filters, additional_table_columns=None, additional_query_columns=None):
|
||||
def _execute(filters, additional_table_columns=None):
|
||||
if not filters:
|
||||
filters = frappe._dict({})
|
||||
|
||||
invoice_list = get_invoices(filters, additional_query_columns)
|
||||
invoice_list = get_invoices(filters, get_query_columns(additional_table_columns))
|
||||
columns, income_accounts, tax_accounts, unrealized_profit_loss_accounts = get_columns(
|
||||
invoice_list, additional_table_columns
|
||||
)
|
||||
@@ -54,30 +55,22 @@ def _execute(filters, additional_table_columns=None, additional_query_columns=No
|
||||
"posting_date": inv.posting_date,
|
||||
"customer": inv.customer,
|
||||
"customer_name": inv.customer_name,
|
||||
**get_values_for_columns(additional_table_columns, inv),
|
||||
"customer_group": inv.get("customer_group"),
|
||||
"territory": inv.get("territory"),
|
||||
"tax_id": inv.get("tax_id"),
|
||||
"receivable_account": inv.debit_to,
|
||||
"mode_of_payment": ", ".join(mode_of_payments.get(inv.name, [])),
|
||||
"project": inv.project,
|
||||
"owner": inv.owner,
|
||||
"remarks": inv.remarks,
|
||||
"sales_order": ", ".join(sales_order),
|
||||
"delivery_note": ", ".join(delivery_note),
|
||||
"cost_center": ", ".join(cost_center),
|
||||
"warehouse": ", ".join(warehouse),
|
||||
"currency": company_currency,
|
||||
}
|
||||
|
||||
if additional_query_columns:
|
||||
for col in additional_query_columns:
|
||||
row.update({col: inv.get(col)})
|
||||
|
||||
row.update(
|
||||
{
|
||||
"customer_group": inv.get("customer_group"),
|
||||
"territory": inv.get("territory"),
|
||||
"tax_id": inv.get("tax_id"),
|
||||
"receivable_account": inv.debit_to,
|
||||
"mode_of_payment": ", ".join(mode_of_payments.get(inv.name, [])),
|
||||
"project": inv.project,
|
||||
"owner": inv.owner,
|
||||
"remarks": inv.remarks,
|
||||
"sales_order": ", ".join(sales_order),
|
||||
"delivery_note": ", ".join(delivery_note),
|
||||
"cost_center": ", ".join(cost_center),
|
||||
"warehouse": ", ".join(warehouse),
|
||||
"currency": company_currency,
|
||||
}
|
||||
)
|
||||
|
||||
# map income values
|
||||
base_net_total = 0
|
||||
for income_acc in income_accounts:
|
||||
@@ -402,9 +395,6 @@ def get_conditions(filters):
|
||||
|
||||
|
||||
def get_invoices(filters, additional_query_columns):
|
||||
if additional_query_columns:
|
||||
additional_query_columns = ", " + ", ".join(additional_query_columns)
|
||||
|
||||
conditions = get_conditions(filters)
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
@@ -413,10 +403,10 @@ def get_invoices(filters, additional_query_columns):
|
||||
base_net_total, base_grand_total, base_rounded_total, outstanding_amount,
|
||||
is_internal_customer, represents_company, company {0}
|
||||
from `tabSales Invoice`
|
||||
where docstatus = 1 %s order by posting_date desc, name desc""".format(
|
||||
additional_query_columns or ""
|
||||
)
|
||||
% conditions,
|
||||
where docstatus = 1 {1}
|
||||
order by posting_date desc, name desc""".format(
|
||||
additional_query_columns, conditions
|
||||
),
|
||||
filters,
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
@@ -67,8 +67,9 @@ def get_all_transfers(date, shareholder):
|
||||
# condition = 'AND company = %(company)s '
|
||||
return frappe.db.sql(
|
||||
"""SELECT * FROM `tabShare Transfer`
|
||||
WHERE (DATE(date) <= %(date)s AND from_shareholder = %(shareholder)s {condition})
|
||||
OR (DATE(date) <= %(date)s AND to_shareholder = %(shareholder)s {condition})
|
||||
WHERE ((DATE(date) <= %(date)s AND from_shareholder = %(shareholder)s {condition})
|
||||
OR (DATE(date) <= %(date)s AND to_shareholder = %(shareholder)s {condition}))
|
||||
AND docstatus = 1
|
||||
ORDER BY date""".format(
|
||||
condition=condition
|
||||
),
|
||||
|
||||
@@ -17,7 +17,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
"label": __("Fiscal Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||
"reqd": 1,
|
||||
"on_change": function(query_report) {
|
||||
var fiscal_year = query_report.get_values().fiscal_year;
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import cstr, flt, formatdate, getdate
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import add_days, cstr, flt, formatdate, getdate
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
@@ -16,6 +17,7 @@ from erpnext.accounts.report.financial_statements import (
|
||||
filter_out_zero_value_rows,
|
||||
set_gl_entries_by_account,
|
||||
)
|
||||
from erpnext.accounts.report.utils import convert_to_presentation_currency, get_currency
|
||||
|
||||
value_fields = (
|
||||
"opening_debit",
|
||||
@@ -38,7 +40,7 @@ def validate_filters(filters):
|
||||
if not filters.fiscal_year:
|
||||
frappe.throw(_("Fiscal Year {0} is required").format(filters.fiscal_year))
|
||||
|
||||
fiscal_year = frappe.db.get_value(
|
||||
fiscal_year = frappe.get_cached_value(
|
||||
"Fiscal Year", filters.fiscal_year, ["year_start_date", "year_end_date"], as_dict=True
|
||||
)
|
||||
if not fiscal_year:
|
||||
@@ -115,6 +117,7 @@ def get_data(filters):
|
||||
filters,
|
||||
gl_entries_by_account,
|
||||
ignore_closing_entries=not flt(filters.with_period_closing_entry),
|
||||
ignore_opening_entries=True,
|
||||
)
|
||||
|
||||
calculate_values(accounts, gl_entries_by_account, opening_balances)
|
||||
@@ -137,58 +140,146 @@ def get_opening_balances(filters):
|
||||
|
||||
|
||||
def get_rootwise_opening_balances(filters, report_type):
|
||||
additional_conditions = ""
|
||||
if not filters.show_unclosed_fy_pl_balances:
|
||||
additional_conditions = (
|
||||
" and posting_date >= %(year_start_date)s" if report_type == "Profit and Loss" else ""
|
||||
)
|
||||
gle = []
|
||||
|
||||
if not flt(filters.with_period_closing_entry):
|
||||
additional_conditions += " and ifnull(voucher_type, '')!='Period Closing Voucher'"
|
||||
|
||||
if filters.cost_center:
|
||||
lft, rgt = frappe.db.get_value("Cost Center", filters.cost_center, ["lft", "rgt"])
|
||||
additional_conditions += """ and cost_center in (select name from `tabCost Center`
|
||||
where lft >= %s and rgt <= %s)""" % (
|
||||
lft,
|
||||
rgt,
|
||||
)
|
||||
|
||||
if filters.project:
|
||||
additional_conditions += " and project = %(project)s"
|
||||
|
||||
company_fb = frappe.db.get_value("Company", filters.company, "default_finance_book")
|
||||
|
||||
if filters.get("include_default_book_entries"):
|
||||
if filters.get("finance_book"):
|
||||
if company_fb and cstr(filters.get("finance_book")) != cstr(company_fb):
|
||||
frappe.throw(
|
||||
_("To use a different finance book, please uncheck 'Include Default Book Entries'")
|
||||
)
|
||||
else:
|
||||
additional_conditions += (
|
||||
" AND (finance_book in (%(finance_book)s, '') OR finance_book IS NULL)"
|
||||
)
|
||||
else:
|
||||
additional_conditions += " AND (finance_book in (%(company_fb)s, '') OR finance_book IS NULL)"
|
||||
else:
|
||||
if filters.get("finance_book"):
|
||||
additional_conditions += " AND (finance_book in (%(finance_book)s, '') OR finance_book IS NULL)"
|
||||
else:
|
||||
additional_conditions += " AND (finance_book in ('') OR finance_book IS NULL)"
|
||||
last_period_closing_voucher = frappe.db.get_all(
|
||||
"Period Closing Voucher",
|
||||
filters={"docstatus": 1, "company": filters.company, "posting_date": ("<", filters.from_date)},
|
||||
fields=["posting_date", "name"],
|
||||
order_by="posting_date desc",
|
||||
limit=1,
|
||||
)
|
||||
|
||||
accounting_dimensions = get_accounting_dimensions(as_list=False)
|
||||
|
||||
query_filters = {
|
||||
"company": filters.company,
|
||||
"from_date": filters.from_date,
|
||||
"to_date": filters.to_date,
|
||||
"report_type": report_type,
|
||||
"year_start_date": filters.year_start_date,
|
||||
"project": filters.project,
|
||||
"finance_book": filters.finance_book,
|
||||
"company_fb": company_fb,
|
||||
}
|
||||
if last_period_closing_voucher:
|
||||
gle = get_opening_balance(
|
||||
"Account Closing Balance",
|
||||
filters,
|
||||
report_type,
|
||||
accounting_dimensions,
|
||||
period_closing_voucher=last_period_closing_voucher[0].name,
|
||||
)
|
||||
|
||||
# Report getting generate from the mid of a fiscal year
|
||||
if getdate(last_period_closing_voucher[0].posting_date) < getdate(
|
||||
add_days(filters.from_date, -1)
|
||||
):
|
||||
start_date = add_days(last_period_closing_voucher[0].posting_date, 1)
|
||||
gle += get_opening_balance(
|
||||
"GL Entry", filters, report_type, accounting_dimensions, start_date=start_date
|
||||
)
|
||||
else:
|
||||
gle = get_opening_balance("GL Entry", filters, report_type, accounting_dimensions)
|
||||
|
||||
opening = frappe._dict()
|
||||
for d in gle:
|
||||
opening.setdefault(
|
||||
d.account,
|
||||
{
|
||||
"account": d.account,
|
||||
"opening_debit": 0.0,
|
||||
"opening_credit": 0.0,
|
||||
},
|
||||
)
|
||||
opening[d.account]["opening_debit"] += flt(d.debit)
|
||||
opening[d.account]["opening_credit"] += flt(d.credit)
|
||||
|
||||
return opening
|
||||
|
||||
|
||||
def get_opening_balance(
|
||||
doctype, filters, report_type, accounting_dimensions, period_closing_voucher=None, start_date=None
|
||||
):
|
||||
closing_balance = frappe.qb.DocType(doctype)
|
||||
account = frappe.qb.DocType("Account")
|
||||
|
||||
opening_balance = (
|
||||
frappe.qb.from_(closing_balance)
|
||||
.select(
|
||||
closing_balance.account,
|
||||
closing_balance.account_currency,
|
||||
Sum(closing_balance.debit).as_("debit"),
|
||||
Sum(closing_balance.credit).as_("credit"),
|
||||
Sum(closing_balance.debit_in_account_currency).as_("debit_in_account_currency"),
|
||||
Sum(closing_balance.credit_in_account_currency).as_("credit_in_account_currency"),
|
||||
)
|
||||
.where(
|
||||
(closing_balance.company == filters.company)
|
||||
& (
|
||||
closing_balance.account.isin(
|
||||
frappe.qb.from_(account).select("name").where(account.report_type == report_type)
|
||||
)
|
||||
)
|
||||
)
|
||||
.groupby(closing_balance.account)
|
||||
)
|
||||
|
||||
if period_closing_voucher:
|
||||
opening_balance = opening_balance.where(
|
||||
closing_balance.period_closing_voucher == period_closing_voucher
|
||||
)
|
||||
else:
|
||||
if start_date:
|
||||
opening_balance = opening_balance.where(
|
||||
(closing_balance.posting_date >= start_date)
|
||||
& (closing_balance.posting_date < filters.from_date)
|
||||
)
|
||||
opening_balance = opening_balance.where(closing_balance.is_opening == "No")
|
||||
else:
|
||||
opening_balance = opening_balance.where(
|
||||
(closing_balance.posting_date < filters.from_date) | (closing_balance.is_opening == "Yes")
|
||||
)
|
||||
|
||||
if doctype == "GL Entry":
|
||||
opening_balance = opening_balance.where(closing_balance.is_cancelled == 0)
|
||||
|
||||
if (
|
||||
not filters.show_unclosed_fy_pl_balances
|
||||
and report_type == "Profit and Loss"
|
||||
and doctype == "GL Entry"
|
||||
):
|
||||
opening_balance = opening_balance.where(closing_balance.posting_date >= filters.year_start_date)
|
||||
|
||||
if not flt(filters.with_period_closing_entry):
|
||||
if doctype == "Account Closing Balance":
|
||||
opening_balance = opening_balance.where(closing_balance.is_period_closing_voucher_entry == 0)
|
||||
else:
|
||||
opening_balance = opening_balance.where(
|
||||
closing_balance.voucher_type != "Period Closing Voucher"
|
||||
)
|
||||
|
||||
if filters.cost_center:
|
||||
lft, rgt = frappe.db.get_value("Cost Center", filters.cost_center, ["lft", "rgt"])
|
||||
cost_center = frappe.qb.DocType("Cost Center")
|
||||
opening_balance = opening_balance.where(
|
||||
closing_balance.cost_center.in_(
|
||||
frappe.qb.from_(cost_center)
|
||||
.select("name")
|
||||
.where((cost_center.lft >= lft) & (cost_center.rgt <= rgt))
|
||||
)
|
||||
)
|
||||
|
||||
if filters.project:
|
||||
opening_balance = opening_balance.where(closing_balance.project == filters.project)
|
||||
|
||||
if filters.get("include_default_book_entries"):
|
||||
company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book")
|
||||
|
||||
if filters.finance_book and company_fb and cstr(filters.finance_book) != cstr(company_fb):
|
||||
frappe.throw(
|
||||
_("To use a different finance book, please uncheck 'Include Default Book Entries'")
|
||||
)
|
||||
|
||||
opening_balance = opening_balance.where(
|
||||
(closing_balance.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""]))
|
||||
| (closing_balance.finance_book.isnull())
|
||||
)
|
||||
else:
|
||||
opening_balance = opening_balance.where(
|
||||
(closing_balance.finance_book.isin([cstr(filters.finance_book), ""]))
|
||||
| (closing_balance.finance_book.isnull())
|
||||
)
|
||||
|
||||
if accounting_dimensions:
|
||||
for dimension in accounting_dimensions:
|
||||
@@ -197,35 +288,20 @@ def get_rootwise_opening_balances(filters, report_type):
|
||||
filters[dimension.fieldname] = get_dimension_with_children(
|
||||
dimension.document_type, filters.get(dimension.fieldname)
|
||||
)
|
||||
additional_conditions += " and {0} in %({0})s".format(dimension.fieldname)
|
||||
opening_balance = opening_balance.where(
|
||||
closing_balance[dimension.fieldname].isin(filters[dimension.fieldname])
|
||||
)
|
||||
else:
|
||||
additional_conditions += " and {0} in %({0})s".format(dimension.fieldname)
|
||||
opening_balance = opening_balance.where(
|
||||
closing_balance[dimension.fieldname].isin(filters[dimension.fieldname])
|
||||
)
|
||||
|
||||
query_filters.update({dimension.fieldname: filters.get(dimension.fieldname)})
|
||||
gle = opening_balance.run(as_dict=1)
|
||||
|
||||
gle = frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
account, sum(debit) as opening_debit, sum(credit) as opening_credit
|
||||
from `tabGL Entry`
|
||||
where
|
||||
company=%(company)s
|
||||
{additional_conditions}
|
||||
and (posting_date < %(from_date)s or (ifnull(is_opening, 'No') = 'Yes' and posting_date <= %(to_date)s))
|
||||
and account in (select name from `tabAccount` where report_type=%(report_type)s)
|
||||
and is_cancelled = 0
|
||||
group by account""".format(
|
||||
additional_conditions=additional_conditions
|
||||
),
|
||||
query_filters,
|
||||
as_dict=True,
|
||||
)
|
||||
if filters and filters.get("presentation_currency"):
|
||||
convert_to_presentation_currency(gle, get_currency(filters))
|
||||
|
||||
opening = frappe._dict()
|
||||
for d in gle:
|
||||
opening.setdefault(d.account, d)
|
||||
|
||||
return opening
|
||||
return gle
|
||||
|
||||
|
||||
def calculate_values(accounts, gl_entries_by_account, opening_balances):
|
||||
|
||||
@@ -16,7 +16,7 @@ frappe.query_reports["Trial Balance for Party"] = {
|
||||
"label": __("Fiscal Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||
"reqd": 1,
|
||||
"on_change": function(query_report) {
|
||||
var fiscal_year = query_report.get_values().fiscal_year;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import frappe
|
||||
from frappe.utils import flt, formatdate, get_datetime_str
|
||||
from frappe.utils import flt, formatdate, get_datetime_str, get_table_name
|
||||
|
||||
from erpnext import get_company_currency, get_default_company
|
||||
from erpnext.accounts.doctype.fiscal_year.fiscal_year import get_from_and_to_date
|
||||
@@ -78,7 +78,7 @@ def get_rate_as_at(date, from_currency, to_currency):
|
||||
return rate
|
||||
|
||||
|
||||
def convert_to_presentation_currency(gl_entries, currency_info, company):
|
||||
def convert_to_presentation_currency(gl_entries, currency_info):
|
||||
"""
|
||||
Take a list of GL Entries and change the 'debit' and 'credit' values to currencies
|
||||
in `currency_info`.
|
||||
@@ -93,7 +93,6 @@ def convert_to_presentation_currency(gl_entries, currency_info, company):
|
||||
account_currencies = list(set(entry["account_currency"] for entry in gl_entries))
|
||||
|
||||
for entry in gl_entries:
|
||||
account = entry["account"]
|
||||
debit = flt(entry["debit"])
|
||||
credit = flt(entry["credit"])
|
||||
debit_in_account_currency = flt(entry["debit_in_account_currency"])
|
||||
@@ -151,3 +150,32 @@ def get_invoiced_item_gross_margin(
|
||||
result = sum(d.gross_profit for d in result)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_query_columns(report_columns):
|
||||
if not report_columns:
|
||||
return ""
|
||||
|
||||
columns = []
|
||||
for column in report_columns:
|
||||
fieldname = column["fieldname"]
|
||||
|
||||
if doctype := column.get("_doctype"):
|
||||
columns.append(f"`{get_table_name(doctype)}`.`{fieldname}`")
|
||||
else:
|
||||
columns.append(fieldname)
|
||||
|
||||
return ", " + ", ".join(columns)
|
||||
|
||||
|
||||
def get_values_for_columns(report_columns, report_row):
|
||||
values = {}
|
||||
|
||||
if not report_columns:
|
||||
return values
|
||||
|
||||
for column in report_columns:
|
||||
fieldname = column["fieldname"]
|
||||
values[fieldname] = report_row.get(fieldname)
|
||||
|
||||
return values
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports["Voucher-wise Balance"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname": "company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company"
|
||||
},
|
||||
{
|
||||
"fieldname":"from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.datetime.add_months(frappe.datetime.get_today(), -1),
|
||||
"width": "60px"
|
||||
},
|
||||
{
|
||||
"fieldname":"to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.datetime.get_today(),
|
||||
"width": "60px"
|
||||
},
|
||||
]
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"columns": [],
|
||||
"creation": "2023-06-27 16:40:15.109554",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"json": "{}",
|
||||
"letter_head": "LetterHead",
|
||||
"modified": "2023-06-27 16:40:32.493725",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Voucher-wise Balance",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "GL Entry",
|
||||
"report_name": "Voucher-wise Balance",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Accounts User"
|
||||
},
|
||||
{
|
||||
"role": "Accounts Manager"
|
||||
},
|
||||
{
|
||||
"role": "Auditor"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.query_builder.functions import Sum
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
columns = get_columns()
|
||||
data = get_data(filters)
|
||||
return columns, data
|
||||
|
||||
|
||||
def get_columns():
|
||||
return [
|
||||
{"label": _("Voucher Type"), "fieldname": "voucher_type", "width": 300},
|
||||
{
|
||||
"label": _("Voucher No"),
|
||||
"fieldname": "voucher_no",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "voucher_type",
|
||||
"width": 300,
|
||||
},
|
||||
{
|
||||
"fieldname": "debit",
|
||||
"label": _("Debit"),
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 300,
|
||||
},
|
||||
{
|
||||
"fieldname": "credit",
|
||||
"label": _("Credit"),
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 300,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def get_data(filters):
|
||||
gle = frappe.qb.DocType("GL Entry")
|
||||
query = (
|
||||
frappe.qb.from_(gle)
|
||||
.select(
|
||||
gle.voucher_type, gle.voucher_no, Sum(gle.debit).as_("debit"), Sum(gle.credit).as_("credit")
|
||||
)
|
||||
.groupby(gle.voucher_no)
|
||||
)
|
||||
query = apply_filters(query, filters, gle)
|
||||
gl_entries = query.run(as_dict=True)
|
||||
unmatched = [entry for entry in gl_entries if entry.debit != entry.credit]
|
||||
return unmatched
|
||||
|
||||
|
||||
def apply_filters(query, filters, gle):
|
||||
if filters.get("company"):
|
||||
query = query.where(gle.company == filters.company)
|
||||
if filters.get("voucher_type"):
|
||||
query = query.where(gle.voucher_type == filters.voucher_type)
|
||||
if filters.get("from_date"):
|
||||
query = query.where(gle.posting_date >= filters.from_date)
|
||||
if filters.get("to_date"):
|
||||
query = query.where(gle.posting_date <= filters.to_date)
|
||||
return query
|
||||
@@ -51,13 +51,25 @@ GL_REPOSTING_CHUNK = 100
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_fiscal_year(
|
||||
date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False
|
||||
date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False, boolean=False
|
||||
):
|
||||
return get_fiscal_years(date, fiscal_year, label, verbose, company, as_dict=as_dict)[0]
|
||||
fiscal_years = get_fiscal_years(
|
||||
date, fiscal_year, label, verbose, company, as_dict=as_dict, boolean=boolean
|
||||
)
|
||||
if boolean:
|
||||
return fiscal_years
|
||||
else:
|
||||
return fiscal_years[0]
|
||||
|
||||
|
||||
def get_fiscal_years(
|
||||
transaction_date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False
|
||||
transaction_date=None,
|
||||
fiscal_year=None,
|
||||
label="Date",
|
||||
verbose=1,
|
||||
company=None,
|
||||
as_dict=False,
|
||||
boolean=False,
|
||||
):
|
||||
fiscal_years = frappe.cache().hget("fiscal_years", company) or []
|
||||
|
||||
@@ -121,8 +133,12 @@ def get_fiscal_years(
|
||||
if company:
|
||||
error_msg = _("""{0} for {1}""").format(error_msg, frappe.bold(company))
|
||||
|
||||
if boolean:
|
||||
return False
|
||||
|
||||
if verbose == 1:
|
||||
frappe.msgprint(error_msg)
|
||||
|
||||
raise FiscalYearError(error_msg)
|
||||
|
||||
|
||||
@@ -221,11 +237,6 @@ def get_balance_on(
|
||||
if not (frappe.flags.ignore_account_permission or ignore_account_permission):
|
||||
acc.check_permission("read")
|
||||
|
||||
if report_type == "Profit and Loss":
|
||||
# for pl accounts, get balance within a fiscal year
|
||||
cond.append(
|
||||
"posting_date >= '%s' and voucher_type != 'Period Closing Voucher'" % year_start_date
|
||||
)
|
||||
# different filter for group and ledger - improved performance
|
||||
if acc.is_group:
|
||||
cond.append(
|
||||
@@ -836,7 +847,7 @@ def get_held_invoices(party_type, party):
|
||||
|
||||
if party_type == "Supplier":
|
||||
held_invoices = frappe.db.sql(
|
||||
"select name from `tabPurchase Invoice` where release_date IS NOT NULL and release_date > CURDATE()",
|
||||
"select name from `tabPurchase Invoice` where on_hold = 1 and release_date IS NOT NULL and release_date > CURDATE()",
|
||||
as_dict=1,
|
||||
)
|
||||
held_invoices = set(d["name"] for d in held_invoices)
|
||||
@@ -1095,6 +1106,12 @@ def get_autoname_with_number(number_value, doc_title, company):
|
||||
return " - ".join(parts)
|
||||
|
||||
|
||||
def parse_naming_series_variable(doc, variable):
|
||||
if variable == "FY":
|
||||
date = doc.get("posting_date") or doc.get("transaction_date") or getdate()
|
||||
return get_fiscal_year(date=date, company=doc.get("company"))[0]
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_coa(doctype, parent, is_root, chart=None):
|
||||
from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import (
|
||||
@@ -1393,6 +1410,50 @@ def check_and_delete_linked_reports(report):
|
||||
frappe.delete_doc("Desktop Icon", icon)
|
||||
|
||||
|
||||
def create_err_and_its_journals(companies: list = None) -> None:
|
||||
if companies:
|
||||
for company in companies:
|
||||
err = frappe.new_doc("Exchange Rate Revaluation")
|
||||
err.company = company.name
|
||||
err.posting_date = nowdate()
|
||||
err.rounding_loss_allowance = 0.0
|
||||
|
||||
err.fetch_and_calculate_accounts_data()
|
||||
if err.accounts:
|
||||
err.save().submit()
|
||||
response = err.make_jv_entries()
|
||||
|
||||
if company.submit_err_jv:
|
||||
jv = response.get("revaluation_jv", None)
|
||||
jv and frappe.get_doc("Journal Entry", jv).submit()
|
||||
jv = response.get("zero_balance_jv", None)
|
||||
jv and frappe.get_doc("Journal Entry", jv).submit()
|
||||
|
||||
|
||||
def auto_create_exchange_rate_revaluation_daily() -> None:
|
||||
"""
|
||||
Executed by background job
|
||||
"""
|
||||
companies = frappe.db.get_all(
|
||||
"Company",
|
||||
filters={"auto_exchange_rate_revaluation": 1, "auto_err_frequency": "Daily"},
|
||||
fields=["name", "submit_err_jv"],
|
||||
)
|
||||
create_err_and_its_journals(companies)
|
||||
|
||||
|
||||
def auto_create_exchange_rate_revaluation_weekly() -> None:
|
||||
"""
|
||||
Executed by background job
|
||||
"""
|
||||
companies = frappe.db.get_all(
|
||||
"Company",
|
||||
filters={"auto_exchange_rate_revaluation": 1, "auto_err_frequency": "Weekly"},
|
||||
fields=["name", "submit_err_jv"],
|
||||
)
|
||||
create_err_and_its_journals(companies)
|
||||
|
||||
|
||||
def get_payment_ledger_entries(gl_entries, cancel=0):
|
||||
ple_map = []
|
||||
if gl_entries:
|
||||
|
||||
@@ -1413,6 +1413,8 @@ def create_new_asset_after_split(asset, split_qty):
|
||||
)
|
||||
|
||||
new_asset.gross_purchase_amount = new_gross_purchase_amount
|
||||
if asset.purchase_receipt_amount:
|
||||
new_asset.purchase_receipt_amount = new_gross_purchase_amount
|
||||
new_asset.opening_accumulated_depreciation = opening_accumulated_depreciation
|
||||
new_asset.asset_quantity = split_qty
|
||||
new_asset.split_from = asset.name
|
||||
|
||||
@@ -34,6 +34,7 @@ def post_depreciation_entries(date=None):
|
||||
date = today()
|
||||
|
||||
failed_asset_names = []
|
||||
error_log_names = []
|
||||
|
||||
for asset_name in get_depreciable_assets(date):
|
||||
try:
|
||||
@@ -42,10 +43,12 @@ def post_depreciation_entries(date=None):
|
||||
except Exception as e:
|
||||
frappe.db.rollback()
|
||||
failed_asset_names.append(asset_name)
|
||||
error_log = frappe.log_error(e)
|
||||
error_log_names.append(error_log.name)
|
||||
|
||||
if failed_asset_names:
|
||||
set_depr_entry_posting_status_for_failed_assets(failed_asset_names)
|
||||
notify_depr_entry_posting_error(failed_asset_names)
|
||||
notify_depr_entry_posting_error(failed_asset_names, error_log_names)
|
||||
|
||||
frappe.db.commit()
|
||||
|
||||
@@ -137,15 +140,15 @@ def make_depreciation_entry(asset_name, date=None):
|
||||
je.flags.ignore_permissions = True
|
||||
je.flags.planned_depr_entry = True
|
||||
je.save()
|
||||
if not je.meta.get_workflow():
|
||||
je.submit()
|
||||
|
||||
d.db_set("journal_entry", je.name)
|
||||
|
||||
idx = cint(d.finance_book_id)
|
||||
finance_books = asset.get("finance_books")[idx - 1]
|
||||
finance_books.value_after_depreciation -= d.depreciation_amount
|
||||
finance_books.db_update()
|
||||
if not je.meta.get_workflow():
|
||||
je.submit()
|
||||
idx = cint(d.finance_book_id)
|
||||
finance_books = asset.get("finance_books")[idx - 1]
|
||||
finance_books.value_after_depreciation -= d.depreciation_amount
|
||||
finance_books.db_update()
|
||||
|
||||
asset.db_set("depr_entry_posting_status", "Successful")
|
||||
|
||||
@@ -217,7 +220,7 @@ def set_depr_entry_posting_status_for_failed_assets(failed_asset_names):
|
||||
frappe.db.set_value("Asset", asset_name, "depr_entry_posting_status", "Failed")
|
||||
|
||||
|
||||
def notify_depr_entry_posting_error(failed_asset_names):
|
||||
def notify_depr_entry_posting_error(failed_asset_names, error_log_names):
|
||||
recipients = get_users_with_role("Accounts Manager")
|
||||
|
||||
if not recipients:
|
||||
@@ -225,7 +228,8 @@ def notify_depr_entry_posting_error(failed_asset_names):
|
||||
|
||||
subject = _("Error while posting depreciation entries")
|
||||
|
||||
asset_links = get_comma_separated_asset_links(failed_asset_names)
|
||||
asset_links = get_comma_separated_links(failed_asset_names, "Asset")
|
||||
error_log_links = get_comma_separated_links(error_log_names, "Error Log")
|
||||
|
||||
message = (
|
||||
_("Hello,")
|
||||
@@ -235,23 +239,26 @@ def notify_depr_entry_posting_error(failed_asset_names):
|
||||
)
|
||||
+ "."
|
||||
+ "<br><br>"
|
||||
+ _(
|
||||
"Please raise a support ticket and share this email, or forward this email to your development team so that they can find the issue in the developer console by manually creating the depreciation entry via the asset's depreciation schedule table."
|
||||
+ _("Here are the error logs for the aforementioned failed depreciation entries: {0}").format(
|
||||
error_log_links
|
||||
)
|
||||
+ "."
|
||||
+ "<br><br>"
|
||||
+ _("Please share this email with your support team so that they can find and fix the issue.")
|
||||
)
|
||||
|
||||
frappe.sendmail(recipients=recipients, subject=subject, message=message)
|
||||
|
||||
|
||||
def get_comma_separated_asset_links(asset_names):
|
||||
asset_links = []
|
||||
def get_comma_separated_links(names, doctype):
|
||||
links = []
|
||||
|
||||
for asset_name in asset_names:
|
||||
asset_links.append(get_link_to_form("Asset", asset_name))
|
||||
for name in names:
|
||||
links.append(get_link_to_form(doctype, name))
|
||||
|
||||
asset_links = ", ".join(asset_links)
|
||||
links = ", ".join(links)
|
||||
|
||||
return asset_links
|
||||
return links
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@@ -343,6 +350,9 @@ def modify_depreciation_schedule_for_asset_repairs(asset):
|
||||
|
||||
|
||||
def reverse_depreciation_entry_made_after_disposal(asset, date):
|
||||
if not asset.calculate_depreciation:
|
||||
return
|
||||
|
||||
row = -1
|
||||
finance_book = asset.get("schedules")[0].get("finance_book")
|
||||
for schedule in asset.get("schedules"):
|
||||
@@ -483,18 +493,22 @@ def get_gl_entries_on_asset_disposal(
|
||||
},
|
||||
item=asset,
|
||||
),
|
||||
asset.get_gl_dict(
|
||||
{
|
||||
"account": accumulated_depr_account,
|
||||
"debit_in_account_currency": accumulated_depr_amount,
|
||||
"debit": accumulated_depr_amount,
|
||||
"cost_center": depreciation_cost_center,
|
||||
"posting_date": date,
|
||||
},
|
||||
item=asset,
|
||||
),
|
||||
]
|
||||
|
||||
if accumulated_depr_amount:
|
||||
gl_entries.append(
|
||||
asset.get_gl_dict(
|
||||
{
|
||||
"account": accumulated_depr_account,
|
||||
"debit_in_account_currency": accumulated_depr_amount,
|
||||
"debit": accumulated_depr_amount,
|
||||
"cost_center": depreciation_cost_center,
|
||||
"posting_date": date,
|
||||
},
|
||||
item=asset,
|
||||
),
|
||||
)
|
||||
|
||||
profit_amount = flt(selling_amount) - flt(value_after_depreciation)
|
||||
if profit_amount:
|
||||
get_profit_gl_entries(
|
||||
|
||||
@@ -14,7 +14,6 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
|
||||
}
|
||||
|
||||
refresh() {
|
||||
erpnext.hide_company();
|
||||
this.show_general_ledger();
|
||||
if ((this.frm.doc.stock_items && this.frm.doc.stock_items.length) || !this.frm.doc.target_is_fixed_asset) {
|
||||
this.show_stock_ledger();
|
||||
@@ -105,10 +104,6 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
|
||||
return this.get_target_item_details();
|
||||
}
|
||||
|
||||
target_asset() {
|
||||
return this.get_target_asset_details();
|
||||
}
|
||||
|
||||
item_code(doc, cdt, cdn) {
|
||||
var row = frappe.get_doc(cdt, cdn);
|
||||
if (cdt === "Asset Capitalization Stock Item") {
|
||||
@@ -223,26 +218,6 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
|
||||
}
|
||||
}
|
||||
|
||||
get_target_asset_details() {
|
||||
var me = this;
|
||||
|
||||
if (me.frm.doc.target_asset) {
|
||||
return me.frm.call({
|
||||
method: "erpnext.assets.doctype.asset_capitalization.asset_capitalization.get_target_asset_details",
|
||||
child: me.frm.doc,
|
||||
args: {
|
||||
asset: me.frm.doc.target_asset,
|
||||
company: me.frm.doc.company,
|
||||
},
|
||||
callback: function (r) {
|
||||
if (!r.exc) {
|
||||
me.frm.refresh_fields();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
get_consumed_stock_item_details(row) {
|
||||
var me = this;
|
||||
|
||||
|
||||
@@ -11,13 +11,14 @@
|
||||
"naming_series",
|
||||
"entry_type",
|
||||
"target_item_code",
|
||||
"target_asset",
|
||||
"target_item_name",
|
||||
"target_is_fixed_asset",
|
||||
"target_has_batch_no",
|
||||
"target_has_serial_no",
|
||||
"column_break_9",
|
||||
"target_asset",
|
||||
"target_asset_name",
|
||||
"target_asset_location",
|
||||
"target_warehouse",
|
||||
"target_qty",
|
||||
"target_stock_uom",
|
||||
@@ -85,14 +86,13 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.entry_type=='Capitalization'",
|
||||
"fieldname": "target_asset",
|
||||
"fieldtype": "Link",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Target Asset",
|
||||
"mandatory_depends_on": "eval:doc.entry_type=='Capitalization'",
|
||||
"no_copy": 1,
|
||||
"options": "Asset"
|
||||
"options": "Asset",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.entry_type=='Capitalization'",
|
||||
@@ -108,11 +108,11 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fetch_from": "asset.company",
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"remember_last_selected_value": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@@ -158,7 +158,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.docstatus == 0 || (doc.stock_items && doc.stock_items.length)",
|
||||
"depends_on": "eval:doc.entry_type=='Capitalization' && (doc.docstatus == 0 || (doc.stock_items && doc.stock_items.length))",
|
||||
"fieldname": "section_break_16",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Consumed Stock Items"
|
||||
@@ -189,7 +189,7 @@
|
||||
"fieldname": "target_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Target Qty",
|
||||
"read_only_depends_on": "target_is_fixed_asset"
|
||||
"read_only_depends_on": "eval:doc.entry_type=='Capitalization'"
|
||||
},
|
||||
{
|
||||
"fetch_from": "target_item_code.stock_uom",
|
||||
@@ -227,7 +227,7 @@
|
||||
"depends_on": "eval:doc.docstatus == 0 || (doc.asset_items && doc.asset_items.length)",
|
||||
"fieldname": "section_break_26",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Consumed Asset Items"
|
||||
"label": "Consumed Assets"
|
||||
},
|
||||
{
|
||||
"fieldname": "asset_items",
|
||||
@@ -266,7 +266,7 @@
|
||||
"options": "Finance Book"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.docstatus == 0 || (doc.service_items && doc.service_items.length)",
|
||||
"depends_on": "eval:doc.entry_type=='Capitalization' && (doc.docstatus == 0 || (doc.service_items && doc.service_items.length))",
|
||||
"fieldname": "service_expenses_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Service Expenses"
|
||||
@@ -329,12 +329,20 @@
|
||||
"label": "Target Fixed Asset Account",
|
||||
"options": "Account",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.entry_type=='Capitalization'",
|
||||
"fieldname": "target_asset_location",
|
||||
"fieldtype": "Link",
|
||||
"label": "Target Asset Location",
|
||||
"mandatory_depends_on": "eval:doc.entry_type=='Capitalization'",
|
||||
"options": "Location"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-09-12 15:09:40.771332",
|
||||
"modified": "2023-06-22 14:17:07.995120",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Capitalization",
|
||||
|
||||
@@ -7,7 +7,7 @@ import frappe
|
||||
|
||||
# import erpnext
|
||||
from frappe import _
|
||||
from frappe.utils import cint, flt
|
||||
from frappe.utils import cint, flt, get_link_to_form
|
||||
from six import string_types
|
||||
|
||||
import erpnext
|
||||
@@ -43,7 +43,6 @@ force_fields = [
|
||||
"target_has_batch_no",
|
||||
"target_stock_uom",
|
||||
"stock_uom",
|
||||
"target_fixed_asset_account",
|
||||
"fixed_asset_account",
|
||||
"valuation_rate",
|
||||
]
|
||||
@@ -54,7 +53,6 @@ class AssetCapitalization(StockController):
|
||||
self.validate_posting_time()
|
||||
self.set_missing_values(for_validate=True)
|
||||
self.validate_target_item()
|
||||
self.validate_target_asset()
|
||||
self.validate_consumed_stock_item()
|
||||
self.validate_consumed_asset_item()
|
||||
self.validate_service_item()
|
||||
@@ -65,17 +63,18 @@ class AssetCapitalization(StockController):
|
||||
|
||||
def before_submit(self):
|
||||
self.validate_source_mandatory()
|
||||
if self.entry_type == "Capitalization":
|
||||
self.create_target_asset()
|
||||
|
||||
def on_submit(self):
|
||||
self.update_stock_ledger()
|
||||
self.make_gl_entries()
|
||||
self.update_target_asset()
|
||||
|
||||
def on_cancel(self):
|
||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation")
|
||||
self.update_stock_ledger()
|
||||
self.make_gl_entries()
|
||||
self.update_target_asset()
|
||||
self.restore_consumed_asset_items()
|
||||
|
||||
def set_title(self):
|
||||
self.title = self.target_asset_name or self.target_item_name or self.target_item_code
|
||||
@@ -86,15 +85,6 @@ class AssetCapitalization(StockController):
|
||||
if self.meta.has_field(k) and (not self.get(k) or k in force_fields):
|
||||
self.set(k, v)
|
||||
|
||||
# Remove asset if item not a fixed asset
|
||||
if not self.target_is_fixed_asset:
|
||||
self.target_asset = None
|
||||
|
||||
target_asset_details = get_target_asset_details(self.target_asset, self.company)
|
||||
for k, v in target_asset_details.items():
|
||||
if self.meta.has_field(k) and (not self.get(k) or k in force_fields):
|
||||
self.set(k, v)
|
||||
|
||||
for d in self.stock_items:
|
||||
args = self.as_dict()
|
||||
args.update(d.as_dict())
|
||||
@@ -146,9 +136,6 @@ class AssetCapitalization(StockController):
|
||||
|
||||
if not target_item.is_stock_item:
|
||||
self.target_warehouse = None
|
||||
if not target_item.is_fixed_asset:
|
||||
self.target_asset = None
|
||||
self.target_fixed_asset_account = None
|
||||
if not target_item.has_batch_no:
|
||||
self.target_batch_no = None
|
||||
if not target_item.has_serial_no:
|
||||
@@ -159,17 +146,6 @@ class AssetCapitalization(StockController):
|
||||
|
||||
self.validate_item(target_item)
|
||||
|
||||
def validate_target_asset(self):
|
||||
if self.target_asset:
|
||||
target_asset = self.get_asset_for_validation(self.target_asset)
|
||||
|
||||
if target_asset.item_code != self.target_item_code:
|
||||
frappe.throw(
|
||||
_("Asset {0} does not belong to Item {1}").format(self.target_asset, self.target_item_code)
|
||||
)
|
||||
|
||||
self.validate_asset(target_asset)
|
||||
|
||||
def validate_consumed_stock_item(self):
|
||||
for d in self.stock_items:
|
||||
if d.item_code:
|
||||
@@ -379,7 +355,11 @@ class AssetCapitalization(StockController):
|
||||
gl_entries, target_account, target_against, precision
|
||||
)
|
||||
|
||||
if not self.stock_items and not self.service_items and self.are_all_asset_items_non_depreciable:
|
||||
return []
|
||||
|
||||
self.get_gl_entries_for_target_item(gl_entries, target_against, precision)
|
||||
|
||||
return gl_entries
|
||||
|
||||
def get_target_account(self):
|
||||
@@ -422,11 +402,14 @@ class AssetCapitalization(StockController):
|
||||
def get_gl_entries_for_consumed_asset_items(
|
||||
self, gl_entries, target_account, target_against, precision
|
||||
):
|
||||
self.are_all_asset_items_non_depreciable = True
|
||||
|
||||
# Consumed Assets
|
||||
for item in self.asset_items:
|
||||
asset = self.get_asset(item)
|
||||
asset = frappe.get_doc("Asset", item.asset)
|
||||
|
||||
if asset.calculate_depreciation:
|
||||
self.are_all_asset_items_non_depreciable = False
|
||||
depreciate_asset(asset, self.posting_date)
|
||||
asset.reload()
|
||||
|
||||
@@ -507,30 +490,41 @@ class AssetCapitalization(StockController):
|
||||
)
|
||||
)
|
||||
|
||||
def update_target_asset(self):
|
||||
def create_target_asset(self):
|
||||
total_target_asset_value = flt(self.total_value, self.precision("total_value"))
|
||||
if self.docstatus == 1 and self.entry_type == "Capitalization":
|
||||
asset_doc = frappe.get_doc("Asset", self.target_asset)
|
||||
asset_doc.purchase_date = self.posting_date
|
||||
asset_doc.gross_purchase_amount = total_target_asset_value
|
||||
asset_doc.purchase_receipt_amount = total_target_asset_value
|
||||
asset_doc.prepare_depreciation_data()
|
||||
asset_doc.flags.ignore_validate_update_after_submit = True
|
||||
asset_doc.save()
|
||||
elif self.docstatus == 2:
|
||||
for item in self.asset_items:
|
||||
asset = self.get_asset(item)
|
||||
asset.db_set("disposal_date", None)
|
||||
self.set_consumed_asset_status(asset)
|
||||
asset_doc = frappe.new_doc("Asset")
|
||||
asset_doc.company = self.company
|
||||
asset_doc.item_code = self.target_item_code
|
||||
asset_doc.is_existing_asset = 1
|
||||
asset_doc.location = self.target_asset_location
|
||||
asset_doc.available_for_use_date = self.posting_date
|
||||
asset_doc.purchase_date = self.posting_date
|
||||
asset_doc.gross_purchase_amount = total_target_asset_value
|
||||
asset_doc.purchase_receipt_amount = total_target_asset_value
|
||||
asset_doc.flags.ignore_validate = True
|
||||
asset_doc.insert()
|
||||
|
||||
if asset.calculate_depreciation:
|
||||
reverse_depreciation_entry_made_after_disposal(asset, self.posting_date)
|
||||
reset_depreciation_schedule(asset, self.posting_date)
|
||||
self.target_asset = asset_doc.name
|
||||
|
||||
def get_asset(self, item):
|
||||
asset = frappe.get_doc("Asset", item.asset)
|
||||
self.check_finance_books(item, asset)
|
||||
return asset
|
||||
self.target_fixed_asset_account = get_asset_category_account(
|
||||
"fixed_asset_account", item=self.target_item_code, company=asset_doc.company
|
||||
)
|
||||
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"Asset {0} has been created. Please set the depreciation details if any and submit it."
|
||||
).format(get_link_to_form("Asset", asset_doc.name))
|
||||
)
|
||||
|
||||
def restore_consumed_asset_items(self):
|
||||
for item in self.asset_items:
|
||||
asset = frappe.get_doc("Asset", item.asset)
|
||||
asset.db_set("disposal_date", None)
|
||||
self.set_consumed_asset_status(asset)
|
||||
|
||||
if asset.calculate_depreciation:
|
||||
reverse_depreciation_entry_made_after_disposal(asset, self.posting_date)
|
||||
reset_depreciation_schedule(asset, self.posting_date)
|
||||
|
||||
def set_consumed_asset_status(self, asset):
|
||||
if self.docstatus == 1:
|
||||
@@ -580,33 +574,6 @@ def get_target_item_details(item_code=None, company=None):
|
||||
return out
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_target_asset_details(asset=None, company=None):
|
||||
out = frappe._dict()
|
||||
|
||||
# Get Asset Details
|
||||
asset_details = frappe._dict()
|
||||
if asset:
|
||||
asset_details = frappe.db.get_value("Asset", asset, ["asset_name", "item_code"], as_dict=1)
|
||||
if not asset_details:
|
||||
frappe.throw(_("Asset {0} does not exist").format(asset))
|
||||
|
||||
# Re-set item code from Asset
|
||||
out.target_item_code = asset_details.item_code
|
||||
|
||||
# Set Asset Details
|
||||
out.asset_name = asset_details.asset_name
|
||||
|
||||
if asset_details.item_code:
|
||||
out.target_fixed_asset_account = get_asset_category_account(
|
||||
"fixed_asset_account", item=asset_details.item_code, company=company
|
||||
)
|
||||
else:
|
||||
out.target_fixed_asset_account = None
|
||||
|
||||
return out
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_consumed_stock_item_details(args):
|
||||
if isinstance(args, string_types):
|
||||
|
||||
@@ -39,13 +39,6 @@ class TestAssetCapitalization(unittest.TestCase):
|
||||
|
||||
total_amount = 103000
|
||||
|
||||
# Create assets
|
||||
target_asset = create_asset(
|
||||
asset_name="Asset Capitalization Target Asset",
|
||||
submit=1,
|
||||
warehouse="Stores - TCP1",
|
||||
company=company,
|
||||
)
|
||||
consumed_asset = create_asset(
|
||||
asset_name="Asset Capitalization Consumable Asset",
|
||||
asset_value=consumed_asset_value,
|
||||
@@ -57,7 +50,8 @@ class TestAssetCapitalization(unittest.TestCase):
|
||||
# Create and submit Asset Captitalization
|
||||
asset_capitalization = create_asset_capitalization(
|
||||
entry_type="Capitalization",
|
||||
target_asset=target_asset.name,
|
||||
target_item_code="Macbook Pro",
|
||||
target_asset_location="Test Location",
|
||||
stock_qty=stock_qty,
|
||||
stock_rate=stock_rate,
|
||||
consumed_asset=consumed_asset.name,
|
||||
@@ -86,7 +80,7 @@ class TestAssetCapitalization(unittest.TestCase):
|
||||
self.assertEqual(asset_capitalization.target_incoming_rate, total_amount)
|
||||
|
||||
# Test Target Asset values
|
||||
target_asset.reload()
|
||||
target_asset = frappe.get_doc("Asset", asset_capitalization.target_asset)
|
||||
self.assertEqual(target_asset.gross_purchase_amount, total_amount)
|
||||
self.assertEqual(target_asset.purchase_receipt_amount, total_amount)
|
||||
|
||||
@@ -134,13 +128,6 @@ class TestAssetCapitalization(unittest.TestCase):
|
||||
|
||||
total_amount = 103000
|
||||
|
||||
# Create assets
|
||||
target_asset = create_asset(
|
||||
asset_name="Asset Capitalization Target Asset",
|
||||
submit=1,
|
||||
warehouse="Stores - _TC",
|
||||
company=company,
|
||||
)
|
||||
consumed_asset = create_asset(
|
||||
asset_name="Asset Capitalization Consumable Asset",
|
||||
asset_value=consumed_asset_value,
|
||||
@@ -152,7 +139,8 @@ class TestAssetCapitalization(unittest.TestCase):
|
||||
# Create and submit Asset Captitalization
|
||||
asset_capitalization = create_asset_capitalization(
|
||||
entry_type="Capitalization",
|
||||
target_asset=target_asset.name,
|
||||
target_item_code="Macbook Pro",
|
||||
target_asset_location="Test Location",
|
||||
stock_qty=stock_qty,
|
||||
stock_rate=stock_rate,
|
||||
consumed_asset=consumed_asset.name,
|
||||
@@ -181,7 +169,7 @@ class TestAssetCapitalization(unittest.TestCase):
|
||||
self.assertEqual(asset_capitalization.target_incoming_rate, total_amount)
|
||||
|
||||
# Test Target Asset values
|
||||
target_asset.reload()
|
||||
target_asset = frappe.get_doc("Asset", asset_capitalization.target_asset)
|
||||
self.assertEqual(target_asset.gross_purchase_amount, total_amount)
|
||||
self.assertEqual(target_asset.purchase_receipt_amount, total_amount)
|
||||
|
||||
@@ -343,6 +331,7 @@ def create_asset_capitalization(**args):
|
||||
"posting_time": args.posting_time or now.strftime("%H:%M:%S.%f"),
|
||||
"target_item_code": target_item_code,
|
||||
"target_asset": target_asset.name,
|
||||
"target_asset_location": "Test Location",
|
||||
"target_warehouse": target_warehouse,
|
||||
"target_qty": flt(args.target_qty) or 1,
|
||||
"target_batch_no": args.target_batch_no,
|
||||
|
||||
@@ -63,26 +63,28 @@ frappe.ui.form.on('Asset Movement', {
|
||||
fieldnames_to_be_altered = {
|
||||
target_location: { read_only: 0, reqd: 1 },
|
||||
source_location: { read_only: 1, reqd: 0 },
|
||||
from_employee: { read_only: 0, reqd: 1 },
|
||||
from_employee: { read_only: 0, reqd: 0 },
|
||||
to_employee: { read_only: 1, reqd: 0 }
|
||||
};
|
||||
}
|
||||
else if (frm.doc.purpose === 'Issue') {
|
||||
fieldnames_to_be_altered = {
|
||||
target_location: { read_only: 1, reqd: 0 },
|
||||
source_location: { read_only: 1, reqd: 1 },
|
||||
source_location: { read_only: 1, reqd: 0 },
|
||||
from_employee: { read_only: 1, reqd: 0 },
|
||||
to_employee: { read_only: 0, reqd: 1 }
|
||||
};
|
||||
}
|
||||
Object.keys(fieldnames_to_be_altered).forEach(fieldname => {
|
||||
let property_to_be_altered = fieldnames_to_be_altered[fieldname];
|
||||
Object.keys(property_to_be_altered).forEach(property => {
|
||||
let value = property_to_be_altered[property];
|
||||
frm.set_df_property(fieldname, property, value, cdn, 'assets');
|
||||
if (fieldnames_to_be_altered) {
|
||||
Object.keys(fieldnames_to_be_altered).forEach(fieldname => {
|
||||
let property_to_be_altered = fieldnames_to_be_altered[fieldname];
|
||||
Object.keys(property_to_be_altered).forEach(property => {
|
||||
let value = property_to_be_altered[property];
|
||||
frm.fields_dict['assets'].grid.update_docfield_property(fieldname, property, value);
|
||||
});
|
||||
});
|
||||
});
|
||||
frm.refresh_field('assets');
|
||||
frm.refresh_field('assets');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "Now",
|
||||
"fieldname": "transaction_date",
|
||||
"fieldtype": "Datetime",
|
||||
"in_list_view": 1,
|
||||
@@ -95,10 +96,11 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-01-22 12:30:55.295670",
|
||||
"modified": "2023-06-28 16:54:26.571083",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Movement",
|
||||
"naming_rule": "Expression",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@@ -148,5 +150,6 @@
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -28,25 +28,20 @@ class AssetMovement(Document):
|
||||
def validate_location(self):
|
||||
for d in self.assets:
|
||||
if self.purpose in ["Transfer", "Issue"]:
|
||||
if not d.source_location:
|
||||
d.source_location = frappe.db.get_value("Asset", d.asset, "location")
|
||||
|
||||
if not d.source_location:
|
||||
frappe.throw(_("Source Location is required for the Asset {0}").format(d.asset))
|
||||
|
||||
current_location = frappe.db.get_value("Asset", d.asset, "location")
|
||||
if d.source_location:
|
||||
current_location = frappe.db.get_value("Asset", d.asset, "location")
|
||||
|
||||
if current_location != d.source_location:
|
||||
frappe.throw(
|
||||
_("Asset {0} does not belongs to the location {1}").format(d.asset, d.source_location)
|
||||
)
|
||||
else:
|
||||
d.source_location = current_location
|
||||
|
||||
if self.purpose == "Issue":
|
||||
if d.target_location:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Issuing cannot be done to a location. Please enter employee who has issued Asset {0}"
|
||||
"Issuing cannot be done to a location. Please enter employee to issue the Asset {0} to"
|
||||
).format(d.asset),
|
||||
title=_("Incorrect Movement Purpose"),
|
||||
)
|
||||
@@ -67,27 +62,19 @@ class AssetMovement(Document):
|
||||
frappe.throw(_("Source and Target Location cannot be same"))
|
||||
|
||||
if self.purpose == "Receipt":
|
||||
# only when asset is bought and first entry is made
|
||||
if not d.source_location and not (d.target_location or d.to_employee):
|
||||
if not (d.source_location) and not (d.target_location or d.to_employee):
|
||||
frappe.throw(
|
||||
_("Target Location or To Employee is required while receiving Asset {0}").format(d.asset)
|
||||
)
|
||||
elif d.source_location:
|
||||
# when asset is received from an employee
|
||||
if d.target_location and not d.from_employee:
|
||||
frappe.throw(
|
||||
_("From employee is required while receiving Asset {0} to a target location").format(
|
||||
d.asset
|
||||
)
|
||||
)
|
||||
if d.from_employee and not d.target_location:
|
||||
frappe.throw(
|
||||
_("Target Location is required while receiving Asset {0} from an employee").format(d.asset)
|
||||
)
|
||||
if d.to_employee and d.target_location:
|
||||
elif d.to_employee and d.target_location:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Asset {0} cannot be received at a location and given to employee in a single movement"
|
||||
"Asset {0} cannot be received at a location and given to an employee in a single movement"
|
||||
).format(d.asset)
|
||||
)
|
||||
|
||||
@@ -107,12 +94,12 @@ class AssetMovement(Document):
|
||||
)
|
||||
|
||||
def on_submit(self):
|
||||
self.set_latest_location_in_asset()
|
||||
self.set_latest_location_and_custodian_in_asset()
|
||||
|
||||
def on_cancel(self):
|
||||
self.set_latest_location_in_asset()
|
||||
self.set_latest_location_and_custodian_in_asset()
|
||||
|
||||
def set_latest_location_in_asset(self):
|
||||
def set_latest_location_and_custodian_in_asset(self):
|
||||
current_location, current_employee = "", ""
|
||||
cond = "1=1"
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ class TestAssetMovement(unittest.TestCase):
|
||||
if not frappe.db.exists("Location", "Test Location 2"):
|
||||
frappe.get_doc({"doctype": "Location", "location_name": "Test Location 2"}).insert()
|
||||
|
||||
movement1 = create_asset_movement(
|
||||
create_asset_movement(
|
||||
purpose="Transfer",
|
||||
company=asset.company,
|
||||
assets=[
|
||||
@@ -58,7 +58,7 @@ class TestAssetMovement(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2")
|
||||
|
||||
create_asset_movement(
|
||||
movement1 = create_asset_movement(
|
||||
purpose="Transfer",
|
||||
company=asset.company,
|
||||
assets=[
|
||||
@@ -70,21 +70,32 @@ class TestAssetMovement(unittest.TestCase):
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location")
|
||||
|
||||
movement1.cancel()
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location")
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2")
|
||||
|
||||
employee = make_employee("testassetmovemp@example.com", company="_Test Company")
|
||||
create_asset_movement(
|
||||
purpose="Issue",
|
||||
company=asset.company,
|
||||
assets=[{"asset": asset.name, "source_location": "Test Location", "to_employee": employee}],
|
||||
assets=[{"asset": asset.name, "source_location": "Test Location 2", "to_employee": employee}],
|
||||
reference_doctype="Purchase Receipt",
|
||||
reference_name=pr.name,
|
||||
)
|
||||
|
||||
# after issuing asset should belong to an employee not at a location
|
||||
# after issuing, asset should belong to an employee not at a location
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), None)
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "custodian"), employee)
|
||||
|
||||
create_asset_movement(
|
||||
purpose="Receipt",
|
||||
company=asset.company,
|
||||
assets=[{"asset": asset.name, "from_employee": employee, "target_location": "Test Location"}],
|
||||
reference_doctype="Purchase Receipt",
|
||||
reference_name=pr.name,
|
||||
)
|
||||
|
||||
# after receiving, asset should belong to a location not at an employee
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location")
|
||||
|
||||
def test_last_movement_cancellation(self):
|
||||
pr = make_purchase_receipt(
|
||||
item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user