Compare commits

..

1 Commits

Author SHA1 Message Date
rohitwaghchaure
4a748f45c9 fix: linter issue 2024-03-12 15:28:33 +05:30
1146 changed files with 20129 additions and 175180 deletions

View File

@@ -28,7 +28,4 @@ b147b85e6ac19a9220cd1e2958a6ebd99373283a
494bd9ef78313436f0424b918f200dab8fc7c20b
# bulk format python code with black
baec607ff5905b1c67531096a9cf50ec7ff00a5d
# ruff
960ef14b7a68cfec9e309ec12845f521cb6a721c
baec607ff5905b1c67531096a9cf50ec7ff00a5d

View File

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

View File

@@ -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=$!

View File

@@ -57,7 +57,7 @@ jobs:
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
- name: Cache pip
uses: actions/cache@v4
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }}
@@ -66,7 +66,7 @@ jobs:
${{ runner.os }}-
- name: Cache node modules
uses: actions/cache@v4
uses: actions/cache@v2
env:
cache-name: cache-node-modules
with:
@@ -81,7 +81,7 @@ jobs:
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v4
- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}

View File

@@ -76,7 +76,7 @@ jobs:
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
- name: Cache pip
uses: actions/cache@v4
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }}
@@ -85,7 +85,7 @@ jobs:
${{ runner.os }}-
- name: Cache node modules
uses: actions/cache@v4
uses: actions/cache@v2
env:
cache-name: cache-node-modules
with:
@@ -100,7 +100,7 @@ jobs:
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v4
- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}

View File

@@ -66,7 +66,7 @@ jobs:
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
- name: Cache pip
uses: actions/cache@v4
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }}
@@ -75,7 +75,7 @@ jobs:
${{ runner.os }}-
- name: Cache node modules
uses: actions/cache@v4
uses: actions/cache@v2
env:
cache-name: cache-node-modules
with:
@@ -90,7 +90,7 @@ jobs:
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v4
- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}

View File

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

View File

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

View File

@@ -2,9 +2,8 @@ import functools
import inspect
import frappe
from frappe.utils.user import is_website_user
__version__ = "15.51.1"
__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

View File

@@ -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"),
)

View File

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

View File

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

View File

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

View File

@@ -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,14 +123,13 @@
"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",
"fieldname": "tax_rate",
"fieldtype": "Float",
"label": "Tax Rate",
"label": "Rate",
"oldfieldname": "tax_rate",
"oldfieldtype": "Currency"
},
@@ -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
}
}

View File

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

View File

