Compare commits
1 Commits
v15.46.2
...
rohitwaghc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a748f45c9 |
@@ -28,7 +28,4 @@ b147b85e6ac19a9220cd1e2958a6ebd99373283a
|
||||
494bd9ef78313436f0424b918f200dab8fc7c20b
|
||||
|
||||
# bulk format python code with black
|
||||
baec607ff5905b1c67531096a9cf50ec7ff00a5d
|
||||
|
||||
# ruff
|
||||
960ef14b7a68cfec9e309ec12845f521cb6a721c
|
||||
baec607ff5905b1c67531096a9cf50ec7ff00a5d
|
||||
12
.github/helper/install.sh
vendored
12
.github/helper/install.sh
vendored
@@ -6,7 +6,7 @@ cd ~ || exit
|
||||
|
||||
sudo apt update
|
||||
sudo apt remove mysql-server mysql-client
|
||||
sudo apt install libcups2-dev redis-server mariadb-client
|
||||
sudo apt install libcups2-dev redis-server mariadb-client-10.6
|
||||
|
||||
pip install frappe-bench
|
||||
|
||||
@@ -44,9 +44,13 @@ fi
|
||||
|
||||
|
||||
install_whktml() {
|
||||
wget -O /tmp/wkhtmltox.deb https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-2/wkhtmltox_0.12.6.1-2.jammy_amd64.deb
|
||||
sudo apt install /tmp/wkhtmltox.deb
|
||||
|
||||
if [ "$(lsb_release -rs)" = "22.04" ]; then
|
||||
wget -O /tmp/wkhtmltox.deb https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-2/wkhtmltox_0.12.6.1-2.jammy_amd64.deb
|
||||
sudo apt install /tmp/wkhtmltox.deb
|
||||
else
|
||||
echo "Please update this script to support wkhtmltopdf for $(lsb_release -ds)"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
install_whktml &
|
||||
wkpid=$!
|
||||
|
||||
@@ -55,18 +55,29 @@ repos:
|
||||
erpnext/templates/includes/.*
|
||||
)$
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.2.0
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 6.0.0
|
||||
hooks:
|
||||
- id: ruff
|
||||
name: "Run ruff import sorter"
|
||||
args: ["--select=I", "--fix"]
|
||||
- id: flake8
|
||||
additional_dependencies: [
|
||||
'flake8-bugbear',
|
||||
'flake8-tuple',
|
||||
]
|
||||
args: ['--config', '.github/helper/.flake8_strict']
|
||||
exclude: ".*setup.py$"
|
||||
|
||||
- id: ruff
|
||||
name: "Run ruff linter"
|
||||
- repo: https://github.com/adityahase/black
|
||||
rev: 9cb0a69f4d0030cdf687eddf314468b39ed54119
|
||||
hooks:
|
||||
- id: black
|
||||
additional_dependencies: ['click==8.0.4']
|
||||
|
||||
- repo: https://github.com/PyCQA/isort
|
||||
rev: 5.12.0
|
||||
hooks:
|
||||
- id: isort
|
||||
exclude: ".*setup.py$"
|
||||
|
||||
- id: ruff-format
|
||||
name: "Run ruff formatter"
|
||||
|
||||
ci:
|
||||
autoupdate_schedule: weekly
|
||||
|
||||
16
CODEOWNERS
16
CODEOWNERS
@@ -4,21 +4,21 @@
|
||||
# the repo. Unless a later match takes precedence,
|
||||
|
||||
erpnext/accounts/ @deepeshgarg007 @ruthra-kumar
|
||||
erpnext/assets/ @khushi8112 @deepeshgarg007
|
||||
erpnext/assets/ @anandbaburajan @deepeshgarg007
|
||||
erpnext/regional @deepeshgarg007 @ruthra-kumar
|
||||
erpnext/selling @deepeshgarg007 @ruthra-kumar
|
||||
erpnext/support/ @deepeshgarg007
|
||||
pos*
|
||||
|
||||
erpnext/buying/ @rohitwaghchaure
|
||||
erpnext/maintenance/ @rohitwaghchaure
|
||||
erpnext/manufacturing/ @rohitwaghchaure
|
||||
erpnext/quality_management/ @rohitwaghchaure
|
||||
erpnext/stock/ @rohitwaghchaure
|
||||
erpnext/subcontracting @rohitwaghchaure
|
||||
erpnext/buying/ @rohitwaghchaure @s-aga-r
|
||||
erpnext/maintenance/ @rohitwaghchaure @s-aga-r
|
||||
erpnext/manufacturing/ @rohitwaghchaure @s-aga-r
|
||||
erpnext/quality_management/ @rohitwaghchaure @s-aga-r
|
||||
erpnext/stock/ @rohitwaghchaure @s-aga-r
|
||||
erpnext/subcontracting @rohitwaghchaure @s-aga-r
|
||||
|
||||
erpnext/controllers/ @deepeshgarg007 @rohitwaghchaure
|
||||
erpnext/patches/ @deepeshgarg007
|
||||
|
||||
.github/ @deepeshgarg007
|
||||
pyproject.toml @akhilnarang
|
||||
pyproject.toml @phot0n
|
||||
|
||||
@@ -2,9 +2,8 @@ import functools
|
||||
import inspect
|
||||
|
||||
import frappe
|
||||
from frappe.utils.user import is_website_user
|
||||
|
||||
__version__ = "15.46.2"
|
||||
__version__ = "15.16.2"
|
||||
|
||||
|
||||
def get_default_company(user=None):
|
||||
@@ -37,8 +36,10 @@ def get_default_cost_center(company):
|
||||
|
||||
if not frappe.flags.company_cost_center:
|
||||
frappe.flags.company_cost_center = {}
|
||||
if company not in frappe.flags.company_cost_center:
|
||||
frappe.flags.company_cost_center[company] = frappe.get_cached_value("Company", company, "cost_center")
|
||||
if not company in frappe.flags.company_cost_center:
|
||||
frappe.flags.company_cost_center[company] = frappe.get_cached_value(
|
||||
"Company", company, "cost_center"
|
||||
)
|
||||
return frappe.flags.company_cost_center[company]
|
||||
|
||||
|
||||
@@ -46,7 +47,7 @@ def get_company_currency(company):
|
||||
"""Returns the default company currency"""
|
||||
if not frappe.flags.company_currency:
|
||||
frappe.flags.company_currency = {}
|
||||
if company not in frappe.flags.company_currency:
|
||||
if not company in frappe.flags.company_currency:
|
||||
frappe.flags.company_currency[company] = frappe.db.get_value(
|
||||
"Company", company, "default_currency", cache=True
|
||||
)
|
||||
@@ -80,7 +81,7 @@ def is_perpetual_inventory_enabled(company):
|
||||
if not hasattr(frappe.local, "enable_perpetual_inventory"):
|
||||
frappe.local.enable_perpetual_inventory = {}
|
||||
|
||||
if company not in frappe.local.enable_perpetual_inventory:
|
||||
if not company in frappe.local.enable_perpetual_inventory:
|
||||
frappe.local.enable_perpetual_inventory[company] = (
|
||||
frappe.get_cached_value("Company", company, "enable_perpetual_inventory") or 0
|
||||
)
|
||||
@@ -95,7 +96,7 @@ def get_default_finance_book(company=None):
|
||||
if not hasattr(frappe.local, "default_finance_book"):
|
||||
frappe.local.default_finance_book = {}
|
||||
|
||||
if company not in frappe.local.default_finance_book:
|
||||
if not company in frappe.local.default_finance_book:
|
||||
frappe.local.default_finance_book[company] = frappe.get_cached_value(
|
||||
"Company", company, "default_finance_book"
|
||||
)
|
||||
@@ -107,7 +108,7 @@ def get_party_account_type(party_type):
|
||||
if not hasattr(frappe.local, "party_account_types"):
|
||||
frappe.local.party_account_types = {}
|
||||
|
||||
if party_type not in frappe.local.party_account_types:
|
||||
if not party_type in frappe.local.party_account_types:
|
||||
frappe.local.party_account_types[party_type] = (
|
||||
frappe.db.get_value("Party Type", party_type, "account_type") or ""
|
||||
)
|
||||
@@ -150,13 +151,3 @@ def allow_regional(fn):
|
||||
return frappe.get_attr(overrides[function_path][-1])(*args, **kwargs)
|
||||
|
||||
return caller
|
||||
|
||||
|
||||
def check_app_permission():
|
||||
if frappe.session.user == "Administrator":
|
||||
return True
|
||||
|
||||
if is_website_user():
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@@ -11,14 +11,14 @@ class ERPNextAddress(Address):
|
||||
def validate(self):
|
||||
self.validate_reference()
|
||||
self.update_compnay_address()
|
||||
super().validate()
|
||||
super(ERPNextAddress, self).validate()
|
||||
|
||||
def link_address(self):
|
||||
"""Link address based on owner"""
|
||||
if self.is_your_company_address:
|
||||
return
|
||||
|
||||
return super().link_address()
|
||||
return super(ERPNextAddress, self).link_address()
|
||||
|
||||
def update_compnay_address(self):
|
||||
for link in self.get("links"):
|
||||
@@ -26,11 +26,11 @@ class ERPNextAddress(Address):
|
||||
self.is_your_company_address = 1
|
||||
|
||||
def validate_reference(self):
|
||||
if self.is_your_company_address and not [row for row in self.links if row.link_doctype == "Company"]:
|
||||
if self.is_your_company_address and not [
|
||||
row for row in self.links if row.link_doctype == "Company"
|
||||
]:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Address needs to be linked to a Company. Please add a row for Company in the Links table."
|
||||
),
|
||||
_("Address needs to be linked to a Company. Please add a row for Company in the Links table."),
|
||||
title=_("Company Not Linked"),
|
||||
)
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ def get(
|
||||
filters = frappe.parse_json(filters) or frappe.parse_json(chart.filters_json)
|
||||
|
||||
account = filters.get("account")
|
||||
filters.get("company")
|
||||
company = filters.get("company")
|
||||
|
||||
if not account and chart_name:
|
||||
frappe.throw(
|
||||
@@ -83,6 +83,7 @@ def build_result(account, dates, gl_entries):
|
||||
|
||||
# get balances in debit
|
||||
for entry in gl_entries:
|
||||
|
||||
# entry date is after the current pointer, so move the pointer forward
|
||||
while getdate(entry.posting_date) > result[date_index][0]:
|
||||
date_index += 1
|
||||
@@ -132,6 +133,8 @@ def get_dates_from_timegrain(from_date, to_date, timegrain):
|
||||
|
||||
dates = [get_period_ending(from_date, timegrain)]
|
||||
while getdate(dates[-1]) < getdate(to_date):
|
||||
date = get_period_ending(add_to_date(dates[-1], years=years, months=months, days=days), timegrain)
|
||||
date = get_period_ending(
|
||||
add_to_date(dates[-1], years=years, months=months, days=days), timegrain
|
||||
)
|
||||
dates.append(date)
|
||||
return dates
|
||||
|
||||
@@ -24,10 +24,14 @@ from erpnext.accounts.utils import get_account_currency
|
||||
def validate_service_stop_date(doc):
|
||||
"""Validates service_stop_date for Purchase Invoice and Sales Invoice"""
|
||||
|
||||
enable_check = "enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense"
|
||||
enable_check = (
|
||||
"enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense"
|
||||
)
|
||||
|
||||
old_stop_dates = {}
|
||||
old_doc = frappe.db.get_all(f"{doc.doctype} Item", {"parent": doc.name}, ["name", "service_stop_date"])
|
||||
old_doc = frappe.db.get_all(
|
||||
"{0} Item".format(doc.doctype), {"parent": doc.name}, ["name", "service_stop_date"]
|
||||
)
|
||||
|
||||
for d in old_doc:
|
||||
old_stop_dates[d.name] = d.service_stop_date or ""
|
||||
@@ -58,14 +62,16 @@ def build_conditions(process_type, account, company):
|
||||
)
|
||||
|
||||
if account:
|
||||
conditions += f"AND {deferred_account}={frappe.db.escape(account)}"
|
||||
conditions += "AND %s='%s'" % (deferred_account, account)
|
||||
elif company:
|
||||
conditions += f"AND p.company = {frappe.db.escape(company)}"
|
||||
|
||||
return conditions
|
||||
|
||||
|
||||
def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_date=None, conditions=""):
|
||||
def convert_deferred_expense_to_expense(
|
||||
deferred_process, start_date=None, end_date=None, conditions=""
|
||||
):
|
||||
# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
|
||||
|
||||
if not start_date:
|
||||
@@ -75,14 +81,16 @@ def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_d
|
||||
|
||||
# check for the purchase invoice for which GL entries has to be done
|
||||
invoices = frappe.db.sql_list(
|
||||
f"""
|
||||
"""
|
||||
select distinct item.parent
|
||||
from `tabPurchase Invoice Item` item, `tabPurchase Invoice` p
|
||||
where item.service_start_date<=%s and item.service_end_date>=%s
|
||||
and item.enable_deferred_expense = 1 and item.parent=p.name
|
||||
and item.docstatus = 1 and ifnull(item.amount, 0) > 0
|
||||
{conditions}
|
||||
""",
|
||||
{0}
|
||||
""".format(
|
||||
conditions
|
||||
),
|
||||
(end_date, start_date),
|
||||
) # nosec
|
||||
|
||||
@@ -95,7 +103,9 @@ def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_d
|
||||
send_mail(deferred_process)
|
||||
|
||||
|
||||
def convert_deferred_revenue_to_income(deferred_process, start_date=None, end_date=None, conditions=""):
|
||||
def convert_deferred_revenue_to_income(
|
||||
deferred_process, start_date=None, end_date=None, conditions=""
|
||||
):
|
||||
# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
|
||||
|
||||
if not start_date:
|
||||
@@ -105,14 +115,16 @@ def convert_deferred_revenue_to_income(deferred_process, start_date=None, end_da
|
||||
|
||||
# check for the sales invoice for which GL entries has to be done
|
||||
invoices = frappe.db.sql_list(
|
||||
f"""
|
||||
"""
|
||||
select distinct item.parent
|
||||
from `tabSales Invoice Item` item, `tabSales Invoice` p
|
||||
where item.service_start_date<=%s and item.service_end_date>=%s
|
||||
and item.enable_deferred_revenue = 1 and item.parent=p.name
|
||||
and item.docstatus = 1 and ifnull(item.amount, 0) > 0
|
||||
{conditions}
|
||||
""",
|
||||
{0}
|
||||
""".format(
|
||||
conditions
|
||||
),
|
||||
(end_date, start_date),
|
||||
) # nosec
|
||||
|
||||
@@ -231,7 +243,9 @@ def calculate_monthly_amount(
|
||||
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(
|
||||
doc, item
|
||||
)
|
||||
base_amount = flt(item.base_net_amount - already_booked_amount, item.precision("base_net_amount"))
|
||||
base_amount = flt(
|
||||
item.base_net_amount - already_booked_amount, item.precision("base_net_amount")
|
||||
)
|
||||
if account_currency == doc.company_currency:
|
||||
amount = base_amount
|
||||
else:
|
||||
@@ -251,13 +265,17 @@ def calculate_amount(doc, item, last_gl_entry, total_days, total_booking_days, a
|
||||
if account_currency == doc.company_currency:
|
||||
amount = base_amount
|
||||
else:
|
||||
amount = flt(item.net_amount * total_booking_days / flt(total_days), item.precision("net_amount"))
|
||||
amount = flt(
|
||||
item.net_amount * total_booking_days / flt(total_days), item.precision("net_amount")
|
||||
)
|
||||
else:
|
||||
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(
|
||||
doc, item
|
||||
)
|
||||
|
||||
base_amount = flt(item.base_net_amount - already_booked_amount, item.precision("base_net_amount"))
|
||||
base_amount = flt(
|
||||
item.base_net_amount - already_booked_amount, item.precision("base_net_amount")
|
||||
)
|
||||
if account_currency == doc.company_currency:
|
||||
amount = base_amount
|
||||
else:
|
||||
@@ -278,22 +296,26 @@ def get_already_booked_amount(doc, item):
|
||||
|
||||
gl_entries_details = frappe.db.sql(
|
||||
"""
|
||||
select sum({}) as total_credit, sum({}) as total_credit_in_account_currency, voucher_detail_no
|
||||
select sum({0}) as total_credit, sum({1}) as total_credit_in_account_currency, voucher_detail_no
|
||||
from `tabGL Entry` where company=%s and account=%s and voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
|
||||
and is_cancelled = 0
|
||||
group by voucher_detail_no
|
||||
""".format(total_credit_debit, total_credit_debit_currency),
|
||||
""".format(
|
||||
total_credit_debit, total_credit_debit_currency
|
||||
),
|
||||
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
journal_entry_details = frappe.db.sql(
|
||||
"""
|
||||
SELECT sum(c.{}) as total_credit, sum(c.{}) as total_credit_in_account_currency, reference_detail_no
|
||||
SELECT sum(c.{0}) as total_credit, sum(c.{1}) as total_credit_in_account_currency, reference_detail_no
|
||||
FROM `tabJournal Entry` p , `tabJournal Entry Account` c WHERE p.name = c.parent and
|
||||
p.company = %s and c.account=%s and c.reference_type=%s and c.reference_name=%s and c.reference_detail_no=%s
|
||||
and p.docstatus < 2 group by reference_detail_no
|
||||
""".format(total_credit_debit, total_credit_debit_currency),
|
||||
""".format(
|
||||
total_credit_debit, total_credit_debit_currency
|
||||
),
|
||||
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
|
||||
as_dict=True,
|
||||
)
|
||||
@@ -315,7 +337,9 @@ def get_already_booked_amount(doc, item):
|
||||
|
||||
|
||||
def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
|
||||
enable_check = "enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense"
|
||||
enable_check = (
|
||||
"enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense"
|
||||
)
|
||||
|
||||
accounts_frozen_upto = frappe.db.get_single_value("Accounts Settings", "acc_frozen_upto")
|
||||
|
||||
@@ -360,45 +384,45 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
|
||||
)
|
||||
|
||||
if not amount:
|
||||
prev_posting_date = end_date
|
||||
else:
|
||||
gl_posting_date = end_date
|
||||
prev_posting_date = None
|
||||
# check if books nor frozen till endate:
|
||||
if accounts_frozen_upto and getdate(end_date) <= getdate(accounts_frozen_upto):
|
||||
gl_posting_date = get_last_day(add_days(accounts_frozen_upto, 1))
|
||||
prev_posting_date = end_date
|
||||
return
|
||||
|
||||
if via_journal_entry:
|
||||
book_revenue_via_journal_entry(
|
||||
doc,
|
||||
credit_account,
|
||||
debit_account,
|
||||
amount,
|
||||
base_amount,
|
||||
gl_posting_date,
|
||||
project,
|
||||
account_currency,
|
||||
item.cost_center,
|
||||
item,
|
||||
deferred_process,
|
||||
submit_journal_entry,
|
||||
)
|
||||
else:
|
||||
make_gl_entries(
|
||||
doc,
|
||||
credit_account,
|
||||
debit_account,
|
||||
against,
|
||||
amount,
|
||||
base_amount,
|
||||
gl_posting_date,
|
||||
project,
|
||||
account_currency,
|
||||
item.cost_center,
|
||||
item,
|
||||
deferred_process,
|
||||
)
|
||||
gl_posting_date = end_date
|
||||
prev_posting_date = None
|
||||
# check if books nor frozen till endate:
|
||||
if accounts_frozen_upto and getdate(end_date) <= getdate(accounts_frozen_upto):
|
||||
gl_posting_date = get_last_day(add_days(accounts_frozen_upto, 1))
|
||||
prev_posting_date = end_date
|
||||
|
||||
if via_journal_entry:
|
||||
book_revenue_via_journal_entry(
|
||||
doc,
|
||||
credit_account,
|
||||
debit_account,
|
||||
amount,
|
||||
base_amount,
|
||||
gl_posting_date,
|
||||
project,
|
||||
account_currency,
|
||||
item.cost_center,
|
||||
item,
|
||||
deferred_process,
|
||||
submit_journal_entry,
|
||||
)
|
||||
else:
|
||||
make_gl_entries(
|
||||
doc,
|
||||
credit_account,
|
||||
debit_account,
|
||||
against,
|
||||
amount,
|
||||
base_amount,
|
||||
gl_posting_date,
|
||||
project,
|
||||
account_currency,
|
||||
item.cost_center,
|
||||
item,
|
||||
deferred_process,
|
||||
)
|
||||
|
||||
# Returned in case of any errors because it tries to submit the same record again and again in case of errors
|
||||
if frappe.flags.deferred_accounting_error:
|
||||
@@ -416,7 +440,9 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
|
||||
via_journal_entry = cint(
|
||||
frappe.db.get_singles_value("Accounts Settings", "book_deferred_entries_via_journal_entry")
|
||||
)
|
||||
submit_journal_entry = cint(frappe.db.get_singles_value("Accounts Settings", "submit_journal_entries"))
|
||||
submit_journal_entry = cint(
|
||||
frappe.db.get_singles_value("Accounts Settings", "submit_journal_entries")
|
||||
)
|
||||
book_deferred_entries_based_on = frappe.db.get_singles_value(
|
||||
"Accounts Settings", "book_deferred_entries_based_on"
|
||||
)
|
||||
@@ -436,7 +462,9 @@ def process_deferred_accounting(posting_date=None):
|
||||
posting_date = today()
|
||||
|
||||
if not cint(
|
||||
frappe.db.get_singles_value("Accounts Settings", "automatically_process_deferred_accounting_entry")
|
||||
frappe.db.get_singles_value(
|
||||
"Accounts Settings", "automatically_process_deferred_accounting_entry"
|
||||
)
|
||||
):
|
||||
return
|
||||
|
||||
@@ -559,13 +587,16 @@ def book_revenue_via_journal_entry(
|
||||
deferred_process=None,
|
||||
submit="No",
|
||||
):
|
||||
|
||||
if amount == 0:
|
||||
return
|
||||
|
||||
journal_entry = frappe.new_doc("Journal Entry")
|
||||
journal_entry.posting_date = posting_date
|
||||
journal_entry.company = doc.company
|
||||
journal_entry.voucher_type = "Deferred Revenue" if doc.doctype == "Sales Invoice" else "Deferred Expense"
|
||||
journal_entry.voucher_type = (
|
||||
"Deferred Revenue" if doc.doctype == "Sales Invoice" else "Deferred Expense"
|
||||
)
|
||||
journal_entry.process_deferred_accounting = deferred_process
|
||||
|
||||
debit_entry = {
|
||||
@@ -614,6 +645,7 @@ def book_revenue_via_journal_entry(
|
||||
|
||||
|
||||
def get_deferred_booking_accounts(doctype, voucher_detail_no, dr_or_cr):
|
||||
|
||||
if doctype == "Sales Invoice":
|
||||
credit_account, debit_account = frappe.db.get_value(
|
||||
"Sales Invoice Item",
|
||||
|
||||
@@ -22,7 +22,8 @@ frappe.ui.form.on("Account", {
|
||||
// hide fields if group
|
||||
frm.toggle_display(["tax_rate"], cint(frm.doc.is_group) == 0);
|
||||
|
||||
frm.toggle_enable(["is_group", "company", "account_number"], frm.is_new());
|
||||
// disable fields
|
||||
frm.toggle_enable(["is_group", "company"], false);
|
||||
|
||||
if (cint(frm.doc.is_group) == 0) {
|
||||
frm.toggle_display("freeze_account", frm.doc.__onload && frm.doc.__onload.can_freeze_account);
|
||||
|
||||
@@ -55,7 +55,8 @@
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Account Number"
|
||||
"label": "Account Number",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@@ -71,6 +72,7 @@
|
||||
"oldfieldname": "company",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Company",
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
@@ -121,8 +123,7 @@
|
||||
"label": "Account Type",
|
||||
"oldfieldname": "account_type",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "\nAccumulated Depreciation\nAsset Received But Not Billed\nBank\nCash\nChargeable\nCapital Work in Progress\nCost of Goods Sold\nCurrent Asset\nCurrent Liability\nDepreciation\nDirect Expense\nDirect Income\nEquity\nExpense Account\nExpenses Included In Asset Valuation\nExpenses Included In Valuation\nFixed Asset\nIncome Account\nIndirect Expense\nIndirect Income\nLiability\nPayable\nReceivable\nRound Off\nRound Off for Opening\nStock\nStock Adjustment\nStock Received But Not Billed\nService Received But Not Billed\nTax\nTemporary",
|
||||
"search_index": 1
|
||||
"options": "\nAccumulated Depreciation\nAsset Received But Not Billed\nBank\nCash\nChargeable\nCapital Work in Progress\nCost of Goods Sold\nCurrent Asset\nCurrent Liability\nDepreciation\nDirect Expense\nDirect Income\nEquity\nExpense Account\nExpenses Included In Asset Valuation\nExpenses Included In Valuation\nFixed Asset\nIncome Account\nIndirect Expense\nIndirect Income\nLiability\nPayable\nReceivable\nRound Off\nStock\nStock Adjustment\nStock Received But Not Billed\nService Received But Not Billed\nTax\nTemporary"
|
||||
},
|
||||
{
|
||||
"description": "Rate at which this tax is applied",
|
||||
@@ -191,7 +192,7 @@
|
||||
"idx": 1,
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
"modified": "2024-08-19 15:19:11.095045",
|
||||
"modified": "2023-07-20 18:18:44.405723",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Account",
|
||||
@@ -252,4 +253,4 @@
|
||||
"sort_order": "ASC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,6 @@ class Account(NestedSet):
|
||||
"Payable",
|
||||
"Receivable",
|
||||
"Round Off",
|
||||
"Round Off for Opening",
|
||||
"Stock",
|
||||
"Stock Adjustment",
|
||||
"Stock Received But Not Billed",
|
||||
@@ -89,7 +88,7 @@ class Account(NestedSet):
|
||||
if frappe.local.flags.ignore_update_nsm:
|
||||
return
|
||||
else:
|
||||
super().on_update()
|
||||
super(Account, self).on_update()
|
||||
|
||||
def onload(self):
|
||||
frozen_accounts_modifier = frappe.db.get_value(
|
||||
@@ -104,12 +103,14 @@ class Account(NestedSet):
|
||||
self.name = get_autoname_with_number(self.account_number, self.account_name, self.company)
|
||||
|
||||
def validate(self):
|
||||
from erpnext.accounts.utils import validate_field_number
|
||||
|
||||
if frappe.local.flags.allow_unverified_charts:
|
||||
return
|
||||
self.validate_parent()
|
||||
self.validate_parent_child_account_type()
|
||||
self.validate_root_details()
|
||||
self.validate_account_number()
|
||||
validate_field_number("Account", self.name, self.account_number, self.company, "account_number")
|
||||
self.validate_group_or_ledger()
|
||||
self.set_root_and_report_type()
|
||||
self.validate_mandatory()
|
||||
@@ -201,7 +202,7 @@ class Account(NestedSet):
|
||||
msg = _(
|
||||
"There are ledger entries against this account. Changing {0} to non-{1} in live system will cause incorrect output in 'Accounts {2}' report"
|
||||
).format(
|
||||
frappe.bold(_("Account Type")), doc_before_save.account_type, doc_before_save.account_type
|
||||
frappe.bold("Account Type"), doc_before_save.account_type, doc_before_save.account_type
|
||||
)
|
||||
frappe.msgprint(msg)
|
||||
self.add_comment("Comment", msg)
|
||||
@@ -217,7 +218,9 @@ class Account(NestedSet):
|
||||
|
||||
def validate_root_company_and_sync_account_to_children(self):
|
||||
# ignore validation while creating new compnay or while syncing to child companies
|
||||
if frappe.local.flags.ignore_root_company_validation or self.flags.ignore_root_company_validation:
|
||||
if (
|
||||
frappe.local.flags.ignore_root_company_validation or self.flags.ignore_root_company_validation
|
||||
):
|
||||
return
|
||||
ancestors = get_root_company(self.company)
|
||||
if ancestors:
|
||||
@@ -310,22 +313,6 @@ class Account(NestedSet):
|
||||
if frappe.db.get_value("GL Entry", {"account": self.name}):
|
||||
frappe.throw(_("Currency can not be changed after making entries using some other currency"))
|
||||
|
||||
def validate_account_number(self, account_number=None):
|
||||
if not account_number:
|
||||
account_number = self.account_number
|
||||
|
||||
if account_number:
|
||||
account_with_same_number = frappe.db.get_value(
|
||||
"Account",
|
||||
{"account_number": account_number, "company": self.company, "name": ["!=", self.name]},
|
||||
)
|
||||
if account_with_same_number:
|
||||
frappe.throw(
|
||||
_("Account Number {0} already used in account {1}").format(
|
||||
account_number, account_with_same_number
|
||||
)
|
||||
)
|
||||
|
||||
def create_account_for_child_company(self, parent_acc_name_map, descendants, parent_acc_name):
|
||||
for company in descendants:
|
||||
company_bold = frappe.bold(company)
|
||||
@@ -431,7 +418,7 @@ class Account(NestedSet):
|
||||
if self.check_gle_exists():
|
||||
throw(_("Account with existing transaction can not be deleted"))
|
||||
|
||||
super().on_trash(True)
|
||||
super(Account, self).on_trash(True)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@@ -439,8 +426,9 @@ class Account(NestedSet):
|
||||
def get_parent_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql(
|
||||
"""select name from tabAccount
|
||||
where is_group = 1 and docstatus != 2 and company = {}
|
||||
and {} like {} order by name limit {} offset {}""".format("%s", searchfield, "%s", "%s", "%s"),
|
||||
where is_group = 1 and docstatus != 2 and company = %s
|
||||
and %s like %s order by name limit %s offset %s"""
|
||||
% ("%s", searchfield, "%s", "%s", "%s"),
|
||||
(filters["company"], "%%%s%%" % txt, page_len, start),
|
||||
as_list=1,
|
||||
)
|
||||
@@ -479,6 +467,19 @@ def get_account_autoname(account_number, account_name, company):
|
||||
return " - ".join(parts)
|
||||
|
||||
|
||||
def validate_account_number(name, account_number, company):
|
||||
if account_number:
|
||||
account_with_same_number = frappe.db.get_value(
|
||||
"Account", {"account_number": account_number, "company": company, "name": ["!=", name]}
|
||||
)
|
||||
if account_with_same_number:
|
||||
frappe.throw(
|
||||
_("Account Number {0} already used in account {1}").format(
|
||||
account_number, account_with_same_number
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_account_number(name, account_name, account_number=None, from_descendant=False):
|
||||
account = frappe.get_cached_doc("Account", name)
|
||||
@@ -519,7 +520,7 @@ def update_account_number(name, account_name, account_number=None, from_descenda
|
||||
|
||||
frappe.throw(message, title=_("Rename Not Allowed"))
|
||||
|
||||
account.validate_account_number(account_number)
|
||||
validate_account_number(name, account_number, account.company)
|
||||
if account_number:
|
||||
frappe.db.set_value("Account", name, "account_number", account_number.strip())
|
||||
else:
|
||||
@@ -593,5 +594,7 @@ def sync_update_account_number_in_child(
|
||||
if old_acc_number:
|
||||
filters["account_number"] = old_acc_number
|
||||
|
||||
for d in frappe.db.get_values("Account", filters=filters, fieldname=["company", "name"], as_dict=True):
|
||||
for d in frappe.db.get_values(
|
||||
"Account", filters=filters, fieldname=["company", "name"], as_dict=True
|
||||
):
|
||||
update_account_number(d["name"], account_name, account_number, from_descendant=True)
|
||||
|
||||
@@ -222,7 +222,7 @@ frappe.treeview_settings["Account"] = {
|
||||
"General Ledger",
|
||||
"Balance Sheet",
|
||||
"Profit and Loss Statement",
|
||||
"Cash Flow",
|
||||
"Cash Flow Statement",
|
||||
"Accounts Payable",
|
||||
"Accounts Receivable",
|
||||
]) {
|
||||
|
||||
@@ -31,6 +31,7 @@ def create_charts(
|
||||
"tax_rate",
|
||||
"account_currency",
|
||||
]:
|
||||
|
||||
account_number = cstr(child.get("account_number")).strip()
|
||||
account_name, account_name_in_db = add_suffix_if_duplicate(
|
||||
account_name, account_number, accounts
|
||||
@@ -38,9 +39,7 @@ def create_charts(
|
||||
|
||||
is_group = identify_is_group(child)
|
||||
report_type = (
|
||||
"Balance Sheet"
|
||||
if root_type in ["Asset", "Liability", "Equity"]
|
||||
else "Profit and Loss"
|
||||
"Balance Sheet" if root_type in ["Asset", "Liability", "Equity"] else "Profit and Loss"
|
||||
)
|
||||
|
||||
account = frappe.get_doc(
|
||||
@@ -142,7 +141,7 @@ def get_chart(chart_template, existing_company=None):
|
||||
for fname in os.listdir(path):
|
||||
fname = frappe.as_unicode(fname)
|
||||
if fname.endswith(".json"):
|
||||
with open(os.path.join(path, fname)) as f:
|
||||
with open(os.path.join(path, fname), "r") as f:
|
||||
chart = f.read()
|
||||
if chart and json.loads(chart).get("name") == chart_template:
|
||||
return json.loads(chart).get("tree")
|
||||
@@ -174,7 +173,7 @@ def get_charts_for_country(country, with_standard=False):
|
||||
for fname in os.listdir(path):
|
||||
fname = frappe.as_unicode(fname)
|
||||
if (fname.startswith(country_code) or fname.startswith(country)) and fname.endswith(".json"):
|
||||
with open(os.path.join(path, fname)) as f:
|
||||
with open(os.path.join(path, fname), "r") as f:
|
||||
_get_chart_name(f.read())
|
||||
|
||||
# if more than one charts, returned then add the standard
|
||||
@@ -248,13 +247,7 @@ def validate_bank_account(coa, bank_account):
|
||||
|
||||
def _get_account_names(account_master):
|
||||
for account_name, child in account_master.items():
|
||||
if account_name not in [
|
||||
"account_number",
|
||||
"account_type",
|
||||
"root_type",
|
||||
"is_group",
|
||||
"tax_rate",
|
||||
]:
|
||||
if account_name not in ["account_number", "account_type", "root_type", "is_group", "tax_rate"]:
|
||||
accounts.append(account_name)
|
||||
|
||||
_get_account_names(child)
|
||||
|
||||
@@ -0,0 +1,531 @@
|
||||
{
|
||||
"country_code": "tr",
|
||||
"name": "Turkey - Tek D\u00fczen Hesap Plan\u0131",
|
||||
"tree": {
|
||||
"Duran Varl\u0131klar": {
|
||||
"Di\u011fer Alacaklar": {
|
||||
"Ba\u011fl\u0131 Ortakl\u0131klardan Alacaklar": {},
|
||||
"Di\u011fer Alacak Senetleri Reeskontu(-)": {},
|
||||
"Di\u011fer \u00c7e\u015fitli Alacaklar": {},
|
||||
"Ortaklardan Alacaklar": {},
|
||||
"Personelden Alacaklar": {},
|
||||
"\u0130\u015ftiraklerden Alacaklar": {},
|
||||
"\u015e\u00fcpheli Di\u011fer Alacaklar Kar\u015f\u0131l\u0131\u011f\u0131(-)": {}
|
||||
},
|
||||
"Di\u011fer Duran Varl\u0131klar": {
|
||||
"Birikmi\u015f Amortismanlar(-)": {},
|
||||
"Di\u011fer KDV": {},
|
||||
"Di\u011fer \u00c7e\u015fitli Duran Varl\u0131klar": {},
|
||||
"Elden \u00c7\u0131kar\u0131lacak Stoklar Ve Maddi Duran Varl\u0131klar": {},
|
||||
"Gelecek Y\u0131llar \u0130htiyac\u0131 Stoklar": {},
|
||||
"Gelecek Y\u0131llarda \u0130ndirilecek KDV": {},
|
||||
"Pe\u015fin \u00d6denen Vergi Ve Fonlar": {},
|
||||
"Stok De\u011fer D\u00fc\u015f\u00fckl\u00fc\u011f\u00fc Kar\u015f\u0131l\u0131\u011f\u0131(-)": {}
|
||||
},
|
||||
"Gelecek Y\u0131llara Ait Giderler ve Gelir Tahakkuklar\u0131": {
|
||||
"Gelecek Y\u0131llara Ait Giderler": {},
|
||||
"Gelir Tahakkuklar\u0131": {}
|
||||
},
|
||||
"Maddi Duran Varl\u0131klar": {
|
||||
"Arazi Ve Arsalar": {},
|
||||
"Binalar": {},
|
||||
"Birikmi\u015f Amortismanlar(-)": {},
|
||||
"Demirba\u015flar": {},
|
||||
"Di\u011fer Maddi Duran Varl\u0131klar": {},
|
||||
"Ta\u015f\u0131tlar": {},
|
||||
"Tesis, Makine Ve Cihazlar": {},
|
||||
"Verilen Avanslar": {},
|
||||
"Yap\u0131lmakta Olan Yat\u0131r\u0131mlar": {},
|
||||
"Yer Alt\u0131 Ve Yer \u00dcst\u00fc D\u00fczenleri": {}
|
||||
},
|
||||
"Maddi Olmayan Duran Varl\u0131klar": {
|
||||
"Ara\u015ft\u0131rma Ve Geli\u015ftirme Giderleri": {},
|
||||
"Birikmi\u015f Amortismanlar(-)": {},
|
||||
"Di\u011fer Maddi Olmayan Duran Varl\u0131klar": {},
|
||||
"Haklar": {},
|
||||
"Kurulu\u015f Ve \u00d6rg\u00fctlenme Giderleri": {},
|
||||
"Verilen Avanslar": {},
|
||||
"\u00d6zel Maliyetler": {},
|
||||
"\u015eerefiye": {}
|
||||
},
|
||||
"Mali Duran Varl\u0131klar": {
|
||||
"Ba\u011fl\u0131 Menkul K\u0131ymetler": {},
|
||||
"Ba\u011fl\u0131 Menkul K\u0131ymetler De\u011fer D\u00fc\u015f\u00fckl\u00fc\u011f\u00fc Kar\u015f\u0131l\u0131\u011f\u0131(-)": {},
|
||||
"Ba\u011fl\u0131 Ortakl\u0131klar": {},
|
||||
"Ba\u011fl\u0131 Ortakl\u0131klar Sermaye Paylar\u0131 De\u011fer D\u00fc\u015f\u00fckl\u00fc\u011f\u00fc Kar\u015f\u0131l\u0131\u011f\u0131(-)": {},
|
||||
"Ba\u011fl\u0131 Ortakl\u0131klara Sermaye Taahh\u00fctleri(-)": {},
|
||||
"Di\u011fer Mali Duran Varl\u0131klar": {},
|
||||
"Di\u011fer Mali Duran Varl\u0131klar Kar\u015f\u0131l\u0131\u011f\u0131(-)": {},
|
||||
"\u0130\u015ftirakler": {},
|
||||
"\u0130\u015ftirakler Sermaye Paylar\u0131 De\u011fer D\u00fc\u015f\u00fckl\u00fc\u011f\u00fc Kar\u015f\u0131l\u0131\u011f\u0131(-)": {},
|
||||
"\u0130\u015ftiraklere Sermaye Taahh\u00fctleri(-)": {}
|
||||
},
|
||||
"Ticari Alacaklar": {
|
||||
"Alacak Senetleri": {},
|
||||
"Alacak Senetleri Reeskontu(-)": {},
|
||||
"Al\u0131c\u0131lar": {},
|
||||
"Kazaqn\u0131lmam\u0131\u015f Finansal Kiralama Faiz Gelirleri(-)": {},
|
||||
"Verilen Depozito Ve Teminatlar": {},
|
||||
"\u015e\u00fcpheli Ticari Alacaklar Kar\u015f\u0131l\u0131\u011f\u0131(-)": {}
|
||||
},
|
||||
"root_type": "",
|
||||
"\u00d6zel T\u00fckenmeye Tabi Varl\u0131klar": {
|
||||
"Arama Giderleri": {},
|
||||
"Birikmi\u015f T\u00fckenme Paylar\u0131(-)": {},
|
||||
"Di\u011fer \u00d6zel T\u00fckenmeye Tabi Varl\u0131klar": {},
|
||||
"Haz\u0131rl\u0131k Ve Geli\u015ftirme Giderleri": {},
|
||||
"Verilen Avanslar": {}
|
||||
}
|
||||
},
|
||||
"D\u00f6nen Varl\u0131klar": {
|
||||
"Di\u011fer Alacaklar": {
|
||||
"Ba\u011fl\u0131 Ortakl\u0131klardan Alacaklar": {},
|
||||
"Di\u011fer Alacak Senetleri Reeskontu(-)": {},
|
||||
"Di\u011fer \u00c7e\u015fitli Alacaklar": {},
|
||||
"Ortaklardan Alacaklar": {},
|
||||
"Personelden Alacaklar": {},
|
||||
"\u0130\u015ftiraklerden Alacaklar": {},
|
||||
"\u015e\u00fcpheli Di\u011fer Alacaklar": {},
|
||||
"\u015e\u00fcpheli Di\u011fer Alacaklar Kar\u015f\u0131l\u0131\u011f\u0131(-)": {}
|
||||
},
|
||||
"Di\u011fer D\u00f6nen Varl\u0131klar": {
|
||||
"Devreden KDV": {},
|
||||
"Di\u011fer D\u00f6nen Varl\u0131klar Kar\u015f\u0131l\u0131\u011f\u0131(-)": {},
|
||||
"Di\u011fer KDV": {},
|
||||
"Di\u011fer \u00c7e\u015fitli D\u00f6nen Varl\u0131klar": {},
|
||||
"Personel Avanslar\u0131": {},
|
||||
"Pe\u015fin \u00d6denen Vergiler Ve Fonlar": {},
|
||||
"Say\u0131m Ve Tesell\u00fcm Noksanlar\u0131": {},
|
||||
"\u0130ndirilecek KDV": {},
|
||||
"\u0130\u015f Avanslar\u0131": {}
|
||||
},
|
||||
"Gelecek Aylara Ait Giderler ve Gelir Tahakkuklar\u0131": {
|
||||
"Gelecek Aylara Ait Giderler": {},
|
||||
"Gelir Tahakkuklar\u0131": {}
|
||||
},
|
||||
"Haz\u0131r De\u011ferler": {
|
||||
"Al\u0131nan \u00c7ekler": {},
|
||||
"Bankalar": {
|
||||
"account_type": "Bank"
|
||||
},
|
||||
"Di\u011fer Haz\u0131r De\u011ferler": {},
|
||||
"Kasa": {
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Verilen \u00c7ekler ve \u00d6deme Emirleri(-)": {}
|
||||
},
|
||||
"Menkul K\u0131ymetler": {
|
||||
"Di\u011fer Menkul K\u0131ymetler": {},
|
||||
"Hisse Senetleri": {},
|
||||
"Kamu Kesimi Tahvil, Senet ve Bonolar\u0131": {},
|
||||
"Menkul K\u0131ymetler De\u011fer D\u00fc\u015f\u00fckl\u00fc\u011f\u00fc Kar\u015f\u0131l\u0131\u011f\u0131(-)": {},
|
||||
"\u00d6zel Kesim Tahvil Senet Ve Bonolar\u0131": {}
|
||||
},
|
||||
"Stoklar": {
|
||||
"Mamuller": {},
|
||||
"Stok De\u011fer D\u00fc\u015f\u00fckl\u00fc\u011f\u00fc Kar\u015f\u0131l\u0131\u011f\u0131(-)": {},
|
||||
"Ticari Mallar": {},
|
||||
"Verilen Sipari\u015f Avanslar\u0131": {},
|
||||
"Yar\u0131 Mamuller": {},
|
||||
"\u0130lk Madde Malzeme": {}
|
||||
},
|
||||
"Ticari Alacaklar": {
|
||||
"Alacak Senetleri": {},
|
||||
"Alacak Senetleri Reeskontu(-)": {},
|
||||
"Al\u0131c\u0131lar": {},
|
||||
"Di\u011fer Ticari Alacaklar": {},
|
||||
"Kazan\u0131lmam\u0131\u015f Finansal Kiralama Faiz Gelirleri(-)": {},
|
||||
"Verilen Depozito ve Teminatlar": {},
|
||||
"\u015e\u00fcpheli Ticari Alacaklar": {},
|
||||
"\u015e\u00fcpheli Ticari Alacaklar Kar\u015f\u0131l\u0131\u011f\u0131": {}
|
||||
},
|
||||
"Y\u0131llara Yayg\u0131n \u0130n\u015faat ve Onar\u0131m Maliyetleri": {
|
||||
"Ta\u015feronlara Verilen Avanslar": {},
|
||||
"Y\u0131llara Yayg\u0131n \u0130n\u015faat Ve Onar\u0131m Maliyetleri": {}
|
||||
},
|
||||
"root_type": ""
|
||||
},
|
||||
"Gelir Tablosu Hesaplar\u0131": {
|
||||
"Br\u00fct Sat\u0131\u015flar": {
|
||||
"Di\u011fer Gelirler": {},
|
||||
"Yurt D\u0131\u015f\u0131 Sat\u0131\u015flar": {},
|
||||
"Yurt \u0130\u00e7i Sat\u0131\u015flar": {}
|
||||
},
|
||||
"Di\u011fer Faaliyetlerden Olu\u015fan Gelir ve K\u00e2rlar": {
|
||||
"Ba\u011fl\u0131 Ortakl\u0131klardan Temett\u00fc Gelirleri": {},
|
||||
"Di\u011fer Ola\u011fan Gelir Ve K\u00e2rlar": {},
|
||||
"Enflasyon D\u00fczeltme K\u00e2rlar\u0131": {},
|
||||
"Faiz Gelirleri": {},
|
||||
"Kambiyo K\u00e2rlar\u0131": {},
|
||||
"Komisyon Gelirleri": {},
|
||||
"Konusu Kalmayan Kar\u015f\u0131l\u0131klar": {},
|
||||
"Menkul K\u0131ymet Sat\u0131\u015f K\u00e2rlar\u0131": {},
|
||||
"Reeskont Faiz Gelirleri": {},
|
||||
"\u0130\u015ftiraklerden Temett\u00fc Gelirleri": {}
|
||||
},
|
||||
"Di\u011fer Faaliyetlerden Olu\u015fan Gider ve Zararlar (-)": {
|
||||
"Di\u011fer Ola\u011fan Gider Ve Zararlar(-)": {},
|
||||
"Enflasyon D\u00fczeltmesi Zararlar\u0131(-)": {},
|
||||
"Kambiyo Zararlar\u0131(-)": {},
|
||||
"Kar\u015f\u0131l\u0131k Giderleri(-)": {},
|
||||
"Komisyon Giderleri(-)": {},
|
||||
"Menkul K\u0131ymet Sat\u0131\u015f Zararlar\u0131(-)": {},
|
||||
"Reeskont Faiz Giderleri(-)": {}
|
||||
},
|
||||
"D\u00f6nem Net K\u00e2r\u0131 Ve Zarar\u0131": {
|
||||
"D\u00f6nem K\u00e2r\u0131 Vergi Ve Di\u011fer Yasal Y\u00fck\u00fcml\u00fcl\u00fck Kar\u015f\u0131l\u0131klar\u0131(-)": {},
|
||||
"D\u00f6nem K\u00e2r\u0131 Veya Zarar\u0131": {},
|
||||
"D\u00f6nem Net K\u00e2r\u0131 Veya Zarar\u0131": {},
|
||||
"Enflasyon D\u00fczeltme Hesab\u0131": {},
|
||||
"Y\u0131llara Yayg\u0131n \u0130n\u015faat Ve Enflasyon D\u00fczeltme Hesab\u0131": {}
|
||||
},
|
||||
"Faaliyet Giderleri(-)": {
|
||||
"Ara\u015ft\u0131rma Ve Geli\u015ftirme Giderleri(-)": {},
|
||||
"Genel Y\u00f6netim Giderleri(-)": {},
|
||||
"Pazarlama Sat\u0131\u015f Ve Da\u011f\u0131t\u0131m Giderleri(-)": {}
|
||||
},
|
||||
"Finansman Giderleri": {
|
||||
"K\u0131sa Vadeli Bor\u00e7lanma Giderleri(-)": {},
|
||||
"Uzun Vadeli Bor\u00e7lanma Giderleri(-)": {}
|
||||
},
|
||||
"Ola\u011fan D\u0131\u015f\u0131 Gelir Ve K\u00e2rlar": {
|
||||
"Di\u011fer Ola\u011fan D\u0131\u015f\u0131 Gelir Ve K\u00e2rlar": {},
|
||||
"\u00d6nceki D\u00f6nem Gelir Ve K\u00e2rlar\u0131": {}
|
||||
},
|
||||
"Ola\u011fan D\u0131\u015f\u0131 Gider Ve Zaralar(-)": {
|
||||
"Di\u011fer Ola\u011fan D\u0131\u015f\u0131 Gider Ve Zararlar(-)": {},
|
||||
"\u00c7al\u0131\u015fmayan K\u0131s\u0131m Gider Ve Zararlar\u0131(-)": {},
|
||||
"\u00d6nceki D\u00f6nem Gider Ve Zararlar\u0131(-)": {}
|
||||
},
|
||||
"Sat\u0131\u015f \u0130ndirimleri (-)": {
|
||||
"Di\u011fer \u0130ndirimler": {},
|
||||
"Sat\u0131\u015f \u0130ndirimleri(-)": {},
|
||||
"Sat\u0131\u015ftan \u0130adeler(-)": {}
|
||||
},
|
||||
"Sat\u0131\u015flar\u0131n Maliyeti(-)": {
|
||||
"Di\u011fer Sat\u0131\u015flar\u0131n Maliyeti(-)": {},
|
||||
"Sat\u0131lan Hizmet Maliyeti(-)": {},
|
||||
"Sat\u0131lan Mamuller Maliyeti(-)": {},
|
||||
"Sat\u0131lan Ticari Mallar Maliyeti(-)": {}
|
||||
},
|
||||
"root_type": ""
|
||||
},
|
||||
"K\u0131sa Vadeli Yabanc\u0131 Kaynaklar": {
|
||||
"Al\u0131nan Avanslar": {
|
||||
"Al\u0131nan Di\u011fer Avanslar": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Al\u0131nan Sipari\u015f Avanslar\u0131": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Bor\u00e7 ve Gider Kar\u015f\u0131l\u0131klar\u0131": {
|
||||
"Di\u011fer Bor\u00e7 Ve Gider Kar\u015f\u0131l\u0131klar\u0131": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"D\u00f6nem K\u00e2r\u0131 Vergi Ve Di\u011fer Yasal Y\u00fck\u00fcml\u00fcl\u00fck Kar\u015f\u0131l\u0131klar\u0131": {
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"D\u00f6nem K\u00e2r\u0131n\u0131n Pe\u015fin \u00d6denen Vergi Ve Di\u011fer Y\u00fck\u00fcml\u00fcl\u00fckler(-)": {
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"K\u0131dem Tazminat\u0131 Kar\u015f\u0131l\u0131\u011f\u0131": {},
|
||||
"Maliyet Giderleri Kar\u015f\u0131l\u0131\u011f\u0131": {},
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Di\u011fer Bor\u00e7lar": {
|
||||
"Ba\u011fl\u0131 Ortakl\u0131klara Bor\u00e7lar": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Di\u011fer Bor\u00e7 Senetleri Reeskontu(-)": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Di\u011fer \u00c7e\u015fitli Bor\u00e7lar": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Ortaklara Bor\u00e7lar": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Personele Bor\u00e7lar": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"account_type": "Payable",
|
||||
"\u0130\u015ftiraklere Bor\u00e7lar": {
|
||||
"account_type": "Payable"
|
||||
}
|
||||
},
|
||||
"Di\u011fer K\u0131sa Vadeli Yabanc\u0131 Kaynaklar": {
|
||||
"Di\u011fer KDV": {
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"Di\u011fer \u00c7e\u015fitli Yabanc\u0131 Kaynaklar": {},
|
||||
"Hesaplanan KDV": {
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"Merkez Ve \u015eubeler Cari Hesab\u0131": {},
|
||||
"Say\u0131m Ve Tesell\u00fcm Fazlalar\u0131": {},
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Gelecek Aylara Ait Gelirler Ve Gider Tahakkuklar\u0131": {
|
||||
"Gelecek Aylara Ait Gelirler": {},
|
||||
"Gider Tahakkuklar\u0131": {}
|
||||
},
|
||||
"Mali Bor\u00e7lar": {
|
||||
"Banka Kredileri": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Di\u011fer Mali Bor\u00e7lar": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Ertelenmi\u015f Finansal Kiralama Bor\u00e7lanma Maliyetleri(-)": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Finansal Kiralama \u0130\u015flemlerinden Bor\u00e7lar": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Menkul K\u0131ymetler \u0130hra\u00e7 Fark\u0131(-)": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Tahvil Anapara Bor\u00e7, Taksit Ve Faizleri": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Uzun Vadeli Kredilerin Anapara Taksitleri Ve Faizleri": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"account_type": "Payable",
|
||||
"\u00c7\u0131kar\u0131lan Bonolar Ve Senetler": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"\u00c7\u0131kar\u0131lm\u0131\u015f Di\u011fer Menkul K\u0131ymetler": {
|
||||
"account_type": "Payable"
|
||||
}
|
||||
},
|
||||
"Ticari Bor\u00e7lar": {
|
||||
"Al\u0131nan Depozito Ve Teminatlar": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Bor\u00e7 Senetleri": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Bor\u00e7 Senetleri Reeskontu(-)": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Di\u011fer Ticari Bor\u00e7lar": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Sat\u0131c\u0131lar": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Y\u0131llara Yayg\u0131n \u0130n\u015faat Ve Onar\u0131m Hakedi\u015fleri": {
|
||||
"350 Y\u0131llara Yayg\u0131n \u0130n\u015faat Ve Onar\u0131m Hakedi\u015fleri Bedelleri": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"root_type": "",
|
||||
"\u00d6denecek Vergi ve Di\u011fer Y\u00fck\u00fcml\u00fcl\u00fckler": {
|
||||
"Vadesi Ge\u00e7mi\u015f, Ertelenmi\u015f Veya Taksitlendirilmi\u015f Vergi Ve Di\u011fer Y\u00fck\u00fcml\u00fcl\u00fckler": {
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"account_type": "Tax",
|
||||
"\u00d6denecek Di\u011fer Y\u00fck\u00fcml\u00fcl\u00fckler": {
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"\u00d6denecek Sosyal G\u00fcvenl\u00fck Kesintileri": {
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"\u00d6denecek Vergi Ve Fonlar": {
|
||||
"account_type": "Tax"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Maliyet Hesaplar\u0131": {
|
||||
"Ara\u015ft\u0131rma Ve Geli\u015ftirme Giderleri": {},
|
||||
"Direkt \u0130lk Madde Ve Malzeme Giderleri": {
|
||||
"Direk \u0130lk Madde Ve Malzeme Giderleri Hesab\u0131": {},
|
||||
"Direkt \u0130lk Madde Ve Malzeme Fiyat Fark\u0131": {},
|
||||
"Direkt \u0130lk Madde Ve Malzeme Miktar Fark\u0131": {},
|
||||
"Direkt \u0130lk Madde Ve Malzeme Yans\u0131tma Hesab\u0131": {}
|
||||
},
|
||||
"Direkt \u0130\u015f\u00e7ilik Giderleri": {
|
||||
"Direkt \u0130\u015f\u00e7ilik Giderleri": {},
|
||||
"Direkt \u0130\u015f\u00e7ilik Giderleri Yans\u0131tma Hesab\u0131": {},
|
||||
"Direkt \u0130\u015f\u00e7ilik S\u00fcre Farklar\u0131": {},
|
||||
"Direkt \u0130\u015f\u00e7ilik \u00dccret Farklar\u0131": {}
|
||||
},
|
||||
"Finansman Giderleri": {
|
||||
"Finansman Giderleri": {},
|
||||
"Finansman Giderleri Fark Hesab\u0131": {},
|
||||
"Finansman Giderleri Yans\u0131tma Hesab\u0131": {}
|
||||
},
|
||||
"Genel Y\u00f6netim Giderleri": {
|
||||
"Genel Y\u00f6netim Gider Farklar\u0131 Hesab\u0131": {},
|
||||
"Genel Y\u00f6netim Giderleri": {},
|
||||
"Genel Y\u00f6netim Giderleri Yans\u0131tma Hesab\u0131": {}
|
||||
},
|
||||
"Genel \u00dcretim Giderleri": {
|
||||
"Genel \u00dcretim Giderleri": {},
|
||||
"Genel \u00dcretim Giderleri B\u00fct\u00e7e Farklar\u0131": {},
|
||||
"Genel \u00dcretim Giderleri Kapasite Farklar\u0131": {},
|
||||
"Genel \u00dcretim Giderleri Verimlilik Giderleri": {},
|
||||
"Genel \u00dcretim Giderleri Yans\u0131tma Hesab\u0131": {}
|
||||
},
|
||||
"Hizmet \u00dcretim Maliyeti": {
|
||||
"Hizmet \u00dcretim Maliyeti": {},
|
||||
"Hizmet \u00dcretim Maliyeti Fark Hesaplar\u0131": {},
|
||||
"Hizmet \u00dcretim Maliyeti Yans\u0131tma Hesab\u0131": {}
|
||||
},
|
||||
"Maliyet Muhasebesi Ba\u011flant\u0131 Hesaplar\u0131": {
|
||||
"Maliyet Muhasebesi Ba\u011flant\u0131 Hesab\u0131": {},
|
||||
"Maliyet Muhasebesi Yans\u0131tma Hesab\u0131": {}
|
||||
},
|
||||
"Pazarlama, Sat\u0131\u015f Ve Da\u011f\u0131t\u0131m Giderleri": {
|
||||
"Atra\u015ft\u0131rma Ve Geli\u015ftirme Giderleri": {},
|
||||
"Pazarlama Sat\u0131\u015f Ve Dag\u0131t\u0131m Giderleri Yans\u0131tma Hesab\u0131": {},
|
||||
"Pazarlama Sat\u0131\u015f Ve Da\u011f\u0131t\u0131m Giderleri Fark Hesab\u0131": {}
|
||||
},
|
||||
"root_type": ""
|
||||
},
|
||||
"Naz\u0131m Hesaplar": {
|
||||
"root_type": ""
|
||||
},
|
||||
"Serbest Hesaplar": {
|
||||
"root_type": ""
|
||||
},
|
||||
"Uzun Vadeli Yabanc\u0131 Kaynaklar": {
|
||||
"Al\u0131nan Avanslar": {
|
||||
"Al\u0131nan Di\u011fer Avanslar": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Al\u0131nan Sipari\u015f Avanslar\u0131": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Bor\u00e7 Ve Gider Kar\u015f\u0131l\u0131klar\u0131": {
|
||||
"Di\u011fer Bor\u00e7 Ve Gider Kar\u015f\u0131l\u0131klar\u0131": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"K\u0131dem Tazminat\u0131 Kar\u015f\u0131l\u0131\u011f\u0131": {},
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Di\u011fer Bor\u00e7lar": {
|
||||
"Ba\u011fl\u0131 Ortakl\u0131klara Bor\u00e7lar": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Di\u011fer Bor\u00e7 Senetleri Reeskontu(-)": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Di\u011fer \u00c7e\u015fitli Bor\u00e7lar": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Kamuya Olan Ertelenmi\u015f Veya Taksitlendirilmi\u015f Bor\u00e7lar": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Ortaklara Bor\u00e7lar": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"account_type": "Payable",
|
||||
"\u0130\u015ftiraklere Bor\u00e7lar": {
|
||||
"account_type": "Payable"
|
||||
}
|
||||
},
|
||||
"Di\u011fer Uzun Vadeli Yabanc\u0131 Kaynaklar": {
|
||||
"Di\u011fer \u00c7e\u015fitli Uzun Vadeli Yabanc\u0131 Kaynaklar": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Gelecek Y\u0131llara Ertelenmi\u015f Veya Terkin Edilecek KDV": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Tesise Kat\u0131lma Paylar\u0131": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Gelecek Y\u0131llara Ait Gelirler Ve Gider Tahakkuklar\u0131": {
|
||||
"Gelecek Y\u0131llara Ait Gelirler": {},
|
||||
"Gider Tahakkuklar\u0131": {}
|
||||
},
|
||||
"Mali Bor\u00e7lar": {
|
||||
"Banka Kredileri": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Di\u011fer Mali Bor\u00e7lar": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Ertelenmi\u015f Finansal Kiralama Bor\u00e7lanma Maliyetleri(-)": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Finansal Kiralama \u0130\u015flemlerinden Bor\u00e7lar": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Menkul K\u0131ymetler \u0130hra\u00e7 Fark\u0131(-)": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"account_type": "Payable",
|
||||
"\u00c7\u0131kar\u0131lm\u0131\u015f Di\u011fer Menkul K\u0131ymetler": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"\u00c7\u0131kar\u0131lm\u0131\u015f Tahviller": {
|
||||
"account_type": "Payable"
|
||||
}
|
||||
},
|
||||
"Ticari Bor\u00e7lar": {
|
||||
"Al\u0131nan Depozito Ve Teminatlar": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Bor\u00e7 Senetleri": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Bor\u00e7 Senetleri Reeskontu(-)": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Di\u011fer Ticari Bor\u00e7lar": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Sat\u0131c\u0131lar": {
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"root_type": ""
|
||||
},
|
||||
"\u00d6z Kaynaklar": {
|
||||
"D\u00f6nem Net K\u00e2r\u0131 (Zarar\u0131)": {
|
||||
"D\u00f6nem Net K\u00e2r\u0131": {},
|
||||
"D\u00f6nem Net Zarar\u0131(-)": {}
|
||||
},
|
||||
"Ge\u00e7mi\u015f Y\u0131llar K\u00e2rlar\u0131": {
|
||||
"Ge\u00e7mi\u015f Y\u0131llar K\u00e2rlar\u0131": {}
|
||||
},
|
||||
"Ge\u00e7mi\u015f Y\u0131llar Zararlar\u0131(-)": {
|
||||
"Ge\u00e7mi\u015f Y\u0131llar Zararlar\u0131(-)": {}
|
||||
},
|
||||
"K\u00e2r Yedekleri": {
|
||||
"Di\u011fer K\u00e2r Yedekleri": {},
|
||||
"Ola\u011fan\u00fcst\u00fc Yedekler": {},
|
||||
"Stat\u00fc Yedekleri": {},
|
||||
"Yasal Yedekler": {},
|
||||
"\u00d6zel Fonlar": {}
|
||||
},
|
||||
"Sermaye Yedekleri": {
|
||||
"Di\u011fer Sermaye Yedekleri": {},
|
||||
"Hisse Senedi \u0130ptal K\u00e2rlar\u0131": {},
|
||||
"Hisse Senetleri \u0130hra\u00e7 Primleri": {},
|
||||
"Maddi Duran Varl\u0131k Yeniden De\u011ferlenme Art\u0131\u015flar\u0131": {},
|
||||
"Maliyet Art\u0131\u015flar\u0131 Fonu": {},
|
||||
"\u0130\u015ftirakler Yeniden De\u011ferleme Art\u0131\u015flar\u0131": {}
|
||||
},
|
||||
"root_type": "",
|
||||
"\u00d6denmi\u015f Sermaye": {
|
||||
"Sermaye": {},
|
||||
"\u00d6denmi\u015f Sermaye(-)": {
|
||||
"account_type": "Payable"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1525,8 +1525,7 @@
|
||||
"41-Clients et comptes rattach\u00e9s (PASSIF)": {
|
||||
"Clients cr\u00e9diteurs": {
|
||||
"Clients - Avances et acomptes re\u00e7us sur commandes": {
|
||||
"account_number": "4191",
|
||||
"account_type": "Income Account"
|
||||
"account_number": "4191"
|
||||
},
|
||||
"Clients - Dettes pour emballages et mat\u00e9riels consign\u00e9s": {
|
||||
"account_number": "4196"
|
||||
@@ -3142,4 +3141,4 @@
|
||||
"account_number": "7"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,8 +109,7 @@
|
||||
"Utility Expenses": {},
|
||||
"Write Off": {},
|
||||
"Exchange Gain/Loss": {},
|
||||
"Gain/Loss on Asset Disposal": {},
|
||||
"Impairment": {}
|
||||
"Gain/Loss on Asset Disposal": {}
|
||||
},
|
||||
"root_type": "Expense"
|
||||
},
|
||||
@@ -133,8 +132,7 @@
|
||||
"Source of Funds (Liabilities)": {
|
||||
"Capital Account": {
|
||||
"Reserves and Surplus": {},
|
||||
"Shareholders Funds": {},
|
||||
"Revaluation Surplus": {}
|
||||
"Shareholders Funds": {}
|
||||
},
|
||||
"Current Liabilities": {
|
||||
"Accounts Payable": {
|
||||
|
||||
@@ -72,7 +72,6 @@ def get():
|
||||
_("Write Off"): {},
|
||||
_("Exchange Gain/Loss"): {},
|
||||
_("Gain/Loss on Asset Disposal"): {},
|
||||
_("Impairment"): {},
|
||||
},
|
||||
"root_type": "Expense",
|
||||
},
|
||||
@@ -105,7 +104,6 @@ def get():
|
||||
_("Dividends Paid"): {"account_type": "Equity"},
|
||||
_("Opening Balance Equity"): {"account_type": "Equity"},
|
||||
_("Retained Earnings"): {"account_type": "Equity"},
|
||||
_("Revaluation Surplus"): {"account_type": "Equity"},
|
||||
"root_type": "Equity",
|
||||
},
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -261,20 +261,28 @@ class TestAccount(unittest.TestCase):
|
||||
acc.insert()
|
||||
|
||||
self.assertTrue(
|
||||
frappe.db.exists("Account", {"account_name": "Test Group Account", "company": "_Test Company 4"})
|
||||
frappe.db.exists(
|
||||
"Account", {"account_name": "Test Group Account", "company": "_Test Company 4"}
|
||||
)
|
||||
)
|
||||
self.assertTrue(
|
||||
frappe.db.exists("Account", {"account_name": "Test Group Account", "company": "_Test Company 5"})
|
||||
frappe.db.exists(
|
||||
"Account", {"account_name": "Test Group Account", "company": "_Test Company 5"}
|
||||
)
|
||||
)
|
||||
|
||||
# Try renaming child company account
|
||||
acc_tc_5 = frappe.db.get_value(
|
||||
"Account", {"account_name": "Test Group Account", "company": "_Test Company 5"}
|
||||
)
|
||||
self.assertRaises(frappe.ValidationError, update_account_number, acc_tc_5, "Test Modified Account")
|
||||
self.assertRaises(
|
||||
frappe.ValidationError, update_account_number, acc_tc_5, "Test Modified Account"
|
||||
)
|
||||
|
||||
# Rename child company account with allow_account_creation_against_child_company enabled
|
||||
frappe.db.set_value("Company", "_Test Company 5", "allow_account_creation_against_child_company", 1)
|
||||
frappe.db.set_value(
|
||||
"Company", "_Test Company 5", "allow_account_creation_against_child_company", 1
|
||||
)
|
||||
|
||||
update_account_number(acc_tc_5, "Test Modified Account")
|
||||
self.assertTrue(
|
||||
@@ -283,7 +291,9 @@ class TestAccount(unittest.TestCase):
|
||||
)
|
||||
)
|
||||
|
||||
frappe.db.set_value("Company", "_Test Company 5", "allow_account_creation_against_child_company", 0)
|
||||
frappe.db.set_value(
|
||||
"Company", "_Test Company 5", "allow_account_creation_against_child_company", 0
|
||||
)
|
||||
|
||||
to_delete = [
|
||||
"Test Group Account - _TC3",
|
||||
@@ -308,7 +318,9 @@ class TestAccount(unittest.TestCase):
|
||||
self.assertEqual(acc.account_currency, "INR")
|
||||
|
||||
# Make a JV against this account
|
||||
make_journal_entry("Test Currency Account - _TC", "Miscellaneous Expenses - _TC", 100, submit=True)
|
||||
make_journal_entry(
|
||||
"Test Currency Account - _TC", "Miscellaneous Expenses - _TC", 100, submit=True
|
||||
)
|
||||
|
||||
acc.account_currency = "USD"
|
||||
self.assertRaises(frappe.ValidationError, acc.save)
|
||||
|
||||
@@ -40,12 +40,16 @@ class AccountClosingBalance(Document):
|
||||
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)
|
||||
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)
|
||||
merged_entries = aggregate_with_last_account_closing_balance(
|
||||
combined_entries, accounting_dimensions
|
||||
)
|
||||
|
||||
for _key, value in merged_entries.items():
|
||||
for key, value in merged_entries.items():
|
||||
cle = frappe.new_doc("Account Closing Balance")
|
||||
cle.update(value)
|
||||
cle.update(value["dimensions"])
|
||||
@@ -113,9 +117,9 @@ 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, "period_end_date": ("<", closing_date)},
|
||||
filters={"docstatus": 1, "company": company, "posting_date": ("<", closing_date)},
|
||||
fields=["name"],
|
||||
order_by="period_end_date desc",
|
||||
order_by="posting_date desc",
|
||||
limit=1,
|
||||
)
|
||||
|
||||
|
||||
@@ -57,12 +57,9 @@ frappe.ui.form.on("Accounting Dimension", {
|
||||
}
|
||||
},
|
||||
|
||||
label: function (frm) {
|
||||
frm.set_value("fieldname", frm.doc.label.replace(/ /g, "_").replace(/-/g, "_").toLowerCase());
|
||||
},
|
||||
|
||||
document_type: function (frm) {
|
||||
frm.set_value("label", frm.doc.document_type);
|
||||
frm.set_value("fieldname", frappe.model.scrub(frm.doc.document_type));
|
||||
|
||||
frappe.db.get_value(
|
||||
"Accounting Dimension",
|
||||
|
||||
@@ -7,7 +7,6 @@ import json
|
||||
import frappe
|
||||
from frappe import _, scrub
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
||||
from frappe.database.schema import validate_column_name
|
||||
from frappe.model import core_doctypes_list
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cstr
|
||||
@@ -41,8 +40,7 @@ class AccountingDimension(Document):
|
||||
self.set_fieldname_and_label()
|
||||
|
||||
def validate(self):
|
||||
if self.document_type in (
|
||||
*core_doctypes_list,
|
||||
if self.document_type in core_doctypes_list + (
|
||||
"Accounting Dimension",
|
||||
"Project",
|
||||
"Cost Center",
|
||||
@@ -50,10 +48,13 @@ class AccountingDimension(Document):
|
||||
"Company",
|
||||
"Account",
|
||||
):
|
||||
|
||||
msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type)
|
||||
frappe.throw(msg)
|
||||
|
||||
exists = frappe.db.get_value("Accounting Dimension", {"document_type": self.document_type}, ["name"])
|
||||
exists = frappe.db.get_value(
|
||||
"Accounting Dimension", {"document_type": self.document_type}, ["name"]
|
||||
)
|
||||
|
||||
if exists and self.is_new():
|
||||
frappe.throw(_("Document Type already used as a dimension"))
|
||||
@@ -61,7 +62,6 @@ class AccountingDimension(Document):
|
||||
if not self.is_new():
|
||||
self.validate_document_type_change()
|
||||
|
||||
validate_column_name(self.fieldname)
|
||||
self.validate_dimension_defaults()
|
||||
|
||||
def validate_document_type_change(self):
|
||||
@@ -113,6 +113,7 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None):
|
||||
repostable_doctypes = get_allowed_types_from_settings()
|
||||
|
||||
for doctype in doclist:
|
||||
|
||||
if (doc_count + 1) % 2 == 0:
|
||||
insert_after_field = "dimension_col_break"
|
||||
else:
|
||||
@@ -147,7 +148,7 @@ def add_dimension_to_budget_doctype(df, doc):
|
||||
df.update(
|
||||
{
|
||||
"insert_after": "cost_center",
|
||||
"depends_on": f"eval:doc.budget_against == '{doc.document_type}'",
|
||||
"depends_on": "eval:doc.budget_against == '{0}'".format(doc.document_type),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -181,17 +182,19 @@ def delete_accounting_dimension(doc):
|
||||
frappe.db.sql(
|
||||
"""
|
||||
DELETE FROM `tabCustom Field`
|
||||
WHERE fieldname = {}
|
||||
AND dt IN ({})""".format("%s", ", ".join(["%s"] * len(doclist))), # nosec
|
||||
tuple([doc.fieldname, *doclist]),
|
||||
WHERE fieldname = %s
|
||||
AND dt IN (%s)"""
|
||||
% ("%s", ", ".join(["%s"] * len(doclist))), # nosec
|
||||
tuple([doc.fieldname] + doclist),
|
||||
)
|
||||
|
||||
frappe.db.sql(
|
||||
"""
|
||||
DELETE FROM `tabProperty Setter`
|
||||
WHERE field_name = {}
|
||||
AND doc_type IN ({})""".format("%s", ", ".join(["%s"] * len(doclist))), # nosec
|
||||
tuple([doc.fieldname, *doclist]),
|
||||
WHERE field_name = %s
|
||||
AND doc_type IN (%s)"""
|
||||
% ("%s", ", ".join(["%s"] * len(doclist))), # nosec
|
||||
tuple([doc.fieldname] + doclist),
|
||||
)
|
||||
|
||||
budget_against_property = frappe.get_doc("Property Setter", "Budget-budget_against-options")
|
||||
@@ -240,6 +243,7 @@ def get_doctypes_with_dimensions():
|
||||
|
||||
|
||||
def get_accounting_dimensions(as_list=True, filters=None):
|
||||
|
||||
if not filters:
|
||||
filters = {"disabled": 0}
|
||||
|
||||
@@ -257,19 +261,18 @@ def get_accounting_dimensions(as_list=True, filters=None):
|
||||
|
||||
|
||||
def get_checks_for_pl_and_bs_accounts():
|
||||
if frappe.flags.accounting_dimensions_details is None:
|
||||
# nosemgrep
|
||||
frappe.flags.accounting_dimensions_details = frappe.db.sql(
|
||||
"""SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
|
||||
FROM `tabAccounting Dimension`p ,`tabAccounting Dimension Detail` c
|
||||
WHERE p.name = c.parent""",
|
||||
as_dict=1,
|
||||
)
|
||||
dimensions = frappe.db.sql(
|
||||
"""SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
|
||||
FROM `tabAccounting Dimension`p ,`tabAccounting Dimension Detail` c
|
||||
WHERE p.name = c.parent""",
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
return frappe.flags.accounting_dimensions_details
|
||||
return dimensions
|
||||
|
||||
|
||||
def get_dimension_with_children(doctype, dimensions):
|
||||
|
||||
if isinstance(dimensions, str):
|
||||
dimensions = [dimensions]
|
||||
|
||||
@@ -277,7 +280,9 @@ def get_dimension_with_children(doctype, dimensions):
|
||||
|
||||
for dimension in dimensions:
|
||||
lft, rgt = frappe.db.get_value(doctype, dimension, ["lft", "rgt"])
|
||||
children = frappe.get_all(doctype, filters={"lft": [">=", lft], "rgt": ["<=", rgt]}, order_by="lft")
|
||||
children = frappe.get_all(
|
||||
doctype, filters={"lft": [">=", lft], "rgt": ["<=", rgt]}, order_by="lft"
|
||||
)
|
||||
all_dimensions += [c.name for c in children]
|
||||
|
||||
return all_dimensions
|
||||
@@ -285,10 +290,14 @@ def get_dimension_with_children(doctype, dimensions):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_dimensions(with_cost_center_and_project=False):
|
||||
|
||||
c = frappe.qb.DocType("Accounting Dimension Detail")
|
||||
p = frappe.qb.DocType("Accounting Dimension")
|
||||
dimension_filters = (
|
||||
frappe.qb.from_(p).select(p.label, p.fieldname, p.document_type).where(p.disabled == 0).run(as_dict=1)
|
||||
frappe.qb.from_(p)
|
||||
.select(p.label, p.fieldname, p.document_type)
|
||||
.where(p.disabled == 0)
|
||||
.run(as_dict=1)
|
||||
)
|
||||
default_dimensions = (
|
||||
frappe.qb.from_(c)
|
||||
|
||||
@@ -78,8 +78,6 @@ class TestAccountingDimension(unittest.TestCase):
|
||||
|
||||
def tearDown(self):
|
||||
disable_dimension()
|
||||
frappe.flags.accounting_dimensions_details = None
|
||||
frappe.flags.dimension_filter_map = None
|
||||
|
||||
|
||||
def create_dimension():
|
||||
|
||||
@@ -66,40 +66,37 @@ class AccountingDimensionFilter(Document):
|
||||
|
||||
|
||||
def get_dimension_filter_map():
|
||||
if not frappe.flags.get("dimension_filter_map"):
|
||||
# nosemgrep
|
||||
filters = frappe.db.sql(
|
||||
"""
|
||||
SELECT
|
||||
a.applicable_on_account, d.dimension_value, p.accounting_dimension,
|
||||
p.allow_or_restrict, a.is_mandatory
|
||||
FROM
|
||||
`tabApplicable On Account` a,
|
||||
`tabAccounting Dimension Filter` p
|
||||
LEFT JOIN `tabAllowed Dimension` d ON d.parent = p.name
|
||||
WHERE
|
||||
p.name = a.parent
|
||||
AND p.disabled = 0
|
||||
""",
|
||||
as_dict=1,
|
||||
filters = frappe.db.sql(
|
||||
"""
|
||||
SELECT
|
||||
a.applicable_on_account, d.dimension_value, p.accounting_dimension,
|
||||
p.allow_or_restrict, a.is_mandatory
|
||||
FROM
|
||||
`tabApplicable On Account` a,
|
||||
`tabAccounting Dimension Filter` p
|
||||
LEFT JOIN `tabAllowed Dimension` d ON d.parent = p.name
|
||||
WHERE
|
||||
p.name = a.parent
|
||||
AND p.disabled = 0
|
||||
""",
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
dimension_filter_map = {}
|
||||
|
||||
for f in filters:
|
||||
f.fieldname = scrub(f.accounting_dimension)
|
||||
|
||||
build_map(
|
||||
dimension_filter_map,
|
||||
f.fieldname,
|
||||
f.applicable_on_account,
|
||||
f.dimension_value,
|
||||
f.allow_or_restrict,
|
||||
f.is_mandatory,
|
||||
)
|
||||
|
||||
dimension_filter_map = {}
|
||||
|
||||
for f in filters:
|
||||
f.fieldname = scrub(f.accounting_dimension)
|
||||
|
||||
build_map(
|
||||
dimension_filter_map,
|
||||
f.fieldname,
|
||||
f.applicable_on_account,
|
||||
f.dimension_value,
|
||||
f.allow_or_restrict,
|
||||
f.is_mandatory,
|
||||
)
|
||||
frappe.flags.dimension_filter_map = dimension_filter_map
|
||||
|
||||
return frappe.flags.dimension_filter_map
|
||||
return dimension_filter_map
|
||||
|
||||
|
||||
def build_map(map_object, dimension, account, filter_value, allow_or_restrict, is_mandatory):
|
||||
|
||||
@@ -47,8 +47,6 @@ class TestAccountingDimensionFilter(unittest.TestCase):
|
||||
def tearDown(self):
|
||||
disable_dimension_filter()
|
||||
disable_dimension()
|
||||
frappe.flags.accounting_dimensions_details = None
|
||||
frappe.flags.dimension_filter_map = None
|
||||
|
||||
for si in self.invoice_list:
|
||||
si.load_from_db()
|
||||
@@ -57,7 +55,9 @@ class TestAccountingDimensionFilter(unittest.TestCase):
|
||||
|
||||
|
||||
def create_accounting_dimension_filter():
|
||||
if not frappe.db.get_value("Accounting Dimension Filter", {"accounting_dimension": "Cost Center"}):
|
||||
if not frappe.db.get_value(
|
||||
"Accounting Dimension Filter", {"accounting_dimension": "Cost Center"}
|
||||
):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Accounting Dimension Filter",
|
||||
|
||||
@@ -84,10 +84,7 @@ class AccountingPeriod(Document):
|
||||
for doctype_for_closing in self.get_doctypes_for_closing():
|
||||
self.append(
|
||||
"closed_documents",
|
||||
{
|
||||
"document_type": doctype_for_closing.document_type,
|
||||
"closed": doctype_for_closing.closed,
|
||||
},
|
||||
{"document_type": doctype_for_closing.document_type, "closed": doctype_for_closing.closed},
|
||||
)
|
||||
|
||||
|
||||
@@ -101,8 +98,6 @@ def validate_accounting_period_on_doc_save(doc, method=None):
|
||||
date = doc.available_for_use_date
|
||||
elif doc.doctype == "Asset Repair":
|
||||
date = doc.completion_date
|
||||
elif doc.doctype == "Period Closing Voucher":
|
||||
date = doc.period_end_date
|
||||
else:
|
||||
date = doc.posting_date
|
||||
|
||||
|
||||
@@ -34,7 +34,9 @@ class TestAccountingPeriod(unittest.TestCase):
|
||||
ap1 = create_accounting_period(period_name="Test Accounting Period 2")
|
||||
ap1.save()
|
||||
|
||||
doc = create_sales_invoice(do_not_save=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC")
|
||||
doc = create_sales_invoice(
|
||||
do_not_save=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC"
|
||||
)
|
||||
self.assertRaises(ClosedAccountingPeriod, doc.save)
|
||||
|
||||
def tearDown(self):
|
||||
|
||||
@@ -3,23 +3,4 @@
|
||||
|
||||
frappe.ui.form.on("Accounts Settings", {
|
||||
refresh: function (frm) {},
|
||||
enable_immutable_ledger: function (frm) {
|
||||
if (!frm.doc.enable_immutable_ledger) {
|
||||
return;
|
||||
}
|
||||
|
||||
let msg = __("Enabling this will change the way how cancelled transactions are handled.");
|
||||
msg += " ";
|
||||
msg += __("Please enable only if the understand the effects of enabling this.");
|
||||
msg += "<br>";
|
||||
msg += "Do you still want to enable immutable ledger?";
|
||||
|
||||
frappe.confirm(
|
||||
msg,
|
||||
() => {},
|
||||
() => {
|
||||
frm.set_value("enable_immutable_ledger", 0);
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
"unlink_advance_payment_on_cancelation_of_order",
|
||||
"column_break_13",
|
||||
"delete_linked_ledger_entries",
|
||||
"enable_immutable_ledger",
|
||||
"invoicing_features_section",
|
||||
"check_supplier_invoice_uniqueness",
|
||||
"automatically_fetch_payment_terms",
|
||||
@@ -55,8 +54,6 @@
|
||||
"post_change_gl_entries",
|
||||
"assets_tab",
|
||||
"asset_settings_section",
|
||||
"calculate_depr_using_total_days",
|
||||
"column_break_gjcc",
|
||||
"book_asset_depreciation_entry_automatically",
|
||||
"closing_settings_tab",
|
||||
"period_closing_settings_section",
|
||||
@@ -73,9 +70,7 @@
|
||||
"remarks_section",
|
||||
"general_ledger_remarks_length",
|
||||
"column_break_lvjk",
|
||||
"receivable_payable_remarks_length",
|
||||
"payment_request_settings",
|
||||
"create_pr_in_draft_status"
|
||||
"receivable_payable_remarks_length"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -110,7 +105,7 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Enabling this ensures each Purchase Invoice has a unique value in Supplier Invoice No. field within a particular fiscal year",
|
||||
"description": "Enabling ensure each Purchase Invoice has a unique value in Supplier Invoice No. field",
|
||||
"fieldname": "check_supplier_invoice_uniqueness",
|
||||
"fieldtype": "Check",
|
||||
"label": "Check Supplier Invoice Number Uniqueness"
|
||||
@@ -459,36 +454,6 @@
|
||||
"fieldname": "remarks_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Remarks Column Length"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "On enabling this cancellation entries will be posted on the actual cancellation date and reports will consider cancelled entries as well",
|
||||
"fieldname": "enable_immutable_ledger",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Immutable Ledger"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_gjcc",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Enable this option to calculate daily depreciation by considering the total number of days in the entire depreciation period, (including leap years) while using daily pro-rata based depreciation",
|
||||
"fieldname": "calculate_depr_using_total_days",
|
||||
"fieldtype": "Check",
|
||||
"label": "Calculate daily depreciation using total days in depreciation period"
|
||||
},
|
||||
{
|
||||
"description": "Payment Request created from Sales Order or Purchase Order will be in Draft status. When disabled document will be in unsaved state.",
|
||||
"fieldname": "payment_request_settings",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Payment Request"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "create_pr_in_draft_status",
|
||||
"fieldtype": "Check",
|
||||
"label": "Create in Draft Status"
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
@@ -496,7 +461,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2024-07-26 06:48:52.714630",
|
||||
"modified": "2024-01-30 14:04:26.553554",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
|
||||
@@ -33,15 +33,12 @@ class AccountsSettings(Document):
|
||||
book_deferred_entries_based_on: DF.Literal["Days", "Months"]
|
||||
book_deferred_entries_via_journal_entry: DF.Check
|
||||
book_tax_discount_loss: DF.Check
|
||||
calculate_depr_using_total_days: DF.Check
|
||||
check_supplier_invoice_uniqueness: DF.Check
|
||||
create_pr_in_draft_status: DF.Check
|
||||
credit_controller: DF.Link | None
|
||||
delete_linked_ledger_entries: DF.Check
|
||||
determine_address_tax_category_from: DF.Literal["Billing Address", "Shipping Address"]
|
||||
enable_common_party_accounting: DF.Check
|
||||
enable_fuzzy_matching: DF.Check
|
||||
enable_immutable_ledger: DF.Check
|
||||
enable_party_matching: DF.Check
|
||||
frozen_accounts_modifier: DF.Link | None
|
||||
general_ledger_remarks_length: DF.Int
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Advance Payment Ledger Entry", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@@ -1,113 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2024-10-16 16:57:12.085072",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"company",
|
||||
"voucher_type",
|
||||
"voucher_no",
|
||||
"against_voucher_type",
|
||||
"against_voucher_no",
|
||||
"amount",
|
||||
"currency",
|
||||
"event"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "voucher_type",
|
||||
"fieldtype": "Link",
|
||||
"label": "Voucher Type",
|
||||
"options": "DocType",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "voucher_no",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"label": "Voucher No",
|
||||
"options": "voucher_type",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "against_voucher_type",
|
||||
"fieldtype": "Link",
|
||||
"label": "Against Voucher Type",
|
||||
"options": "DocType",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "against_voucher_no",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"label": "Against Voucher No",
|
||||
"options": "against_voucher_type",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Amount",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Currency",
|
||||
"options": "Currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "event",
|
||||
"fieldtype": "Data",
|
||||
"label": "Event",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2024-11-05 10:31:28.736671",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Advance Payment Ledger Entry",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User",
|
||||
"share": 1
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"share": 1
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Auditor",
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class AdvancePaymentLedgerEntry(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
against_voucher_no: DF.DynamicLink | None
|
||||
against_voucher_type: DF.Link | None
|
||||
amount: DF.Currency
|
||||
company: DF.Link | None
|
||||
currency: DF.Link | None
|
||||
event: DF.Data | None
|
||||
voucher_no: DF.DynamicLink | None
|
||||
voucher_type: DF.Link | None
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -1,222 +0,0 @@
|
||||
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import nowdate, today
|
||||
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
||||
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
|
||||
|
||||
class TestAdvancePaymentLedgerEntry(AccountsTestMixin, FrappeTestCase):
|
||||
"""
|
||||
Integration tests for AdvancePaymentLedgerEntry.
|
||||
Use this class for testing interactions between multiple components.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.create_company()
|
||||
self.create_usd_receivable_account()
|
||||
self.create_usd_payable_account()
|
||||
self.create_item()
|
||||
self.clear_old_entries()
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
def create_sales_order(self, qty=1, rate=100, currency="INR", do_not_submit=False):
|
||||
"""
|
||||
Helper method
|
||||
"""
|
||||
so = make_sales_order(
|
||||
company=self.company,
|
||||
customer=self.customer,
|
||||
currency=currency,
|
||||
item=self.item,
|
||||
qty=qty,
|
||||
rate=rate,
|
||||
transaction_date=today(),
|
||||
do_not_submit=do_not_submit,
|
||||
)
|
||||
return so
|
||||
|
||||
def create_purchase_order(self, qty=1, rate=100, currency="INR", do_not_submit=False):
|
||||
"""
|
||||
Helper method
|
||||
"""
|
||||
po = create_purchase_order(
|
||||
company=self.company,
|
||||
customer=self.supplier,
|
||||
currency=currency,
|
||||
item=self.item,
|
||||
qty=qty,
|
||||
rate=rate,
|
||||
transaction_date=today(),
|
||||
do_not_submit=do_not_submit,
|
||||
)
|
||||
return po
|
||||
|
||||
def test_so_advance_paid_and_currency_with_payment(self):
|
||||
self.create_customer("_Test USD Customer", "USD")
|
||||
|
||||
so = self.create_sales_order(currency="USD", do_not_submit=True)
|
||||
so.conversion_rate = 80
|
||||
so.submit()
|
||||
|
||||
pe_exchange_rate = 85
|
||||
pe = get_payment_entry(so.doctype, so.name, bank_account=self.cash)
|
||||
pe.reference_no = "1"
|
||||
pe.reference_date = nowdate()
|
||||
pe.paid_from = self.debtors_usd
|
||||
pe.paid_from_account_currency = "USD"
|
||||
pe.source_exchange_rate = pe_exchange_rate
|
||||
pe.paid_amount = so.grand_total
|
||||
pe.received_amount = pe_exchange_rate * pe.paid_amount
|
||||
pe.references[0].outstanding_amount = 100
|
||||
pe.references[0].total_amount = 100
|
||||
pe.references[0].allocated_amount = 100
|
||||
pe.save().submit()
|
||||
|
||||
so.reload()
|
||||
self.assertEqual(so.advance_paid, 100)
|
||||
self.assertEqual(so.party_account_currency, "USD")
|
||||
|
||||
# cancel advance payment
|
||||
pe.reload()
|
||||
pe.cancel()
|
||||
|
||||
so.reload()
|
||||
self.assertEqual(so.advance_paid, 0)
|
||||
self.assertEqual(so.party_account_currency, "USD")
|
||||
|
||||
def test_so_advance_paid_and_currency_with_journal(self):
|
||||
self.create_customer("_Test USD Customer", "USD")
|
||||
|
||||
so = self.create_sales_order(currency="USD", do_not_submit=True)
|
||||
so.conversion_rate = 80
|
||||
so.submit()
|
||||
|
||||
je_exchange_rate = 85
|
||||
je = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Journal Entry",
|
||||
"company": self.company,
|
||||
"voucher_type": "Journal Entry",
|
||||
"posting_date": so.transaction_date,
|
||||
"multi_currency": True,
|
||||
"accounts": [
|
||||
{
|
||||
"account": self.debtors_usd,
|
||||
"party_type": "Customer",
|
||||
"party": so.customer,
|
||||
"credit": 8500,
|
||||
"credit_in_account_currency": 100,
|
||||
"is_advance": "Yes",
|
||||
"reference_type": so.doctype,
|
||||
"reference_name": so.name,
|
||||
"exchange_rate": je_exchange_rate,
|
||||
},
|
||||
{
|
||||
"account": self.cash,
|
||||
"debit": 8500,
|
||||
"debit_in_account_currency": 8500,
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
je.save().submit()
|
||||
so.reload()
|
||||
self.assertEqual(so.advance_paid, 100)
|
||||
self.assertEqual(so.party_account_currency, "USD")
|
||||
|
||||
# cancel advance payment
|
||||
je.reload()
|
||||
je.cancel()
|
||||
|
||||
so.reload()
|
||||
self.assertEqual(so.advance_paid, 0)
|
||||
self.assertEqual(so.party_account_currency, "USD")
|
||||
|
||||
def test_po_advance_paid_and_currency_with_payment(self):
|
||||
self.create_supplier("_Test USD Supplier", "USD")
|
||||
|
||||
po = self.create_purchase_order(currency="USD", do_not_submit=True)
|
||||
po.conversion_rate = 80
|
||||
po.submit()
|
||||
|
||||
pe_exchange_rate = 85
|
||||
pe = get_payment_entry(po.doctype, po.name, bank_account=self.cash)
|
||||
pe.reference_no = "1"
|
||||
pe.reference_date = nowdate()
|
||||
pe.paid_to = self.creditors_usd
|
||||
pe.paid_to_account_currency = "USD"
|
||||
pe.target_exchange_rate = pe_exchange_rate
|
||||
pe.received_amount = po.grand_total
|
||||
pe.paid_amount = pe_exchange_rate * pe.received_amount
|
||||
pe.references[0].outstanding_amount = 100
|
||||
pe.references[0].total_amount = 100
|
||||
pe.references[0].allocated_amount = 100
|
||||
pe.save().submit()
|
||||
|
||||
po.reload()
|
||||
self.assertEqual(po.advance_paid, 100)
|
||||
self.assertEqual(po.party_account_currency, "USD")
|
||||
|
||||
# cancel advance payment
|
||||
pe.reload()
|
||||
pe.cancel()
|
||||
|
||||
po.reload()
|
||||
self.assertEqual(po.advance_paid, 0)
|
||||
self.assertEqual(po.party_account_currency, "USD")
|
||||
|
||||
def test_po_advance_paid_and_currency_with_journal(self):
|
||||
self.create_supplier("_Test USD Supplier", "USD")
|
||||
|
||||
po = self.create_purchase_order(currency="USD", do_not_submit=True)
|
||||
po.conversion_rate = 80
|
||||
po.submit()
|
||||
|
||||
je_exchange_rate = 85
|
||||
je = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Journal Entry",
|
||||
"company": self.company,
|
||||
"voucher_type": "Journal Entry",
|
||||
"posting_date": po.transaction_date,
|
||||
"multi_currency": True,
|
||||
"accounts": [
|
||||
{
|
||||
"account": self.creditors_usd,
|
||||
"party_type": "Supplier",
|
||||
"party": po.supplier,
|
||||
"debit": 8500,
|
||||
"debit_in_account_currency": 100,
|
||||
"is_advance": "Yes",
|
||||
"reference_type": po.doctype,
|
||||
"reference_name": po.name,
|
||||
"exchange_rate": je_exchange_rate,
|
||||
},
|
||||
{
|
||||
"account": self.cash,
|
||||
"credit": 8500,
|
||||
"credit_in_account_currency": 8500,
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
je.save().submit()
|
||||
po.reload()
|
||||
self.assertEqual(po.advance_paid, 100)
|
||||
self.assertEqual(po.party_account_currency, "USD")
|
||||
|
||||
# cancel advance payment
|
||||
je.reload()
|
||||
je.cancel()
|
||||
|
||||
po.reload()
|
||||
self.assertEqual(po.advance_paid, 0)
|
||||
self.assertEqual(po.party_account_currency, "USD")
|
||||
@@ -208,49 +208,8 @@
|
||||
"label": "Disabled"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"group": "Transactions",
|
||||
"link_doctype": "Payment Request",
|
||||
"link_fieldname": "bank_account"
|
||||
},
|
||||
{
|
||||
"group": "Transactions",
|
||||
"link_doctype": "Payment Order",
|
||||
"link_fieldname": "bank_account"
|
||||
},
|
||||
{
|
||||
"group": "Transactions",
|
||||
"link_doctype": "Bank Guarantee",
|
||||
"link_fieldname": "bank_account"
|
||||
},
|
||||
{
|
||||
"group": "Transactions",
|
||||
"link_doctype": "Bank Transaction",
|
||||
"link_fieldname": "bank_account"
|
||||
},
|
||||
{
|
||||
"group": "Accounting",
|
||||
"link_doctype": "Payment Entry",
|
||||
"link_fieldname": "bank_account"
|
||||
},
|
||||
{
|
||||
"group": "Accounting",
|
||||
"link_doctype": "Journal Entry",
|
||||
"link_fieldname": "bank_account"
|
||||
},
|
||||
{
|
||||
"group": "Party",
|
||||
"link_doctype": "Customer",
|
||||
"link_fieldname": "default_bank_account"
|
||||
},
|
||||
{
|
||||
"group": "Party",
|
||||
"link_doctype": "Supplier",
|
||||
"link_fieldname": "default_bank_account"
|
||||
}
|
||||
],
|
||||
"modified": "2024-10-30 09:41:14.113414",
|
||||
"links": [],
|
||||
"modified": "2023-09-22 21:31:34.763977",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bank Account",
|
||||
@@ -287,4 +246,4 @@
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,6 @@ class BankAccount(Document):
|
||||
self.validate_company()
|
||||
self.validate_iban()
|
||||
self.validate_account()
|
||||
self.update_default_bank_account()
|
||||
|
||||
def validate_account(self):
|
||||
if self.account:
|
||||
@@ -101,51 +100,19 @@ class BankAccount(Document):
|
||||
if to_check % 97 != 1:
|
||||
frappe.throw(_("IBAN is not valid"))
|
||||
|
||||
def update_default_bank_account(self):
|
||||
if self.is_default and not self.disabled:
|
||||
frappe.db.set_value(
|
||||
"Bank Account",
|
||||
{
|
||||
"party_type": self.party_type,
|
||||
"party": self.party,
|
||||
"is_company_account": self.is_company_account,
|
||||
"is_default": 1,
|
||||
"disabled": 0,
|
||||
},
|
||||
"is_default",
|
||||
0,
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_bank_account(doctype, docname):
|
||||
doc = frappe.new_doc("Bank Account")
|
||||
doc.party_type = doctype
|
||||
doc.party = docname
|
||||
doc.is_default = 1
|
||||
|
||||
return doc
|
||||
|
||||
|
||||
def get_party_bank_account(party_type, party):
|
||||
return frappe.db.get_value(
|
||||
"Bank Account",
|
||||
{"party_type": party_type, "party": party, "is_default": 1, "disabled": 0},
|
||||
"name",
|
||||
)
|
||||
|
||||
|
||||
def get_default_company_bank_account(company, party_type, party):
|
||||
default_company_bank_account = frappe.db.get_value(party_type, party, "default_bank_account")
|
||||
if default_company_bank_account:
|
||||
if company != frappe.get_cached_value("Bank Account", default_company_bank_account, "company"):
|
||||
default_company_bank_account = None
|
||||
|
||||
if not default_company_bank_account:
|
||||
default_company_bank_account = frappe.db.get_value(
|
||||
"Bank Account", {"company": company, "is_company_account": 1, "is_default": 1}
|
||||
)
|
||||
|
||||
return default_company_bank_account
|
||||
return frappe.db.get_value(party_type, party, "default_bank_account")
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
from frappe import _
|
||||
|
||||
|
||||
def get_data():
|
||||
return {
|
||||
"fieldname": "bank_account",
|
||||
"non_standard_fieldnames": {
|
||||
"Customer": "default_bank_account",
|
||||
"Supplier": "default_bank_account",
|
||||
},
|
||||
"transactions": [
|
||||
{
|
||||
"label": _("Payments"),
|
||||
"items": ["Payment Entry", "Payment Request", "Payment Order", "Payroll Entry"],
|
||||
},
|
||||
{"label": _("Party"), "items": ["Customer", "Supplier"]},
|
||||
{"items": ["Bank Guarantee"]},
|
||||
{"items": ["Journal Entry"]},
|
||||
],
|
||||
}
|
||||
@@ -37,11 +37,11 @@ class TestBankAccount(unittest.TestCase):
|
||||
try:
|
||||
bank_account.validate_iban()
|
||||
except ValidationError:
|
||||
msg = f"BankAccount.validate_iban() failed for valid IBAN {iban}"
|
||||
msg = "BankAccount.validate_iban() failed for valid IBAN {}".format(iban)
|
||||
self.fail(msg=msg)
|
||||
|
||||
for not_iban in invalid_ibans:
|
||||
bank_account.iban = not_iban
|
||||
msg = f"BankAccount.validate_iban() accepted invalid IBAN {not_iban}"
|
||||
msg = "BankAccount.validate_iban() accepted invalid IBAN {}".format(not_iban)
|
||||
with self.assertRaises(ValidationError, msg=msg):
|
||||
bank_account.validate_iban()
|
||||
|
||||
@@ -38,11 +38,6 @@ frappe.ui.form.on("Bank Clearance", {
|
||||
frm.add_custom_button(__("Get Payment Entries"), () => frm.trigger("get_payment_entries"));
|
||||
|
||||
frm.change_custom_button_type(__("Get Payment Entries"), null, "primary");
|
||||
if (frm.doc.payment_entries.length) {
|
||||
frm.add_custom_button(__("Update Clearance Date"), () => frm.trigger("update_clearance_date"));
|
||||
frm.change_custom_button_type(__("Get Payment Entries"), null, "default");
|
||||
frm.change_custom_button_type(__("Update Clearance Date"), null, "primary");
|
||||
}
|
||||
},
|
||||
|
||||
update_clearance_date: function (frm) {
|
||||
@@ -50,7 +45,13 @@ frappe.ui.form.on("Bank Clearance", {
|
||||
method: "update_clearance_date",
|
||||
doc: frm.doc,
|
||||
callback: function (r, rt) {
|
||||
frm.refresh();
|
||||
frm.refresh_field("payment_entries");
|
||||
frm.refresh_fields();
|
||||
|
||||
if (!frm.doc.payment_entries.length) {
|
||||
frm.change_custom_button_type(__("Get Payment Entries"), null, "primary");
|
||||
frm.change_custom_button_type(__("Update Clearance Date"), null, "default");
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
@@ -59,8 +60,17 @@ frappe.ui.form.on("Bank Clearance", {
|
||||
return frappe.call({
|
||||
method: "get_payment_entries",
|
||||
doc: frm.doc,
|
||||
callback: function () {
|
||||
frm.refresh();
|
||||
callback: function (r, rt) {
|
||||
frm.refresh_field("payment_entries");
|
||||
|
||||
if (frm.doc.payment_entries.length) {
|
||||
frm.add_custom_button(__("Update Clearance Date"), () =>
|
||||
frm.trigger("update_clearance_date")
|
||||
);
|
||||
|
||||
frm.change_custom_button_type(__("Get Payment Entries"), null, "default");
|
||||
frm.change_custom_button_type(__("Update Clearance Date"), null, "primary");
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@ import frappe
|
||||
from frappe import _, msgprint
|
||||
from frappe.model.document import Document
|
||||
from frappe.query_builder.custom import ConstantColumn
|
||||
from frappe.utils import flt, fmt_money, get_link_to_form, getdate
|
||||
from frappe.utils import flt, fmt_money, getdate
|
||||
from pypika import Order
|
||||
|
||||
import erpnext
|
||||
@@ -96,11 +96,8 @@ class BankClearance(Document):
|
||||
|
||||
if d.cheque_date and getdate(d.clearance_date) < getdate(d.cheque_date):
|
||||
frappe.throw(
|
||||
_("Row #{0}: For {1} Clearance date {2} cannot be before Cheque Date {3}").format(
|
||||
d.idx,
|
||||
get_link_to_form(d.payment_document, d.payment_entry),
|
||||
d.clearance_date,
|
||||
d.cheque_date,
|
||||
_("Row #{0}: Clearance date {1} cannot be before Cheque Date {2}").format(
|
||||
d.idx, d.clearance_date, d.cheque_date
|
||||
)
|
||||
)
|
||||
|
||||
@@ -108,18 +105,8 @@ class BankClearance(Document):
|
||||
if not d.clearance_date:
|
||||
d.clearance_date = None
|
||||
|
||||
if d.payment_document == "Sales Invoice":
|
||||
frappe.db.set_value(
|
||||
"Sales Invoice Payment",
|
||||
{"parent": d.payment_entry, "account": self.get("account"), "amount": [">", 0]},
|
||||
"clearance_date",
|
||||
d.clearance_date,
|
||||
)
|
||||
|
||||
else:
|
||||
frappe.db.set_value(
|
||||
d.payment_document, d.payment_entry, "clearance_date", d.clearance_date
|
||||
)
|
||||
payment_entry = frappe.get_doc(d.payment_document, d.payment_entry)
|
||||
payment_entry.db_set("clearance_date", d.clearance_date)
|
||||
|
||||
clearance_date_updated = True
|
||||
|
||||
@@ -140,7 +127,7 @@ def get_payment_entries_for_bank_clearance(
|
||||
condition = "and (clearance_date IS NULL or clearance_date='0000-00-00')"
|
||||
|
||||
journal_entries = frappe.db.sql(
|
||||
f"""
|
||||
"""
|
||||
select
|
||||
"Journal Entry" as payment_document, t1.name as payment_entry,
|
||||
t1.cheque_no as cheque_number, t1.cheque_date,
|
||||
@@ -154,7 +141,9 @@ def get_payment_entries_for_bank_clearance(
|
||||
and ifnull(t1.is_opening, 'No') = 'No' {condition}
|
||||
group by t2.account, t1.name
|
||||
order by t1.posting_date ASC, t1.name DESC
|
||||
""",
|
||||
""".format(
|
||||
condition=condition
|
||||
),
|
||||
{"account": account, "from": from_date, "to": to_date},
|
||||
as_dict=1,
|
||||
)
|
||||
@@ -163,12 +152,12 @@ def get_payment_entries_for_bank_clearance(
|
||||
condition += "and bank_account = %(bank_account)s"
|
||||
|
||||
payment_entries = frappe.db.sql(
|
||||
f"""
|
||||
"""
|
||||
select
|
||||
"Payment Entry" as payment_document, name as payment_entry,
|
||||
reference_no as cheque_number, reference_date as cheque_date,
|
||||
if(paid_from=%(account)s, paid_amount + total_taxes_and_charges, 0) as credit,
|
||||
if(paid_from=%(account)s, 0, received_amount + total_taxes_and_charges) as debit,
|
||||
if(paid_from=%(account)s, 0, received_amount) as debit,
|
||||
posting_date, ifnull(party,if(paid_from=%(account)s,paid_to,paid_from)) as against_account, clearance_date,
|
||||
if(paid_to=%(account)s, paid_to_account_currency, paid_from_account_currency) as account_currency
|
||||
from `tabPayment Entry`
|
||||
@@ -178,7 +167,9 @@ def get_payment_entries_for_bank_clearance(
|
||||
{condition}
|
||||
order by
|
||||
posting_date ASC, name DESC
|
||||
""",
|
||||
""".format(
|
||||
condition=condition
|
||||
),
|
||||
{
|
||||
"account": account,
|
||||
"from": from_date,
|
||||
@@ -248,7 +239,10 @@ def get_payment_entries_for_bank_clearance(
|
||||
).run(as_dict=True)
|
||||
|
||||
entries = (
|
||||
list(payment_entries) + list(journal_entries) + list(pos_sales_invoices) + list(pos_purchase_invoices)
|
||||
list(payment_entries)
|
||||
+ list(journal_entries)
|
||||
+ list(pos_sales_invoices)
|
||||
+ list(pos_purchase_invoices)
|
||||
)
|
||||
|
||||
return entries
|
||||
|
||||
@@ -6,29 +6,16 @@ import unittest
|
||||
import frappe
|
||||
from frappe.utils import add_months, getdate
|
||||
|
||||
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
from erpnext.tests.utils import if_lending_app_installed, if_lending_app_not_installed
|
||||
|
||||
|
||||
class TestBankClearance(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
create_warehouse(
|
||||
warehouse_name="_Test Warehouse",
|
||||
properties={"parent_warehouse": "All Warehouses - _TC"},
|
||||
company="_Test Company",
|
||||
)
|
||||
create_item("_Test Item")
|
||||
create_cost_center(cost_center_name="_Test Cost Center", company="_Test Company")
|
||||
|
||||
clear_payment_entries()
|
||||
clear_loan_transactions()
|
||||
clear_pos_sales_invoices()
|
||||
make_bank_account()
|
||||
add_transactions()
|
||||
|
||||
@@ -81,7 +68,9 @@ class TestBankClearance(unittest.TestCase):
|
||||
)
|
||||
loan.submit()
|
||||
make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=getdate())
|
||||
repayment_entry = create_repayment_entry(loan.name, "_Test Customer", getdate(), loan.loan_amount)
|
||||
repayment_entry = create_repayment_entry(
|
||||
loan.name, "_Test Customer", getdate(), loan.loan_amount
|
||||
)
|
||||
repayment_entry.save()
|
||||
repayment_entry.submit()
|
||||
|
||||
@@ -96,41 +85,11 @@ class TestBankClearance(unittest.TestCase):
|
||||
bank_clearance.get_payment_entries()
|
||||
self.assertEqual(len(bank_clearance.payment_entries), 3)
|
||||
|
||||
def test_update_clearance_date_on_si(self):
|
||||
sales_invoice = make_pos_sales_invoice()
|
||||
|
||||
date = getdate()
|
||||
bank_clearance = frappe.get_doc("Bank Clearance")
|
||||
bank_clearance.account = "_Test Bank Clearance - _TC"
|
||||
bank_clearance.from_date = add_months(date, -1)
|
||||
bank_clearance.to_date = date
|
||||
bank_clearance.include_pos_transactions = 1
|
||||
bank_clearance.get_payment_entries()
|
||||
|
||||
self.assertNotEqual(len(bank_clearance.payment_entries), 0)
|
||||
for payment in bank_clearance.payment_entries:
|
||||
if payment.payment_entry == sales_invoice.name:
|
||||
payment.clearance_date = date
|
||||
|
||||
bank_clearance.update_clearance_date()
|
||||
|
||||
si_clearance_date = frappe.db.get_value(
|
||||
"Sales Invoice Payment",
|
||||
{"parent": sales_invoice.name, "account": bank_clearance.account},
|
||||
"clearance_date",
|
||||
)
|
||||
|
||||
self.assertEqual(si_clearance_date, date)
|
||||
|
||||
|
||||
def clear_payment_entries():
|
||||
frappe.db.delete("Payment Entry")
|
||||
|
||||
|
||||
def clear_pos_sales_invoices():
|
||||
frappe.db.delete("Sales Invoice", {"is_pos": 1})
|
||||
|
||||
|
||||
@if_lending_app_installed
|
||||
def clear_loan_transactions():
|
||||
for dt in [
|
||||
@@ -158,45 +117,9 @@ def add_transactions():
|
||||
|
||||
|
||||
def make_payment_entry():
|
||||
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
|
||||
|
||||
supplier = create_supplier(supplier_name="_Test Supplier")
|
||||
pi = make_purchase_invoice(
|
||||
supplier=supplier,
|
||||
supplier_warehouse="_Test Warehouse - _TC",
|
||||
expense_account="Cost of Goods Sold - _TC",
|
||||
uom="Nos",
|
||||
qty=1,
|
||||
rate=690,
|
||||
)
|
||||
pi = make_purchase_invoice(supplier="_Test Supplier", qty=1, rate=690)
|
||||
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank Clearance - _TC")
|
||||
pe.reference_no = "Conrad Oct 18"
|
||||
pe.reference_date = "2018-10-24"
|
||||
pe.insert()
|
||||
pe.submit()
|
||||
|
||||
|
||||
def make_pos_sales_invoice():
|
||||
from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import (
|
||||
make_customer,
|
||||
)
|
||||
|
||||
mode_of_payment = frappe.get_doc({"doctype": "Mode of Payment", "name": "Cash"})
|
||||
|
||||
if not frappe.db.get_value("Mode of Payment Account", {"company": "_Test Company", "parent": "Cash"}):
|
||||
mode_of_payment.append(
|
||||
"accounts", {"company": "_Test Company", "default_account": "_Test Bank Clearance - _TC"}
|
||||
)
|
||||
mode_of_payment.save()
|
||||
|
||||
customer = make_customer(customer="_Test Customer")
|
||||
|
||||
si = create_sales_invoice(customer=customer, item="_Test Item", is_pos=1, qty=1, rate=1000, do_not_save=1)
|
||||
si.set("payments", [])
|
||||
si.append(
|
||||
"payments", {"mode_of_payment": "Cash", "account": "_Test Bank Clearance - _TC", "amount": 1000}
|
||||
)
|
||||
si.insert()
|
||||
si.submit()
|
||||
|
||||
return si
|
||||
|
||||
@@ -59,10 +59,6 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
|
||||
);
|
||||
|
||||
frm.add_custom_button(__("Auto Reconcile"), function () {
|
||||
if (!frm.doc.bank_account) {
|
||||
frappe.msgprint(__("Please select Bank Account"));
|
||||
return;
|
||||
}
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.auto_reconcile_vouchers",
|
||||
args: {
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Company",
|
||||
"options": "Company"
|
||||
},
|
||||
@@ -119,7 +118,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2024-04-28 14:40:50.910884",
|
||||
"modified": "2023-03-07 11:02:24.535714",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bank Reconciliation Tool",
|
||||
@@ -140,4 +139,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
@@ -81,7 +81,9 @@ def get_bank_transactions(bank_account, from_date=None, to_date=None):
|
||||
def get_account_balance(bank_account, till_date):
|
||||
# returns account balance till the specified date
|
||||
account = frappe.db.get_value("Bank Account", bank_account, "account")
|
||||
filters = frappe._dict({"account": account, "report_date": till_date, "include_pos_transactions": 1})
|
||||
filters = frappe._dict(
|
||||
{"account": account, "report_date": till_date, "include_pos_transactions": 1}
|
||||
)
|
||||
data = get_entries(filters)
|
||||
|
||||
balance_as_per_system = get_balance_on(filters["account"], filters["report_date"])
|
||||
@@ -94,7 +96,10 @@ def get_account_balance(bank_account, till_date):
|
||||
amounts_not_reflected_in_system = get_amounts_not_reflected_in_system(filters)
|
||||
|
||||
bank_bal = (
|
||||
flt(balance_as_per_system) - flt(total_debit) + flt(total_credit) + amounts_not_reflected_in_system
|
||||
flt(balance_as_per_system)
|
||||
- flt(total_debit)
|
||||
+ flt(total_credit)
|
||||
+ amounts_not_reflected_in_system
|
||||
)
|
||||
|
||||
return bank_bal
|
||||
@@ -495,12 +500,12 @@ def check_matching(
|
||||
bank_account,
|
||||
company,
|
||||
transaction,
|
||||
document_types=None,
|
||||
from_date=None,
|
||||
to_date=None,
|
||||
filter_by_reference_date=None,
|
||||
from_reference_date=None,
|
||||
to_reference_date=None,
|
||||
document_types,
|
||||
from_date,
|
||||
to_date,
|
||||
filter_by_reference_date,
|
||||
from_reference_date,
|
||||
to_reference_date,
|
||||
):
|
||||
exact_match = True if "exact_match" in document_types else False
|
||||
|
||||
@@ -533,21 +538,23 @@ def check_matching(
|
||||
for query in queries:
|
||||
matching_vouchers.extend(query.run(as_dict=True))
|
||||
|
||||
return sorted(matching_vouchers, key=lambda x: x["rank"], reverse=True) if matching_vouchers else []
|
||||
return (
|
||||
sorted(matching_vouchers, key=lambda x: x["rank"], reverse=True) if matching_vouchers else []
|
||||
)
|
||||
|
||||
|
||||
def get_queries(
|
||||
bank_account,
|
||||
company,
|
||||
transaction,
|
||||
document_types=None,
|
||||
from_date=None,
|
||||
to_date=None,
|
||||
filter_by_reference_date=None,
|
||||
from_reference_date=None,
|
||||
to_reference_date=None,
|
||||
exact_match=None,
|
||||
common_filters=None,
|
||||
document_types,
|
||||
from_date,
|
||||
to_date,
|
||||
filter_by_reference_date,
|
||||
from_reference_date,
|
||||
to_reference_date,
|
||||
exact_match,
|
||||
common_filters,
|
||||
):
|
||||
# get queries to get matching vouchers
|
||||
account_from_to = "paid_to" if transaction.deposit > 0.0 else "paid_from"
|
||||
@@ -580,15 +587,15 @@ def get_matching_queries(
|
||||
bank_account,
|
||||
company,
|
||||
transaction,
|
||||
document_types=None,
|
||||
exact_match=None,
|
||||
account_from_to=None,
|
||||
from_date=None,
|
||||
to_date=None,
|
||||
filter_by_reference_date=None,
|
||||
from_reference_date=None,
|
||||
to_reference_date=None,
|
||||
common_filters=None,
|
||||
document_types,
|
||||
exact_match,
|
||||
account_from_to,
|
||||
from_date,
|
||||
to_date,
|
||||
filter_by_reference_date,
|
||||
from_reference_date,
|
||||
to_reference_date,
|
||||
common_filters,
|
||||
):
|
||||
queries = []
|
||||
currency = get_account_currency(bank_account)
|
||||
@@ -647,13 +654,17 @@ def get_bt_matching_query(exact_match, transaction):
|
||||
amount_rank = frappe.qb.terms.Case().when(amount_equality, 1).else_(0)
|
||||
amount_condition = amount_equality if exact_match else getattr(bt, field) > 0.0
|
||||
|
||||
ref_rank = frappe.qb.terms.Case().when(bt.reference_number == transaction.reference_number, 1).else_(0)
|
||||
ref_rank = (
|
||||
frappe.qb.terms.Case().when(bt.reference_number == transaction.reference_number, 1).else_(0)
|
||||
)
|
||||
unallocated_rank = (
|
||||
frappe.qb.terms.Case().when(bt.unallocated_amount == transaction.unallocated_amount, 1).else_(0)
|
||||
)
|
||||
|
||||
party_condition = (
|
||||
(bt.party_type == transaction.party_type) & (bt.party == transaction.party) & bt.party.isnotnull()
|
||||
(bt.party_type == transaction.party_type)
|
||||
& (bt.party == transaction.party)
|
||||
& bt.party.isnotnull()
|
||||
)
|
||||
party_rank = frappe.qb.terms.Case().when(party_condition, 1).else_(0)
|
||||
|
||||
@@ -705,7 +716,9 @@ def get_pe_matching_query(
|
||||
amount_condition = amount_equality if exact_match else pe.paid_amount > 0.0
|
||||
|
||||
party_condition = (
|
||||
(pe.party_type == transaction.party_type) & (pe.party == transaction.party) & pe.party.isnotnull()
|
||||
(pe.party_type == transaction.party_type)
|
||||
& (pe.party == transaction.party)
|
||||
& pe.party.isnotnull()
|
||||
)
|
||||
party_rank = frappe.qb.terms.Case().when(party_condition, 1).else_(0)
|
||||
|
||||
@@ -719,7 +732,7 @@ def get_pe_matching_query(
|
||||
(ref_rank + amount_rank + party_rank + 1).as_("rank"),
|
||||
ConstantColumn("Payment Entry").as_("doctype"),
|
||||
pe.name,
|
||||
pe.paid_amount_after_tax.as_("paid_amount"),
|
||||
pe.paid_amount,
|
||||
pe.reference_no,
|
||||
pe.reference_date,
|
||||
pe.party,
|
||||
@@ -736,7 +749,7 @@ def get_pe_matching_query(
|
||||
.orderby(pe.reference_date if cint(filter_by_reference_date) else pe.posting_date)
|
||||
)
|
||||
|
||||
if frappe.flags.auto_reconcile_vouchers is True:
|
||||
if frappe.flags.auto_reconcile_vouchers == True:
|
||||
query = query.where(ref_condition)
|
||||
|
||||
return query
|
||||
@@ -797,7 +810,7 @@ def get_je_matching_query(
|
||||
.orderby(je.cheque_date if cint(filter_by_reference_date) else je.posting_date)
|
||||
)
|
||||
|
||||
if frappe.flags.auto_reconcile_vouchers is True:
|
||||
if frappe.flags.auto_reconcile_vouchers == True:
|
||||
query = query.where(ref_condition)
|
||||
|
||||
return query
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe import qb
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import add_days, today
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.utils import add_days, flt, getdate, today
|
||||
|
||||
from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import (
|
||||
auto_reconcile_vouchers,
|
||||
@@ -21,7 +22,7 @@ class TestBankReconciliationTool(AccountsTestMixin, FrappeTestCase):
|
||||
self.create_customer()
|
||||
self.clear_old_entries()
|
||||
bank_dt = qb.DocType("Bank")
|
||||
qb.from_(bank_dt).delete().where(bank_dt.name == "HDFC").run()
|
||||
q = qb.from_(bank_dt).delete().where(bank_dt.name == "HDFC").run()
|
||||
self.create_bank_account()
|
||||
|
||||
def tearDown(self):
|
||||
|
||||
@@ -120,66 +120,52 @@ frappe.ui.form.on("Bank Statement Import", {
|
||||
},
|
||||
|
||||
show_import_status(frm) {
|
||||
if (frm.doc.status == "Pending") return;
|
||||
let import_log = JSON.parse(frm.doc.statement_import_log || "[]");
|
||||
let successful_records = import_log.filter((log) => log.success);
|
||||
let failed_records = import_log.filter((log) => !log.success);
|
||||
if (successful_records.length === 0) return;
|
||||
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.doctype.bank_statement_import.bank_statement_import.get_import_status",
|
||||
args: {
|
||||
docname: frm.doc.name,
|
||||
},
|
||||
callback: function (r) {
|
||||
let successful_records = cint(r.message.success);
|
||||
let failed_records = cint(r.message.failed);
|
||||
let total_records = cint(r.message.total_records);
|
||||
|
||||
if (!total_records) {
|
||||
return;
|
||||
}
|
||||
|
||||
let message;
|
||||
if (failed_records === 0) {
|
||||
let message_args = [successful_records];
|
||||
if (frm.doc.import_type === "Insert New Records") {
|
||||
message =
|
||||
successful_records > 1
|
||||
? __("Successfully imported {0} records.", message_args)
|
||||
: __("Successfully imported {0} record.", message_args);
|
||||
} else {
|
||||
message =
|
||||
successful_records > 1
|
||||
? __("Successfully updated {0} records.", message_args)
|
||||
: __("Successfully updated {0} record.", message_args);
|
||||
}
|
||||
} else {
|
||||
let message_args = [successful_records, total_records];
|
||||
if (frm.doc.import_type === "Insert New Records") {
|
||||
message =
|
||||
successful_records > 1
|
||||
? __(
|
||||
"Successfully imported {0} records out of {1}. Click on Export Errored Rows, fix the errors and import again.",
|
||||
message_args
|
||||
)
|
||||
: __(
|
||||
"Successfully imported {0} record out of {1}. Click on Export Errored Rows, fix the errors and import again.",
|
||||
message_args
|
||||
);
|
||||
} else {
|
||||
message =
|
||||
successful_records > 1
|
||||
? __(
|
||||
"Successfully updated {0} records out of {1}. Click on Export Errored Rows, fix the errors and import again.",
|
||||
message_args
|
||||
)
|
||||
: __(
|
||||
"Successfully updated {0} record out of {1}. Click on Export Errored Rows, fix the errors and import again.",
|
||||
message_args
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
frm.dashboard.set_headline(message);
|
||||
},
|
||||
});
|
||||
let message;
|
||||
if (failed_records.length === 0) {
|
||||
let message_args = [successful_records.length];
|
||||
if (frm.doc.import_type === "Insert New Records") {
|
||||
message =
|
||||
successful_records.length > 1
|
||||
? __("Successfully imported {0} records.", message_args)
|
||||
: __("Successfully imported {0} record.", message_args);
|
||||
} else {
|
||||
message =
|
||||
successful_records.length > 1
|
||||
? __("Successfully updated {0} records.", message_args)
|
||||
: __("Successfully updated {0} record.", message_args);
|
||||
}
|
||||
} else {
|
||||
let message_args = [successful_records.length, import_log.length];
|
||||
if (frm.doc.import_type === "Insert New Records") {
|
||||
message =
|
||||
successful_records.length > 1
|
||||
? __(
|
||||
"Successfully imported {0} records out of {1}. Click on Export Errored Rows, fix the errors and import again.",
|
||||
message_args
|
||||
)
|
||||
: __(
|
||||
"Successfully imported {0} record out of {1}. Click on Export Errored Rows, fix the errors and import again.",
|
||||
message_args
|
||||
);
|
||||
} else {
|
||||
message =
|
||||
successful_records.length > 1
|
||||
? __(
|
||||
"Successfully updated {0} records out of {1}. Click on Export Errored Rows, fix the errors and import again.",
|
||||
message_args
|
||||
)
|
||||
: __(
|
||||
"Successfully updated {0} record out of {1}. Click on Export Errored Rows, fix the errors and import again.",
|
||||
message_args
|
||||
);
|
||||
}
|
||||
}
|
||||
frm.dashboard.set_headline(message);
|
||||
},
|
||||
|
||||
show_report_error_button(frm) {
|
||||
@@ -301,7 +287,7 @@ frappe.ui.form.on("Bank Statement Import", {
|
||||
// method: 'frappe.core.doctype.data_import.data_import.get_preview_from_template',
|
||||
|
||||
show_import_preview(frm, preview_data) {
|
||||
let import_log = preview_data.import_log;
|
||||
let import_log = JSON.parse(frm.doc.statement_import_log || "[]");
|
||||
|
||||
if (frm.import_preview && frm.import_preview.doctype === frm.doc.reference_doctype) {
|
||||
frm.import_preview.preview_data = preview_data;
|
||||
@@ -340,15 +326,6 @@ frappe.ui.form.on("Bank Statement Import", {
|
||||
);
|
||||
},
|
||||
|
||||
export_import_log(frm) {
|
||||
open_url_post(
|
||||
"/api/method/erpnext.accounts.doctype.bank_statement_import.bank_statement_import.download_import_log",
|
||||
{
|
||||
data_import_name: frm.doc.name,
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
show_import_warnings(frm, preview_data) {
|
||||
let columns = preview_data.columns;
|
||||
let warnings = JSON.parse(frm.doc.template_warnings || "[]");
|
||||
@@ -424,50 +401,49 @@ frappe.ui.form.on("Bank Statement Import", {
|
||||
frm.trigger("show_import_log");
|
||||
},
|
||||
|
||||
render_import_log(frm) {
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.doctype.bank_statement_import.bank_statement_import.get_import_logs",
|
||||
args: {
|
||||
docname: frm.doc.name,
|
||||
},
|
||||
callback: function (r) {
|
||||
let logs = r.message;
|
||||
show_import_log(frm) {
|
||||
let import_log = JSON.parse(frm.doc.statement_import_log || "[]");
|
||||
let logs = import_log;
|
||||
frm.toggle_display("import_log", false);
|
||||
frm.toggle_display("import_log_section", logs.length > 0);
|
||||
|
||||
if (logs.length === 0) return;
|
||||
if (logs.length === 0) {
|
||||
frm.get_field("import_log_preview").$wrapper.empty();
|
||||
return;
|
||||
}
|
||||
|
||||
frm.toggle_display("import_log_section", true);
|
||||
|
||||
let rows = logs
|
||||
.map((log) => {
|
||||
let html = "";
|
||||
if (log.success) {
|
||||
if (frm.doc.import_type === "Insert New Records") {
|
||||
html = __("Successfully imported {0}", [
|
||||
`<span class="underline">${frappe.utils.get_form_link(
|
||||
frm.doc.reference_doctype,
|
||||
log.docname,
|
||||
true
|
||||
)}<span>`,
|
||||
]);
|
||||
} else {
|
||||
html = __("Successfully updated {0}", [
|
||||
`<span class="underline">${frappe.utils.get_form_link(
|
||||
frm.doc.reference_doctype,
|
||||
log.docname,
|
||||
true
|
||||
)}<span>`,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
let messages = JSON.parse(log.messages || "[]")
|
||||
.map((m) => {
|
||||
let title = m.title ? `<strong>${m.title}</strong>` : "";
|
||||
let message = m.message ? `<div>${m.message}</div>` : "";
|
||||
return title + message;
|
||||
})
|
||||
.join("");
|
||||
let id = frappe.dom.get_unique_id();
|
||||
html = `${messages}
|
||||
let rows = logs
|
||||
.map((log) => {
|
||||
let html = "";
|
||||
if (log.success) {
|
||||
if (frm.doc.import_type === "Insert New Records") {
|
||||
html = __("Successfully imported {0}", [
|
||||
`<span class="underline">${frappe.utils.get_form_link(
|
||||
frm.doc.reference_doctype,
|
||||
log.docname,
|
||||
true
|
||||
)}<span>`,
|
||||
]);
|
||||
} else {
|
||||
html = __("Successfully updated {0}", [
|
||||
`<span class="underline">${frappe.utils.get_form_link(
|
||||
frm.doc.reference_doctype,
|
||||
log.docname,
|
||||
true
|
||||
)}<span>`,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
let messages = log.messages
|
||||
.map(JSON.parse)
|
||||
.map((m) => {
|
||||
let title = m.title ? `<strong>${m.title}</strong>` : "";
|
||||
let message = m.message ? `<div>${m.message}</div>` : "";
|
||||
return title + message;
|
||||
})
|
||||
.join("");
|
||||
let id = frappe.dom.get_unique_id();
|
||||
html = `${messages}
|
||||
<button class="btn btn-default btn-xs" type="button" data-toggle="collapse" data-target="#${id}" aria-expanded="false" aria-controls="${id}" style="margin-top: 15px;">
|
||||
${__("Show Traceback")}
|
||||
</button>
|
||||
@@ -476,16 +452,16 @@ frappe.ui.form.on("Bank Statement Import", {
|
||||
<pre>${log.exception}</pre>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
let indicator_color = log.success ? "green" : "red";
|
||||
let title = log.success ? __("Success") : __("Failure");
|
||||
}
|
||||
let indicator_color = log.success ? "green" : "red";
|
||||
let title = log.success ? __("Success") : __("Failure");
|
||||
|
||||
if (frm.doc.show_failed_logs && log.success) {
|
||||
return "";
|
||||
}
|
||||
if (frm.doc.show_failed_logs && log.success) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return `<tr>
|
||||
<td>${JSON.parse(log.row_indexes).join(", ")}</td>
|
||||
return `<tr>
|
||||
<td>${log.row_indexes.join(", ")}</td>
|
||||
<td>
|
||||
<div class="indicator ${indicator_color}">${title}</div>
|
||||
</td>
|
||||
@@ -493,16 +469,16 @@ frappe.ui.form.on("Bank Statement Import", {
|
||||
${html}
|
||||
</td>
|
||||
</tr>`;
|
||||
})
|
||||
.join("");
|
||||
})
|
||||
.join("");
|
||||
|
||||
if (!rows && frm.doc.show_failed_logs) {
|
||||
rows = `<tr><td class="text-center text-muted" colspan=3>
|
||||
if (!rows && frm.doc.show_failed_logs) {
|
||||
rows = `<tr><td class="text-center text-muted" colspan=3>
|
||||
${__("No failed logs")}
|
||||
</td></tr>`;
|
||||
}
|
||||
}
|
||||
|
||||
frm.get_field("import_log_preview").$wrapper.html(`
|
||||
frm.get_field("import_log_preview").$wrapper.html(`
|
||||
<table class="table table-bordered">
|
||||
<tr class="text-muted">
|
||||
<th width="10%">${__("Row Number")}</th>
|
||||
@@ -512,34 +488,5 @@ frappe.ui.form.on("Bank Statement Import", {
|
||||
${rows}
|
||||
</table>
|
||||
`);
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
show_import_log(frm) {
|
||||
frm.toggle_display("import_log_section", false);
|
||||
|
||||
if (frm.is_new() || frm.import_in_progress) {
|
||||
return;
|
||||
}
|
||||
|
||||
frappe.call({
|
||||
method: "frappe.client.get_count",
|
||||
args: {
|
||||
doctype: "Data Import Log",
|
||||
filters: {
|
||||
data_import: frm.doc.name,
|
||||
},
|
||||
},
|
||||
callback: function (r) {
|
||||
let count = r.message;
|
||||
if (count < 5000) {
|
||||
frm.trigger("render_import_log");
|
||||
} else {
|
||||
frm.toggle_display("import_log_section", false);
|
||||
frm.add_custom_button(__("Export Import Log"), () => frm.trigger("export_import_log"));
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
"bank_account",
|
||||
"bank",
|
||||
"column_break_4",
|
||||
"custom_delimiters",
|
||||
"delimiter_options",
|
||||
"google_sheets_url",
|
||||
"refresh_google_sheet",
|
||||
"html_5",
|
||||
@@ -26,6 +24,7 @@
|
||||
"section_import_preview",
|
||||
"import_preview",
|
||||
"import_log_section",
|
||||
"statement_import_log",
|
||||
"show_failed_logs",
|
||||
"import_log_preview",
|
||||
"reference_doctype",
|
||||
@@ -195,23 +194,15 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "custom_delimiters",
|
||||
"fieldtype": "Check",
|
||||
"label": "Custom delimiters"
|
||||
},
|
||||
{
|
||||
"default": ",;\\t|",
|
||||
"depends_on": "custom_delimiters",
|
||||
"description": "If your CSV uses a different delimiter, add that character here, ensuring no spaces or additional characters are included.",
|
||||
"fieldname": "delimiter_options",
|
||||
"fieldtype": "Data",
|
||||
"label": "Delimiter options"
|
||||
"fieldname": "statement_import_log",
|
||||
"fieldtype": "Code",
|
||||
"label": "Statement Import Log",
|
||||
"options": "JSON"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"links": [],
|
||||
"modified": "2024-06-25 17:32:07.658250",
|
||||
"modified": "2022-09-07 11:11:40.293317",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bank Statement Import",
|
||||
|
||||
@@ -31,14 +31,13 @@ class BankStatementImport(DataImport):
|
||||
bank: DF.Link | None
|
||||
bank_account: DF.Link
|
||||
company: DF.Link
|
||||
custom_delimiters: DF.Check
|
||||
delimiter_options: DF.Data | None
|
||||
google_sheets_url: DF.Data | None
|
||||
import_file: DF.Attach | None
|
||||
import_type: DF.Literal["", "Insert New Records", "Update Existing Records"]
|
||||
mute_emails: DF.Check
|
||||
reference_doctype: DF.Link
|
||||
show_failed_logs: DF.Check
|
||||
statement_import_log: DF.Code | None
|
||||
status: DF.Literal["Pending", "Success", "Partial Success", "Error"]
|
||||
submit_after_import: DF.Check
|
||||
template_options: DF.Code | None
|
||||
@@ -46,7 +45,7 @@ class BankStatementImport(DataImport):
|
||||
# end: auto-generated types
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
super(BankStatementImport, self).__init__(*args, **kwargs)
|
||||
|
||||
def validate(self):
|
||||
doc_before_save = self.get_doc_before_save()
|
||||
@@ -55,6 +54,7 @@ class BankStatementImport(DataImport):
|
||||
or (doc_before_save and doc_before_save.import_file != self.import_file)
|
||||
or (doc_before_save and doc_before_save.google_sheets_url != self.google_sheets_url)
|
||||
):
|
||||
|
||||
template_options_dict = {}
|
||||
column_to_field_map = {}
|
||||
bank = frappe.get_doc("Bank", self.bank)
|
||||
@@ -69,6 +69,7 @@ class BankStatementImport(DataImport):
|
||||
self.validate_google_sheets_url()
|
||||
|
||||
def start_import(self):
|
||||
|
||||
preview = frappe.get_doc("Bank Statement Import", self.name).get_preview_from_template(
|
||||
self.import_file, self.google_sheets_url
|
||||
)
|
||||
@@ -121,15 +122,10 @@ def download_errored_template(data_import_name):
|
||||
data_import.export_errored_rows()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def download_import_log(data_import_name):
|
||||
return frappe.get_doc("Bank Statement Import", data_import_name).download_import_log()
|
||||
|
||||
|
||||
def parse_data_from_template(raw_data):
|
||||
data = []
|
||||
|
||||
for _i, row in enumerate(raw_data):
|
||||
for i, row in enumerate(raw_data):
|
||||
if all(v in INVALID_VALUES for v in row):
|
||||
# empty row
|
||||
continue
|
||||
@@ -139,7 +135,9 @@ def parse_data_from_template(raw_data):
|
||||
return data
|
||||
|
||||
|
||||
def start_import(data_import, bank_account, import_file_path, google_sheets_url, bank, template_options):
|
||||
def start_import(
|
||||
data_import, bank_account, import_file_path, google_sheets_url, bank, template_options
|
||||
):
|
||||
"""This method runs in background job"""
|
||||
|
||||
update_mapping_db(bank, template_options)
|
||||
@@ -150,9 +148,6 @@ def start_import(data_import, bank_account, import_file_path, google_sheets_url,
|
||||
import_file = ImportFile("Bank Transaction", file=file, import_type="Insert New Records")
|
||||
|
||||
data = parse_data_from_template(import_file.raw_data)
|
||||
# Importer expects 'Data Import' class, which has 'payload_count' attribute
|
||||
if not data_import.get("payload_count"):
|
||||
data_import.payload_count = len(data) - 1
|
||||
|
||||
if import_file_path:
|
||||
add_bank_account(data, bank_account)
|
||||
@@ -247,47 +242,6 @@ def write_xlsx(data, sheet_name, wb=None, column_widths=None, file_path=None):
|
||||
return True
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_import_status(docname):
|
||||
import_status = {}
|
||||
|
||||
data_import = frappe.get_doc("Bank Statement Import", docname)
|
||||
import_status["status"] = data_import.status
|
||||
|
||||
logs = frappe.get_all(
|
||||
"Data Import Log",
|
||||
fields=["count(*) as count", "success"],
|
||||
filters={"data_import": docname},
|
||||
group_by="success",
|
||||
)
|
||||
|
||||
total_payload_count = 0
|
||||
|
||||
for log in logs:
|
||||
total_payload_count += log.get("count", 0)
|
||||
if log.get("success"):
|
||||
import_status["success"] = log.get("count")
|
||||
else:
|
||||
import_status["failed"] = log.get("count")
|
||||
|
||||
import_status["total_records"] = total_payload_count
|
||||
|
||||
return import_status
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_import_logs(docname: str):
|
||||
frappe.has_permission("Bank Statement Import")
|
||||
|
||||
return frappe.get_all(
|
||||
"Data Import Log",
|
||||
fields=["success", "docname", "messages", "exception", "row_indexes"],
|
||||
filters={"data_import": docname},
|
||||
limit_page_length=5000,
|
||||
order_by="log_index",
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def upload_bank_statement(**args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from typing import Tuple, Union
|
||||
|
||||
import frappe
|
||||
from frappe.utils import flt
|
||||
from rapidfuzz import fuzz, process
|
||||
@@ -17,7 +19,7 @@ class AutoMatchParty:
|
||||
def get(self, key):
|
||||
return self.__dict__.get(key, None)
|
||||
|
||||
def match(self) -> tuple | None:
|
||||
def match(self) -> Union[Tuple, None]:
|
||||
result = None
|
||||
result = AutoMatchbyAccountIBAN(
|
||||
bank_party_account_number=self.bank_party_account_number,
|
||||
@@ -48,7 +50,7 @@ class AutoMatchbyAccountIBAN:
|
||||
result = self.match_account_in_party()
|
||||
return result
|
||||
|
||||
def match_account_in_party(self) -> tuple | None:
|
||||
def match_account_in_party(self) -> Union[Tuple, None]:
|
||||
"""Check if there is a IBAN/Account No. match in Customer/Supplier/Employee"""
|
||||
result = None
|
||||
parties = get_parties_in_order(self.deposit)
|
||||
@@ -68,9 +70,6 @@ class AutoMatchbyAccountIBAN:
|
||||
party, or_filters=or_filters, pluck="name", limit_page_length=1
|
||||
)
|
||||
|
||||
if "bank_ac_no" in or_filters:
|
||||
or_filters["bank_account_no"] = or_filters.pop("bank_ac_no")
|
||||
|
||||
if party_result:
|
||||
result = (
|
||||
party,
|
||||
@@ -98,7 +97,7 @@ class AutoMatchbyPartyNameDescription:
|
||||
def get(self, key):
|
||||
return self.__dict__.get(key, None)
|
||||
|
||||
def match(self) -> tuple | None:
|
||||
def match(self) -> Union[Tuple, None]:
|
||||
# fuzzy search by customer/supplier & employee
|
||||
if not (self.bank_party_name or self.description):
|
||||
return None
|
||||
@@ -106,7 +105,7 @@ class AutoMatchbyPartyNameDescription:
|
||||
result = self.match_party_name_desc_in_party()
|
||||
return result
|
||||
|
||||
def match_party_name_desc_in_party(self) -> tuple | None:
|
||||
def match_party_name_desc_in_party(self) -> Union[Tuple, None]:
|
||||
"""Fuzzy search party name and/or description against parties in the system"""
|
||||
result = None
|
||||
parties = get_parties_in_order(self.deposit)
|
||||
@@ -130,7 +129,7 @@ class AutoMatchbyPartyNameDescription:
|
||||
|
||||
return result
|
||||
|
||||
def fuzzy_search_and_return_result(self, party, names, field) -> tuple | None:
|
||||
def fuzzy_search_and_return_result(self, party, names, field) -> Union[Tuple, None]:
|
||||
skip = False
|
||||
result = process.extract(query=self.get(field), choices=names, scorer=fuzz.token_set_ratio)
|
||||
party_name, skip = self.process_fuzzy_result(result)
|
||||
@@ -143,7 +142,7 @@ class AutoMatchbyPartyNameDescription:
|
||||
party_name,
|
||||
), skip
|
||||
|
||||
def process_fuzzy_result(self, result: list | None):
|
||||
def process_fuzzy_result(self, result: Union[list, None]):
|
||||
"""
|
||||
If there are multiple valid close matches return None as result may be faulty.
|
||||
Return the result only if one accurate match stands out.
|
||||
|
||||
@@ -56,19 +56,17 @@ class BankTransaction(Document):
|
||||
Bank Transaction should be on the same currency as the Bank Account.
|
||||
"""
|
||||
if self.currency and self.bank_account:
|
||||
if account := frappe.get_cached_value("Bank Account", self.bank_account, "account"):
|
||||
account_currency = frappe.get_cached_value("Account", account, "account_currency")
|
||||
account = frappe.get_cached_value("Bank Account", self.bank_account, "account")
|
||||
account_currency = frappe.get_cached_value("Account", account, "account_currency")
|
||||
|
||||
if self.currency != account_currency:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Transaction currency: {0} cannot be different from Bank Account({1}) currency: {2}"
|
||||
).format(
|
||||
frappe.bold(self.currency),
|
||||
frappe.bold(self.bank_account),
|
||||
frappe.bold(account_currency),
|
||||
)
|
||||
if self.currency != account_currency:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Transaction currency: {0} cannot be different from Bank Account({1}) currency: {2}"
|
||||
).format(
|
||||
frappe.bold(self.currency), frappe.bold(self.bank_account), frappe.bold(account_currency)
|
||||
)
|
||||
)
|
||||
|
||||
def set_status(self):
|
||||
if self.docstatus == 2:
|
||||
@@ -182,7 +180,7 @@ class BankTransaction(Document):
|
||||
frappe.throw(_("Voucher {0} is over-allocated by {1}").format(unallocated_amount))
|
||||
|
||||
for payment_entry in to_remove:
|
||||
self.remove(payment_entry)
|
||||
self.remove(to_remove)
|
||||
|
||||
@frappe.whitelist()
|
||||
def remove_payment_entries(self):
|
||||
@@ -208,17 +206,13 @@ class BankTransaction(Document):
|
||||
if self.party_type and self.party:
|
||||
return
|
||||
|
||||
result = None
|
||||
try:
|
||||
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()
|
||||
except Exception:
|
||||
frappe.log_error(title=_("Error in party matching for Bank Transaction {0}").format(self.name))
|
||||
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 not result:
|
||||
return
|
||||
@@ -241,7 +235,9 @@ def get_clearance_details(transaction, payment_entry):
|
||||
"""
|
||||
gl_bank_account = frappe.db.get_value("Bank Account", transaction.bank_account, "account")
|
||||
gles = get_related_bank_gl_entries(payment_entry.payment_document, payment_entry.payment_entry)
|
||||
bt_allocations = get_total_allocated_amount(payment_entry.payment_document, payment_entry.payment_entry)
|
||||
bt_allocations = get_total_allocated_amount(
|
||||
payment_entry.payment_document, payment_entry.payment_entry
|
||||
)
|
||||
|
||||
unallocated_amount = min(
|
||||
transaction.unallocated_amount,
|
||||
@@ -336,6 +332,7 @@ def get_total_allocated_amount(doctype, docname):
|
||||
|
||||
def get_paid_amount(payment_entry, currency, gl_bank_account):
|
||||
if payment_entry.payment_document in ["Payment Entry", "Sales Invoice", "Purchase Invoice"]:
|
||||
|
||||
paid_amount_field = "paid_amount"
|
||||
if payment_entry.payment_document == "Payment Entry":
|
||||
doc = frappe.get_doc("Payment Entry", payment_entry.payment_entry)
|
||||
@@ -374,7 +371,9 @@ def get_paid_amount(payment_entry, currency, gl_bank_account):
|
||||
)
|
||||
|
||||
elif payment_entry.payment_document == "Loan Repayment":
|
||||
return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "amount_paid")
|
||||
return frappe.db.get_value(
|
||||
payment_entry.payment_document, payment_entry.payment_entry, "amount_paid"
|
||||
)
|
||||
|
||||
elif payment_entry.payment_document == "Bank Transaction":
|
||||
dep, wth = frappe.db.get_value(
|
||||
@@ -384,7 +383,9 @@ def get_paid_amount(payment_entry, currency, gl_bank_account):
|
||||
|
||||
else:
|
||||
frappe.throw(
|
||||
f"Please reconcile {payment_entry.payment_document}: {payment_entry.payment_entry} manually"
|
||||
"Please reconcile {0}: {1} manually".format(
|
||||
payment_entry.payment_document, payment_entry.payment_entry
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -18,12 +18,12 @@ def upload_bank_statement():
|
||||
fcontent = frappe.local.uploaded_file
|
||||
fname = frappe.local.uploaded_filename
|
||||
|
||||
if frappe.safe_encode(fname).lower().endswith(b"csv"):
|
||||
if frappe.safe_encode(fname).lower().endswith("csv".encode("utf-8")):
|
||||
from frappe.utils.csvutils import read_csv_content
|
||||
|
||||
rows = read_csv_content(fcontent, False)
|
||||
|
||||
elif frappe.safe_encode(fname).lower().endswith(b"xlsx"):
|
||||
elif frappe.safe_encode(fname).lower().endswith("xlsx".encode("utf-8")):
|
||||
from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file
|
||||
|
||||
rows = read_xlsx_file_from_attached_file(fcontent=fcontent)
|
||||
|
||||
@@ -436,7 +436,9 @@ def add_vouchers(gl_account="_Test Bank - _TC"):
|
||||
|
||||
mode_of_payment = frappe.get_doc({"doctype": "Mode of Payment", "name": "Cash"})
|
||||
|
||||
if not frappe.db.get_value("Mode of Payment Account", {"company": "_Test Company", "parent": "Cash"}):
|
||||
if not frappe.db.get_value(
|
||||
"Mode of Payment Account", {"company": "_Test Company", "parent": "Cash"}
|
||||
):
|
||||
mode_of_payment.append("accounts", {"company": "_Test Company", "default_account": gl_account})
|
||||
mode_of_payment.save()
|
||||
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("Bisect Accounting Statements", {
|
||||
onload(frm) {
|
||||
frm.trigger("render_heatmap");
|
||||
},
|
||||
refresh(frm) {
|
||||
frm.add_custom_button(__("Bisect Left"), () => {
|
||||
frm.trigger("bisect_left");
|
||||
});
|
||||
|
||||
frm.add_custom_button(__("Bisect Right"), () => {
|
||||
frm.trigger("bisect_right");
|
||||
});
|
||||
|
||||
frm.add_custom_button(__("Up"), () => {
|
||||
frm.trigger("move_up");
|
||||
});
|
||||
frm.add_custom_button(__("Build Tree"), () => {
|
||||
frm.trigger("build_tree");
|
||||
});
|
||||
},
|
||||
render_heatmap(frm) {
|
||||
let bisect_heatmap = frm.get_field("bisect_heatmap").$wrapper;
|
||||
bisect_heatmap.addClass("bisect_heatmap_location");
|
||||
|
||||
// milliseconds in a day
|
||||
let msiad = 24 * 60 * 60 * 1000;
|
||||
let datapoints = {};
|
||||
let fr_dt = new Date(frm.doc.from_date).getTime();
|
||||
let to_dt = new Date(frm.doc.to_date).getTime();
|
||||
let bisect_start = new Date(frm.doc.current_from_date).getTime();
|
||||
let bisect_end = new Date(frm.doc.current_to_date).getTime();
|
||||
|
||||
for (let x = fr_dt; x <= to_dt; x += msiad) {
|
||||
let epoch_in_seconds = x / 1000;
|
||||
if (bisect_start <= x && x <= bisect_end) {
|
||||
datapoints[epoch_in_seconds] = 1.0;
|
||||
} else {
|
||||
datapoints[epoch_in_seconds] = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
new frappe.Chart(".bisect_heatmap_location", {
|
||||
type: "heatmap",
|
||||
data: {
|
||||
dataPoints: datapoints,
|
||||
start: new Date(frm.doc.from_date),
|
||||
end: new Date(frm.doc.to_date),
|
||||
},
|
||||
countLabel: "Bisecting",
|
||||
discreteDomains: 1,
|
||||
});
|
||||
},
|
||||
bisect_left(frm) {
|
||||
frm.call({
|
||||
doc: frm.doc,
|
||||
method: "bisect_left",
|
||||
freeze: true,
|
||||
freeze_message: __("Bisecting Left ..."),
|
||||
callback: (r) => {
|
||||
frm.trigger("render_heatmap");
|
||||
},
|
||||
});
|
||||
},
|
||||
bisect_right(frm) {
|
||||
frm.call({
|
||||
doc: frm.doc,
|
||||
freeze: true,
|
||||
freeze_message: __("Bisecting Right ..."),
|
||||
method: "bisect_right",
|
||||
callback: (r) => {
|
||||
frm.trigger("render_heatmap");
|
||||
},
|
||||
});
|
||||
},
|
||||
move_up(frm) {
|
||||
frm.call({
|
||||
doc: frm.doc,
|
||||
freeze: true,
|
||||
freeze_message: __("Moving up in tree ..."),
|
||||
method: "move_up",
|
||||
callback: (r) => {
|
||||
frm.trigger("render_heatmap");
|
||||
},
|
||||
});
|
||||
},
|
||||
build_tree(frm) {
|
||||
frm.call({
|
||||
doc: frm.doc,
|
||||
freeze: true,
|
||||
freeze_message: __("Rebuilding BTree for period ..."),
|
||||
method: "build_tree",
|
||||
callback: (r) => {
|
||||
frm.trigger("render_heatmap");
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -1,194 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2023-09-15 21:28:28.054773",
|
||||
"default_view": "List",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"section_break_cvfg",
|
||||
"company",
|
||||
"column_break_hcam",
|
||||
"from_date",
|
||||
"column_break_qxbi",
|
||||
"to_date",
|
||||
"column_break_iwny",
|
||||
"algorithm",
|
||||
"section_break_8ph9",
|
||||
"current_node",
|
||||
"section_break_ngid",
|
||||
"bisect_heatmap",
|
||||
"section_break_hmsy",
|
||||
"bisecting_from",
|
||||
"current_from_date",
|
||||
"column_break_uqyd",
|
||||
"bisecting_to",
|
||||
"current_to_date",
|
||||
"section_break_hbyo",
|
||||
"heading_cppb",
|
||||
"p_l_summary",
|
||||
"column_break_aivo",
|
||||
"balance_sheet_summary",
|
||||
"b_s_summary",
|
||||
"column_break_gvwx",
|
||||
"difference_heading",
|
||||
"difference"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "column_break_qxbi",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "from_date",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "From Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "To Date"
|
||||
},
|
||||
{
|
||||
"default": "BFS",
|
||||
"fieldname": "algorithm",
|
||||
"fieldtype": "Select",
|
||||
"label": "Algorithm",
|
||||
"options": "BFS\nDFS"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_iwny",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "current_node",
|
||||
"fieldtype": "Link",
|
||||
"label": "Current Node",
|
||||
"options": "Bisect Nodes"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_hmsy",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "current_from_date",
|
||||
"fieldtype": "Datetime",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "current_to_date",
|
||||
"fieldtype": "Datetime",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_uqyd",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_hbyo",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "p_l_summary",
|
||||
"fieldtype": "Float",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "b_s_summary",
|
||||
"fieldtype": "Float",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "difference",
|
||||
"fieldtype": "Float",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_aivo",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_gvwx",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"options": "Company"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_hcam",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_ngid",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_8ph9",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "bisect_heatmap",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Heatmap"
|
||||
},
|
||||
{
|
||||
"fieldname": "heading_cppb",
|
||||
"fieldtype": "Heading",
|
||||
"label": "Profit and Loss Summary"
|
||||
},
|
||||
{
|
||||
"fieldname": "balance_sheet_summary",
|
||||
"fieldtype": "Heading",
|
||||
"label": "Balance Sheet Summary"
|
||||
},
|
||||
{
|
||||
"fieldname": "difference_heading",
|
||||
"fieldtype": "Heading",
|
||||
"label": "Difference"
|
||||
},
|
||||
{
|
||||
"fieldname": "bisecting_from",
|
||||
"fieldtype": "Heading",
|
||||
"label": "Bisecting From"
|
||||
},
|
||||
{
|
||||
"fieldname": "bisecting_to",
|
||||
"fieldtype": "Heading",
|
||||
"label": "Bisecting To"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_cvfg",
|
||||
"fieldtype": "Section Break"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-12-01 16:49:54.073890",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bisect Accounting Statements",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "Administrator",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"read_only": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -1,226 +0,0 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import datetime
|
||||
from collections import deque
|
||||
from math import floor
|
||||
|
||||
import frappe
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import getdate
|
||||
from frappe.utils.data import guess_date_format
|
||||
|
||||
|
||||
class BisectAccountingStatements(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
algorithm: DF.Literal["BFS", "DFS"]
|
||||
b_s_summary: DF.Float
|
||||
company: DF.Link | None
|
||||
current_from_date: DF.Datetime | None
|
||||
current_node: DF.Link | None
|
||||
current_to_date: DF.Datetime | None
|
||||
difference: DF.Float
|
||||
from_date: DF.Datetime | None
|
||||
p_l_summary: DF.Float
|
||||
to_date: DF.Datetime | None
|
||||
# end: auto-generated types
|
||||
|
||||
def validate(self):
|
||||
self.validate_dates()
|
||||
|
||||
def validate_dates(self):
|
||||
if getdate(self.from_date) > getdate(self.to_date):
|
||||
frappe.throw(
|
||||
_("From Date: {0} cannot be greater than To date: {1}").format(
|
||||
frappe.bold(self.from_date), frappe.bold(self.to_date)
|
||||
)
|
||||
)
|
||||
|
||||
def bfs(self, from_date: datetime, to_date: datetime):
|
||||
# Make Root node
|
||||
node = frappe.new_doc("Bisect Nodes")
|
||||
node.root = None
|
||||
node.period_from_date = from_date
|
||||
node.period_to_date = to_date
|
||||
node.insert()
|
||||
|
||||
period_queue = deque([node])
|
||||
while period_queue:
|
||||
cur_node = period_queue.popleft()
|
||||
delta = cur_node.period_to_date - cur_node.period_from_date
|
||||
if delta.days == 0:
|
||||
continue
|
||||
else:
|
||||
cur_floor = floor(delta.days / 2)
|
||||
next_to_date = cur_node.period_from_date + relativedelta(days=+cur_floor)
|
||||
left_node = frappe.new_doc("Bisect Nodes")
|
||||
left_node.period_from_date = cur_node.period_from_date
|
||||
left_node.period_to_date = next_to_date
|
||||
left_node.root = cur_node.name
|
||||
left_node.generated = False
|
||||
left_node.insert()
|
||||
cur_node.left_child = left_node.name
|
||||
period_queue.append(left_node)
|
||||
|
||||
next_from_date = cur_node.period_from_date + relativedelta(days=+(cur_floor + 1))
|
||||
right_node = frappe.new_doc("Bisect Nodes")
|
||||
right_node.period_from_date = next_from_date
|
||||
right_node.period_to_date = cur_node.period_to_date
|
||||
right_node.root = cur_node.name
|
||||
right_node.generated = False
|
||||
right_node.insert()
|
||||
cur_node.right_child = right_node.name
|
||||
period_queue.append(right_node)
|
||||
|
||||
cur_node.save()
|
||||
|
||||
def dfs(self, from_date: datetime, to_date: datetime):
|
||||
# Make Root node
|
||||
node = frappe.new_doc("Bisect Nodes")
|
||||
node.root = None
|
||||
node.period_from_date = from_date
|
||||
node.period_to_date = to_date
|
||||
node.insert()
|
||||
|
||||
period_stack = [node]
|
||||
while period_stack:
|
||||
cur_node = period_stack.pop()
|
||||
delta = cur_node.period_to_date - cur_node.period_from_date
|
||||
if delta.days == 0:
|
||||
continue
|
||||
else:
|
||||
cur_floor = floor(delta.days / 2)
|
||||
next_to_date = cur_node.period_from_date + relativedelta(days=+cur_floor)
|
||||
left_node = frappe.new_doc("Bisect Nodes")
|
||||
left_node.period_from_date = cur_node.period_from_date
|
||||
left_node.period_to_date = next_to_date
|
||||
left_node.root = cur_node.name
|
||||
left_node.generated = False
|
||||
left_node.insert()
|
||||
cur_node.left_child = left_node.name
|
||||
period_stack.append(left_node)
|
||||
|
||||
next_from_date = cur_node.period_from_date + relativedelta(days=+(cur_floor + 1))
|
||||
right_node = frappe.new_doc("Bisect Nodes")
|
||||
right_node.period_from_date = next_from_date
|
||||
right_node.period_to_date = cur_node.period_to_date
|
||||
right_node.root = cur_node.name
|
||||
right_node.generated = False
|
||||
right_node.insert()
|
||||
cur_node.right_child = right_node.name
|
||||
period_stack.append(right_node)
|
||||
|
||||
cur_node.save()
|
||||
|
||||
@frappe.whitelist()
|
||||
def build_tree(self):
|
||||
frappe.db.delete("Bisect Nodes")
|
||||
|
||||
# Convert str to datetime format
|
||||
dt_format = guess_date_format(self.from_date)
|
||||
from_date = datetime.datetime.strptime(self.from_date, dt_format)
|
||||
to_date = datetime.datetime.strptime(self.to_date, dt_format)
|
||||
|
||||
if self.algorithm == "BFS":
|
||||
self.bfs(from_date, to_date)
|
||||
|
||||
if self.algorithm == "DFS":
|
||||
self.dfs(from_date, to_date)
|
||||
|
||||
# set root as current node
|
||||
root = frappe.db.get_all("Bisect Nodes", filters={"root": ["is", "not set"]})[0]
|
||||
self.get_report_summary()
|
||||
self.current_node = root.name
|
||||
self.current_from_date = self.from_date
|
||||
self.current_to_date = self.to_date
|
||||
self.save()
|
||||
|
||||
def get_report_summary(self):
|
||||
filters = {
|
||||
"company": self.company,
|
||||
"filter_based_on": "Date Range",
|
||||
"period_start_date": self.current_from_date,
|
||||
"period_end_date": self.current_to_date,
|
||||
"periodicity": "Yearly",
|
||||
}
|
||||
pl_summary = frappe.get_doc("Report", "Profit and Loss Statement")
|
||||
self.p_l_summary = pl_summary.execute_script_report(filters=filters)[5]
|
||||
bs_summary = frappe.get_doc("Report", "Balance Sheet")
|
||||
self.b_s_summary = bs_summary.execute_script_report(filters=filters)[5]
|
||||
self.difference = abs(self.p_l_summary - self.b_s_summary)
|
||||
|
||||
def update_node(self):
|
||||
current_node = frappe.get_doc("Bisect Nodes", self.current_node)
|
||||
current_node.balance_sheet_summary = self.b_s_summary
|
||||
current_node.profit_loss_summary = self.p_l_summary
|
||||
current_node.difference = self.difference
|
||||
current_node.generated = True
|
||||
current_node.save()
|
||||
|
||||
def current_node_has_summary_info(self):
|
||||
"Assertion method"
|
||||
return frappe.db.get_value("Bisect Nodes", self.current_node, "generated")
|
||||
|
||||
def fetch_summary_info_from_current_node(self):
|
||||
current_node = frappe.get_doc("Bisect Nodes", self.current_node)
|
||||
self.p_l_summary = current_node.balance_sheet_summary
|
||||
self.b_s_summary = current_node.profit_loss_summary
|
||||
self.difference = abs(self.p_l_summary - self.b_s_summary)
|
||||
|
||||
def fetch_or_calculate(self):
|
||||
if self.current_node_has_summary_info():
|
||||
self.fetch_summary_info_from_current_node()
|
||||
else:
|
||||
self.get_report_summary()
|
||||
self.update_node()
|
||||
|
||||
@frappe.whitelist()
|
||||
def bisect_left(self):
|
||||
if self.current_node is not None:
|
||||
cur_node = frappe.get_doc("Bisect Nodes", self.current_node)
|
||||
if cur_node.left_child is not None:
|
||||
lft_node = frappe.get_doc("Bisect Nodes", cur_node.left_child)
|
||||
self.current_node = cur_node.left_child
|
||||
self.current_from_date = lft_node.period_from_date
|
||||
self.current_to_date = lft_node.period_to_date
|
||||
self.fetch_or_calculate()
|
||||
self.save()
|
||||
else:
|
||||
frappe.msgprint(_("No more children on Left"))
|
||||
|
||||
@frappe.whitelist()
|
||||
def bisect_right(self):
|
||||
if self.current_node is not None:
|
||||
cur_node = frappe.get_doc("Bisect Nodes", self.current_node)
|
||||
if cur_node.right_child is not None:
|
||||
rgt_node = frappe.get_doc("Bisect Nodes", cur_node.right_child)
|
||||
self.current_node = cur_node.right_child
|
||||
self.current_from_date = rgt_node.period_from_date
|
||||
self.current_to_date = rgt_node.period_to_date
|
||||
self.fetch_or_calculate()
|
||||
self.save()
|
||||
else:
|
||||
frappe.msgprint(_("No more children on Right"))
|
||||
|
||||
@frappe.whitelist()
|
||||
def move_up(self):
|
||||
if self.current_node is not None:
|
||||
cur_node = frappe.get_doc("Bisect Nodes", self.current_node)
|
||||
if cur_node.root is not None:
|
||||
root = frappe.get_doc("Bisect Nodes", cur_node.root)
|
||||
self.current_node = cur_node.root
|
||||
self.current_from_date = root.period_from_date
|
||||
self.current_to_date = root.period_to_date
|
||||
self.fetch_or_calculate()
|
||||
self.save()
|
||||
else:
|
||||
frappe.msgprint(_("Reached Root"))
|
||||
@@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
|
||||
class TestBisectAccountingStatements(FrappeTestCase):
|
||||
pass
|
||||
@@ -1,8 +0,0 @@
|
||||
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Bisect Nodes", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@@ -1,97 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "autoincrement",
|
||||
"creation": "2023-09-27 14:56:38.112462",
|
||||
"default_view": "List",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"root",
|
||||
"left_child",
|
||||
"right_child",
|
||||
"period_from_date",
|
||||
"period_to_date",
|
||||
"difference",
|
||||
"balance_sheet_summary",
|
||||
"profit_loss_summary",
|
||||
"generated"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "root",
|
||||
"fieldtype": "Link",
|
||||
"label": "Root",
|
||||
"options": "Bisect Nodes"
|
||||
},
|
||||
{
|
||||
"fieldname": "left_child",
|
||||
"fieldtype": "Link",
|
||||
"label": "Left Child",
|
||||
"options": "Bisect Nodes"
|
||||
},
|
||||
{
|
||||
"fieldname": "right_child",
|
||||
"fieldtype": "Link",
|
||||
"label": "Right Child",
|
||||
"options": "Bisect Nodes"
|
||||
},
|
||||
{
|
||||
"fieldname": "period_from_date",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Period_from_date"
|
||||
},
|
||||
{
|
||||
"fieldname": "period_to_date",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Period To Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "difference",
|
||||
"fieldtype": "Float",
|
||||
"label": "Difference"
|
||||
},
|
||||
{
|
||||
"fieldname": "balance_sheet_summary",
|
||||
"fieldtype": "Float",
|
||||
"label": "Balance Sheet Summary"
|
||||
},
|
||||
{
|
||||
"fieldname": "profit_loss_summary",
|
||||
"fieldtype": "Float",
|
||||
"label": "Profit and Loss Summary"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "generated",
|
||||
"fieldtype": "Check",
|
||||
"label": "Generated"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-12-01 17:46:12.437996",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bisect Nodes",
|
||||
"naming_rule": "Autoincrement",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Administrator",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"read_only": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class BisectNodes(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
balance_sheet_summary: DF.Float
|
||||
difference: DF.Float
|
||||
generated: DF.Check
|
||||
left_child: DF.Link | None
|
||||
name: DF.Int | None
|
||||
period_from_date: DF.Datetime | None
|
||||
period_to_date: DF.Datetime | None
|
||||
profit_loss_summary: DF.Float
|
||||
right_child: DF.Link | None
|
||||
root: DF.Link | None
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -1,9 +0,0 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
|
||||
class TestBisectNodes(FrappeTestCase):
|
||||
pass
|
||||
@@ -70,11 +70,10 @@ class Budget(Document):
|
||||
select
|
||||
b.name, ba.account from `tabBudget` b, `tabBudget Account` ba
|
||||
where
|
||||
ba.parent = b.name and b.docstatus < 2 and b.company = {} and {}={} and
|
||||
b.fiscal_year={} and b.name != {} and ba.account in ({}) """.format(
|
||||
"%s", budget_against_field, "%s", "%s", "%s", ",".join(["%s"] * len(accounts))
|
||||
),
|
||||
(self.company, budget_against, self.fiscal_year, self.name, *tuple(accounts)),
|
||||
ba.parent = b.name and b.docstatus < 2 and b.company = %s and %s=%s and
|
||||
b.fiscal_year=%s and b.name != %s and ba.account in (%s) """
|
||||
% ("%s", budget_against_field, "%s", "%s", "%s", ",".join(["%s"] * len(accounts))),
|
||||
(self.company, budget_against, self.fiscal_year, self.name) + tuple(accounts),
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
@@ -97,14 +96,12 @@ class Budget(Document):
|
||||
if account_details.is_group:
|
||||
frappe.throw(_("Budget cannot be assigned against Group Account {0}").format(d.account))
|
||||
elif account_details.company != self.company:
|
||||
frappe.throw(
|
||||
_("Account {0} does not belongs to company {1}").format(d.account, self.company)
|
||||
)
|
||||
frappe.throw(_("Account {0} does not belongs to company {1}").format(d.account, self.company))
|
||||
elif account_details.report_type != "Profit and Loss":
|
||||
frappe.throw(
|
||||
_(
|
||||
"Budget cannot be assigned against {0}, as it's not an Income or Expense account"
|
||||
).format(d.account)
|
||||
_("Budget cannot be assigned against {0}, as it's not an Income or Expense account").format(
|
||||
d.account
|
||||
)
|
||||
)
|
||||
|
||||
if d.account in account_list:
|
||||
@@ -142,8 +139,6 @@ class Budget(Document):
|
||||
|
||||
def validate_expense_against_budget(args, expense_amount=0):
|
||||
args = frappe._dict(args)
|
||||
if not frappe.get_all("Budget", limit=1):
|
||||
return
|
||||
|
||||
if args.get("company") and not args.fiscal_year:
|
||||
args.fiscal_year = get_fiscal_year(args.get("posting_date"), company=args.get("company"))[0]
|
||||
@@ -151,9 +146,6 @@ def validate_expense_against_budget(args, expense_amount=0):
|
||||
"Company", args.get("company"), "exception_budget_approver_role"
|
||||
)
|
||||
|
||||
if not frappe.get_cached_value("Budget", {"fiscal_year": args.fiscal_year, "company": args.company}): # nosec
|
||||
return
|
||||
|
||||
if not args.account:
|
||||
args.account = args.get("expense_account")
|
||||
|
||||
@@ -180,26 +172,32 @@ def validate_expense_against_budget(args, expense_amount=0):
|
||||
if (
|
||||
args.get(budget_against)
|
||||
and args.account
|
||||
and (frappe.get_cached_value("Account", args.account, "root_type") == "Expense")
|
||||
and frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"})
|
||||
):
|
||||
|
||||
doctype = dimension.get("document_type")
|
||||
|
||||
if frappe.get_cached_value("DocType", doctype, "is_tree"):
|
||||
lft, rgt = frappe.get_cached_value(doctype, args.get(budget_against), ["lft", "rgt"])
|
||||
condition = f"""and exists(select name from `tab{doctype}`
|
||||
where lft<={lft} and rgt>={rgt} and name=b.{budget_against})""" # nosec
|
||||
lft, rgt = frappe.db.get_value(doctype, args.get(budget_against), ["lft", "rgt"])
|
||||
condition = """and exists(select name from `tab%s`
|
||||
where lft<=%s and rgt>=%s and name=b.%s)""" % (
|
||||
doctype,
|
||||
lft,
|
||||
rgt,
|
||||
budget_against,
|
||||
) # nosec
|
||||
args.is_tree = True
|
||||
else:
|
||||
condition = f"and b.{budget_against}={frappe.db.escape(args.get(budget_against))}"
|
||||
condition = "and b.%s=%s" % (budget_against, frappe.db.escape(args.get(budget_against)))
|
||||
args.is_tree = False
|
||||
|
||||
args.budget_against_field = budget_against
|
||||
args.budget_against_doctype = doctype
|
||||
|
||||
budget_records = frappe.db.sql(
|
||||
f"""
|
||||
"""
|
||||
select
|
||||
b.{budget_against} as budget_against, ba.budget_amount, b.monthly_distribution,
|
||||
b.{budget_against_field} as budget_against, ba.budget_amount, b.monthly_distribution,
|
||||
ifnull(b.applicable_on_material_request, 0) as for_material_request,
|
||||
ifnull(applicable_on_purchase_order, 0) as for_purchase_order,
|
||||
ifnull(applicable_on_booking_actual_expenses,0) as for_actual_expenses,
|
||||
@@ -212,7 +210,9 @@ def validate_expense_against_budget(args, expense_amount=0):
|
||||
b.name=ba.parent and b.fiscal_year=%s
|
||||
and ba.account=%s and b.docstatus=1
|
||||
{condition}
|
||||
""",
|
||||
""".format(
|
||||
condition=condition, budget_against_field=budget_against
|
||||
),
|
||||
(args.fiscal_year, args.account),
|
||||
as_dict=True,
|
||||
) # nosec
|
||||
@@ -224,18 +224,12 @@ def validate_expense_against_budget(args, expense_amount=0):
|
||||
def validate_budget_records(args, budget_records, expense_amount):
|
||||
for budget in budget_records:
|
||||
if flt(budget.budget_amount):
|
||||
amount = expense_amount or get_amount(args, budget)
|
||||
yearly_action, monthly_action = get_actions(args, budget)
|
||||
args["for_material_request"] = budget.for_material_request
|
||||
args["for_purchase_order"] = budget.for_purchase_order
|
||||
|
||||
if yearly_action in ("Stop", "Warn"):
|
||||
compare_expense_with_budget(
|
||||
args,
|
||||
flt(budget.budget_amount),
|
||||
_("Annual"),
|
||||
yearly_action,
|
||||
budget.budget_against,
|
||||
expense_amount,
|
||||
args, flt(budget.budget_amount), _("Annual"), yearly_action, budget.budget_against, amount
|
||||
)
|
||||
|
||||
if monthly_action in ["Stop", "Warn"]:
|
||||
@@ -246,32 +240,18 @@ def validate_budget_records(args, budget_records, expense_amount):
|
||||
args["month_end_date"] = get_last_day(args.posting_date)
|
||||
|
||||
compare_expense_with_budget(
|
||||
args,
|
||||
budget_amount,
|
||||
_("Accumulated Monthly"),
|
||||
monthly_action,
|
||||
budget.budget_against,
|
||||
expense_amount,
|
||||
args, budget_amount, _("Accumulated Monthly"), monthly_action, budget.budget_against, amount
|
||||
)
|
||||
|
||||
|
||||
def compare_expense_with_budget(args, budget_amount, action_for, action, budget_against, amount=0):
|
||||
args.actual_expense, args.requested_amount, args.ordered_amount = get_actual_expense(args), 0, 0
|
||||
if not amount:
|
||||
args.requested_amount, args.ordered_amount = get_requested_amount(args), get_ordered_amount(args)
|
||||
|
||||
if args.get("doctype") == "Material Request" and args.for_material_request:
|
||||
amount = args.requested_amount + args.ordered_amount
|
||||
|
||||
elif args.get("doctype") == "Purchase Order" and args.for_purchase_order:
|
||||
amount = args.ordered_amount
|
||||
|
||||
total_expense = args.actual_expense + amount
|
||||
actual_expense = get_actual_expense(args)
|
||||
total_expense = actual_expense + amount
|
||||
|
||||
if total_expense > budget_amount:
|
||||
if args.actual_expense > budget_amount:
|
||||
if actual_expense > budget_amount:
|
||||
error_tense = _("is already")
|
||||
diff = args.actual_expense - budget_amount
|
||||
diff = actual_expense - budget_amount
|
||||
else:
|
||||
error_tense = _("will be")
|
||||
diff = total_expense - budget_amount
|
||||
@@ -288,10 +268,9 @@ def compare_expense_with_budget(args, budget_amount, action_for, action, budget_
|
||||
frappe.bold(fmt_money(diff, currency=currency)),
|
||||
)
|
||||
|
||||
msg += get_expense_breakup(args, currency, budget_against)
|
||||
|
||||
if frappe.flags.exception_approver_role and frappe.flags.exception_approver_role in frappe.get_roles(
|
||||
frappe.session.user
|
||||
if (
|
||||
frappe.flags.exception_approver_role
|
||||
and frappe.flags.exception_approver_role in frappe.get_roles(frappe.session.user)
|
||||
):
|
||||
action = "Warn"
|
||||
|
||||
@@ -301,83 +280,6 @@ def compare_expense_with_budget(args, budget_amount, action_for, action, budget_
|
||||
frappe.msgprint(msg, indicator="orange", title=_("Budget Exceeded"))
|
||||
|
||||
|
||||
def get_expense_breakup(args, currency, budget_against):
|
||||
msg = "<hr>Total Expenses booked through - <ul>"
|
||||
|
||||
common_filters = frappe._dict(
|
||||
{
|
||||
args.budget_against_field: budget_against,
|
||||
"account": args.account,
|
||||
"company": args.company,
|
||||
}
|
||||
)
|
||||
|
||||
msg += (
|
||||
"<li>"
|
||||
+ frappe.utils.get_link_to_report(
|
||||
"General Ledger",
|
||||
label="Actual Expenses",
|
||||
filters=common_filters.copy().update(
|
||||
{
|
||||
"from_date": frappe.get_cached_value("Fiscal Year", args.fiscal_year, "year_start_date"),
|
||||
"to_date": frappe.get_cached_value("Fiscal Year", args.fiscal_year, "year_end_date"),
|
||||
"is_cancelled": 0,
|
||||
}
|
||||
),
|
||||
)
|
||||
+ " - "
|
||||
+ frappe.bold(fmt_money(args.actual_expense, currency=currency))
|
||||
+ "</li>"
|
||||
)
|
||||
|
||||
msg += (
|
||||
"<li>"
|
||||
+ frappe.utils.get_link_to_report(
|
||||
"Material Request",
|
||||
label="Material Requests",
|
||||
report_type="Report Builder",
|
||||
doctype="Material Request",
|
||||
filters=common_filters.copy().update(
|
||||
{
|
||||
"status": [["!=", "Stopped"]],
|
||||
"docstatus": 1,
|
||||
"material_request_type": "Purchase",
|
||||
"schedule_date": [["fiscal year", "2023-2024"]],
|
||||
"item_code": args.item_code,
|
||||
"per_ordered": [["<", 100]],
|
||||
}
|
||||
),
|
||||
)
|
||||
+ " - "
|
||||
+ frappe.bold(fmt_money(args.requested_amount, currency=currency))
|
||||
+ "</li>"
|
||||
)
|
||||
|
||||
msg += (
|
||||
"<li>"
|
||||
+ frappe.utils.get_link_to_report(
|
||||
"Purchase Order",
|
||||
label="Unbilled Orders",
|
||||
report_type="Report Builder",
|
||||
doctype="Purchase Order",
|
||||
filters=common_filters.copy().update(
|
||||
{
|
||||
"status": [["!=", "Closed"]],
|
||||
"docstatus": 1,
|
||||
"transaction_date": [["fiscal year", "2023-2024"]],
|
||||
"item_code": args.item_code,
|
||||
"per_billed": [["<", 100]],
|
||||
}
|
||||
),
|
||||
)
|
||||
+ " - "
|
||||
+ frappe.bold(fmt_money(args.ordered_amount, currency=currency))
|
||||
+ "</li></ul>"
|
||||
)
|
||||
|
||||
return msg
|
||||
|
||||
|
||||
def get_actions(args, budget):
|
||||
yearly_action = budget.action_if_annual_budget_exceeded
|
||||
monthly_action = budget.action_if_accumulated_monthly_budget_exceeded
|
||||
@@ -393,15 +295,31 @@ def get_actions(args, budget):
|
||||
return yearly_action, monthly_action
|
||||
|
||||
|
||||
def get_requested_amount(args):
|
||||
def get_amount(args, budget):
|
||||
amount = 0
|
||||
|
||||
if args.get("doctype") == "Material Request" and budget.for_material_request:
|
||||
amount = (
|
||||
get_requested_amount(args, budget) + get_ordered_amount(args, budget) + get_actual_expense(args)
|
||||
)
|
||||
|
||||
elif args.get("doctype") == "Purchase Order" and budget.for_purchase_order:
|
||||
amount = get_ordered_amount(args, budget) + get_actual_expense(args)
|
||||
|
||||
return amount
|
||||
|
||||
|
||||
def get_requested_amount(args, budget):
|
||||
item_code = args.get("item_code")
|
||||
condition = get_other_condition(args, "Material Request")
|
||||
condition = get_other_condition(args, budget, "Material Request")
|
||||
|
||||
data = frappe.db.sql(
|
||||
""" select ifnull((sum(child.stock_qty - child.ordered_qty) * rate), 0) as amount
|
||||
from `tabMaterial Request Item` child, `tabMaterial Request` parent where parent.name = child.parent and
|
||||
child.item_code = %s and parent.docstatus = 1 and child.stock_qty > child.ordered_qty and {} and
|
||||
parent.material_request_type = 'Purchase' and parent.status != 'Stopped'""".format(condition),
|
||||
child.item_code = %s and parent.docstatus = 1 and child.stock_qty > child.ordered_qty and {0} and
|
||||
parent.material_request_type = 'Purchase' and parent.status != 'Stopped'""".format(
|
||||
condition
|
||||
),
|
||||
item_code,
|
||||
as_list=1,
|
||||
)
|
||||
@@ -409,15 +327,17 @@ def get_requested_amount(args):
|
||||
return data[0][0] if data else 0
|
||||
|
||||
|
||||
def get_ordered_amount(args):
|
||||
def get_ordered_amount(args, budget):
|
||||
item_code = args.get("item_code")
|
||||
condition = get_other_condition(args, "Purchase Order")
|
||||
condition = get_other_condition(args, budget, "Purchase Order")
|
||||
|
||||
data = frappe.db.sql(
|
||||
f""" select ifnull(sum(child.amount - child.billed_amt), 0) as amount
|
||||
""" select ifnull(sum(child.amount - child.billed_amt), 0) as amount
|
||||
from `tabPurchase Order Item` child, `tabPurchase Order` parent where
|
||||
parent.name = child.parent and child.item_code = %s and parent.docstatus = 1 and child.amount > child.billed_amt
|
||||
and parent.status != 'Closed' and {condition}""",
|
||||
and parent.status != 'Closed' and {0}""".format(
|
||||
condition
|
||||
),
|
||||
item_code,
|
||||
as_list=1,
|
||||
)
|
||||
@@ -425,12 +345,12 @@ def get_ordered_amount(args):
|
||||
return data[0][0] if data else 0
|
||||
|
||||
|
||||
def get_other_condition(args, for_doc):
|
||||
def get_other_condition(args, budget, for_doc):
|
||||
condition = "expense_account = '%s'" % (args.expense_account)
|
||||
budget_against_field = args.get("budget_against_field")
|
||||
|
||||
if budget_against_field and args.get(budget_against_field):
|
||||
condition += f" and child.{budget_against_field} = '{args.get(budget_against_field)}'"
|
||||
condition += " and child.%s = '%s'" % (budget_against_field, args.get(budget_against_field))
|
||||
|
||||
if args.get("fiscal_year"):
|
||||
date_field = "schedule_date" if for_doc == "Material Request" else "transaction_date"
|
||||
@@ -438,8 +358,12 @@ def get_other_condition(args, for_doc):
|
||||
"Fiscal Year", args.get("fiscal_year"), ["year_start_date", "year_end_date"]
|
||||
)
|
||||
|
||||
condition += f""" and parent.{date_field}
|
||||
between '{start_date}' and '{end_date}' """
|
||||
condition += """ and parent.%s
|
||||
between '%s' and '%s' """ % (
|
||||
date_field,
|
||||
start_date,
|
||||
end_date,
|
||||
)
|
||||
|
||||
return condition
|
||||
|
||||
@@ -458,17 +382,21 @@ def get_actual_expense(args):
|
||||
|
||||
args.update(lft_rgt)
|
||||
|
||||
condition2 = f"""and exists(select name from `tab{args.budget_against_doctype}`
|
||||
condition2 = """and exists(select name from `tab{doctype}`
|
||||
where lft>=%(lft)s and rgt<=%(rgt)s
|
||||
and name=gle.{budget_against_field})"""
|
||||
and name=gle.{budget_against_field})""".format(
|
||||
doctype=args.budget_against_doctype, budget_against_field=budget_against_field # nosec
|
||||
)
|
||||
else:
|
||||
condition2 = f"""and exists(select name from `tab{args.budget_against_doctype}`
|
||||
where name=gle.{budget_against_field} and
|
||||
gle.{budget_against_field} = %({budget_against_field})s)"""
|
||||
condition2 = """and exists(select name from `tab{doctype}`
|
||||
where name=gle.{budget_against} and
|
||||
gle.{budget_against} = %({budget_against})s)""".format(
|
||||
doctype=args.budget_against_doctype, budget_against=budget_against_field
|
||||
)
|
||||
|
||||
amount = flt(
|
||||
frappe.db.sql(
|
||||
f"""
|
||||
"""
|
||||
select sum(gle.debit) - sum(gle.credit)
|
||||
from `tabGL Entry` gle
|
||||
where
|
||||
@@ -479,7 +407,9 @@ def get_actual_expense(args):
|
||||
and gle.company=%(company)s
|
||||
and gle.docstatus=1
|
||||
{condition2}
|
||||
""",
|
||||
""".format(
|
||||
condition1=condition1, condition2=condition2
|
||||
),
|
||||
(args),
|
||||
)[0][0]
|
||||
) # nosec
|
||||
|
||||
@@ -41,7 +41,9 @@ class TestBudget(unittest.TestCase):
|
||||
|
||||
budget = make_budget(budget_against="Cost Center")
|
||||
|
||||
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
|
||||
frappe.db.set_value(
|
||||
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
|
||||
)
|
||||
|
||||
jv = make_journal_entry(
|
||||
"_Test Account Cost for Goods Sold - _TC",
|
||||
@@ -61,7 +63,9 @@ class TestBudget(unittest.TestCase):
|
||||
|
||||
budget = make_budget(budget_against="Cost Center")
|
||||
|
||||
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
|
||||
frappe.db.set_value(
|
||||
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
|
||||
)
|
||||
|
||||
jv = make_journal_entry(
|
||||
"_Test Account Cost for Goods Sold - _TC",
|
||||
@@ -93,7 +97,9 @@ class TestBudget(unittest.TestCase):
|
||||
)
|
||||
|
||||
fiscal_year = get_fiscal_year(nowdate())[0]
|
||||
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
|
||||
frappe.db.set_value(
|
||||
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
|
||||
)
|
||||
frappe.db.set_value("Budget", budget.name, "fiscal_year", fiscal_year)
|
||||
|
||||
mr = frappe.get_doc(
|
||||
@@ -132,7 +138,9 @@ class TestBudget(unittest.TestCase):
|
||||
)
|
||||
|
||||
fiscal_year = get_fiscal_year(nowdate())[0]
|
||||
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
|
||||
frappe.db.set_value(
|
||||
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
|
||||
)
|
||||
frappe.db.set_value("Budget", budget.name, "fiscal_year", fiscal_year)
|
||||
|
||||
po = create_purchase_order(transaction_date=nowdate(), do_not_submit=True)
|
||||
@@ -150,7 +158,9 @@ class TestBudget(unittest.TestCase):
|
||||
|
||||
budget = make_budget(budget_against="Project")
|
||||
|
||||
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
|
||||
frappe.db.set_value(
|
||||
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
|
||||
)
|
||||
|
||||
project = frappe.get_value("Project", {"project_name": "_Test Project"})
|
||||
|
||||
@@ -213,7 +223,7 @@ class TestBudget(unittest.TestCase):
|
||||
if month > 9:
|
||||
month = 9
|
||||
|
||||
for _i in range(month + 1):
|
||||
for i in range(month + 1):
|
||||
jv = make_journal_entry(
|
||||
"_Test Account Cost for Goods Sold - _TC",
|
||||
"_Test Bank - _TC",
|
||||
@@ -227,7 +237,9 @@ class TestBudget(unittest.TestCase):
|
||||
frappe.db.get_value("GL Entry", {"voucher_type": "Journal Entry", "voucher_no": jv.name})
|
||||
)
|
||||
|
||||
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
|
||||
frappe.db.set_value(
|
||||
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
|
||||
)
|
||||
|
||||
self.assertRaises(BudgetError, jv.cancel)
|
||||
|
||||
@@ -243,7 +255,7 @@ class TestBudget(unittest.TestCase):
|
||||
month = 9
|
||||
|
||||
project = frappe.get_value("Project", {"project_name": "_Test Project"})
|
||||
for _i in range(month + 1):
|
||||
for i in range(month + 1):
|
||||
jv = make_journal_entry(
|
||||
"_Test Account Cost for Goods Sold - _TC",
|
||||
"_Test Bank - _TC",
|
||||
@@ -258,7 +270,9 @@ class TestBudget(unittest.TestCase):
|
||||
frappe.db.get_value("GL Entry", {"voucher_type": "Journal Entry", "voucher_no": jv.name})
|
||||
)
|
||||
|
||||
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
|
||||
frappe.db.set_value(
|
||||
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
|
||||
)
|
||||
|
||||
self.assertRaises(BudgetError, jv.cancel)
|
||||
|
||||
@@ -270,7 +284,9 @@ class TestBudget(unittest.TestCase):
|
||||
set_total_expense_zero(nowdate(), "cost_center", "_Test Cost Center 2 - _TC")
|
||||
|
||||
budget = make_budget(budget_against="Cost Center", cost_center="_Test Company - _TC")
|
||||
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
|
||||
frappe.db.set_value(
|
||||
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
|
||||
)
|
||||
|
||||
jv = make_journal_entry(
|
||||
"_Test Account Cost for Goods Sold - _TC",
|
||||
@@ -300,7 +316,9 @@ class TestBudget(unittest.TestCase):
|
||||
).insert(ignore_permissions=True)
|
||||
|
||||
budget = make_budget(budget_against="Cost Center", cost_center=cost_center)
|
||||
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
|
||||
frappe.db.set_value(
|
||||
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
|
||||
)
|
||||
|
||||
jv = make_journal_entry(
|
||||
"_Test Account Cost for Goods Sold - _TC",
|
||||
@@ -405,11 +423,13 @@ def make_budget(**args):
|
||||
fiscal_year = get_fiscal_year(nowdate())[0]
|
||||
|
||||
if budget_against == "Project":
|
||||
project_name = "{}%".format("_Test Project/" + fiscal_year)
|
||||
project_name = "{0}%".format("_Test Project/" + fiscal_year)
|
||||
budget_list = frappe.get_all("Budget", fields=["name"], filters={"name": ("like", project_name)})
|
||||
else:
|
||||
cost_center_name = "{}%".format(cost_center or "_Test Cost Center - _TC/" + fiscal_year)
|
||||
budget_list = frappe.get_all("Budget", fields=["name"], filters={"name": ("like", cost_center_name)})
|
||||
cost_center_name = "{0}%".format(cost_center or "_Test Cost Center - _TC/" + fiscal_year)
|
||||
budget_list = frappe.get_all(
|
||||
"Budget", fields=["name"], filters={"name": ("like", cost_center_name)}
|
||||
)
|
||||
for d in budget_list:
|
||||
frappe.db.sql("delete from `tabBudget` where name = %(name)s", d)
|
||||
frappe.db.sql("delete from `tabBudget Account` where parent = %(name)s", d)
|
||||
@@ -431,18 +451,24 @@ def make_budget(**args):
|
||||
budget.action_if_annual_budget_exceeded = "Stop"
|
||||
budget.action_if_accumulated_monthly_budget_exceeded = "Ignore"
|
||||
budget.budget_against = budget_against
|
||||
budget.append("accounts", {"account": "_Test Account Cost for Goods Sold - _TC", "budget_amount": 200000})
|
||||
budget.append(
|
||||
"accounts", {"account": "_Test Account Cost for Goods Sold - _TC", "budget_amount": 200000}
|
||||
)
|
||||
|
||||
if args.applicable_on_material_request:
|
||||
budget.applicable_on_material_request = 1
|
||||
budget.action_if_annual_budget_exceeded_on_mr = args.action_if_annual_budget_exceeded_on_mr or "Warn"
|
||||
budget.action_if_annual_budget_exceeded_on_mr = (
|
||||
args.action_if_annual_budget_exceeded_on_mr or "Warn"
|
||||
)
|
||||
budget.action_if_accumulated_monthly_budget_exceeded_on_mr = (
|
||||
args.action_if_accumulated_monthly_budget_exceeded_on_mr or "Warn"
|
||||
)
|
||||
|
||||
if args.applicable_on_purchase_order:
|
||||
budget.applicable_on_purchase_order = 1
|
||||
budget.action_if_annual_budget_exceeded_on_po = args.action_if_annual_budget_exceeded_on_po or "Warn"
|
||||
budget.action_if_annual_budget_exceeded_on_po = (
|
||||
args.action_if_annual_budget_exceeded_on_po or "Warn"
|
||||
)
|
||||
budget.action_if_accumulated_monthly_budget_exceeded_on_po = (
|
||||
args.action_if_accumulated_monthly_budget_exceeded_on_po or "Warn"
|
||||
)
|
||||
|
||||
@@ -1,42 +1,94 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2016-05-16 11:54:09.286135",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"account",
|
||||
"budget_amount"
|
||||
],
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2016-05-16 11:54:09.286135",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "account",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Account",
|
||||
"options": "Account",
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "account",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Account",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Account",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "budget_amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Budget Amount",
|
||||
"options": "Company:company:default_currency",
|
||||
"reqd": 1
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "budget_amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Budget Amount",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-04 15:43:27.016947",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Budget Account",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-01-02 17:02:53.339420",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Budget Account",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_seen": 0
|
||||
}
|
||||
@@ -38,7 +38,9 @@ class ChartofAccountsImporter(Document):
|
||||
|
||||
def validate(self):
|
||||
if self.import_file:
|
||||
get_coa("Chart of Accounts Importer", "All Accounts", file_name=self.import_file, for_validate=1)
|
||||
get_coa(
|
||||
"Chart of Accounts Importer", "All Accounts", file_name=self.import_file, for_validate=1
|
||||
)
|
||||
|
||||
|
||||
def validate_columns(data):
|
||||
@@ -47,11 +49,9 @@ def validate_columns(data):
|
||||
|
||||
no_of_columns = max([len(d) for d in data])
|
||||
|
||||
if no_of_columns != 8:
|
||||
if no_of_columns > 8:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Columns are not according to template. Please compare the uploaded file with standard template"
|
||||
),
|
||||
_("More columns found than expected. Please compare the uploaded file with standard template"),
|
||||
title=(_("Wrong Template")),
|
||||
)
|
||||
|
||||
@@ -116,7 +116,7 @@ def generate_data_from_csv(file_doc, as_dict=False):
|
||||
file_path = file_doc.get_full_path()
|
||||
|
||||
data = []
|
||||
with open(file_path) as in_file:
|
||||
with open(file_path, "r") as in_file:
|
||||
csv_reader = list(csv.reader(in_file))
|
||||
headers = csv_reader[0]
|
||||
del csv_reader[0] # delete top row and headers row
|
||||
@@ -215,10 +215,10 @@ def build_forest(data):
|
||||
for row in data:
|
||||
account_name, parent_account, account_number, parent_account_number = row[0:4]
|
||||
if account_number:
|
||||
account_name = f"{account_number} - {account_name}"
|
||||
account_name = "{} - {}".format(account_number, account_name)
|
||||
if parent_account_number:
|
||||
parent_account_number = cstr(parent_account_number).strip()
|
||||
parent_account = f"{parent_account_number} - {parent_account}"
|
||||
parent_account = "{} - {}".format(parent_account_number, parent_account)
|
||||
|
||||
if parent_account == account_name == child:
|
||||
return [parent_account]
|
||||
@@ -230,7 +230,7 @@ def build_forest(data):
|
||||
frappe.bold(parent_account)
|
||||
)
|
||||
)
|
||||
return [child, *parent_account_list]
|
||||
return [child] + parent_account_list
|
||||
|
||||
charts_map, paths = {}, []
|
||||
|
||||
@@ -250,12 +250,12 @@ def build_forest(data):
|
||||
) = i
|
||||
|
||||
if not account_name:
|
||||
error_messages.append(f"Row {line_no}: Please enter Account Name")
|
||||
error_messages.append("Row {0}: Please enter Account Name".format(line_no))
|
||||
|
||||
name = account_name
|
||||
if account_number:
|
||||
account_number = cstr(account_number).strip()
|
||||
account_name = f"{account_number} - {account_name}"
|
||||
account_name = "{} - {}".format(account_number, account_name)
|
||||
|
||||
charts_map[account_name] = {}
|
||||
charts_map[account_name]["account_name"] = name
|
||||
@@ -352,9 +352,9 @@ def get_template(template_type, company):
|
||||
|
||||
def get_sample_template(writer, company):
|
||||
currency = frappe.db.get_value("Company", company, "default_currency")
|
||||
with open(os.path.join(os.path.dirname(__file__), "coa_sample_template.csv")) as f:
|
||||
with open(os.path.join(os.path.dirname(__file__), "coa_sample_template.csv"), "r") as f:
|
||||
for row in f:
|
||||
row = [*row.strip().split(","), currency]
|
||||
row = row.strip().split(",") + [currency]
|
||||
writer.writerow(row)
|
||||
|
||||
return writer
|
||||
@@ -463,7 +463,7 @@ def unset_existing_data(company):
|
||||
"Purchase Taxes and Charges Template",
|
||||
]:
|
||||
frappe.db.sql(
|
||||
f'''delete from `tab{doctype}` where `company`="%s"''' % (company) # nosec
|
||||
'''delete from `tab{0}` where `company`="%s"'''.format(doctype) % (company) # nosec
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -66,71 +66,71 @@ def create_or_update_cheque_print_format(template_name):
|
||||
|
||||
cheque_print.html = """
|
||||
<style>
|
||||
.print-format {{
|
||||
.print-format {
|
||||
padding: 0px;
|
||||
}}
|
||||
@media screen {{
|
||||
.print-format {{
|
||||
}
|
||||
@media screen {
|
||||
.print-format {
|
||||
padding: 0in;
|
||||
}}
|
||||
}}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div style="position: relative; top:{starting_position_from_top_edge}cm">
|
||||
<div style="width:{cheque_width}cm;height:{cheque_height}cm;">
|
||||
<span style="top:{acc_pay_dist_from_top_edge}cm; left:{acc_pay_dist_from_left_edge}cm;
|
||||
<div style="position: relative; top:%(starting_position_from_top_edge)scm">
|
||||
<div style="width:%(cheque_width)scm;height:%(cheque_height)scm;">
|
||||
<span style="top:%(acc_pay_dist_from_top_edge)scm; left:%(acc_pay_dist_from_left_edge)scm;
|
||||
border-bottom: solid 1px;border-top:solid 1px; width:2cm;text-align: center; position: absolute;">
|
||||
{message_to_show}
|
||||
%(message_to_show)s
|
||||
</span>
|
||||
<span style="top:{date_dist_from_top_edge}cm; left:{date_dist_from_left_edge}cm;
|
||||
<span style="top:%(date_dist_from_top_edge)scm; left:%(date_dist_from_left_edge)scm;
|
||||
position: absolute;">
|
||||
{{{{ frappe.utils.formatdate(doc.reference_date) or '' }}}}
|
||||
{{ frappe.utils.formatdate(doc.reference_date) or '' }}
|
||||
</span>
|
||||
<span style="top:{acc_no_dist_from_top_edge}cm;left:{acc_no_dist_from_left_edge}cm;
|
||||
<span style="top:%(acc_no_dist_from_top_edge)scm;left:%(acc_no_dist_from_left_edge)scm;
|
||||
position: absolute; min-width: 6cm;">
|
||||
{{{{ doc.account_no or '' }}}}
|
||||
{{ doc.account_no or '' }}
|
||||
</span>
|
||||
<span style="top:{payer_name_from_top_edge}cm;left: {payer_name_from_left_edge}cm;
|
||||
<span style="top:%(payer_name_from_top_edge)scm;left: %(payer_name_from_left_edge)scm;
|
||||
position: absolute; min-width: 6cm;">
|
||||
{{{{doc.party_name}}}}
|
||||
{{doc.party_name}}
|
||||
</span>
|
||||
<span style="top:{amt_in_words_from_top_edge}cm; left:{amt_in_words_from_left_edge}cm;
|
||||
position: absolute; display: block; width: {amt_in_word_width}cm;
|
||||
line-height:{amt_in_words_line_spacing}cm; word-wrap: break-word;">
|
||||
{{{{frappe.utils.money_in_words(doc.base_paid_amount or doc.base_received_amount)}}}}
|
||||
<span style="top:%(amt_in_words_from_top_edge)scm; left:%(amt_in_words_from_left_edge)scm;
|
||||
position: absolute; display: block; width: %(amt_in_word_width)scm;
|
||||
line-height:%(amt_in_words_line_spacing)scm; word-wrap: break-word;">
|
||||
{{frappe.utils.money_in_words(doc.base_paid_amount or doc.base_received_amount)}}
|
||||
</span>
|
||||
<span style="top:{amt_in_figures_from_top_edge}cm;left: {amt_in_figures_from_left_edge}cm;
|
||||
<span style="top:%(amt_in_figures_from_top_edge)scm;left: %(amt_in_figures_from_left_edge)scm;
|
||||
position: absolute; min-width: 4cm;">
|
||||
{{{{doc.get_formatted("base_paid_amount") or doc.get_formatted("base_received_amount")}}}}
|
||||
{{doc.get_formatted("base_paid_amount") or doc.get_formatted("base_received_amount")}}
|
||||
</span>
|
||||
<span style="top:{signatory_from_top_edge}cm;left: {signatory_from_left_edge}cm;
|
||||
<span style="top:%(signatory_from_top_edge)scm;left: %(signatory_from_left_edge)scm;
|
||||
position: absolute; min-width: 6cm;">
|
||||
{{{{doc.company}}}}
|
||||
{{doc.company}}
|
||||
</span>
|
||||
</div>
|
||||
</div>""".format(
|
||||
starting_position_from_top_edge=doc.starting_position_from_top_edge
|
||||
</div>""" % {
|
||||
"starting_position_from_top_edge": doc.starting_position_from_top_edge
|
||||
if doc.cheque_size == "A4"
|
||||
else 0.0,
|
||||
cheque_width=doc.cheque_width,
|
||||
cheque_height=doc.cheque_height,
|
||||
acc_pay_dist_from_top_edge=doc.acc_pay_dist_from_top_edge,
|
||||
acc_pay_dist_from_left_edge=doc.acc_pay_dist_from_left_edge,
|
||||
message_to_show=doc.message_to_show if doc.message_to_show else _("Account Pay Only"),
|
||||
date_dist_from_top_edge=doc.date_dist_from_top_edge,
|
||||
date_dist_from_left_edge=doc.date_dist_from_left_edge,
|
||||
acc_no_dist_from_top_edge=doc.acc_no_dist_from_top_edge,
|
||||
acc_no_dist_from_left_edge=doc.acc_no_dist_from_left_edge,
|
||||
payer_name_from_top_edge=doc.payer_name_from_top_edge,
|
||||
payer_name_from_left_edge=doc.payer_name_from_left_edge,
|
||||
amt_in_words_from_top_edge=doc.amt_in_words_from_top_edge,
|
||||
amt_in_words_from_left_edge=doc.amt_in_words_from_left_edge,
|
||||
amt_in_word_width=doc.amt_in_word_width,
|
||||
amt_in_words_line_spacing=doc.amt_in_words_line_spacing,
|
||||
amt_in_figures_from_top_edge=doc.amt_in_figures_from_top_edge,
|
||||
amt_in_figures_from_left_edge=doc.amt_in_figures_from_left_edge,
|
||||
signatory_from_top_edge=doc.signatory_from_top_edge,
|
||||
signatory_from_left_edge=doc.signatory_from_left_edge,
|
||||
)
|
||||
"cheque_width": doc.cheque_width,
|
||||
"cheque_height": doc.cheque_height,
|
||||
"acc_pay_dist_from_top_edge": doc.acc_pay_dist_from_top_edge,
|
||||
"acc_pay_dist_from_left_edge": doc.acc_pay_dist_from_left_edge,
|
||||
"message_to_show": doc.message_to_show if doc.message_to_show else _("Account Pay Only"),
|
||||
"date_dist_from_top_edge": doc.date_dist_from_top_edge,
|
||||
"date_dist_from_left_edge": doc.date_dist_from_left_edge,
|
||||
"acc_no_dist_from_top_edge": doc.acc_no_dist_from_top_edge,
|
||||
"acc_no_dist_from_left_edge": doc.acc_no_dist_from_left_edge,
|
||||
"payer_name_from_top_edge": doc.payer_name_from_top_edge,
|
||||
"payer_name_from_left_edge": doc.payer_name_from_left_edge,
|
||||
"amt_in_words_from_top_edge": doc.amt_in_words_from_top_edge,
|
||||
"amt_in_words_from_left_edge": doc.amt_in_words_from_left_edge,
|
||||
"amt_in_word_width": doc.amt_in_word_width,
|
||||
"amt_in_words_line_spacing": doc.amt_in_words_line_spacing,
|
||||
"amt_in_figures_from_top_edge": doc.amt_in_figures_from_top_edge,
|
||||
"amt_in_figures_from_left_edge": doc.amt_in_figures_from_left_edge,
|
||||
"signatory_from_top_edge": doc.signatory_from_top_edge,
|
||||
"signatory_from_left_edge": doc.signatory_from_left_edge,
|
||||
}
|
||||
|
||||
cheque_print.save(ignore_permissions=True)
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@
|
||||
"idx": 1,
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
"modified": "2024-04-24 10:55:54.083042",
|
||||
"modified": "2022-01-31 13:22:58.916273",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Cost Center",
|
||||
@@ -163,15 +163,6 @@
|
||||
{
|
||||
"read": 1,
|
||||
"role": "Purchase User"
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"report": 1,
|
||||
"role": "Employee",
|
||||
"select": 1,
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"search_fields": "parent_cost_center, is_group",
|
||||
|
||||
@@ -34,7 +34,9 @@ class CostCenter(NestedSet):
|
||||
def autoname(self):
|
||||
from erpnext.accounts.utils import get_autoname_with_number
|
||||
|
||||
self.name = get_autoname_with_number(self.cost_center_number, self.cost_center_name, self.company)
|
||||
self.name = get_autoname_with_number(
|
||||
self.cost_center_number, self.cost_center_name, self.company
|
||||
)
|
||||
|
||||
def validate(self):
|
||||
self.validate_mandatory()
|
||||
@@ -107,14 +109,14 @@ class CostCenter(NestedSet):
|
||||
new_cost_center = get_name_with_abbr(newdn, self.company)
|
||||
|
||||
# Validate properties before merging
|
||||
super().before_rename(olddn, new_cost_center, merge, "is_group")
|
||||
super(CostCenter, self).before_rename(olddn, new_cost_center, merge, "is_group")
|
||||
if not merge:
|
||||
new_cost_center = get_name_with_number(new_cost_center, self.cost_center_number)
|
||||
|
||||
return new_cost_center
|
||||
|
||||
def after_rename(self, olddn, newdn, merge=False):
|
||||
super().after_rename(olddn, newdn, merge)
|
||||
super(CostCenter, self).after_rename(olddn, newdn, merge)
|
||||
|
||||
if not merge:
|
||||
new_cost_center = frappe.db.get_value(
|
||||
|
||||
@@ -10,6 +10,7 @@ test_records = frappe.get_test_records("Cost Center")
|
||||
|
||||
class TestCostCenter(unittest.TestCase):
|
||||
def test_cost_center_creation_against_child_node(self):
|
||||
|
||||
if not frappe.db.get_value("Cost Center", {"name": "_Test Cost Center 2 - _TC"}):
|
||||
frappe.get_doc(test_records[1]).insert()
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import add_days, flt, format_date, getdate
|
||||
from frappe.utils import add_days, format_date, getdate
|
||||
|
||||
|
||||
class MainCostCenterCantBeChild(frappe.ValidationError):
|
||||
@@ -48,7 +48,7 @@ class CostCenterAllocation(Document):
|
||||
# end: auto-generated types
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
super(CostCenterAllocation, self).__init__(*args, **kwargs)
|
||||
self._skip_from_date_validation = False
|
||||
|
||||
def validate(self):
|
||||
@@ -60,10 +60,12 @@ class CostCenterAllocation(Document):
|
||||
self.validate_child_cost_centers()
|
||||
|
||||
def validate_total_allocation_percentage(self):
|
||||
total_percentage = sum([flt(d.percentage) for d in self.get("allocation_percentages", [])])
|
||||
total_percentage = sum([d.percentage for d in self.get("allocation_percentages", [])])
|
||||
|
||||
if total_percentage != 100:
|
||||
frappe.throw(_("Total percentage against cost centers should be 100"), WrongPercentageAllocation)
|
||||
frappe.throw(
|
||||
_("Total percentage against cost centers should be 100"), WrongPercentageAllocation
|
||||
)
|
||||
|
||||
def validate_from_date_based_on_existing_gle(self):
|
||||
# Check if GLE exists against the main cost center
|
||||
|
||||
@@ -22,10 +22,8 @@ class TestCostCenterAllocation(unittest.TestCase):
|
||||
cost_centers = [
|
||||
"Main Cost Center 1",
|
||||
"Main Cost Center 2",
|
||||
"Main Cost Center 3",
|
||||
"Sub Cost Center 1",
|
||||
"Sub Cost Center 2",
|
||||
"Sub Cost Center 3",
|
||||
]
|
||||
for cc in cost_centers:
|
||||
create_cost_center(cost_center_name=cc, company="_Test Company")
|
||||
@@ -38,7 +36,7 @@ class TestCostCenterAllocation(unittest.TestCase):
|
||||
)
|
||||
|
||||
jv = make_journal_entry(
|
||||
"Cash - _TC", "Sales - _TC", 100, cost_center="Main Cost Center 1 - _TC", submit=True
|
||||
"_Test Cash - _TC", "Sales - _TC", 100, cost_center="Main Cost Center 1 - _TC", submit=True
|
||||
)
|
||||
|
||||
expected_values = [["Sub Cost Center 1 - _TC", 0.0, 60], ["Sub Cost Center 2 - _TC", 0.0, 40]]
|
||||
@@ -122,7 +120,7 @@ class TestCostCenterAllocation(unittest.TestCase):
|
||||
def test_valid_from_based_on_existing_gle(self):
|
||||
# GLE posted against Sub Cost Center 1 on today
|
||||
jv = make_journal_entry(
|
||||
"Cash - _TC",
|
||||
"_Test Cash - _TC",
|
||||
"Sales - _TC",
|
||||
100,
|
||||
cost_center="Main Cost Center 1 - _TC",
|
||||
@@ -143,53 +141,6 @@ class TestCostCenterAllocation(unittest.TestCase):
|
||||
|
||||
jv.cancel()
|
||||
|
||||
def test_multiple_cost_center_allocation_on_same_main_cost_center(self):
|
||||
coa1 = create_cost_center_allocation(
|
||||
"_Test Company",
|
||||
"Main Cost Center 3 - _TC",
|
||||
{"Sub Cost Center 1 - _TC": 30, "Sub Cost Center 2 - _TC": 30, "Sub Cost Center 3 - _TC": 40},
|
||||
valid_from=add_days(today(), -5),
|
||||
)
|
||||
|
||||
coa2 = create_cost_center_allocation(
|
||||
"_Test Company",
|
||||
"Main Cost Center 3 - _TC",
|
||||
{"Sub Cost Center 1 - _TC": 50, "Sub Cost Center 2 - _TC": 50},
|
||||
valid_from=add_days(today(), -1),
|
||||
)
|
||||
|
||||
jv = make_journal_entry(
|
||||
"Cash - _TC",
|
||||
"Sales - _TC",
|
||||
100,
|
||||
cost_center="Main Cost Center 3 - _TC",
|
||||
posting_date=today(),
|
||||
submit=True,
|
||||
)
|
||||
|
||||
expected_values = {"Sub Cost Center 1 - _TC": 50, "Sub Cost Center 2 - _TC": 50}
|
||||
|
||||
gle = frappe.qb.DocType("GL Entry")
|
||||
gl_entries = (
|
||||
frappe.qb.from_(gle)
|
||||
.select(gle.cost_center, gle.debit, gle.credit)
|
||||
.where(gle.voucher_type == "Journal Entry")
|
||||
.where(gle.voucher_no == jv.name)
|
||||
.where(gle.account == "Sales - _TC")
|
||||
.orderby(gle.cost_center)
|
||||
).run(as_dict=1)
|
||||
|
||||
self.assertTrue(gl_entries)
|
||||
|
||||
for gle in gl_entries:
|
||||
self.assertTrue(gle.cost_center in expected_values)
|
||||
self.assertEqual(gle.debit, 0)
|
||||
self.assertEqual(gle.credit, expected_values[gle.cost_center])
|
||||
|
||||
coa1.cancel()
|
||||
coa2.cancel()
|
||||
jv.cancel()
|
||||
|
||||
|
||||
def create_cost_center_allocation(
|
||||
company,
|
||||
|
||||
@@ -3,36 +3,22 @@
|
||||
|
||||
frappe.ui.form.on("Currency Exchange Settings", {
|
||||
service_provider: function (frm) {
|
||||
frm.call({
|
||||
method: "erpnext.accounts.doctype.currency_exchange_settings.currency_exchange_settings.get_api_endpoint",
|
||||
args: {
|
||||
service_provider: frm.doc.service_provider,
|
||||
use_http: frm.doc.use_http,
|
||||
},
|
||||
callback: function (r) {
|
||||
if (r && r.message) {
|
||||
if (frm.doc.service_provider == "exchangerate.host") {
|
||||
let result = ["result"];
|
||||
let params = {
|
||||
date: "{transaction_date}",
|
||||
from: "{from_currency}",
|
||||
to: "{to_currency}",
|
||||
};
|
||||
add_param(frm, r.message, params, result);
|
||||
} else if (frm.doc.service_provider == "frankfurter.app") {
|
||||
let result = ["rates", "{to_currency}"];
|
||||
let params = {
|
||||
base: "{from_currency}",
|
||||
symbols: "{to_currency}",
|
||||
};
|
||||
add_param(frm, r.message, params, result);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
use_http: function (frm) {
|
||||
frm.trigger("service_provider");
|
||||
if (frm.doc.service_provider == "exchangerate.host") {
|
||||
let result = ["result"];
|
||||
let params = {
|
||||
date: "{transaction_date}",
|
||||
from: "{from_currency}",
|
||||
to: "{to_currency}",
|
||||
};
|
||||
add_param(frm, "https://api.exchangerate.host/convert", params, result);
|
||||
} else if (frm.doc.service_provider == "frankfurter.app") {
|
||||
let result = ["rates", "{to_currency}"];
|
||||
let params = {
|
||||
base: "{from_currency}",
|
||||
symbols: "{to_currency}",
|
||||
};
|
||||
add_param(frm, "https://frankfurter.app/{transaction_date}", params, result);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
"disabled",
|
||||
"service_provider",
|
||||
"api_endpoint",
|
||||
"use_http",
|
||||
"access_key",
|
||||
"url",
|
||||
"column_break_3",
|
||||
@@ -92,19 +91,12 @@
|
||||
"fieldname": "access_key",
|
||||
"fieldtype": "Data",
|
||||
"label": "Access Key"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval: doc.service_provider != \"Custom\"",
|
||||
"fieldname": "use_http",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use HTTP Protocol"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-18 08:32:26.895076",
|
||||
"modified": "2023-10-04 15:30:25.333860",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Currency Exchange Settings",
|
||||
|
||||
@@ -31,7 +31,6 @@ class CurrencyExchangeSettings(Document):
|
||||
result_key: DF.Table[CurrencyExchangeSettingsResult]
|
||||
service_provider: DF.Literal["frankfurter.app", "exchangerate.host", "Custom"]
|
||||
url: DF.Data | None
|
||||
use_http: DF.Check
|
||||
# end: auto-generated types
|
||||
|
||||
def validate(self):
|
||||
@@ -43,6 +42,7 @@ class CurrencyExchangeSettings(Document):
|
||||
|
||||
def set_parameters_and_result(self):
|
||||
if self.service_provider == "exchangerate.host":
|
||||
|
||||
if not self.access_key:
|
||||
frappe.throw(
|
||||
_("Access Key is required for Service Provider: {0}").format(
|
||||
@@ -53,7 +53,7 @@ class CurrencyExchangeSettings(Document):
|
||||
self.set("result_key", [])
|
||||
self.set("req_params", [])
|
||||
|
||||
self.api_endpoint = get_api_endpoint(self.service_provider, self.use_http)
|
||||
self.api_endpoint = "https://api.exchangerate.host/convert"
|
||||
self.append("result_key", {"key": "result"})
|
||||
self.append("req_params", {"key": "access_key", "value": self.access_key})
|
||||
self.append("req_params", {"key": "amount", "value": "1"})
|
||||
@@ -64,7 +64,7 @@ class CurrencyExchangeSettings(Document):
|
||||
self.set("result_key", [])
|
||||
self.set("req_params", [])
|
||||
|
||||
self.api_endpoint = get_api_endpoint(self.service_provider, self.use_http)
|
||||
self.api_endpoint = "https://frankfurter.app/{transaction_date}"
|
||||
self.append("result_key", {"key": "rates"})
|
||||
self.append("result_key", {"key": "{to_currency}"})
|
||||
self.append("req_params", {"key": "base", "value": "{from_currency}"})
|
||||
@@ -77,7 +77,9 @@ class CurrencyExchangeSettings(Document):
|
||||
transaction_date=nowdate(), to_currency="INR", from_currency="USD"
|
||||
)
|
||||
|
||||
api_url = self.api_endpoint.format(transaction_date=nowdate(), to_currency="INR", from_currency="USD")
|
||||
api_url = self.api_endpoint.format(
|
||||
transaction_date=nowdate(), to_currency="INR", from_currency="USD"
|
||||
)
|
||||
|
||||
try:
|
||||
response = requests.get(api_url, params=params)
|
||||
@@ -97,23 +99,7 @@ class CurrencyExchangeSettings(Document):
|
||||
]
|
||||
except Exception:
|
||||
frappe.throw(_("Invalid result key. Response:") + " " + response.text)
|
||||
if not isinstance(value, int | float):
|
||||
if not isinstance(value, (int, float)):
|
||||
frappe.throw(_("Returned exchange rate is neither integer not float."))
|
||||
|
||||
self.url = response.url
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_api_endpoint(service_provider: str | None = None, use_http: bool = False):
|
||||
if service_provider and service_provider in ["exchangerate.host", "frankfurter.app"]:
|
||||
if service_provider == "exchangerate.host":
|
||||
api = "api.exchangerate.host/convert"
|
||||
elif service_provider == "frankfurter.app":
|
||||
api = "api.frankfurter.app/{transaction_date}"
|
||||
|
||||
protocol = "https://"
|
||||
if use_http:
|
||||
protocol = "http://"
|
||||
|
||||
return protocol + api
|
||||
return None
|
||||
|
||||
@@ -19,6 +19,16 @@
|
||||
"currency",
|
||||
"column_break_11",
|
||||
"conversion_rate",
|
||||
"address_and_contact_section",
|
||||
"customer_address",
|
||||
"address_display",
|
||||
"contact_person",
|
||||
"contact_display",
|
||||
"column_break_16",
|
||||
"company_address",
|
||||
"company_address_display",
|
||||
"contact_mobile",
|
||||
"contact_email",
|
||||
"section_break_6",
|
||||
"dunning_type",
|
||||
"column_break_8",
|
||||
@@ -46,21 +56,7 @@
|
||||
"income_account",
|
||||
"column_break_48",
|
||||
"cost_center",
|
||||
"amended_from",
|
||||
"address_and_contact_tab",
|
||||
"address_and_contact_section",
|
||||
"customer_address",
|
||||
"address_display",
|
||||
"column_break_vodj",
|
||||
"contact_person",
|
||||
"contact_display",
|
||||
"contact_mobile",
|
||||
"contact_email",
|
||||
"section_break_xban",
|
||||
"column_break_16",
|
||||
"company_address",
|
||||
"company_address_display",
|
||||
"column_break_lqmf"
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -182,8 +178,10 @@
|
||||
"label": "Rate of Interest (%) Yearly"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "address_and_contact_section",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Address and Contact"
|
||||
},
|
||||
{
|
||||
"fieldname": "address_display",
|
||||
@@ -379,28 +377,11 @@
|
||||
{
|
||||
"fieldname": "column_break_48",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "address_and_contact_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Address & Contact"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_vodj",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_xban",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_lqmf",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-11-26 13:46:07.760867",
|
||||
"modified": "2023-06-15 15:46:53.865712",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Dunning",
|
||||
@@ -454,4 +435,4 @@
|
||||
"states": [],
|
||||
"title_field": "customer_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -139,22 +139,6 @@ class Dunning(AccountsController):
|
||||
)
|
||||
row.dunning_level = len(past_dunnings) + 1
|
||||
|
||||
def on_cancel(self):
|
||||
super().on_cancel()
|
||||
self.ignore_linked_doctypes = [
|
||||
"GL Entry",
|
||||
"Stock Ledger Entry",
|
||||
"Repost Item Valuation",
|
||||
"Repost Payment Ledger",
|
||||
"Repost Payment Ledger Items",
|
||||
"Repost Accounting Ledger",
|
||||
"Repost Accounting Ledger Items",
|
||||
"Unreconcile Payment",
|
||||
"Unreconcile Payment Entries",
|
||||
"Payment Ledger Entry",
|
||||
"Serial and Batch Bundle",
|
||||
]
|
||||
|
||||
|
||||
def resolve_dunning(doc, state):
|
||||
"""
|
||||
@@ -210,31 +194,19 @@ def get_linked_dunnings_as_per_state(sales_invoice, state):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_dunning_letter_text(dunning_type: str, doc: str | dict, language: str | None = None) -> dict:
|
||||
DOCTYPE = "Dunning Letter Text"
|
||||
FIELDS = ["body_text", "closing_text", "language"]
|
||||
|
||||
def get_dunning_letter_text(dunning_type, doc, language=None):
|
||||
if isinstance(doc, str):
|
||||
doc = json.loads(doc)
|
||||
|
||||
if not language:
|
||||
language = doc.get("language")
|
||||
|
||||
if language:
|
||||
letter_text = frappe.db.get_value(
|
||||
DOCTYPE, {"parent": dunning_type, "language": language}, FIELDS, as_dict=1
|
||||
)
|
||||
|
||||
if not letter_text:
|
||||
letter_text = frappe.db.get_value(
|
||||
DOCTYPE, {"parent": dunning_type, "is_default_language": 1}, FIELDS, as_dict=1
|
||||
)
|
||||
|
||||
if not letter_text:
|
||||
return {}
|
||||
|
||||
return {
|
||||
"body_text": frappe.render_template(letter_text.body_text, doc),
|
||||
"closing_text": frappe.render_template(letter_text.closing_text, doc),
|
||||
"language": letter_text.language,
|
||||
}
|
||||
filters = {"parent": dunning_type, "language": language}
|
||||
else:
|
||||
filters = {"parent": dunning_type, "is_default_language": 1}
|
||||
letter_text = frappe.db.get_value(
|
||||
"Dunning Letter Text", filters, ["body_text", "closing_text", "language"], as_dict=1
|
||||
)
|
||||
if letter_text:
|
||||
return {
|
||||
"body_text": frappe.render_template(letter_text.body_text, doc),
|
||||
"closing_text": frappe.render_template(letter_text.closing_text, doc),
|
||||
"language": letter_text.language,
|
||||
}
|
||||
|
||||
@@ -109,7 +109,9 @@ class TestDunning(FrappeTestCase):
|
||||
|
||||
def create_dunning(overdue_days, dunning_type_name=None):
|
||||
posting_date = add_days(today(), -1 * overdue_days)
|
||||
sales_invoice = create_sales_invoice_against_cost_center(posting_date=posting_date, qty=1, rate=100)
|
||||
sales_invoice = create_sales_invoice_against_cost_center(
|
||||
posting_date=posting_date, qty=1, rate=100
|
||||
)
|
||||
dunning = create_dunning_from_sales_invoice(sales_invoice.name)
|
||||
|
||||
if dunning_type_name:
|
||||
|
||||
@@ -85,16 +85,18 @@ frappe.ui.form.on("Exchange Rate Revaluation", {
|
||||
},
|
||||
|
||||
make_jv: function (frm) {
|
||||
let revaluation_journal = null;
|
||||
let zero_balance_journal = null;
|
||||
frappe.call({
|
||||
method: "make_jv_entries",
|
||||
doc: frm.doc,
|
||||
freeze: true,
|
||||
freeze_message: __("Creating Journal Entries..."),
|
||||
freeze_message: "Making Journal Entries...",
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
let response = r.message;
|
||||
if (response["revaluation_jv"] || response["zero_balance_jv"]) {
|
||||
frappe.msgprint(__("Journal entries have been created"));
|
||||
frappe.msgprint(__("Journals have been created"));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -74,21 +74,6 @@ class ExchangeRateRevaluation(Document):
|
||||
if not (self.company and self.posting_date):
|
||||
frappe.throw(_("Please select Company and Posting Date to getting entries"))
|
||||
|
||||
def before_submit(self):
|
||||
self.remove_accounts_without_gain_loss()
|
||||
|
||||
def remove_accounts_without_gain_loss(self):
|
||||
self.accounts = [account for account in self.accounts if account.gain_loss]
|
||||
|
||||
if not self.accounts:
|
||||
frappe.throw(_("At least one account with exchange gain or loss is required"))
|
||||
|
||||
frappe.msgprint(
|
||||
_("Removing rows without exchange gain or loss"),
|
||||
alert=True,
|
||||
indicator="yellow",
|
||||
)
|
||||
|
||||
def on_cancel(self):
|
||||
self.ignore_linked_doctypes = "GL Entry"
|
||||
|
||||
@@ -263,26 +248,27 @@ class ExchangeRateRevaluation(Document):
|
||||
new_exchange_rate = get_exchange_rate(d.account_currency, company_currency, posting_date)
|
||||
new_balance_in_base_currency = flt(d.balance_in_account_currency * new_exchange_rate)
|
||||
gain_loss = flt(new_balance_in_base_currency, precision) - flt(d.balance, precision)
|
||||
|
||||
accounts.append(
|
||||
{
|
||||
"account": d.account,
|
||||
"party_type": d.party_type,
|
||||
"party": d.party,
|
||||
"account_currency": d.account_currency,
|
||||
"balance_in_base_currency": d.balance,
|
||||
"balance_in_account_currency": d.balance_in_account_currency,
|
||||
"zero_balance": d.zero_balance,
|
||||
"current_exchange_rate": current_exchange_rate,
|
||||
"new_exchange_rate": new_exchange_rate,
|
||||
"new_balance_in_base_currency": new_balance_in_base_currency,
|
||||
"new_balance_in_account_currency": d.balance_in_account_currency,
|
||||
"gain_loss": gain_loss,
|
||||
}
|
||||
)
|
||||
if gain_loss:
|
||||
accounts.append(
|
||||
{
|
||||
"account": d.account,
|
||||
"party_type": d.party_type,
|
||||
"party": d.party,
|
||||
"account_currency": d.account_currency,
|
||||
"balance_in_base_currency": d.balance,
|
||||
"balance_in_account_currency": d.balance_in_account_currency,
|
||||
"zero_balance": d.zero_balance,
|
||||
"current_exchange_rate": current_exchange_rate,
|
||||
"new_exchange_rate": new_exchange_rate,
|
||||
"new_balance_in_base_currency": new_balance_in_base_currency,
|
||||
"new_balance_in_account_currency": d.balance_in_account_currency,
|
||||
"gain_loss": gain_loss,
|
||||
}
|
||||
)
|
||||
|
||||
# Handle Accounts with '0' balance in Account/Base Currency
|
||||
for d in [x for x in account_details if x.zero_balance]:
|
||||
|
||||
if d.balance != 0:
|
||||
current_exchange_rate = new_exchange_rate = 0
|
||||
|
||||
@@ -295,30 +281,30 @@ class ExchangeRateRevaluation(Document):
|
||||
new_balance_in_account_currency = 0
|
||||
|
||||
current_exchange_rate = (
|
||||
calculate_exchange_rate_using_last_gle(company, d.account, d.party_type, d.party)
|
||||
or 0.0
|
||||
calculate_exchange_rate_using_last_gle(company, d.account, d.party_type, d.party) or 0.0
|
||||
)
|
||||
|
||||
gain_loss = new_balance_in_account_currency - (
|
||||
current_exchange_rate * d.balance_in_account_currency
|
||||
)
|
||||
|
||||
accounts.append(
|
||||
{
|
||||
"account": d.account,
|
||||
"party_type": d.party_type,
|
||||
"party": d.party,
|
||||
"account_currency": d.account_currency,
|
||||
"balance_in_base_currency": d.balance,
|
||||
"balance_in_account_currency": d.balance_in_account_currency,
|
||||
"zero_balance": d.zero_balance,
|
||||
"current_exchange_rate": current_exchange_rate,
|
||||
"new_exchange_rate": new_exchange_rate,
|
||||
"new_balance_in_base_currency": new_balance_in_base_currency,
|
||||
"new_balance_in_account_currency": new_balance_in_account_currency,
|
||||
"gain_loss": gain_loss,
|
||||
}
|
||||
)
|
||||
if gain_loss:
|
||||
accounts.append(
|
||||
{
|
||||
"account": d.account,
|
||||
"party_type": d.party_type,
|
||||
"party": d.party,
|
||||
"account_currency": d.account_currency,
|
||||
"balance_in_base_currency": d.balance,
|
||||
"balance_in_account_currency": d.balance_in_account_currency,
|
||||
"zero_balance": d.zero_balance,
|
||||
"current_exchange_rate": current_exchange_rate,
|
||||
"new_exchange_rate": new_exchange_rate,
|
||||
"new_balance_in_base_currency": new_balance_in_base_currency,
|
||||
"new_balance_in_account_currency": new_balance_in_account_currency,
|
||||
"gain_loss": gain_loss,
|
||||
}
|
||||
)
|
||||
|
||||
return accounts
|
||||
|
||||
@@ -349,7 +335,9 @@ class ExchangeRateRevaluation(Document):
|
||||
|
||||
revaluation_jv = self.make_jv_for_revaluation()
|
||||
if revaluation_jv:
|
||||
frappe.msgprint(f"Revaluation Journal: {get_link_to_form('Journal Entry', revaluation_jv.name)}")
|
||||
frappe.msgprint(
|
||||
f"Revaluation Journal: {get_link_to_form('Journal Entry', revaluation_jv.name)}"
|
||||
)
|
||||
|
||||
return {
|
||||
"revaluation_jv": revaluation_jv.name if revaluation_jv else None,
|
||||
@@ -406,8 +394,7 @@ class ExchangeRateRevaluation(Document):
|
||||
journal_account.update(
|
||||
{
|
||||
dr_or_cr: flt(
|
||||
abs(d.get("balance_in_account_currency")),
|
||||
d.precision("balance_in_account_currency"),
|
||||
abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")
|
||||
),
|
||||
reverse_dr_or_cr: 0,
|
||||
"debit": 0,
|
||||
@@ -533,9 +520,7 @@ class ExchangeRateRevaluation(Document):
|
||||
abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")
|
||||
),
|
||||
"cost_center": erpnext.get_default_cost_center(self.company),
|
||||
"exchange_rate": flt(
|
||||
d.get("current_exchange_rate"), d.precision("current_exchange_rate")
|
||||
),
|
||||
"exchange_rate": flt(d.get("current_exchange_rate"), d.precision("current_exchange_rate")),
|
||||
"reference_type": "Exchange Rate Revaluation",
|
||||
"reference_name": self.name,
|
||||
}
|
||||
@@ -613,7 +598,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: float | None = 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"))
|
||||
@@ -626,7 +611,7 @@ def get_account_details(
|
||||
frappe.throw(_("Party Type and Party is mandatory for {0} account").format(account_type))
|
||||
|
||||
account_details = {}
|
||||
erpnext.get_company_currency(company)
|
||||
company_currency = erpnext.get_company_currency(company)
|
||||
|
||||
account_details = {
|
||||
"account_currency": account_currency,
|
||||
@@ -640,22 +625,24 @@ def get_account_details(
|
||||
rounding_loss_allowance=rounding_loss_allowance,
|
||||
)
|
||||
|
||||
if account_balance and (account_balance[0].balance or account_balance[0].balance_in_account_currency):
|
||||
if account_with_new_balance := ExchangeRateRevaluation.calculate_new_account_balance(
|
||||
if account_balance and (
|
||||
account_balance[0].balance or account_balance[0].balance_in_account_currency
|
||||
):
|
||||
account_with_new_balance = ExchangeRateRevaluation.calculate_new_account_balance(
|
||||
company, posting_date, account_balance
|
||||
):
|
||||
row = account_with_new_balance[0]
|
||||
account_details.update(
|
||||
{
|
||||
"balance_in_base_currency": row["balance_in_base_currency"],
|
||||
"balance_in_account_currency": row["balance_in_account_currency"],
|
||||
"current_exchange_rate": row["current_exchange_rate"],
|
||||
"new_exchange_rate": row["new_exchange_rate"],
|
||||
"new_balance_in_base_currency": row["new_balance_in_base_currency"],
|
||||
"new_balance_in_account_currency": row["new_balance_in_account_currency"],
|
||||
"zero_balance": row["zero_balance"],
|
||||
"gain_loss": row["gain_loss"],
|
||||
}
|
||||
)
|
||||
)
|
||||
row = account_with_new_balance[0]
|
||||
account_details.update(
|
||||
{
|
||||
"balance_in_base_currency": row["balance_in_base_currency"],
|
||||
"balance_in_account_currency": row["balance_in_account_currency"],
|
||||
"current_exchange_rate": row["current_exchange_rate"],
|
||||
"new_exchange_rate": row["new_exchange_rate"],
|
||||
"new_balance_in_base_currency": row["new_balance_in_base_currency"],
|
||||
"new_balance_in_account_currency": row["new_balance_in_account_currency"],
|
||||
"zero_balance": row["zero_balance"],
|
||||
"gain_loss": row["gain_loss"],
|
||||
}
|
||||
)
|
||||
|
||||
return account_details
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe import qb
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.utils import add_days, flt, today
|
||||
|
||||
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.accounts.test.accounts_mixin import AccountsTestMixin
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
|
||||
|
||||
class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase):
|
||||
@@ -66,7 +73,9 @@ class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase):
|
||||
err.extend("accounts", accounts)
|
||||
row = err.accounts[0]
|
||||
row.new_exchange_rate = 85
|
||||
row.new_balance_in_base_currency = flt(row.new_exchange_rate * flt(row.balance_in_account_currency))
|
||||
row.new_balance_in_base_currency = flt(
|
||||
row.new_exchange_rate * flt(row.balance_in_account_currency)
|
||||
)
|
||||
row.gain_loss = row.new_balance_in_base_currency - flt(row.balance_in_base_currency)
|
||||
err.set_total_gain_loss()
|
||||
err = err.save().submit()
|
||||
@@ -118,9 +127,9 @@ class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase):
|
||||
pe.save().submit()
|
||||
|
||||
# Cancel the auto created gain/loss JE to simulate balance only in base currency
|
||||
je = frappe.db.get_all("Journal Entry Account", filters={"reference_name": si.name}, pluck="parent")[
|
||||
0
|
||||
]
|
||||
je = frappe.db.get_all(
|
||||
"Journal Entry Account", filters={"reference_name": si.name}, pluck="parent"
|
||||
)[0]
|
||||
frappe.get_doc("Journal Entry", je).cancel()
|
||||
|
||||
err = frappe.new_doc("Exchange Rate Revaluation")
|
||||
@@ -188,7 +197,7 @@ class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase):
|
||||
|
||||
pe = get_payment_entry(si.doctype, si.name)
|
||||
pe.paid_amount = 95
|
||||
pe.source_exchange_rate = 84.2105
|
||||
pe.source_exchange_rate = 84.211
|
||||
pe.received_amount = 8000
|
||||
pe.references = []
|
||||
pe.save().submit()
|
||||
@@ -226,10 +235,10 @@ class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase):
|
||||
self.assertEqual(flt(acc.debit, precision), 0.0)
|
||||
self.assertEqual(flt(acc.credit, precision), 0.0)
|
||||
|
||||
row = next(x for x in je.accounts if x.account == self.debtors_usd)
|
||||
row = [x for x in je.accounts if x.account == self.debtors_usd][0]
|
||||
self.assertEqual(flt(row.credit_in_account_currency, precision), 5.0) # in USD
|
||||
row = next(x for x in je.accounts if x.account != self.debtors_usd)
|
||||
self.assertEqual(flt(row.debit_in_account_currency, precision), 421.05) # in INR
|
||||
row = [x for x in je.accounts if x.account != self.debtors_usd][0]
|
||||
self.assertEqual(flt(row.debit_in_account_currency, precision), 421.06) # in INR
|
||||
|
||||
# total_debit and total_credit will be 0.0, as JV is posting only to account currency fields
|
||||
self.assertEqual(flt(je.total_debit, precision), 0.0)
|
||||
@@ -285,5 +294,5 @@ class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase):
|
||||
"new_balance_in_account_currency": 100.0,
|
||||
}
|
||||
|
||||
for key, _val in expected_data.items():
|
||||
for key, val in expected_data.items():
|
||||
self.assertEqual(expected_data.get(key), account_details.get(key))
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
frappe.ui.form.on("Fiscal Year", {
|
||||
onload: function (frm) {
|
||||
if (frm.doc.__islocal) {
|
||||
frm.set_value("year_start_date", frappe.datetime.year_start());
|
||||
frm.set_value(
|
||||
"year_start_date",
|
||||
frappe.datetime.add_days(frappe.defaults.get_default("year_end_date"), 1)
|
||||
);
|
||||
}
|
||||
},
|
||||
year_start_date: function (frm) {
|
||||
|
||||
@@ -72,22 +72,22 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "More/Less than 12 months.",
|
||||
"description": "Less than 12 months.",
|
||||
"fieldname": "is_short_year",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Short/Long Year",
|
||||
"label": "Is Short Year",
|
||||
"set_only_once": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-calendar",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2024-05-27 17:29:55.560840",
|
||||
"modified": "2024-01-30 12:35:38.645968",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Fiscal Year",
|
||||
"naming_rule": "By fieldname",
|
||||
"owner": "Administrator",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
@@ -119,18 +119,6 @@
|
||||
{
|
||||
"read": 1,
|
||||
"role": "Employee"
|
||||
},
|
||||
{
|
||||
"read": 1,
|
||||
"role": "Accounts Manager"
|
||||
},
|
||||
{
|
||||
"read": 1,
|
||||
"role": "Stock Manager"
|
||||
},
|
||||
{
|
||||
"read": 1,
|
||||
"role": "Auditor"
|
||||
}
|
||||
],
|
||||
"show_name_in_global_search": 1,
|
||||
|
||||
@@ -108,9 +108,9 @@ class FiscalYear(Document):
|
||||
|
||||
if overlap:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Year start date or end date is overlapping with {0}. To avoid please set company"
|
||||
).format(existing.name),
|
||||
_("Year start date or end date is overlapping with {0}. To avoid please set company").format(
|
||||
existing.name
|
||||
),
|
||||
frappe.NameError,
|
||||
)
|
||||
|
||||
@@ -126,9 +126,9 @@ def check_duplicate_fiscal_year(doc):
|
||||
not frappe.flags.in_test
|
||||
):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Fiscal Year Start Date and Fiscal Year End Date are already set in Fiscal Year {0}"
|
||||
).format(fiscal_year)
|
||||
_("Fiscal Year Start Date and Fiscal Year End Date are already set in Fiscal Year {0}").format(
|
||||
fiscal_year
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -6,50 +6,38 @@
|
||||
"document_type": "Document",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"dates_section",
|
||||
"posting_date",
|
||||
"transaction_date",
|
||||
"column_break_avko",
|
||||
"fiscal_year",
|
||||
"due_date",
|
||||
"account_details_section",
|
||||
"account",
|
||||
"account_currency",
|
||||
"column_break_ifvf",
|
||||
"against",
|
||||
"party_type",
|
||||
"party",
|
||||
"transaction_details_section",
|
||||
"voucher_type",
|
||||
"voucher_no",
|
||||
"voucher_subtype",
|
||||
"transaction_currency",
|
||||
"column_break_dpsx",
|
||||
"cost_center",
|
||||
"debit",
|
||||
"credit",
|
||||
"account_currency",
|
||||
"debit_in_account_currency",
|
||||
"credit_in_account_currency",
|
||||
"against",
|
||||
"against_voucher_type",
|
||||
"against_voucher",
|
||||
"voucher_type",
|
||||
"voucher_subtype",
|
||||
"voucher_no",
|
||||
"voucher_detail_no",
|
||||
"transaction_exchange_rate",
|
||||
"amounts_section",
|
||||
"debit_in_account_currency",
|
||||
"debit",
|
||||
"debit_in_transaction_currency",
|
||||
"column_break_bm1w",
|
||||
"credit_in_account_currency",
|
||||
"credit",
|
||||
"credit_in_transaction_currency",
|
||||
"dimensions_section",
|
||||
"cost_center",
|
||||
"column_break_lmnm",
|
||||
"project",
|
||||
"more_info_section",
|
||||
"finance_book",
|
||||
"company",
|
||||
"remarks",
|
||||
"is_opening",
|
||||
"is_advance",
|
||||
"column_break_8abq",
|
||||
"fiscal_year",
|
||||
"company",
|
||||
"finance_book",
|
||||
"to_rename",
|
||||
"due_date",
|
||||
"is_cancelled",
|
||||
"remarks"
|
||||
"transaction_currency",
|
||||
"debit_in_transaction_currency",
|
||||
"credit_in_transaction_currency",
|
||||
"transaction_exchange_rate"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -191,8 +179,7 @@
|
||||
"fieldname": "voucher_detail_no",
|
||||
"fieldtype": "Data",
|
||||
"label": "Voucher Detail No",
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "project",
|
||||
@@ -297,67 +284,13 @@
|
||||
"fieldname": "voucher_subtype",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Voucher Subtype"
|
||||
},
|
||||
{
|
||||
"fieldname": "dates_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Dates"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_avko",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "account_details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Account Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_ifvf",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "transaction_details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Transaction Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "amounts_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Amounts"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_dpsx",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "more_info_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "More Info"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_bm1w",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "dimensions_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Dimensions"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_lmnm",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_8abq",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-list",
|
||||
"idx": 1,
|
||||
"in_create": 1,
|
||||
"links": [],
|
||||
"modified": "2024-08-22 13:03:39.997475",
|
||||
"modified": "2023-12-18 15:38:14.006208",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "GL Entry",
|
||||
@@ -389,7 +322,7 @@
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"search_fields": "voucher_no,account,posting_date,against_voucher",
|
||||
"sort_field": "creation",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ class GLEntry(Document):
|
||||
account: DF.Link | None
|
||||
account_currency: DF.Link | None
|
||||
against: DF.Text | None
|
||||
against_link: DF.DynamicLink | None
|
||||
against_type: DF.Link | None
|
||||
against_voucher: DF.DynamicLink | None
|
||||
against_voucher_type: DF.Link | None
|
||||
company: DF.Link | None
|
||||
@@ -105,18 +107,13 @@ class GLEntry(Document):
|
||||
]:
|
||||
# Update outstanding amt on against voucher
|
||||
if (
|
||||
self.against_voucher_type
|
||||
in ["Journal Entry", "Sales Invoice", "Purchase Invoice", "Fees"]
|
||||
self.against_voucher_type in ["Journal Entry", "Sales Invoice", "Purchase Invoice", "Fees"]
|
||||
and self.against_voucher
|
||||
and self.flags.update_outstanding == "Yes"
|
||||
and not frappe.flags.is_reverse_depr_entry
|
||||
):
|
||||
update_outstanding_amt(
|
||||
self.account,
|
||||
self.party_type,
|
||||
self.party,
|
||||
self.against_voucher_type,
|
||||
self.against_voucher,
|
||||
self.account, self.party_type, self.party, self.against_voucher_type, self.against_voucher
|
||||
)
|
||||
|
||||
def check_mandatory(self):
|
||||
@@ -182,13 +179,12 @@ class GLEntry(Document):
|
||||
and self.company == dimension.company
|
||||
and dimension.mandatory_for_pl
|
||||
and not dimension.disabled
|
||||
and not self.is_cancelled
|
||||
):
|
||||
if not self.get(dimension.fieldname):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Accounting Dimension <b>{0}</b> is required for 'Profit and Loss' account {1}."
|
||||
).format(dimension.label, self.account)
|
||||
_("Accounting Dimension <b>{0}</b> is required for 'Profit and Loss' account {1}.").format(
|
||||
dimension.label, self.account
|
||||
)
|
||||
)
|
||||
|
||||
if (
|
||||
@@ -196,13 +192,12 @@ class GLEntry(Document):
|
||||
and self.company == dimension.company
|
||||
and dimension.mandatory_for_bs
|
||||
and not dimension.disabled
|
||||
and not self.is_cancelled
|
||||
):
|
||||
if not self.get(dimension.fieldname):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Accounting Dimension <b>{0}</b> is required for 'Balance Sheet' account {1}."
|
||||
).format(dimension.label, self.account)
|
||||
_("Accounting Dimension <b>{0}</b> is required for 'Balance Sheet' account {1}.").format(
|
||||
dimension.label, self.account
|
||||
)
|
||||
)
|
||||
|
||||
def check_pl_account(self):
|
||||
@@ -250,7 +245,9 @@ class GLEntry(Document):
|
||||
if not self.cost_center:
|
||||
return
|
||||
|
||||
is_group, company = frappe.get_cached_value("Cost Center", self.cost_center, ["is_group", "company"])
|
||||
is_group, company = frappe.get_cached_value(
|
||||
"Cost Center", self.cost_center, ["is_group", "company"]
|
||||
)
|
||||
|
||||
if company != self.company:
|
||||
frappe.throw(
|
||||
@@ -319,27 +316,31 @@ def update_outstanding_amt(
|
||||
account, party_type, party, against_voucher_type, against_voucher, on_cancel=False
|
||||
):
|
||||
if party_type and party:
|
||||
party_condition = " and party_type={} and party={}".format(
|
||||
party_condition = " and party_type={0} and party={1}".format(
|
||||
frappe.db.escape(party_type), frappe.db.escape(party)
|
||||
)
|
||||
else:
|
||||
party_condition = ""
|
||||
|
||||
if against_voucher_type == "Sales Invoice":
|
||||
party_account = frappe.get_cached_value(against_voucher_type, against_voucher, "debit_to")
|
||||
account_condition = f"and account in ({frappe.db.escape(account)}, {frappe.db.escape(party_account)})"
|
||||
party_account = frappe.db.get_value(against_voucher_type, against_voucher, "debit_to")
|
||||
account_condition = "and account in ({0}, {1})".format(
|
||||
frappe.db.escape(account), frappe.db.escape(party_account)
|
||||
)
|
||||
else:
|
||||
account_condition = f" and account = {frappe.db.escape(account)}"
|
||||
account_condition = " and account = {0}".format(frappe.db.escape(account))
|
||||
|
||||
# get final outstanding amt
|
||||
bal = flt(
|
||||
frappe.db.sql(
|
||||
f"""
|
||||
"""
|
||||
select sum(debit_in_account_currency) - sum(credit_in_account_currency)
|
||||
from `tabGL Entry`
|
||||
where against_voucher_type=%s and against_voucher=%s
|
||||
and voucher_type != 'Invoice Discounting'
|
||||
{party_condition} {account_condition}""",
|
||||
{0} {1}""".format(
|
||||
party_condition, account_condition
|
||||
),
|
||||
(against_voucher_type, against_voucher),
|
||||
)[0][0]
|
||||
or 0.0
|
||||
@@ -350,10 +351,12 @@ def update_outstanding_amt(
|
||||
elif against_voucher_type == "Journal Entry":
|
||||
against_voucher_amount = flt(
|
||||
frappe.db.sql(
|
||||
f"""
|
||||
"""
|
||||
select sum(debit_in_account_currency) - sum(credit_in_account_currency)
|
||||
from `tabGL Entry` where voucher_type = 'Journal Entry' and voucher_no = %s
|
||||
and account = %s and (against_voucher is null or against_voucher='') {party_condition}""",
|
||||
and account = %s and (against_voucher is null or against_voucher='') {0}""".format(
|
||||
party_condition
|
||||
),
|
||||
(against_voucher, account),
|
||||
)[0][0]
|
||||
)
|
||||
@@ -372,9 +375,7 @@ def update_outstanding_amt(
|
||||
# Validation : Outstanding can not be negative for JV
|
||||
if bal < 0 and not on_cancel:
|
||||
frappe.throw(
|
||||
_("Outstanding for {0} cannot be less than zero ({1})").format(
|
||||
against_voucher, fmt_money(bal)
|
||||
)
|
||||
_("Outstanding for {0} cannot be less than zero ({1})").format(against_voucher, fmt_money(bal))
|
||||
)
|
||||
|
||||
if against_voucher_type in ["Sales Invoice", "Purchase Invoice", "Fees"]:
|
||||
@@ -390,7 +391,7 @@ def update_outstanding_amt(
|
||||
def validate_frozen_account(account, adv_adj=None):
|
||||
frozen_account = frappe.get_cached_value("Account", account, "freeze_account")
|
||||
if frozen_account == "Yes" and not adv_adj:
|
||||
frozen_accounts_modifier = frappe.get_cached_value(
|
||||
frozen_accounts_modifier = frappe.db.get_value(
|
||||
"Accounts Settings", None, "frozen_accounts_modifier"
|
||||
)
|
||||
|
||||
@@ -430,9 +431,8 @@ def update_against_account(voucher_type, voucher_no):
|
||||
|
||||
|
||||
def on_doctype_update():
|
||||
frappe.db.add_index("GL Entry", ["against_voucher_type", "against_voucher"])
|
||||
frappe.db.add_index("GL Entry", ["voucher_type", "voucher_no"])
|
||||
frappe.db.add_index("GL Entry", ["posting_date", "company"])
|
||||
frappe.db.add_index("GL Entry", ["party_type", "party"])
|
||||
|
||||
|
||||
def rename_gle_sle_docs():
|
||||
@@ -448,7 +448,7 @@ def rename_temporarily_named_docs(doctype):
|
||||
set_name_from_naming_options(frappe.get_meta(doctype).autoname, doc)
|
||||
newname = doc.name
|
||||
frappe.db.sql(
|
||||
f"UPDATE `tab{doctype}` SET name = %s, to_rename = 0 where name = %s",
|
||||
"UPDATE `tab{}` SET name = %s, to_rename = 0 where name = %s".format(doctype),
|
||||
(newname, oldname),
|
||||
auto_commit=True,
|
||||
)
|
||||
|
||||
@@ -14,7 +14,9 @@ from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journ
|
||||
class TestGLEntry(unittest.TestCase):
|
||||
def test_round_off_entry(self):
|
||||
frappe.db.set_value("Company", "_Test Company", "round_off_account", "_Test Write Off - _TC")
|
||||
frappe.db.set_value("Company", "_Test Company", "round_off_cost_center", "_Test Cost Center - _TC")
|
||||
frappe.db.set_value(
|
||||
"Company", "_Test Company", "round_off_cost_center", "_Test Cost Center - _TC"
|
||||
)
|
||||
|
||||
jv = make_journal_entry(
|
||||
"_Test Account Cost for Goods Sold - _TC",
|
||||
@@ -71,9 +73,7 @@ class TestGLEntry(unittest.TestCase):
|
||||
)
|
||||
self.assertTrue(all(entry.to_rename == 0 for entry in new_gl_entries))
|
||||
|
||||
self.assertTrue(
|
||||
all(new.name != old.name for new, old in zip(gl_entries, new_gl_entries, strict=False))
|
||||
)
|
||||
self.assertTrue(all(new.name != old.name for new, old in zip(gl_entries, new_gl_entries)))
|
||||
|
||||
new_naming_series_current_value = frappe.db.sql(
|
||||
"SELECT current from tabSeries where name = %s", naming_series
|
||||
|
||||
@@ -83,7 +83,9 @@ class InvoiceDiscounting(AccountsController):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row({0}): Outstanding Amount cannot be greater than actual Outstanding Amount {1} in {2}"
|
||||
).format(record.idx, frappe.bold(actual_outstanding), frappe.bold(record.sales_invoice))
|
||||
).format(
|
||||
record.idx, frappe.bold(actual_outstanding), frappe.bold(record.sales_invoice)
|
||||
)
|
||||
)
|
||||
|
||||
def calculate_total_amount(self):
|
||||
@@ -103,9 +105,7 @@ class InvoiceDiscounting(AccountsController):
|
||||
self.status = status
|
||||
self.db_set("status", status)
|
||||
for d in self.invoices:
|
||||
frappe.get_doc("Sales Invoice", d.sales_invoice).set_status(
|
||||
update=True, update_modified=False
|
||||
)
|
||||
frappe.get_doc("Sales Invoice", d.sales_invoice).set_status(update=True, update_modified=False)
|
||||
else:
|
||||
self.status = "Draft"
|
||||
if self.docstatus == 1:
|
||||
|
||||
@@ -75,8 +75,8 @@ class TestInvoiceDiscounting(unittest.TestCase):
|
||||
gle = get_gl_entries("Invoice Discounting", inv_disc.name)
|
||||
|
||||
expected_gle = {inv.debit_to: [0.0, 200], self.ar_credit: [200, 0.0]}
|
||||
for _i, gle_value in enumerate(gle):
|
||||
self.assertEqual([gle_value.debit, gle_value.credit], expected_gle.get(gle_value.account))
|
||||
for i, gle in enumerate(gle):
|
||||
self.assertEqual([gle.debit, gle.credit], expected_gle.get(gle.account))
|
||||
|
||||
def test_loan_on_submit(self):
|
||||
inv = create_sales_invoice(rate=300)
|
||||
@@ -92,7 +92,9 @@ class TestInvoiceDiscounting(unittest.TestCase):
|
||||
period=60,
|
||||
)
|
||||
self.assertEqual(inv_disc.status, "Sanctioned")
|
||||
self.assertEqual(inv_disc.loan_end_date, add_days(inv_disc.loan_start_date, inv_disc.loan_period))
|
||||
self.assertEqual(
|
||||
inv_disc.loan_end_date, add_days(inv_disc.loan_start_date, inv_disc.loan_period)
|
||||
)
|
||||
|
||||
def test_on_disbursed(self):
|
||||
inv = create_sales_invoice(rate=500)
|
||||
@@ -260,9 +262,13 @@ class TestInvoiceDiscounting(unittest.TestCase):
|
||||
je_on_payment.submit()
|
||||
|
||||
self.assertEqual(je_on_payment.accounts[0].account, self.ar_discounted)
|
||||
self.assertEqual(je_on_payment.accounts[0].credit_in_account_currency, flt(inv.outstanding_amount))
|
||||
self.assertEqual(
|
||||
je_on_payment.accounts[0].credit_in_account_currency, flt(inv.outstanding_amount)
|
||||
)
|
||||
self.assertEqual(je_on_payment.accounts[1].account, self.bank_account)
|
||||
self.assertEqual(je_on_payment.accounts[1].debit_in_account_currency, flt(inv.outstanding_amount))
|
||||
self.assertEqual(
|
||||
je_on_payment.accounts[1].debit_in_account_currency, flt(inv.outstanding_amount)
|
||||
)
|
||||
|
||||
inv.reload()
|
||||
self.assertEqual(inv.outstanding_amount, 0)
|
||||
@@ -298,9 +304,13 @@ class TestInvoiceDiscounting(unittest.TestCase):
|
||||
je_on_payment.submit()
|
||||
|
||||
self.assertEqual(je_on_payment.accounts[0].account, self.ar_unpaid)
|
||||
self.assertEqual(je_on_payment.accounts[0].credit_in_account_currency, flt(inv.outstanding_amount))
|
||||
self.assertEqual(
|
||||
je_on_payment.accounts[0].credit_in_account_currency, flt(inv.outstanding_amount)
|
||||
)
|
||||
self.assertEqual(je_on_payment.accounts[1].account, self.bank_account)
|
||||
self.assertEqual(je_on_payment.accounts[1].debit_in_account_currency, flt(inv.outstanding_amount))
|
||||
self.assertEqual(
|
||||
je_on_payment.accounts[1].debit_in_account_currency, flt(inv.outstanding_amount)
|
||||
)
|
||||
|
||||
inv.reload()
|
||||
self.assertEqual(inv.outstanding_amount, 0)
|
||||
|
||||
@@ -32,7 +32,7 @@ class ItemTaxTemplate(Document):
|
||||
def autoname(self):
|
||||
if self.company and self.title:
|
||||
abbr = frappe.get_cached_value("Company", self.company, "abbr")
|
||||
self.name = f"{self.title} - {abbr}"
|
||||
self.name = "{0} - {1}".format(self.title, abbr)
|
||||
|
||||
def validate_tax_accounts(self):
|
||||
"""Check whether Tax Rate is not entered twice for same Tax Type"""
|
||||
|
||||
@@ -25,6 +25,30 @@ frappe.ui.form.on("Journal Entry", {
|
||||
refresh: function (frm) {
|
||||
erpnext.toggle_naming_series();
|
||||
|
||||
if (frm.doc.repost_required && frm.doc.docstatus === 1) {
|
||||
frm.set_intro(
|
||||
__(
|
||||
"Accounting entries for this Journal Entry need to be reposted. Please click on 'Repost' button to update."
|
||||
)
|
||||
);
|
||||
frm.add_custom_button(__("Repost Accounting Entries"), () => {
|
||||
frm.call({
|
||||
doc: frm.doc,
|
||||
method: "repost_accounting_entries",
|
||||
freeze: true,
|
||||
freeze_message: __("Reposting..."),
|
||||
callback: (r) => {
|
||||
if (!r.exc) {
|
||||
frappe.msgprint(__("Accounting Entries are reposted."));
|
||||
frm.refresh();
|
||||
}
|
||||
},
|
||||
});
|
||||
})
|
||||
.removeClass("btn-default")
|
||||
.addClass("btn-warning");
|
||||
}
|
||||
|
||||
if (frm.doc.docstatus > 0) {
|
||||
frm.add_custom_button(
|
||||
__("Ledger"),
|
||||
@@ -172,7 +196,7 @@ frappe.ui.form.on("Journal Entry", {
|
||||
!(frm.doc.accounts || []).length ||
|
||||
((frm.doc.accounts || []).length === 1 && !frm.doc.accounts[0].account)
|
||||
) {
|
||||
if (["Bank Entry", "Cash Entry"].includes(frm.doc.voucher_type)) {
|
||||
if (in_list(["Bank Entry", "Cash Entry"], frm.doc.voucher_type)) {
|
||||
return frappe.call({
|
||||
type: "GET",
|
||||
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_default_bank_cash_account",
|
||||
@@ -284,7 +308,7 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
|
||||
filters: [[jvd.reference_type, "docstatus", "=", 1]],
|
||||
};
|
||||
|
||||
if (["Sales Invoice", "Purchase Invoice"].includes(jvd.reference_type)) {
|
||||
if (in_list(["Sales Invoice", "Purchase Invoice"], jvd.reference_type)) {
|
||||
out.filters.push([jvd.reference_type, "outstanding_amount", "!=", 0]);
|
||||
// Filter by cost center
|
||||
if (jvd.cost_center) {
|
||||
@@ -296,7 +320,7 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
|
||||
out.filters.push([jvd.reference_type, party_account_field, "=", jvd.account]);
|
||||
}
|
||||
|
||||
if (["Sales Order", "Purchase Order"].includes(jvd.reference_type)) {
|
||||
if (in_list(["Sales Order", "Purchase Order"], jvd.reference_type)) {
|
||||
// party_type and party mandatory
|
||||
frappe.model.validate_missing(jvd, "party_type");
|
||||
frappe.model.validate_missing(jvd, "party");
|
||||
@@ -360,23 +384,21 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
|
||||
|
||||
accounts_add(doc, cdt, cdn) {
|
||||
var row = frappe.get_doc(cdt, cdn);
|
||||
row.exchange_rate = 1;
|
||||
$.each(doc.accounts, function (i, d) {
|
||||
if (d.account && d.party && d.party_type) {
|
||||
row.account = d.account;
|
||||
row.party = d.party;
|
||||
row.party_type = d.party_type;
|
||||
row.exchange_rate = d.exchange_rate;
|
||||
}
|
||||
});
|
||||
|
||||
// set difference
|
||||
if (doc.difference) {
|
||||
if (doc.difference > 0) {
|
||||
row.credit_in_account_currency = doc.difference / row.exchange_rate;
|
||||
row.credit_in_account_currency = doc.difference;
|
||||
row.credit = doc.difference;
|
||||
} else {
|
||||
row.debit_in_account_currency = -doc.difference / row.exchange_rate;
|
||||
row.debit_in_account_currency = -doc.difference;
|
||||
row.debit = -doc.difference;
|
||||
}
|
||||
}
|
||||
@@ -431,10 +453,7 @@ frappe.ui.form.on("Journal Entry Account", {
|
||||
}
|
||||
},
|
||||
cost_center: function (frm, dt, dn) {
|
||||
// Don't reset for Gain/Loss type journals, as it will make Debit and Credit values '0'
|
||||
if (frm.doc.voucher_type != "Exchange Gain Or Loss") {
|
||||
erpnext.journal_entry.set_account_details(frm, dt, dn);
|
||||
}
|
||||
erpnext.journal_entry.set_account_details(frm, dt, dn);
|
||||
},
|
||||
|
||||
account: function (frm, dt, dn) {
|
||||
@@ -682,7 +701,6 @@ $.extend(erpnext.journal_entry, {
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
$.extend(d, r.message);
|
||||
erpnext.journal_entry.set_amount_on_last_row(frm, dt, dn);
|
||||
erpnext.journal_entry.set_debit_credit_in_company_currency(frm, dt, dn);
|
||||
refresh_field("accounts");
|
||||
}
|
||||
@@ -690,26 +708,4 @@ $.extend(erpnext.journal_entry, {
|
||||
});
|
||||
}
|
||||
},
|
||||
set_amount_on_last_row: function (frm, dt, dn) {
|
||||
let row = locals[dt][dn];
|
||||
let length = frm.doc.accounts.length;
|
||||
if (row.idx != length) return;
|
||||
|
||||
let difference = frm.doc.accounts.reduce((total, row) => {
|
||||
if (row.idx == length) return total;
|
||||
|
||||
return total + row.debit - row.credit;
|
||||
}, 0);
|
||||
|
||||
if (difference) {
|
||||
if (difference > 0) {
|
||||
row.credit_in_account_currency = difference / row.exchange_rate;
|
||||
row.credit = difference;
|
||||
} else {
|
||||
row.debit_in_account_currency = -difference / row.exchange_rate;
|
||||
row.debit = -difference;
|
||||
}
|
||||
}
|
||||
refresh_field("accounts");
|
||||
},
|
||||
});
|
||||
|
||||
@@ -64,7 +64,8 @@
|
||||
"stock_entry",
|
||||
"subscription_section",
|
||||
"auto_repeat",
|
||||
"amended_from"
|
||||
"amended_from",
|
||||
"repost_required"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -543,6 +544,15 @@
|
||||
"label": "Is System Generated",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "repost_required",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Repost Required",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
@@ -557,7 +567,7 @@
|
||||
"table_fieldname": "payment_entries"
|
||||
}
|
||||
],
|
||||
"modified": "2024-07-18 15:32:29.413598",
|
||||
"modified": "2023-11-23 12:11:04.128015",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Journal Entry",
|
||||
@@ -608,4 +618,4 @@
|
||||
"states": [],
|
||||
"title_field": "title",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import json
|
||||
|
||||
import frappe
|
||||
from frappe import _, msgprint, scrub
|
||||
from frappe.utils import comma_and, cstr, flt, fmt_money, formatdate, get_link_to_form, nowdate
|
||||
from frappe.utils import cstr, flt, fmt_money, formatdate, get_link_to_form, nowdate
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.deferred_revenue import get_deferred_booking_accounts
|
||||
@@ -47,7 +47,9 @@ class JournalEntry(AccountsController):
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
from erpnext.accounts.doctype.journal_entry_account.journal_entry_account import JournalEntryAccount
|
||||
from erpnext.accounts.doctype.journal_entry_account.journal_entry_account import (
|
||||
JournalEntryAccount,
|
||||
)
|
||||
|
||||
accounts: DF.Table[JournalEntryAccount]
|
||||
amended_from: DF.Link | None
|
||||
@@ -110,7 +112,7 @@ class JournalEntry(AccountsController):
|
||||
# end: auto-generated types
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
super(JournalEntry, self).__init__(*args, **kwargs)
|
||||
|
||||
def validate(self):
|
||||
if self.voucher_type == "Opening Entry":
|
||||
@@ -127,6 +129,9 @@ class JournalEntry(AccountsController):
|
||||
self.set_amounts_in_company_currency()
|
||||
self.validate_debit_credit_amount()
|
||||
self.set_total_debit_credit()
|
||||
# Do not validate while importing via data import
|
||||
if not frappe.flags.in_import:
|
||||
self.validate_total_debit_and_credit()
|
||||
|
||||
if not frappe.flags.is_reverse_depr_entry:
|
||||
self.validate_against_jv()
|
||||
@@ -141,7 +146,6 @@ class JournalEntry(AccountsController):
|
||||
self.validate_empty_accounts_table()
|
||||
self.validate_inter_company_accounts()
|
||||
self.validate_depr_entry_voucher_type()
|
||||
self.validate_advance_accounts()
|
||||
|
||||
if self.docstatus == 0:
|
||||
self.apply_tax_withholding()
|
||||
@@ -149,20 +153,6 @@ class JournalEntry(AccountsController):
|
||||
if not self.title:
|
||||
self.title = self.get_title()
|
||||
|
||||
def validate_advance_accounts(self):
|
||||
journal_accounts = set([x.account for x in self.accounts])
|
||||
advance_accounts = set()
|
||||
advance_accounts.add(
|
||||
frappe.get_cached_value("Company", self.company, "default_advance_received_account")
|
||||
)
|
||||
advance_accounts.add(frappe.get_cached_value("Company", self.company, "default_advance_paid_account"))
|
||||
if advance_accounts_used := journal_accounts & advance_accounts:
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"Making Journal Entries against advance accounts: {0} is not recommended. These Journals won't be available for Reconciliation."
|
||||
).format(frappe.bold(comma_and(advance_accounts_used)))
|
||||
)
|
||||
|
||||
def validate_for_repost(self):
|
||||
validate_docs_for_voucher_types(["Journal Entry"])
|
||||
validate_docs_for_deferred_accounting([self.name], [])
|
||||
@@ -181,36 +171,27 @@ class JournalEntry(AccountsController):
|
||||
else:
|
||||
return self._cancel()
|
||||
|
||||
def before_submit(self):
|
||||
# Do not validate while importing via data import
|
||||
if not frappe.flags.in_import:
|
||||
self.validate_total_debit_and_credit()
|
||||
|
||||
def on_submit(self):
|
||||
self.validate_cheque_info()
|
||||
self.check_credit_limit()
|
||||
self.make_gl_entries()
|
||||
self.make_advance_payment_ledger_entries()
|
||||
self.update_advance_paid()
|
||||
self.update_asset_value()
|
||||
self.update_inter_company_jv()
|
||||
self.update_invoice_discounting()
|
||||
self.update_booked_depreciation()
|
||||
|
||||
def on_update_after_submit(self):
|
||||
# Flag will be set on Reconciliation
|
||||
# Reconciliation tool will anyways repost ledger entries. So, no need to check and do implicit repost.
|
||||
if self.flags.get("ignore_reposting_on_reconciliation"):
|
||||
return
|
||||
|
||||
self.needs_repost = self.check_if_fields_updated(fields_to_check=[], child_tables={"accounts": []})
|
||||
if self.needs_repost:
|
||||
self.validate_for_repost()
|
||||
self.repost_accounting_entries()
|
||||
if hasattr(self, "repost_required"):
|
||||
self.needs_repost = self.check_if_fields_updated(
|
||||
fields_to_check=[], child_tables={"accounts": []}
|
||||
)
|
||||
if self.needs_repost:
|
||||
self.validate_for_repost()
|
||||
self.db_set("repost_required", self.needs_repost)
|
||||
|
||||
def on_cancel(self):
|
||||
# References for this Journal are removed on the `on_cancel` event in accounts_controller
|
||||
super().on_cancel()
|
||||
super(JournalEntry, self).on_cancel()
|
||||
self.ignore_linked_doctypes = (
|
||||
"GL Entry",
|
||||
"Stock Ledger Entry",
|
||||
@@ -221,17 +202,14 @@ class JournalEntry(AccountsController):
|
||||
"Repost Accounting Ledger Items",
|
||||
"Unreconcile Payment",
|
||||
"Unreconcile Payment Entries",
|
||||
"Advance Payment Ledger Entry",
|
||||
)
|
||||
self.make_gl_entries(1)
|
||||
self.make_advance_payment_ledger_entries()
|
||||
self.update_advance_paid()
|
||||
self.unlink_advance_entry_reference()
|
||||
self.unlink_asset_reference()
|
||||
self.unlink_inter_company_jv()
|
||||
self.unlink_asset_adjustment_entry()
|
||||
self.update_invoice_discounting()
|
||||
self.update_booked_depreciation(1)
|
||||
|
||||
def get_title(self):
|
||||
return self.pay_to_recd_from or self.accounts[0].account
|
||||
@@ -248,7 +226,10 @@ class JournalEntry(AccountsController):
|
||||
frappe.get_doc(voucher_type, voucher_no).set_total_advance_paid()
|
||||
|
||||
def validate_inter_company_accounts(self):
|
||||
if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
|
||||
if (
|
||||
self.voucher_type == "Inter Company Journal Entry"
|
||||
and self.inter_company_journal_entry_reference
|
||||
):
|
||||
doc = frappe.get_doc("Journal Entry", self.inter_company_journal_entry_reference)
|
||||
account_currency = frappe.get_cached_value("Company", self.company, "default_currency")
|
||||
previous_account_currency = frappe.get_cached_value("Company", doc.company, "default_currency")
|
||||
@@ -264,7 +245,7 @@ class JournalEntry(AccountsController):
|
||||
frappe.throw(_("Journal Entry type should be set as Depreciation Entry for asset depreciation"))
|
||||
|
||||
def validate_stock_accounts(self):
|
||||
stock_accounts = get_stock_accounts(self.company, accounts=self.accounts)
|
||||
stock_accounts = get_stock_accounts(self.company, self.doctype, self.name)
|
||||
for account in stock_accounts:
|
||||
account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(
|
||||
account, self.posting_date, self.company
|
||||
@@ -394,7 +375,10 @@ class JournalEntry(AccountsController):
|
||||
asset.set_status()
|
||||
|
||||
def update_inter_company_jv(self):
|
||||
if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
|
||||
if (
|
||||
self.voucher_type == "Inter Company Journal Entry"
|
||||
and self.inter_company_journal_entry_reference
|
||||
):
|
||||
frappe.db.set_value(
|
||||
"Journal Entry",
|
||||
self.inter_company_journal_entry_reference,
|
||||
@@ -422,49 +406,22 @@ class JournalEntry(AccountsController):
|
||||
if d.account == inv_disc_doc.short_term_loan and d.reference_name == inv_disc:
|
||||
if self.docstatus == 1:
|
||||
if d.credit > 0:
|
||||
_validate_invoice_discounting_status(
|
||||
inv_disc, inv_disc_doc.status, "Sanctioned", d.idx
|
||||
)
|
||||
_validate_invoice_discounting_status(inv_disc, inv_disc_doc.status, "Sanctioned", d.idx)
|
||||
status = "Disbursed"
|
||||
elif d.debit > 0:
|
||||
_validate_invoice_discounting_status(
|
||||
inv_disc, inv_disc_doc.status, "Disbursed", d.idx
|
||||
)
|
||||
_validate_invoice_discounting_status(inv_disc, inv_disc_doc.status, "Disbursed", d.idx)
|
||||
status = "Settled"
|
||||
else:
|
||||
if d.credit > 0:
|
||||
_validate_invoice_discounting_status(
|
||||
inv_disc, inv_disc_doc.status, "Disbursed", d.idx
|
||||
)
|
||||
_validate_invoice_discounting_status(inv_disc, inv_disc_doc.status, "Disbursed", d.idx)
|
||||
status = "Sanctioned"
|
||||
elif d.debit > 0:
|
||||
_validate_invoice_discounting_status(
|
||||
inv_disc, inv_disc_doc.status, "Settled", d.idx
|
||||
)
|
||||
_validate_invoice_discounting_status(inv_disc, inv_disc_doc.status, "Settled", d.idx)
|
||||
status = "Disbursed"
|
||||
break
|
||||
if status:
|
||||
inv_disc_doc.set_status(status=status)
|
||||
|
||||
def update_booked_depreciation(self, cancel=0):
|
||||
for d in self.get("accounts"):
|
||||
if (
|
||||
self.voucher_type == "Depreciation Entry"
|
||||
and d.reference_type == "Asset"
|
||||
and d.reference_name
|
||||
and frappe.get_cached_value("Account", d.account, "root_type") == "Expense"
|
||||
and d.debit
|
||||
):
|
||||
asset = frappe.get_doc("Asset", d.reference_name)
|
||||
for fb_row in asset.get("finance_books"):
|
||||
if fb_row.finance_book == self.finance_book:
|
||||
if cancel:
|
||||
fb_row.total_number_of_booked_depreciations -= 1
|
||||
else:
|
||||
fb_row.total_number_of_booked_depreciations += 1
|
||||
fb_row.db_update()
|
||||
break
|
||||
|
||||
def unlink_advance_entry_reference(self):
|
||||
for d in self.get("accounts"):
|
||||
if d.is_advance == "Yes" and d.reference_type in ("Sales Invoice", "Purchase Invoice"):
|
||||
@@ -480,7 +437,7 @@ class JournalEntry(AccountsController):
|
||||
self.voucher_type == "Depreciation Entry"
|
||||
and d.reference_type == "Asset"
|
||||
and d.reference_name
|
||||
and frappe.get_cached_value("Account", d.account, "root_type") == "Expense"
|
||||
and d.account_type == "Depreciation"
|
||||
and d.debit
|
||||
):
|
||||
asset = frappe.get_doc("Asset", d.reference_name)
|
||||
@@ -528,7 +485,10 @@ class JournalEntry(AccountsController):
|
||||
)
|
||||
|
||||
def unlink_inter_company_jv(self):
|
||||
if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
|
||||
if (
|
||||
self.voucher_type == "Inter Company Journal Entry"
|
||||
and self.inter_company_journal_entry_reference
|
||||
):
|
||||
frappe.db.set_value(
|
||||
"Journal Entry",
|
||||
self.inter_company_journal_entry_reference,
|
||||
@@ -550,9 +510,9 @@ class JournalEntry(AccountsController):
|
||||
if account_type in ["Receivable", "Payable"]:
|
||||
if not (d.party_type and d.party):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row {0}: Party Type and Party is required for Receivable / Payable account {1}"
|
||||
).format(d.idx, d.account)
|
||||
_("Row {0}: Party Type and Party is required for Receivable / Payable account {1}").format(
|
||||
d.idx, d.account
|
||||
)
|
||||
)
|
||||
elif (
|
||||
d.party_type
|
||||
@@ -617,18 +577,16 @@ class JournalEntry(AccountsController):
|
||||
|
||||
def system_generated_gain_loss(self):
|
||||
return (
|
||||
self.voucher_type == "Exchange Gain Or Loss" and self.multi_currency and self.is_system_generated
|
||||
self.voucher_type == "Exchange Gain Or Loss"
|
||||
and self.multi_currency
|
||||
and self.is_system_generated
|
||||
)
|
||||
|
||||
def validate_against_jv(self):
|
||||
for d in self.get("accounts"):
|
||||
if d.reference_type == "Journal Entry":
|
||||
account_root_type = frappe.get_cached_value("Account", d.account, "root_type")
|
||||
if (
|
||||
account_root_type == "Asset"
|
||||
and flt(d.debit) > 0
|
||||
and not self.system_generated_gain_loss()
|
||||
):
|
||||
if account_root_type == "Asset" and flt(d.debit) > 0 and not self.system_generated_gain_loss():
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row #{0}: For {1}, you can select reference document only if account gets credited"
|
||||
@@ -710,13 +668,11 @@ class JournalEntry(AccountsController):
|
||||
|
||||
if d.reference_type == "Purchase Order" and flt(d.credit) > 0:
|
||||
frappe.throw(
|
||||
_("Row {0}: Credit entry can not be linked with a {1}").format(
|
||||
d.idx, d.reference_type
|
||||
)
|
||||
_("Row {0}: Credit entry can not be linked with a {1}").format(d.idx, d.reference_type)
|
||||
)
|
||||
|
||||
# set totals
|
||||
if d.reference_name not in self.reference_totals:
|
||||
if not d.reference_name in self.reference_totals:
|
||||
self.reference_totals[d.reference_name] = 0.0
|
||||
|
||||
if self.voucher_type not in ("Deferred Revenue", "Deferred Expense"):
|
||||
@@ -734,10 +690,7 @@ class JournalEntry(AccountsController):
|
||||
|
||||
# check if party and account match
|
||||
if d.reference_type in ("Sales Invoice", "Purchase Invoice"):
|
||||
if (
|
||||
self.voucher_type in ("Deferred Revenue", "Deferred Expense")
|
||||
and d.reference_detail_no
|
||||
):
|
||||
if self.voucher_type in ("Deferred Revenue", "Deferred Expense") and d.reference_detail_no:
|
||||
debit_or_credit = "Debit" if d.debit else "Credit"
|
||||
party_account = get_deferred_booking_accounts(
|
||||
d.reference_type, d.reference_detail_no, debit_or_credit
|
||||
@@ -746,8 +699,7 @@ class JournalEntry(AccountsController):
|
||||
else:
|
||||
if d.reference_type == "Sales Invoice":
|
||||
party_account = (
|
||||
get_party_account_based_on_invoice_discounting(d.reference_name)
|
||||
or against_voucher[1]
|
||||
get_party_account_based_on_invoice_discounting(d.reference_name) or against_voucher[1]
|
||||
)
|
||||
else:
|
||||
party_account = against_voucher[1]
|
||||
@@ -871,9 +823,7 @@ class JournalEntry(AccountsController):
|
||||
if not (self.voucher_type == "Exchange Gain Or Loss" and self.multi_currency):
|
||||
if self.difference:
|
||||
frappe.throw(
|
||||
_("Total Debit must be equal to Total Credit. The difference is {0}").format(
|
||||
self.difference
|
||||
)
|
||||
_("Total Debit must be equal to Total Credit. The difference is {0}").format(self.difference)
|
||||
)
|
||||
|
||||
def set_total_debit_credit(self):
|
||||
@@ -937,6 +887,7 @@ class JournalEntry(AccountsController):
|
||||
and self.posting_date
|
||||
)
|
||||
):
|
||||
|
||||
ignore_exchange_rate = False
|
||||
if self.get("flags") and self.flags.get("ignore_exchange_rate"):
|
||||
ignore_exchange_rate = True
|
||||
@@ -1057,17 +1008,6 @@ class JournalEntry(AccountsController):
|
||||
|
||||
def build_gl_map(self):
|
||||
gl_map = []
|
||||
|
||||
company_currency = erpnext.get_company_currency(self.company)
|
||||
if self.multi_currency:
|
||||
for row in self.get("accounts"):
|
||||
if row.account_currency != company_currency:
|
||||
self.currency = row.account_currency
|
||||
self.conversion_rate = row.exchange_rate
|
||||
break
|
||||
else:
|
||||
self.currency = company_currency
|
||||
|
||||
for d in self.get("accounts"):
|
||||
if d.debit or d.credit or (self.voucher_type == "Exchange Gain Or Loss"):
|
||||
r = [d.user_remark, self.remark]
|
||||
@@ -1193,21 +1133,27 @@ class JournalEntry(AccountsController):
|
||||
self.validate_total_debit_and_credit()
|
||||
|
||||
def get_values(self):
|
||||
cond = f" and outstanding_amount <= {self.write_off_amount}" if flt(self.write_off_amount) > 0 else ""
|
||||
cond = (
|
||||
" and outstanding_amount <= {0}".format(self.write_off_amount)
|
||||
if flt(self.write_off_amount) > 0
|
||||
else ""
|
||||
)
|
||||
|
||||
if self.write_off_based_on == "Accounts Receivable":
|
||||
return frappe.db.sql(
|
||||
"""select name, debit_to as account, customer as party, outstanding_amount
|
||||
from `tabSales Invoice` where docstatus = 1 and company = {}
|
||||
and outstanding_amount > 0 {}""".format("%s", cond),
|
||||
from `tabSales Invoice` where docstatus = 1 and company = %s
|
||||
and outstanding_amount > 0 %s"""
|
||||
% ("%s", cond),
|
||||
self.company,
|
||||
as_dict=True,
|
||||
)
|
||||
elif self.write_off_based_on == "Accounts Payable":
|
||||
return frappe.db.sql(
|
||||
"""select name, credit_to as account, supplier as party, outstanding_amount
|
||||
from `tabPurchase Invoice` where docstatus = 1 and company = {}
|
||||
and outstanding_amount > 0 {}""".format("%s", cond),
|
||||
from `tabPurchase Invoice` where docstatus = 1 and company = %s
|
||||
and outstanding_amount > 0 %s"""
|
||||
% ("%s", cond),
|
||||
self.company,
|
||||
as_dict=True,
|
||||
)
|
||||
@@ -1316,7 +1262,7 @@ def get_payment_entry_against_order(
|
||||
"amount_field_bank": amount_field_bank,
|
||||
"amount": amount,
|
||||
"debit_in_account_currency": debit_in_account_currency,
|
||||
"remarks": f"Advance Payment received against {dt} {dn}",
|
||||
"remarks": "Advance Payment received against {0} {1}".format(dt, dn),
|
||||
"is_advance": "Yes",
|
||||
"bank_account": bank_account,
|
||||
"journal_entry": journal_entry,
|
||||
@@ -1355,7 +1301,7 @@ def get_payment_entry_against_invoice(
|
||||
"amount_field_bank": amount_field_bank,
|
||||
"amount": amount if amount else abs(ref_doc.outstanding_amount),
|
||||
"debit_in_account_currency": debit_in_account_currency,
|
||||
"remarks": f"Payment received against {dt} {dn}. {ref_doc.remarks}",
|
||||
"remarks": "Payment received against {0} {1}. {2}".format(dt, dn, ref_doc.remarks),
|
||||
"is_advance": "No",
|
||||
"bank_account": bank_account,
|
||||
"journal_entry": journal_entry,
|
||||
@@ -1381,7 +1327,9 @@ def get_payment_entry(ref_doc, args):
|
||||
)
|
||||
|
||||
je = frappe.new_doc("Journal Entry")
|
||||
je.update({"voucher_type": "Bank Entry", "company": ref_doc.company, "remark": args.get("remarks")})
|
||||
je.update(
|
||||
{"voucher_type": "Bank Entry", "company": ref_doc.company, "remark": args.get("remarks")}
|
||||
)
|
||||
|
||||
party_row = je.append(
|
||||
"accounts",
|
||||
@@ -1404,7 +1352,9 @@ def get_payment_entry(ref_doc, args):
|
||||
bank_row = je.append("accounts")
|
||||
|
||||
# Make it bank_details
|
||||
bank_account = get_default_bank_cash_account(ref_doc.company, "Bank", account=args.get("bank_account"))
|
||||
bank_account = get_default_bank_cash_account(
|
||||
ref_doc.company, "Bank", account=args.get("bank_account")
|
||||
)
|
||||
if bank_account:
|
||||
bank_row.update(bank_account)
|
||||
# Modified to include the posting date for which the exchange rate is required.
|
||||
@@ -1444,7 +1394,7 @@ def get_against_jv(doctype, txt, searchfield, start, page_len, filters):
|
||||
return []
|
||||
|
||||
return frappe.db.sql(
|
||||
f"""
|
||||
"""
|
||||
SELECT jv.name, jv.posting_date, jv.user_remark
|
||||
FROM `tabJournal Entry` jv, `tabJournal Entry Account` jv_detail
|
||||
WHERE jv_detail.parent = jv.name
|
||||
@@ -1455,14 +1405,16 @@ def get_against_jv(doctype, txt, searchfield, start, page_len, filters):
|
||||
OR jv_detail.reference_type = ''
|
||||
)
|
||||
AND jv.docstatus = 1
|
||||
AND jv.`{searchfield}` LIKE %(txt)s
|
||||
AND jv.`{0}` LIKE %(txt)s
|
||||
ORDER BY jv.name DESC
|
||||
LIMIT %(limit)s offset %(offset)s
|
||||
""",
|
||||
""".format(
|
||||
searchfield
|
||||
),
|
||||
dict(
|
||||
account=filters.get("account"),
|
||||
party=cstr(filters.get("party")),
|
||||
txt=f"%{txt}%",
|
||||
txt="%{0}%".format(txt),
|
||||
offset=start,
|
||||
limit=page_len,
|
||||
),
|
||||
@@ -1484,15 +1436,19 @@ def get_outstanding(args):
|
||||
condition = " and party=%(party)s" if args.get("party") else ""
|
||||
|
||||
against_jv_amount = frappe.db.sql(
|
||||
f"""
|
||||
"""
|
||||
select sum(debit_in_account_currency) - sum(credit_in_account_currency)
|
||||
from `tabJournal Entry Account` where parent=%(docname)s and account=%(account)s {condition}
|
||||
and (reference_type is null or reference_type = '')""",
|
||||
from `tabJournal Entry Account` where parent=%(docname)s and account=%(account)s {0}
|
||||
and (reference_type is null or reference_type = '')""".format(
|
||||
condition
|
||||
),
|
||||
args,
|
||||
)
|
||||
|
||||
against_jv_amount = flt(against_jv_amount[0][0]) if against_jv_amount else 0
|
||||
amount_field = "credit_in_account_currency" if against_jv_amount > 0 else "debit_in_account_currency"
|
||||
amount_field = (
|
||||
"credit_in_account_currency" if against_jv_amount > 0 else "debit_in_account_currency"
|
||||
)
|
||||
return {amount_field: abs(against_jv_amount)}
|
||||
elif args.get("doctype") in ("Sales Invoice", "Purchase Invoice"):
|
||||
party_type = "Customer" if args.get("doctype") == "Sales Invoice" else "Supplier"
|
||||
@@ -1505,7 +1461,9 @@ def get_outstanding(args):
|
||||
|
||||
due_date = invoice.get("due_date")
|
||||
|
||||
exchange_rate = invoice.conversion_rate if (args.get("account_currency") != company_currency) else 1
|
||||
exchange_rate = (
|
||||
invoice.conversion_rate if (args.get("account_currency") != company_currency) else 1
|
||||
)
|
||||
|
||||
if args["doctype"] == "Sales Invoice":
|
||||
amount_field = (
|
||||
@@ -1543,7 +1501,9 @@ def get_party_account_and_currency(company, party_type, party):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_account_details_and_party_type(account, date, company, debit=None, credit=None, exchange_rate=None):
|
||||
def get_account_details_and_party_type(
|
||||
account, date, company, debit=None, credit=None, exchange_rate=None
|
||||
):
|
||||
"""Returns dict of account details and party type to be set in Journal Entry on selection of account."""
|
||||
if not frappe.has_permission("Account"):
|
||||
frappe.msgprint(_("No Permission"), raise_exception=1)
|
||||
@@ -1673,8 +1633,6 @@ def make_reverse_journal_entry(source_name, target_doc=None):
|
||||
"debit": "credit",
|
||||
"credit_in_account_currency": "debit_in_account_currency",
|
||||
"credit": "debit",
|
||||
"reference_type": "reference_type",
|
||||
"reference_name": "reference_name",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -69,8 +69,10 @@ class TestJournalEntry(unittest.TestCase):
|
||||
|
||||
self.assertTrue(
|
||||
frappe.db.sql(
|
||||
f"""select name from `tabJournal Entry Account`
|
||||
where reference_type = %s and reference_name = %s and {dr_or_cr}=400""",
|
||||
"""select name from `tabJournal Entry Account`
|
||||
where reference_type = %s and reference_name = %s and {0}=400""".format(
|
||||
dr_or_cr
|
||||
),
|
||||
(submitted_voucher.doctype, submitted_voucher.name),
|
||||
)
|
||||
)
|
||||
@@ -82,8 +84,9 @@ class TestJournalEntry(unittest.TestCase):
|
||||
def advance_paid_testcase(self, base_jv, test_voucher, dr_or_cr):
|
||||
# Test advance paid field
|
||||
advance_paid = frappe.db.sql(
|
||||
"""select advance_paid from `tab{}`
|
||||
where name={}""".format(test_voucher.doctype, "%s"),
|
||||
"""select advance_paid from `tab%s`
|
||||
where name=%s"""
|
||||
% (test_voucher.doctype, "%s"),
|
||||
(test_voucher.name),
|
||||
)
|
||||
payment_against_order = base_jv.get("accounts")[0].get(dr_or_cr)
|
||||
@@ -156,7 +159,9 @@ class TestJournalEntry(unittest.TestCase):
|
||||
jv.cancel()
|
||||
|
||||
def test_multi_currency(self):
|
||||
jv = make_journal_entry("_Test Bank USD - _TC", "_Test Bank - _TC", 100, exchange_rate=50, save=False)
|
||||
jv = make_journal_entry(
|
||||
"_Test Bank USD - _TC", "_Test Bank - _TC", 100, exchange_rate=50, save=False
|
||||
)
|
||||
|
||||
jv.get("accounts")[1].credit_in_account_currency = 5000
|
||||
jv.submit()
|
||||
@@ -454,9 +459,12 @@ class TestJournalEntry(unittest.TestCase):
|
||||
# Change cost center for bank account - _Test Cost Center for BS Account
|
||||
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
|
||||
jv.accounts[1].cost_center = "_Test Cost Center for BS Account - _TC"
|
||||
# Ledger reposted implicitly upon 'Update After Submit'
|
||||
jv.save()
|
||||
|
||||
# Check if repost flag gets set on update after submit
|
||||
self.assertTrue(jv.repost_required)
|
||||
jv.repost_accounting_entries()
|
||||
|
||||
# Check GL entries after reposting
|
||||
jv.load_from_db()
|
||||
self.expected_gle[0]["cost_center"] = "_Test Cost Center for BS Account - _TC"
|
||||
@@ -469,7 +477,9 @@ class TestJournalEntry(unittest.TestCase):
|
||||
query = query.select(gl[field])
|
||||
|
||||
query = query.where(
|
||||
(gl.voucher_type == "Journal Entry") & (gl.voucher_no == self.voucher_no) & (gl.is_cancelled == 0)
|
||||
(gl.voucher_type == "Journal Entry")
|
||||
& (gl.voucher_no == self.voucher_no)
|
||||
& (gl.is_cancelled == 0)
|
||||
).orderby(gl.account)
|
||||
|
||||
gl_entries = query.run(as_dict=True)
|
||||
@@ -478,109 +488,6 @@ class TestJournalEntry(unittest.TestCase):
|
||||
for field in self.fields:
|
||||
self.assertEqual(self.expected_gle[i][field], gl_entries[i][field])
|
||||
|
||||
def test_negative_debit_and_credit_with_same_account_head(self):
|
||||
from erpnext.accounts.general_ledger import process_gl_map
|
||||
|
||||
# Create JV with defaut cost center - _Test Cost Center
|
||||
frappe.db.set_single_value("Accounts Settings", "merge_similar_account_heads", 0)
|
||||
|
||||
jv = make_journal_entry("_Test Bank - _TC", "_Test Bank - _TC", 100 * -1, save=True)
|
||||
jv.append(
|
||||
"accounts",
|
||||
{
|
||||
"account": "_Test Cash - _TC",
|
||||
"debit": 100 * -1,
|
||||
"credit": 100 * -1,
|
||||
"debit_in_account_currency": 100 * -1,
|
||||
"credit_in_account_currency": 100 * -1,
|
||||
"exchange_rate": 1,
|
||||
},
|
||||
)
|
||||
jv.flags.ignore_validate = True
|
||||
jv.save()
|
||||
|
||||
self.assertEqual(len(jv.accounts), 3)
|
||||
|
||||
gl_map = jv.build_gl_map()
|
||||
|
||||
for row in gl_map:
|
||||
if row.account == "_Test Cash - _TC":
|
||||
self.assertEqual(row.debit_in_account_currency, 100 * -1)
|
||||
self.assertEqual(row.credit_in_account_currency, 100 * -1)
|
||||
|
||||
gl_map = process_gl_map(gl_map, False)
|
||||
|
||||
for row in gl_map:
|
||||
if row.account == "_Test Cash - _TC":
|
||||
self.assertEqual(row.debit_in_account_currency, 100)
|
||||
self.assertEqual(row.credit_in_account_currency, 100)
|
||||
|
||||
def test_toggle_debit_credit_if_negative(self):
|
||||
from erpnext.accounts.general_ledger import process_gl_map
|
||||
|
||||
# Create JV with defaut cost center - _Test Cost Center
|
||||
frappe.db.set_single_value("Accounts Settings", "merge_similar_account_heads", 0)
|
||||
|
||||
jv = frappe.new_doc("Journal Entry")
|
||||
jv.posting_date = nowdate()
|
||||
jv.company = "_Test Company"
|
||||
jv.user_remark = "test"
|
||||
jv.extend(
|
||||
"accounts",
|
||||
[
|
||||
{
|
||||
"account": "_Test Cash - _TC",
|
||||
"debit": 100 * -1,
|
||||
"debit_in_account_currency": 100 * -1,
|
||||
"exchange_rate": 1,
|
||||
},
|
||||
{
|
||||
"account": "_Test Bank - _TC",
|
||||
"credit": 100 * -1,
|
||||
"credit_in_account_currency": 100 * -1,
|
||||
"exchange_rate": 1,
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
jv.flags.ignore_validate = True
|
||||
jv.save()
|
||||
|
||||
self.assertEqual(len(jv.accounts), 2)
|
||||
|
||||
gl_map = jv.build_gl_map()
|
||||
|
||||
for row in gl_map:
|
||||
if row.account == "_Test Cash - _TC":
|
||||
self.assertEqual(row.debit, 100 * -1)
|
||||
self.assertEqual(row.debit_in_account_currency, 100 * -1)
|
||||
self.assertEqual(row.debit_in_transaction_currency, 100 * -1)
|
||||
|
||||
gl_map = process_gl_map(gl_map, False)
|
||||
|
||||
for row in gl_map:
|
||||
if row.account == "_Test Cash - _TC":
|
||||
self.assertEqual(row.credit, 100)
|
||||
self.assertEqual(row.credit_in_account_currency, 100)
|
||||
self.assertEqual(row.credit_in_transaction_currency, 100)
|
||||
|
||||
def test_transaction_exchange_rate_on_journals(self):
|
||||
jv = make_journal_entry("_Test Bank - _TC", "_Test Receivable USD - _TC", 100, save=False)
|
||||
jv.accounts[0].update({"debit_in_account_currency": 8500, "exchange_rate": 1})
|
||||
jv.accounts[1].update({"party_type": "Customer", "party": "_Test Customer USD", "exchange_rate": 85})
|
||||
jv.submit()
|
||||
actual = frappe.db.get_all(
|
||||
"GL Entry",
|
||||
filters={"voucher_no": jv.name, "is_cancelled": 0},
|
||||
fields=["account", "transaction_exchange_rate"],
|
||||
order_by="account",
|
||||
)
|
||||
expected = [
|
||||
{"account": "_Test Bank - _TC", "transaction_exchange_rate": 1.0},
|
||||
{"account": "_Test Receivable USD - _TC", "transaction_exchange_rate": 85.0},
|
||||
]
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
|
||||
def make_journal_entry(
|
||||
account1,
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Ledger Health", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
@@ -1,70 +0,0 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "autoincrement",
|
||||
"creation": "2024-03-26 17:01:47.443986",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"voucher_type",
|
||||
"voucher_no",
|
||||
"checked_on",
|
||||
"debit_credit_mismatch",
|
||||
"general_and_payment_ledger_mismatch"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "voucher_type",
|
||||
"fieldtype": "Data",
|
||||
"label": "Voucher Type"
|
||||
},
|
||||
{
|
||||
"fieldname": "voucher_no",
|
||||
"fieldtype": "Data",
|
||||
"label": "Voucher No"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "debit_credit_mismatch",
|
||||
"fieldtype": "Check",
|
||||
"label": "Debit-Credit mismatch"
|
||||
},
|
||||
{
|
||||
"fieldname": "checked_on",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Checked On"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "general_and_payment_ledger_mismatch",
|
||||
"fieldtype": "Check",
|
||||
"label": "General and Payment Ledger mismatch"
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2024-04-09 11:16:07.044484",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Ledger Health",
|
||||
"naming_rule": "Autoincrement",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"read_only": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class LedgerHealth(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
checked_on: DF.Datetime | None
|
||||
debit_credit_mismatch: DF.Check
|
||||
general_and_payment_ledger_mismatch: DF.Check
|
||||
name: DF.Int | None
|
||||
voucher_no: DF.Data | None
|
||||
voucher_type: DF.Data | None
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -1,109 +0,0 @@
|
||||
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import qb
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import nowdate
|
||||
|
||||
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
||||
from erpnext.accounts.utils import run_ledger_health_checks
|
||||
|
||||
|
||||
class TestLedgerHealth(AccountsTestMixin, FrappeTestCase):
|
||||
def setUp(self):
|
||||
self.create_company()
|
||||
self.create_customer()
|
||||
self.configure_monitoring_tool()
|
||||
self.clear_old_entries()
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
def configure_monitoring_tool(self):
|
||||
monitor_settings = frappe.get_doc("Ledger Health Monitor")
|
||||
monitor_settings.enable_health_monitor = True
|
||||
monitor_settings.enable_for_last_x_days = 60
|
||||
monitor_settings.debit_credit_mismatch = True
|
||||
monitor_settings.general_and_payment_ledger_mismatch = True
|
||||
exists = [x for x in monitor_settings.companies if x.company == self.company]
|
||||
if not exists:
|
||||
monitor_settings.append("companies", {"company": self.company})
|
||||
monitor_settings.save()
|
||||
|
||||
def clear_old_entries(self):
|
||||
super().clear_old_entries()
|
||||
lh = qb.DocType("Ledger Health")
|
||||
qb.from_(lh).delete().run()
|
||||
|
||||
def create_journal(self):
|
||||
je = frappe.new_doc("Journal Entry")
|
||||
je.company = self.company
|
||||
je.voucher_type = "Journal Entry"
|
||||
je.posting_date = nowdate()
|
||||
je.append(
|
||||
"accounts",
|
||||
{
|
||||
"account": self.debit_to,
|
||||
"party_type": "Customer",
|
||||
"party": self.customer,
|
||||
"debit_in_account_currency": 10000,
|
||||
},
|
||||
)
|
||||
je.append("accounts", {"account": self.income_account, "credit_in_account_currency": 10000})
|
||||
je.save().submit()
|
||||
self.je = je
|
||||
|
||||
def test_debit_credit_mismatch(self):
|
||||
self.create_journal()
|
||||
|
||||
# manually cause debit-credit mismatch
|
||||
gle = frappe.db.get_all(
|
||||
"GL Entry", filters={"voucher_no": self.je.name, "account": self.income_account}
|
||||
)[0]
|
||||
frappe.db.set_value("GL Entry", gle.name, "credit", 8000)
|
||||
|
||||
run_ledger_health_checks()
|
||||
expected = {
|
||||
"voucher_type": self.je.doctype,
|
||||
"voucher_no": self.je.name,
|
||||
"debit_credit_mismatch": True,
|
||||
"general_and_payment_ledger_mismatch": False,
|
||||
}
|
||||
actual = frappe.db.get_all(
|
||||
"Ledger Health",
|
||||
fields=[
|
||||
"voucher_type",
|
||||
"voucher_no",
|
||||
"debit_credit_mismatch",
|
||||
"general_and_payment_ledger_mismatch",
|
||||
],
|
||||
)
|
||||
self.assertEqual(len(actual), 1)
|
||||
self.assertEqual(expected, actual[0])
|
||||
|
||||
def test_gl_and_pl_mismatch(self):
|
||||
self.create_journal()
|
||||
|
||||
# manually cause GL and PL discrepancy
|
||||
ple = frappe.db.get_all("Payment Ledger Entry", filters={"voucher_no": self.je.name})[0]
|
||||
frappe.db.set_value("Payment Ledger Entry", ple.name, "amount", 11000)
|
||||
|
||||
run_ledger_health_checks()
|
||||
expected = {
|
||||
"voucher_type": self.je.doctype,
|
||||
"voucher_no": self.je.name,
|
||||
"debit_credit_mismatch": False,
|
||||
"general_and_payment_ledger_mismatch": True,
|
||||
}
|
||||
actual = frappe.db.get_all(
|
||||
"Ledger Health",
|
||||
fields=[
|
||||
"voucher_type",
|
||||
"voucher_no",
|
||||
"debit_credit_mismatch",
|
||||
"general_and_payment_ledger_mismatch",
|
||||
],
|
||||
)
|
||||
self.assertEqual(len(actual), 1)
|
||||
self.assertEqual(expected, actual[0])
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user