@@ -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",
]) {
@@ -237,22 +237,19 @@ frappe.treeview_settings["Account"] = {
},
post_render: function (treeview) {
frappe.treeview_settings["Account"].treeview["tree"] = treeview.tree;
if (treeview.can_create) {
treeview.page.set_primary_action(
__("New"),
function () {
let root_company = treeview.page.fields_dict.root_company.get_value();
if (root_company) {
frappe.throw(__("Please add the account to root level Company - {0}"), [
root_company,
]);
} else {
treeview.new_node();
}
},
"add"
);
}
treeview.page.set_primary_action(
__("New"),
function () {
let root_company = treeview.page.fields_dict.root_company.get_value();
if (root_company) {
frappe.throw(__("Please add the account to root level Company - {0}"), [root_company]);
} else {
treeview.new_node();
}
},
"add"
);
},
toolbar: [
{

View File

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

View File

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

View File

@@ -1,532 +0,0 @@
{
"country_code": "ch",
"name": "240812 Schulkontenrahmen VEB - DE",
"tree": {
"Aktiven": {
"account_number": "1",
"is_group": 1,
"root_type": "Asset",
"Umlaufvermögen": {
"account_number": "10",
"is_group": 1,
"Flüssige Mittel": {
"account_number": "100",
"is_group": 1,
"Kasse": {
"account_number": "1000",
"account_type": "Cash"
},
"Bankguthaben": {
"account_number": "1020",
"account_type": "Bank"
}
},
"Kurzfristig gehaltene Aktiven mit Börsenkurs": {
"account_number": "106",
"is_group": 1,
"Wertschriften": {
"account_number": "1060"
},
"Wertberichtigungen Wertschriften": {
"account_number": "1069"
}
},
"Forderungen aus Lieferungen und Leistungen": {
"account_number": "110",
"is_group": 1,
"Forderungen aus Lieferungen und Leistungen (Debitoren)": {
"account_number": "1100"
},
"Delkredere": {
"account_number": "1109"
}
},
"Übrige kurzfristige Forderungen": {
"account_number": "114",
"is_group": 1,
"Vorschüsse und Darlehen": {
"account_number": "1140"
},
"Wertberichtigungen Vorschüsse und Darlehen": {
"account_number": "1149"
},
"Vorsteuer MWST Material, Waren, Dienstleistungen, Energie": {
"account_number": "1170"
},
"Vorsteuer MWST Investitionen, übriger Betriebsaufwand": {
"account_number": "1171"
},
"Verrechnungssteuer": {
"account_number": "1176"
},
"Forderungen gegenüber Sozialversicherungen und Vorsorgeeinrichtungen": {
"account_number": "1180"
},
"Quellensteuer": {
"account_number": "1189"
},
"Sonstige kurzfristige Forderungen": {
"account_number": "1190"
},
"Wertberichtigungen sonstige kurzfristige Forderungen": {
"account_number": "1199"
}
},
"Vorräte und nicht fakturierte Dienstleistungen": {
"account_number": "120",
"is_group": 1,
"Handelswaren": {
"account_number": "1200"
},
"Rohstoffe": {
"account_number": "1210"
},
"Werkstoffe": {
"account_number": "1220"
},
"Hilfs- und Verbrauchsmaterial": {
"account_number": "1230"
},
"Handelswaren in Konsignation": {
"account_number": "1250"
},
"Fertige Erzeugnisse": {
"account_number": "1260"
},
"Unfertige Erzeugnisse": {
"account_number": "1270"
},
"Nicht fakturierte Dienstleistungen": {
"account_number": "1280"
}
},
"Aktive Rechnungsabgrenzungen": {
"account_number": "130",
"is_group": 1,
"Bezahlter Aufwand des Folgejahres": {
"account_number": "1300"
},
"Noch nicht erhaltener Ertrag": {
"account_number": "1301"
}
}
},
"Anlagevermögen": {
"account_number": "14",
"is_group": 1,
"Finanzanlagen": {
"account_number": "140",
"is_group": 1,
"Wertschriften": {
"account_number": "1400"
},
"Wertberichtigungen Wertschriften": {
"account_number": "1409"
},
"Darlehen": {
"account_number": "1440"
},
"Hypotheken": {
"account_number": "1441"
},
"Wertberichtigungen langfristige Forderungen": {
"account_number": "1449"
}
},
"Beteiligungen": {
"account_number": "148",
"is_group": 1,
"Beteiligungen": {
"account_number": "1480"
},
"Wertberichtigungen Beteiligungen": {
"account_number": "1489"
}
},
"Mobile Sachanlagen": {
"account_number": "150",
"is_group": 1,
"Maschinen und Apparate": {
"account_number": "1500"
},
"Wertberichtigungen Maschinen und Apparate": {
"account_number": "1509"
},
"Mobiliar und Einrichtungen": {
"account_number": "1510"
},
"Wertberichtigungen Mobiliar und Einrichtungen": {
"account_number": "1519"
},
"Büromaschinen, Informatik, Kommunikationstechnologie": {
"account_number": "1520"
},
"Wertberichtigungen Büromaschinen, Informatik, Kommunikationstechnologie": {
"account_number": "1529"
},
"Fahrzeuge": {
"account_number": "1530"
},
"Wertberichtigungen Fahrzeuge": {
"account_number": "1539"
},
"Werkzeuge und Geräte": {
"account_number": "1540"
},
"Wertberichtigungen Werkzeuge und Geräte": {
"account_number": "1549"
}
},
"Immobile Sachanlagen": {
"account_number": "160",
"is_group": 1,
"Geschäftsliegenschaften": {
"account_number": "1600"
},
"Wertberichtigungen Geschäftsliegenschaften": {
"account_number": "1609"
}
},
"Immaterielle Werte": {
"account_number": "170",
"is_group": 1,
"Patente, Know-how, Lizenzen, Rechte, Entwicklungen": {
"account_number": "1700"
},
"Wertberichtigungen Patente, Know-how, Lizenzen, Rechte, Entwicklungen": {
"account_number": "1709"
},
"Goodwill": {
"account_number": "1770"
},
"Wertberichtigungen Goodwill": {
"account_number": "1779"
}
},
"Nicht einbezahltes Grund-, Gesellschafter- oder Stiftungskapital": {
"account_number": "180",
"is_group": 1,
"Nicht einbezahltes Aktien-, Stamm-, Anteilschein- oder Stiftungskapital": {
"account_number": "1850"
}
}
}
},
"Passiven": {
"account_number": "2",
"is_group": 1,
"root_type": "Liability",
"Kurzfristiges Fremdkapital": {
"account_number": "20",
"is_group": 1,
"Verbindlichkeiten aus Lieferungen und Leistungen": {
"account_number": "200",
"is_group": 1,
"Verbindlichkeiten aus Lieferungen und Leistungen (Kreditoren)": {
"account_number": "2000"
},
"Erhaltene Anzahlungen": {
"account_number": "2030"
}
},
"Kurzfristige verzinsliche Verbindlichkeiten": {
"account_number": "210",
"is_group": 1,
"Bankverbindlichkeiten": {
"account_number": "2100"
},
"Verbindlichkeiten aus Finanzierungsleasing": {
"account_number": "2120"
},
"Übrige verzinsliche Verbindlichkeiten": {
"account_number": "2140"
}
},
"Übrige kurzfristige Verbindlichkeiten": {
"account_number": "220",
"is_group": 1,
"Geschuldete MWST (Umsatzsteuer)": {
"account_number": "2200"
},
"Abrechnungskonto MWST": {
"account_number": "2201"
},
"Verrechnungssteuer": {
"account_number": "2206"
},
"Direkte Steuern": {
"account_number": "2208"
},
"Sonstige kurzfristige Verbindlichkeiten": {
"account_number": "2210"
},
"Beschlossene Ausschüttungen": {
"account_number": "2261"
},
"Sozialversicherungen und Vorsorgeeinrichtungen": {
"account_number": "2270"
},
"Quellensteuer": {
"account_number": "2279"
}
},
"Passive Rechnungsabgrenzungen und kurzfristige Rückstellungen": {
"account_number": "230",
"is_group": 1,
"Noch nicht bezahlter Aufwand": {
"account_number": "2300"
},
"Erhaltener Ertrag des Folgejahres": {
"account_number": "2301"
},
"Kurzfristige Rückstellungen": {
"account_number": "2330"
}
}
},
"Langfristiges Fremdkapital": {
"account_number": "24",
"is_group": 1,
"Langfristige verzinsliche Verbindlichkeiten": {
"account_number": "240",
"is_group": 1,
"Bankverbindlichkeiten": {
"account_number": "2400"
},
"Verbindlichkeiten aus Finanzierungsleasing": {
"account_number": "2420"
},
"Obligationenanleihen": {
"account_number": "2430"
},
"Darlehen": {
"account_number": "2450"
},
"Hypotheken": {
"account_number": "2451"
}
},
"Übrige langfristige Verbindlichkeiten": {
"account_number": "250",
"is_group": 1,
"Übrige langfristige Verbindlichkeiten (unverzinslich)": {
"account_number": "2500"
}
},
"Rückstellungen sowie vom Gesetz vorgesehene ähnliche Positionen": {
"account_number": "260",
"is_group": 1,
"Rückstellungen": {
"account_number": "2600"
}
}
},
"Eigenkapital (juristische Personen)": {
"account_number": "28",
"is_group": 1,
"Grund-, Gesellschafter- oder Stiftungskapital": {
"account_number": "280",
"is_group": 1,
"Aktien-, Stamm-, Anteilschein- oder Stiftungskapital": {
"account_number": "2800"
}
},
"Reserven und Jahresgewinn oder Jahresverlust": {
"account_number": "290",
"is_group": 1,
"Gesetzliche Kapitalreserve": {
"account_number": "2900"
},
"Reserve für eigene Kapitalanteile": {
"account_number": "2930"
},
"Aufwertungsreserve": {
"account_number": "2940"
},
"Gesetzliche Gewinnreserve": {
"account_number": "2950"
},
"Freiwillige Gewinnreserven": {
"account_number": "2960"
},
"Gewinnvortrag oder Verlustvortrag": {
"account_number": "2970"
},
"Jahresgewinn oder Jahresverlust": {
"account_number": "2979"
},
"Eigene Aktien, Stammanteile oder Anteilscheine (Minusposten)": {
"account_number": "2980"
}
}
}
},
"Betrieblicher Ertrag aus Lieferungen und Leistungen": {
"account_number": "3",
"is_group": 1,
"root_type": "Income",
"Produktionserlöse": {
"account_number": "3000"
},
"Handelserlöse": {
"account_number": "3200"
},
"Dienstleistungserlöse": {
"account_number": "3400"
},
"Übrige Erlöse aus Lieferungen und Leistungen": {
"account_number": "3600"
},
"Eigenleistungen": {
"account_number": "3700"
},
"Eigenverbrauch": {
"account_number": "3710"
},
"Erlösminderungen": {
"account_number": "3800"
},
"Verluste Forderungen (Debitoren), Veränderung Delkredere": {
"account_number": "3805"
},
"Bestandesänderungen unfertige Erzeugnisse": {
"account_number": "3900"
},
"Bestandesänderungen fertige Erzeugnisse": {
"account_number": "3901"
},
"Bestandesänderungen nicht fakturierte Dienstleistungen": {
"account_number": "3940"
}
},
"Aufwand für Material, Handelswaren, Dienstleistungen und Energie": {
"account_number": "4",
"is_group": 1,
"root_type": "Expense",
"Materialaufwand Produktion": {
"account_number": "4000"
},
"Handelswarenaufwand": {
"account_number": "4200"
},
"Aufwand für bezogene Dienstleistungen": {
"account_number": "4400"
},
"Energieaufwand zur Leistungserstellung": {
"account_number": "4500"
},
"Aufwandminderungen": {
"account_number": "4900"
}
},
"Personalaufwand": {
"account_number": "5",
"is_group": 1,
"root_type": "Expense",
"Lohnaufwand": {
"account_number": "5000"
},
"Sozialversicherungsaufwand": {
"account_number": "5700"
},
"Übriger Personalaufwand": {
"account_number": "5800"
},
"Leistungen Dritter": {
"account_number": "5900"
}
},
"Übriger betrieblicher Aufwand, Abschreibungen und Wertberichtigungen sowie Finanzergebnis": {
"account_number": "6",
"is_group": 1,
"root_type": "Expense",
"Raumaufwand": {
"account_number": "6000"
},
"Unterhalt, Reparaturen, Ersatz mobile Sachanlagen": {
"account_number": "6100"
},
"Leasingaufwand mobile Sachanlagen": {
"account_number": "6105"
},
"Fahrzeug- und Transportaufwand": {
"account_number": "6200"
},
"Fahrzeugleasing und -mieten": {
"account_number": "6260"
},
"Sachversicherungen, Abgaben, Gebühren, Bewilligungen": {
"account_number": "6300"
},
"Energie- und Entsorgungsaufwand": {
"account_number": "6400"
},
"Verwaltungsaufwand": {
"account_number": "6500"
},
"Informatikaufwand inkl. Leasing": {
"account_number": "6570"
},
"Werbeaufwand": {
"account_number": "6600"
},
"Sonstiger betrieblicher Aufwand": {
"account_number": "6700"
},
"Abschreibungen und Wertberichtigungen auf Positionen des Anlagevermögens": {
"account_number": "6800"
},
"Finanzaufwand": {
"account_number": "6900"
},
"Finanzertrag": {
"account_number": "6950"
}
},
"Betrieblicher Nebenerfolg": {
"account_number": "7",
"is_group": 1,
"root_type": "Income",
"Ertrag Nebenbetrieb": {
"account_number": "7000"
},
"Aufwand Nebenbetrieb": {
"account_number": "7010"
},
"Ertrag betriebliche Liegenschaft": {
"account_number": "7500"
},
"Aufwand betriebliche Liegenschaft": {
"account_number": "7510"
}
},
"Betriebsfremder, ausserordentlicher, einmaliger oder periodenfremder Aufwand und Ertrag": {
"account_number": "8",
"is_group": 1,
"root_type": "Expense",
"Betriebsfremder Aufwand": {
"account_number": "8000"
},
"Betriebsfremder Ertrag": {
"account_number": "8100"
},
"Ausserordentlicher, einmaliger oder periodenfremder Aufwand": {
"account_number": "8500"
},
"Ausserordentlicher, einmaliger oder periodenfremder Ertrag": {
"account_number": "8510"
},
"Direkte Steuern": {
"account_number": "8900"
}
},
"Abschluss": {
"account_number": "9",
"is_group": 1,
"root_type": "Equity",
"Jahresgewinn oder Jahresverlust": {
"account_number": "9200"
}
}
}
}

View File

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

View File

@@ -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": {

View File

@@ -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",
},
}

View File

@@ -1,34 +0,0 @@
import json
from pathlib import Path
syscohada_countries = [
"bj", # Bénin
"bf", # Burkina-Faso
"cm", # Cameroun
"cf", # Centrafrique
"ci", # Côte d'Ivoire
"cg", # Congo
"km", # Comores
"ga", # Gabon
"gn", # Guinée
"gw", # Guinée-Bissau
"gq", # Guinée Equatoriale
"ml", # Mali
"ne", # Niger
"cd", # République Démocratique du Congo
"sn", # Sénégal
"td", # Tchad
"tg", # Togo
]
folder = Path(__file__).parent
generic_charts = Path(folder).glob("syscohada*.json")
for file in generic_charts:
with open(file) as f:
chart = json.load(f)
for country in syscohada_countries:
chart["country_code"] = country
json_object = json.dumps(chart, indent=4)
with open(Path(folder, file.name.replace("syscohada", country)), "w") as outfile:
outfile.write(json_object)

View File

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

View File

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

View File

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

View File

@@ -31,8 +31,7 @@
"label": "Reference Document Type",
"options": "DocType",
"read_only_depends_on": "eval:!doc.__islocal",
"reqd": 1,
"search_index": 1
"reqd": 1
},
{
"default": "0",

View File

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

View File

@@ -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():

View File

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

View File

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

View File

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

View File

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

View File

@@ -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);
}
);
},
});

View File

@@ -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",
@@ -40,14 +39,9 @@
"show_payment_schedule_in_print",
"currency_exchange_section",
"allow_stale",
"column_break_yuug",
"stale_days",
"section_break_jpd0",
"auto_reconcile_payments",
"auto_reconciliation_job_trigger",
"reconciliation_queue_size",
"column_break_resa",
"exchange_gain_loss_posting_date",
"stale_days",
"invoicing_settings_tab",
"accounts_transactions_settings_section",
"over_billing_allowance",
@@ -60,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",
@@ -77,11 +69,8 @@
"reports_tab",
"remarks_section",
"general_ledger_remarks_length",
"ignore_is_opening_check_for_reporting",
"column_break_lvjk",
"receivable_payable_remarks_length",
"payment_request_settings",
"create_pr_in_draft_status"
"receivable_payable_remarks_length"
],
"fields": [
{
@@ -116,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"
@@ -390,7 +379,7 @@
{
"fieldname": "section_break_jpd0",
"fieldtype": "Section Break",
"label": "Payment Reconciliation Settings"
"label": "Payment Reconciliations"
},
{
"default": "0",
@@ -465,73 +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"
},
{
"fieldname": "column_break_yuug",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_resa",
"fieldtype": "Column Break"
},
{
"default": "15",
"description": "Interval should be between 1 to 59 MInutes",
"fieldname": "auto_reconciliation_job_trigger",
"fieldtype": "Int",
"label": "Auto Reconciliation Job Trigger"
},
{
"default": "5",
"description": "Documents Processed on each trigger. Queue Size should be between 5 and 100",
"fieldname": "reconciliation_queue_size",
"fieldtype": "Int",
"label": "Reconciliation Queue Size"
},
{
"default": "0",
"description": "Ignores legacy Is Opening field in GL Entry that allows adding opening balance post the system is in use while generating reports",
"fieldname": "ignore_is_opening_check_for_reporting",
"fieldtype": "Check",
"label": "Ignore Is Opening check for reporting"
},
{
"default": "Payment",
"description": "Only applies for Normal Payments",
"fieldname": "exchange_gain_loss_posting_date",
"fieldtype": "Select",
"label": "Posting Date Inheritance for Exchange Gain / Loss",
"options": "Invoice\nPayment\nReconciliation Date"
}
],
"icon": "icon-cog",
@@ -539,7 +461,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2025-01-23 13:15:44.077853",
"modified": "2024-01-30 14:04:26.553554",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",

View File

@@ -10,7 +10,6 @@ from frappe.custom.doctype.property_setter.property_setter import make_property_
from frappe.model.document import Document
from frappe.utils import cint
from erpnext.accounts.utils import sync_auto_reconcile_config
from erpnext.stock.utils import check_pending_reposting
@@ -28,34 +27,27 @@ class AccountsSettings(Document):
allow_multi_currency_invoices_against_single_party_account: DF.Check
allow_stale: DF.Check
auto_reconcile_payments: DF.Check
auto_reconciliation_job_trigger: DF.Int
automatically_fetch_payment_terms: DF.Check
automatically_process_deferred_accounting_entry: DF.Check
book_asset_depreciation_entry_automatically: DF.Check
book_deferred_entries_based_on: DF.Literal["Days", "Months"]
book_deferred_entries_via_journal_entry: DF.Check
book_tax_discount_loss: DF.Check
calculate_depr_using_total_days: DF.Check
check_supplier_invoice_uniqueness: DF.Check
create_pr_in_draft_status: DF.Check
credit_controller: DF.Link | None
delete_linked_ledger_entries: DF.Check
determine_address_tax_category_from: DF.Literal["Billing Address", "Shipping Address"]
enable_common_party_accounting: DF.Check
enable_fuzzy_matching: DF.Check
enable_immutable_ledger: DF.Check
enable_party_matching: DF.Check
exchange_gain_loss_posting_date: DF.Literal["Invoice", "Payment", "Reconciliation Date"]
frozen_accounts_modifier: DF.Link | None
general_ledger_remarks_length: DF.Int
ignore_account_closing_balance: DF.Check
ignore_is_opening_check_for_reporting: DF.Check
make_payment_via_journal_entry: DF.Check
merge_similar_account_heads: DF.Check
over_billing_allowance: DF.Currency
post_change_gl_entries: DF.Check
receivable_payable_remarks_length: DF.Int
reconciliation_queue_size: DF.Int
role_allowed_to_over_bill: DF.Link | None
round_row_wise_tax: DF.Check
show_balance_in_coa: DF.Check
@@ -95,8 +87,6 @@ class AccountsSettings(Document):
if clear_cache:
frappe.clear_cache()
self.validate_and_sync_auto_reconcile_config()
def validate_stale_days(self):
if not self.allow_stale and cint(self.stale_days) <= 0:
frappe.msgprint(
@@ -121,17 +111,3 @@ class AccountsSettings(Document):
def validate_pending_reposts(self):
if self.acc_frozen_upto:
check_pending_reposting(self.acc_frozen_upto)
def validate_and_sync_auto_reconcile_config(self):
if self.has_value_changed("auto_reconciliation_job_trigger"):
if (
cint(self.auto_reconciliation_job_trigger) > 0
and cint(self.auto_reconciliation_job_trigger) < 60
):
sync_auto_reconcile_config(self.auto_reconciliation_job_trigger)
else:
frappe.throw(_("Cron Interval should be between 1 and 59 Min"))
if self.has_value_changed("reconciliation_queue_size"):
if cint(self.reconciliation_queue_size) < 5 or cint(self.reconciliation_queue_size) > 100:
frappe.throw(_("Queue Size should be between 5 and 100"))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -48,13 +48,12 @@ class BankAccount(Document):
self.name = self.account_name + " - " + self.bank
def on_trash(self):
delete_contact_and_address("Bank Account", self.name)
delete_contact_and_address("BankAccount", self.name)
def validate(self):
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()

View File

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

View File

@@ -37,11 +37,11 @@ class TestBankAccount(unittest.TestCase):
try:
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()

View File

@@ -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");
}
},
});
},

View File

@@ -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:
# using db_set to trigger notification
payment_entry = frappe.get_doc(d.payment_document, d.payment_entry)
payment_entry.db_set("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

View File

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

View File

@@ -19,15 +19,10 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
},
onload: function (frm) {
if (!frm.doc.company) {
frm.set_value("company", frappe.defaults.get_default("company"));
}
// Set default filter dates
let today = frappe.datetime.get_today();
frm.doc.bank_statement_from_date = frappe.datetime.add_months(today, -1);
frm.doc.bank_statement_to_date = today;
frm.trigger("bank_account");
},
@@ -64,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: {
@@ -103,7 +94,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
make_reconciliation_tool(frm) {
frm.get_field("reconciliation_tool_cards").$wrapper.empty();
if (frm.doc.company && frm.doc.bank_account && frm.doc.bank_statement_to_date) {
if (frm.doc.bank_account && frm.doc.bank_statement_to_date) {
frm.trigger("get_cleared_balance").then(() => {
if (
frm.doc.bank_account &&
@@ -119,13 +110,12 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
},
get_account_opening_balance(frm) {
if (frm.doc.company && frm.doc.bank_account && frm.doc.bank_statement_from_date) {
if (frm.doc.bank_account && frm.doc.bank_statement_from_date) {
frappe.call({
method: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance",
args: {
bank_account: frm.doc.bank_account,
till_date: frappe.datetime.add_days(frm.doc.bank_statement_from_date, -1),
company: frm.doc.company,
},
callback: (response) => {
frm.set_value("account_opening_balance", response.message);
@@ -135,13 +125,12 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
},
get_cleared_balance(frm) {
if (frm.doc.company && frm.doc.bank_account && frm.doc.bank_statement_to_date) {
if (frm.doc.bank_account && frm.doc.bank_statement_to_date) {
return frappe.call({
method: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance",
args: {
bank_account: frm.doc.bank_account,
till_date: frm.doc.bank_statement_to_date,
company: frm.doc.company,
},
callback: (response) => {
frm.cleared_balance = response.message;

View File

@@ -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": []
}
}

View File

@@ -12,7 +12,6 @@ from frappe.utils import cint, flt
from erpnext import get_default_cost_center
from erpnext.accounts.doctype.bank_transaction.bank_transaction import get_total_allocated_amount
from erpnext.accounts.party import get_party_account
from erpnext.accounts.report.bank_reconciliation_statement.bank_reconciliation_statement import (
get_amounts_not_reflected_in_system,
get_entries,
@@ -79,16 +78,11 @@ def get_bank_transactions(bank_account, from_date=None, to_date=None):
@frappe.whitelist()
def get_account_balance(bank_account, till_date, company):
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,
"company": company,
}
{"account": account, "report_date": till_date, "include_pos_transactions": 1}
)
data = get_entries(filters)
@@ -101,7 +95,14 @@ def get_account_balance(bank_account, till_date, company):
amounts_not_reflected_in_system = get_amounts_not_reflected_in_system(filters)
return flt(balance_as_per_system) - flt(total_debit) + flt(total_credit) + amounts_not_reflected_in_system
bank_bal = (
flt(balance_as_per_system)
- flt(total_debit)
+ flt(total_credit)
+ amounts_not_reflected_in_system
)
return bank_bal
@frappe.whitelist()
@@ -308,56 +309,54 @@ def create_payment_entry_bts(
bank_transaction = frappe.db.get_values(
"Bank Transaction",
bank_transaction_name,
fieldname=["name", "unallocated_amount", "deposit", "bank_account", "currency"],
fieldname=["name", "unallocated_amount", "deposit", "bank_account"],
as_dict=True,
)[0]
paid_amount = bank_transaction.unallocated_amount
payment_type = "Receive" if bank_transaction.deposit > 0.0 else "Pay"
bank_account = frappe.get_cached_value("Bank Account", bank_transaction.bank_account, "account")
company = frappe.get_cached_value("Account", bank_account, "company")
party_account = get_party_account(party_type, party, company)
company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account")
company = frappe.get_value("Account", company_account, "company")
payment_entry_dict = {
"company": company,
"payment_type": payment_type,
"reference_no": reference_number,
"reference_date": reference_date,
"party_type": party_type,
"party": party,
"posting_date": posting_date,
"paid_amount": paid_amount,
"received_amount": paid_amount,
}
payment_entry = frappe.new_doc("Payment Entry")
bank_currency = bank_transaction.currency
party_currency = frappe.get_cached_value("Account", party_account, "account_currency")
payment_entry.update(payment_entry_dict)
exc_rate = get_exchange_rate(bank_currency, party_currency, posting_date)
if mode_of_payment:
payment_entry.mode_of_payment = mode_of_payment
if project:
payment_entry.project = project
if cost_center:
payment_entry.cost_center = cost_center
if payment_type == "Receive":
payment_entry.paid_to = company_account
else:
payment_entry.paid_from = company_account
amt_in_bank_acc_currency = bank_transaction.unallocated_amount
amount_in_party_currency = bank_transaction.unallocated_amount * exc_rate
pe = frappe.new_doc("Payment Entry")
pe.payment_type = payment_type
pe.company = company
pe.reference_no = reference_number
pe.reference_date = reference_date
pe.party_type = party_type
pe.party = party
pe.posting_date = posting_date
pe.paid_from = party_account if payment_type == "Receive" else bank_account
pe.paid_to = party_account if payment_type == "Pay" else bank_account
pe.paid_from_account_currency = party_currency if payment_type == "Receive" else bank_currency
pe.paid_to_account_currency = party_currency if payment_type == "Pay" else bank_currency
pe.paid_amount = amount_in_party_currency if payment_type == "Receive" else amt_in_bank_acc_currency
pe.received_amount = amount_in_party_currency if payment_type == "Pay" else amt_in_bank_acc_currency
pe.mode_of_payment = mode_of_payment
pe.project = project
pe.cost_center = cost_center
pe.validate()
payment_entry.validate()
if allow_edit:
return pe
return payment_entry
pe.insert()
pe.submit()
payment_entry.insert()
payment_entry.submit()
vouchers = json.dumps(
[
{
"payment_doctype": "Payment Entry",
"payment_name": pe.name,
"amount": amt_in_bank_acc_currency,
"payment_name": payment_entry.name,
"amount": paid_amount,
}
]
)
@@ -486,12 +485,8 @@ def get_linked_payments(
def subtract_allocations(gl_account, vouchers):
"Look up & subtract any existing Bank Transaction allocations"
copied = []
voucher_docs = [(voucher.get("doctype"), voucher.get("name")) for voucher in vouchers]
voucher_allocated_amounts = get_total_allocated_amount(voucher_docs)
for voucher in vouchers:
rows = voucher_allocated_amounts.get((voucher.get("doctype"), voucher.get("name"))) or []
rows = get_total_allocated_amount(voucher.get("doctype"), voucher.get("name"))
filtered_row = list(filter(lambda row: row.get("gl_account") == gl_account, rows))
if amount := None if not filtered_row else filtered_row[0]["total"]:
@@ -505,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
@@ -543,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"
@@ -590,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)
@@ -657,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)
@@ -715,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)
@@ -729,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.base_paid_amount_after_tax.as_("paid_amount"),
pe.paid_amount,
pe.reference_no,
pe.reference_date,
pe.party,
@@ -746,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
@@ -802,11 +805,12 @@ def get_je_matching_query(
.where(je.clearance_date.isnull())
.where(jea.account == common_filters.bank_account)
.where(amount_equality if exact_match else getattr(jea, amount_field) > 0.0)
.where(je.docstatus == 1)
.where(filter_by_date)
.orderby(je.cheque_date if cint(filter_by_reference_date) else je.posting_date)
)
if frappe.flags.auto_reconcile_vouchers is True:
if frappe.flags.auto_reconcile_vouchers == True:
query = query.where(ref_condition)
return query

View File

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

View File

@@ -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"));
}
},
});
},
});

View File

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

View File

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

View File

@@ -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,
@@ -45,41 +47,42 @@ class AutoMatchbyAccountIBAN:
if not (self.bank_party_account_number or self.bank_party_iban):
return None
return self.match_account_in_party()
result = self.match_account_in_party()
return result
def match_account_in_party(self) -> tuple | None:
"""
Returns (Party Type, Party) if a matching account is found in Bank Account or Employee:
1. Get party from a matching (iban/account no) Bank Account
2. If not found, get party from Employee with matching bank account details (iban/account no)
"""
if not (self.bank_party_account_number or self.bank_party_iban):
# Nothing to match
return 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)
or_filters = self.get_or_filters()
# Search for a matching Bank Account that has party set
party_result = frappe.db.get_all(
"Bank Account",
or_filters=self.get_or_filters(),
filters={"party_type": ("is", "set"), "party": ("is", "set")},
fields=["party", "party_type"],
limit_page_length=1,
)
if result := party_result[0] if party_result else None:
return (result["party_type"], result["party"])
for party in parties:
party_result = frappe.db.get_all(
"Bank Account", or_filters=or_filters, pluck="party", limit_page_length=1
)
# If no party is found, search in Employee (since it has bank account details)
if employee_result := frappe.db.get_all(
"Employee", or_filters=self.get_or_filters("Employee"), pluck="name", limit_page_length=1
):
return ("Employee", employee_result[0])
if party == "Employee" and not party_result:
# Search in Bank Accounts first for Employee, and then Employee record
if "bank_account_no" in or_filters:
or_filters["bank_ac_no"] = or_filters.pop("bank_account_no")
def get_or_filters(self, party: str | None = None) -> dict:
"""Return OR filters for Bank Account and IBAN"""
party_result = frappe.db.get_all(
party, or_filters=or_filters, pluck="name", limit_page_length=1
)
if party_result:
result = (
party,
party_result[0],
)
break
return result
def get_or_filters(self) -> dict:
or_filters = {}
if self.bank_party_account_number:
bank_ac_field = "bank_ac_no" if party == "Employee" else "bank_account_no"
or_filters[bank_ac_field] = self.bank_party_account_number
or_filters["bank_account_no"] = self.bank_party_account_number
if self.bank_party_iban:
or_filters["iban"] = self.bank_party_iban
@@ -94,22 +97,22 @@ 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
return self.match_party_name_desc_in_party()
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)
for party in parties:
filters = {"status": "Active"} if party == "Employee" else {"disabled": 0}
field = f"{party.lower()}_name"
names = frappe.get_all(party, filters=filters, fields=[f"{field} as party_name", "name"])
names = frappe.get_all(party, filters=filters, pluck=party.lower() + "_name")
for field in ["bank_party_name", "description"]:
if not self.get(field):
@@ -126,48 +129,50 @@ 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={row.get("name"): row.get("party_name") for row in names},
scorer=fuzz.token_set_ratio,
)
result = process.extract(query=self.get(field), choices=names, scorer=fuzz.token_set_ratio)
party_name, skip = self.process_fuzzy_result(result)
return ((party, party_name), skip) if party_name else (None, skip)
if not party_name:
return None, skip
def process_fuzzy_result(self, result: list | None):
return (
party,
party_name,
), skip
def process_fuzzy_result(self, result: Union[list, None]):
"""
If there are multiple valid close matches return None as result may be faulty.
Return the result only if one accurate match stands out.
Returns: Result, Skip (whether or not to discontinue matching)
"""
SCORE, PARTY_ID, CUTOFF = 1, 2, 80
PARTY, SCORE, CUTOFF = 0, 1, 80
if not result or not len(result):
return None, False
first_result = result[0]
if len(result) == 1:
return (first_result[PARTY_ID] if first_result[SCORE] > CUTOFF else None), True
return (first_result[PARTY] if first_result[SCORE] > CUTOFF else None), True
second_result = result[1]
if first_result[SCORE] > CUTOFF:
second_result = result[1]
# If multiple matches with the same score, return None but discontinue matching
# Matches were found but were too close to distinguish between
if first_result[SCORE] == second_result[SCORE]:
return None, True
return first_result[PARTY_ID], True
return first_result[PARTY], True
else:
return None, False
def get_parties_in_order(deposit: float) -> list:
return (
["Customer", "Supplier", "Employee"] # most -> least likely to pay us
if flt(deposit) > 0
else ["Supplier", "Employee", "Customer"] # most -> least likely to receive from us
)
parties = ["Supplier", "Employee", "Customer"] # most -> least likely to receive
if flt(deposit) > 0:
parties = ["Customer", "Supplier", "Employee"] # most -> least likely to pay
return parties

View File

@@ -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:
@@ -154,16 +152,10 @@ class BankTransaction(Document):
"""
remaining_amount = self.unallocated_amount
to_remove = []
payment_entry_docs = [(pe.payment_document, pe.payment_entry) for pe in self.payment_entries]
pe_bt_allocations = get_total_allocated_amount(payment_entry_docs)
for payment_entry in self.payment_entries:
if payment_entry.allocated_amount == 0.0:
unallocated_amount, should_clear, latest_transaction = get_clearance_details(
self,
payment_entry,
pe_bt_allocations.get((payment_entry.payment_document, payment_entry.payment_entry))
or [],
self, payment_entry
)
if 0.0 == unallocated_amount:
@@ -188,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):
@@ -214,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
@@ -238,7 +226,7 @@ def get_doctypes_for_bank_reconciliation():
return frappe.get_hooks("bank_reconciliation_doctypes")
def get_clearance_details(transaction, payment_entry, bt_allocations):
def get_clearance_details(transaction, payment_entry):
"""
There should only be one bank gle for a voucher.
Could be none for a Bank Transaction.
@@ -247,6 +235,9 @@ def get_clearance_details(transaction, payment_entry, bt_allocations):
"""
gl_bank_account = frappe.db.get_value("Bank Account", transaction.bank_account, "account")
gles = get_related_bank_gl_entries(payment_entry.payment_document, payment_entry.payment_entry)
bt_allocations = get_total_allocated_amount(
payment_entry.payment_document, payment_entry.payment_entry
)
unallocated_amount = min(
transaction.unallocated_amount,
@@ -299,56 +290,49 @@ def get_related_bank_gl_entries(doctype, docname):
)
def get_total_allocated_amount(docs):
def get_total_allocated_amount(doctype, docname):
"""
Gets the sum of allocations for a voucher on each bank GL account
along with the latest bank transaction name & date
NOTE: query may also include just saved vouchers/payments but with zero allocated_amount
"""
if not docs:
return {}
# nosemgrep: frappe-semgrep-rules.rules.frappe-using-db-sql
result = frappe.db.sql(
"""
SELECT total, latest_name, latest_date, gl_account, payment_document, payment_entry FROM (
SELECT total, latest_name, latest_date, gl_account FROM (
SELECT
ROW_NUMBER() OVER w AS rownum,
SUM(btp.allocated_amount) OVER(PARTITION BY ba.account, btp.payment_document, btp.payment_entry) AS total,
SUM(btp.allocated_amount) OVER(PARTITION BY ba.account) AS total,
FIRST_VALUE(bt.name) OVER w AS latest_name,
FIRST_VALUE(bt.date) OVER w AS latest_date,
ba.account AS gl_account,
btp.payment_document,
btp.payment_entry
ba.account AS gl_account
FROM
`tabBank Transaction Payments` btp
LEFT JOIN `tabBank Transaction` bt ON bt.name=btp.parent
LEFT JOIN `tabBank Account` ba ON ba.name=bt.bank_account
WHERE
(btp.payment_document, btp.payment_entry) IN %(docs)s
btp.payment_document = %(doctype)s
AND btp.payment_entry = %(docname)s
AND bt.docstatus = 1
WINDOW w AS (PARTITION BY ba.account, btp.payment_document, btp.payment_entry ORDER BY bt.date DESC)
WINDOW w AS (PARTITION BY ba.account ORDER BY bt.date desc)
) temp
WHERE
rownum = 1
""",
dict(docs=docs),
dict(doctype=doctype, docname=docname),
as_dict=True,
)
payment_allocation_details = {}
for row in result:
# Why is this *sometimes* a byte string?
if isinstance(row["latest_name"], bytes):
row["latest_name"] = row["latest_name"].decode()
row["latest_date"] = frappe.utils.getdate(row["latest_date"])
payment_allocation_details.setdefault((row["payment_document"], row["payment_entry"]), []).append(row)
return payment_allocation_details
return result
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)
@@ -387,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(
@@ -397,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
)
)

View File

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

View File

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

View File

@@ -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");
},
});
},
});

View File

@@ -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": []
}

View File

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

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