Compare commits

..

1 Commits

Author SHA1 Message Date
RitvikSardana
3714b795d6 fix: POS opening issue because of Product Bundle 2023-09-18 17:22:02 +05:30
1583 changed files with 84358 additions and 57514 deletions

View File

@@ -9,13 +9,6 @@ trim_trailing_whitespace = true
charset = utf-8 charset = utf-8
# python, js indentation settings # python, js indentation settings
[{*.py,*.js,*.vue,*.css,*.scss,*.html}] [{*.py,*.js}]
indent_style = tab indent_style = tab
indent_size = 4 indent_size = 4
max_line_length = 110
# JSON files - mostly doctype schema files
[{*.json}]
insert_final_newline = false
indent_style = space
indent_size = 2

View File

@@ -156,7 +156,6 @@
"onScan": true, "onScan": true,
"html2canvas": true, "html2canvas": true,
"extend_cscript": true, "extend_cscript": true,
"localforage": true, "localforage": true
"Plaid": true
} }
} }

View File

@@ -29,6 +29,3 @@ b147b85e6ac19a9220cd1e2958a6ebd99373283a
# bulk format python code with black # bulk format python code with black
baec607ff5905b1c67531096a9cf50ec7ff00a5d baec607ff5905b1c67531096a9cf50ec7ff00a5d
# ruff
4d34b1ead73baf4c5430a2ecbe44b9e8468d7626

View File

@@ -23,7 +23,7 @@ jobs:
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules
- name: Download semgrep - name: Download semgrep
run: pip install semgrep run: pip install semgrep==0.97.0
- name: Run Semgrep rules - name: Run Semgrep rules
run: semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness run: semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness

View File

@@ -17,7 +17,7 @@ jobs:
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v2 uses: actions/setup-node@v2
with: with:
node-version: 20 node-version: 18
- name: Setup dependencies - name: Setup dependencies
run: | run: |

View File

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

View File

@@ -5,7 +5,7 @@ fail_fast: false
repos: repos:
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0 rev: v4.0.1
hooks: hooks:
- id: trailing-whitespace - id: trailing-whitespace
files: "erpnext.*" files: "erpnext.*"
@@ -15,68 +15,29 @@ repos:
args: ['--branch', 'develop'] args: ['--branch', 'develop']
- id: check-merge-conflict - id: check-merge-conflict
- id: check-ast - id: check-ast
- id: check-json
- id: check-toml
- id: check-yaml
- id: debug-statements
- repo: https://github.com/pre-commit/mirrors-prettier - repo: https://github.com/PyCQA/flake8
rev: v2.7.1 rev: 5.0.4
hooks: hooks:
- id: prettier - id: flake8
types_or: [javascript, vue, scss] additional_dependencies: [
# Ignore any files that might contain jinja / bundles 'flake8-bugbear',
exclude: | ]
(?x)^( args: ['--config', '.github/helper/.flake8_strict']
erpnext/public/dist/.*| exclude: ".*setup.py$"
cypress/.*|
.*node_modules.*|
.*boilerplate.*|
erpnext/public/js/controllers/.*|
erpnext/templates/pages/order.js|
erpnext/templates/includes/.*|
.*/supplier_quotation.js|
.*/sales_taxes_and_charges_template.js|
.*/purchase_taxes_and_charges_template.js|
.*/subcontracting_order.js|
.*/landed_cost_voucher.js|
.*/payment_entry.js|
.*/loan_interest_accrual.js|
.*/loan_disbursement.js|
.*/loan_application.js|
.*/italy.js|
.*/sales_invoice.js|
.*/subcontracting_receipt.js|
.*/request_for_quotation.js|
.*/pos_profile.js|
.*/opportunity.js|
.*/quotation.js|
.*/sales_common.js|
.*/sales_order.js|
.*/pos_invoice.js|
.*/purchase_invoice.js|
.*/loan_repayment.js|
.*/material_request.js|
.*/purchase_receipt.js|
.*/delivery_note.js|
.*/loan.js|
.*/stock_entry.js|
.*/purchase_order.js|
.*/loan_write_off.js
)$
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/adityahase/black
rev: v0.2.0 rev: 9cb0a69f4d0030cdf687eddf314468b39ed54119
hooks: hooks:
- id: ruff - id: black
name: "Run ruff import sorter" additional_dependencies: ['click==8.0.4']
args: ["--select=I", "--fix"]
- id: ruff - repo: https://github.com/PyCQA/isort
name: "Run ruff linter" rev: 5.12.0
hooks:
- id: isort
exclude: ".*setup.py$"
- id: ruff-format
name: "Run ruff formatter"
ci: ci:
autoupdate_schedule: weekly autoupdate_schedule: weekly

View File

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

View File

@@ -3,7 +3,7 @@ import inspect
import frappe import frappe
__version__ = "14.74.1" __version__ = "14.34.3"
def get_default_company(user=None): def get_default_company(user=None):
@@ -13,7 +13,7 @@ def get_default_company(user=None):
if not user: if not user:
user = frappe.session.user user = frappe.session.user
companies = get_user_default_as_list("company", user) companies = get_user_default_as_list(user, "company")
if companies: if companies:
default_company = companies[0] default_company = companies[0]
else: else:
@@ -36,8 +36,10 @@ def get_default_cost_center(company):
if not frappe.flags.company_cost_center: if not frappe.flags.company_cost_center:
frappe.flags.company_cost_center = {} frappe.flags.company_cost_center = {}
if company not in frappe.flags.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") frappe.flags.company_cost_center[company] = frappe.get_cached_value(
"Company", company, "cost_center"
)
return frappe.flags.company_cost_center[company] return frappe.flags.company_cost_center[company]
@@ -45,7 +47,7 @@ def get_company_currency(company):
"""Returns the default company currency""" """Returns the default company currency"""
if not frappe.flags.company_currency: if not frappe.flags.company_currency:
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( frappe.flags.company_currency[company] = frappe.db.get_value(
"Company", company, "default_currency", cache=True "Company", company, "default_currency", cache=True
) )
@@ -79,7 +81,7 @@ def is_perpetual_inventory_enabled(company):
if not hasattr(frappe.local, "enable_perpetual_inventory"): if not hasattr(frappe.local, "enable_perpetual_inventory"):
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.local.enable_perpetual_inventory[company] = (
frappe.get_cached_value("Company", company, "enable_perpetual_inventory") or 0 frappe.get_cached_value("Company", company, "enable_perpetual_inventory") or 0
) )
@@ -94,7 +96,7 @@ def get_default_finance_book(company=None):
if not hasattr(frappe.local, "default_finance_book"): if not hasattr(frappe.local, "default_finance_book"):
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( frappe.local.default_finance_book[company] = frappe.get_cached_value(
"Company", company, "default_finance_book" "Company", company, "default_finance_book"
) )
@@ -106,7 +108,7 @@ def get_party_account_type(party_type):
if not hasattr(frappe.local, "party_account_types"): if not hasattr(frappe.local, "party_account_types"):
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.local.party_account_types[party_type] = (
frappe.db.get_value("Party Type", party_type, "account_type") or "" frappe.db.get_value("Party Type", party_type, "account_type") or ""
) )

View File

@@ -11,14 +11,14 @@ class ERPNextAddress(Address):
def validate(self): def validate(self):
self.validate_reference() self.validate_reference()
self.update_compnay_address() self.update_compnay_address()
super().validate() super(ERPNextAddress, self).validate()
def link_address(self): def link_address(self):
"""Link address based on owner""" """Link address based on owner"""
if self.is_your_company_address: if self.is_your_company_address:
return return
return super().link_address() return super(ERPNextAddress, self).link_address()
def update_compnay_address(self): def update_compnay_address(self):
for link in self.get("links"): for link in self.get("links"):
@@ -26,11 +26,11 @@ class ERPNextAddress(Address):
self.is_your_company_address = 1 self.is_your_company_address = 1
def validate_reference(self): 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( 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"), title=_("Company Not Linked"),
) )

View File

@@ -1,4 +1,4 @@
frappe.provide("frappe.dashboards.chart_sources"); frappe.provide('frappe.dashboards.chart_sources');
frappe.dashboards.chart_sources["Account Balance Timeline"] = { frappe.dashboards.chart_sources["Account Balance Timeline"] = {
method: "erpnext.accounts.dashboard_chart_source.account_balance_timeline.account_balance_timeline.get", method: "erpnext.accounts.dashboard_chart_source.account_balance_timeline.account_balance_timeline.get",
@@ -9,14 +9,14 @@ frappe.dashboards.chart_sources["Account Balance Timeline"] = {
fieldtype: "Link", fieldtype: "Link",
options: "Company", options: "Company",
default: frappe.defaults.get_user_default("Company"), default: frappe.defaults.get_user_default("Company"),
reqd: 1, reqd: 1
}, },
{ {
fieldname: "account", fieldname: "account",
label: __("Account"), label: __("Account"),
fieldtype: "Link", fieldtype: "Link",
options: "Account", options: "Account",
reqd: 1, reqd: 1
}, },
], ]
}; };

View File

@@ -37,7 +37,7 @@ def get(
filters = frappe.parse_json(filters) or frappe.parse_json(chart.filters_json) filters = frappe.parse_json(filters) or frappe.parse_json(chart.filters_json)
account = filters.get("account") account = filters.get("account")
filters.get("company") company = filters.get("company")
if not account and chart_name: if not account and chart_name:
frappe.throw( frappe.throw(
@@ -83,6 +83,7 @@ def build_result(account, dates, gl_entries):
# get balances in debit # get balances in debit
for entry in gl_entries: for entry in gl_entries:
# entry date is after the current pointer, so move the pointer forward # entry date is after the current pointer, so move the pointer forward
while getdate(entry.posting_date) > result[date_index][0]: while getdate(entry.posting_date) > result[date_index][0]:
date_index += 1 date_index += 1
@@ -132,6 +133,8 @@ def get_dates_from_timegrain(from_date, to_date, timegrain):
dates = [get_period_ending(from_date, timegrain)] dates = [get_period_ending(from_date, timegrain)]
while getdate(dates[-1]) < getdate(to_date): 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) dates.append(date)
return dates return dates

View File

@@ -24,10 +24,14 @@ from erpnext.accounts.utils import get_account_currency
def validate_service_stop_date(doc): def validate_service_stop_date(doc):
"""Validates service_stop_date for Purchase Invoice and Sales Invoice""" """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_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: for d in old_doc:
old_stop_dates[d.name] = d.service_stop_date or "" old_stop_dates[d.name] = d.service_stop_date or ""
@@ -58,14 +62,16 @@ def build_conditions(process_type, account, company):
) )
if account: if account:
conditions += f"AND {deferred_account}='{account}'" conditions += "AND %s='%s'" % (deferred_account, account)
elif company: elif company:
conditions += f"AND p.company = {frappe.db.escape(company)}" conditions += f"AND p.company = {frappe.db.escape(company)}"
return conditions 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 # 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: 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 # check for the purchase invoice for which GL entries has to be done
invoices = frappe.db.sql_list( invoices = frappe.db.sql_list(
f""" """
select distinct item.parent select distinct item.parent
from `tabPurchase Invoice Item` item, `tabPurchase Invoice` p from `tabPurchase Invoice Item` item, `tabPurchase Invoice` p
where item.service_start_date<=%s and item.service_end_date>=%s 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.enable_deferred_expense = 1 and item.parent=p.name
and item.docstatus = 1 and ifnull(item.amount, 0) > 0 and item.docstatus = 1 and ifnull(item.amount, 0) > 0
{conditions} {0}
""", """.format(
conditions
),
(end_date, start_date), (end_date, start_date),
) # nosec ) # nosec
@@ -95,7 +103,9 @@ def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_d
send_mail(deferred_process) 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 # 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: 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 # check for the sales invoice for which GL entries has to be done
invoices = frappe.db.sql_list( invoices = frappe.db.sql_list(
f""" """
select distinct item.parent select distinct item.parent
from `tabSales Invoice Item` item, `tabSales Invoice` p from `tabSales Invoice Item` item, `tabSales Invoice` p
where item.service_start_date<=%s and item.service_end_date>=%s 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.enable_deferred_revenue = 1 and item.parent=p.name
and item.docstatus = 1 and ifnull(item.amount, 0) > 0 and item.docstatus = 1 and ifnull(item.amount, 0) > 0
{conditions} {0}
""", """.format(
conditions
),
(end_date, start_date), (end_date, start_date),
) # nosec ) # nosec
@@ -231,7 +243,9 @@ def calculate_monthly_amount(
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount( already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(
doc, item 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: if account_currency == doc.company_currency:
amount = base_amount amount = base_amount
else: 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: if account_currency == doc.company_currency:
amount = base_amount amount = base_amount
else: 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: else:
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount( already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(
doc, item 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: if account_currency == doc.company_currency:
amount = base_amount amount = base_amount
else: else:
@@ -278,22 +296,26 @@ def get_already_booked_amount(doc, item):
gl_entries_details = frappe.db.sql( 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 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 and is_cancelled = 0
group by voucher_detail_no 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), (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
as_dict=True, as_dict=True,
) )
journal_entry_details = frappe.db.sql( 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 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 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 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), (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
as_dict=True, 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): 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") 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: if not amount:
prev_posting_date = end_date return
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
if via_journal_entry: gl_posting_date = end_date
book_revenue_via_journal_entry( prev_posting_date = None
doc, # check if books nor frozen till endate:
credit_account, if accounts_frozen_upto and getdate(end_date) <= getdate(accounts_frozen_upto):
debit_account, gl_posting_date = get_last_day(add_days(accounts_frozen_upto, 1))
amount, prev_posting_date = end_date
base_amount,
gl_posting_date, if via_journal_entry:
project, book_revenue_via_journal_entry(
account_currency, doc,
item.cost_center, credit_account,
item, debit_account,
deferred_process, amount,
submit_journal_entry, base_amount,
) gl_posting_date,
else: project,
make_gl_entries( account_currency,
doc, item.cost_center,
credit_account, item,
debit_account, deferred_process,
against, submit_journal_entry,
amount, )
base_amount, else:
gl_posting_date, make_gl_entries(
project, doc,
account_currency, credit_account,
item.cost_center, debit_account,
item, against,
deferred_process, 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 # 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: 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( via_journal_entry = cint(
frappe.db.get_singles_value("Accounts Settings", "book_deferred_entries_via_journal_entry") 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( book_deferred_entries_based_on = frappe.db.get_singles_value(
"Accounts Settings", "book_deferred_entries_based_on" "Accounts Settings", "book_deferred_entries_based_on"
) )
@@ -436,7 +462,9 @@ def process_deferred_accounting(posting_date=None):
posting_date = today() posting_date = today()
if not cint( 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 return
@@ -559,13 +587,16 @@ def book_revenue_via_journal_entry(
deferred_process=None, deferred_process=None,
submit="No", submit="No",
): ):
if amount == 0: if amount == 0:
return return
journal_entry = frappe.new_doc("Journal Entry") journal_entry = frappe.new_doc("Journal Entry")
journal_entry.posting_date = posting_date journal_entry.posting_date = posting_date
journal_entry.company = doc.company 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 journal_entry.process_deferred_accounting = deferred_process
debit_entry = { debit_entry = {
@@ -614,6 +645,7 @@ def book_revenue_via_journal_entry(
def get_deferred_booking_accounts(doctype, voucher_detail_no, dr_or_cr): def get_deferred_booking_accounts(doctype, voucher_detail_no, dr_or_cr):
if doctype == "Sales Invoice": if doctype == "Sales Invoice":
credit_account, debit_account = frappe.db.get_value( credit_account, debit_account = frappe.db.get_value(
"Sales Invoice Item", "Sales Invoice Item",

View File

@@ -1,32 +1,33 @@
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors // Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt // License: GNU General Public License v3. See license.txt
frappe.ui.form.on("Account", { frappe.ui.form.on('Account', {
setup: function (frm) { setup: function(frm) {
frm.add_fetch("parent_account", "report_type", "report_type"); frm.add_fetch('parent_account', 'report_type', 'report_type');
frm.add_fetch("parent_account", "root_type", "root_type"); frm.add_fetch('parent_account', 'root_type', 'root_type');
}, },
onload: function (frm) { onload: function(frm) {
frm.set_query("parent_account", function (doc) { frm.set_query('parent_account', function(doc) {
return { return {
filters: { filters: {
is_group: 1, "is_group": 1,
company: doc.company, "company": doc.company
}, }
}; };
}); });
}, },
refresh: function (frm) { refresh: function(frm) {
frm.toggle_display("account_name", frm.is_new()); frm.toggle_display('account_name', frm.is_new());
// hide fields if group // hide fields if group
frm.toggle_display(["account_type", "tax_rate"], cint(frm.doc.is_group) == 0); frm.toggle_display(['account_type', 'tax_rate'], cint(frm.doc.is_group) == 0);
// disable fields // disable fields
frm.toggle_enable(["is_group", "company"], false); frm.toggle_enable(['is_group', 'company'], false);
if (cint(frm.doc.is_group) == 0) { if (cint(frm.doc.is_group) == 0) {
frm.toggle_display("freeze_account", frm.doc.__onload && frm.doc.__onload.can_freeze_account); frm.toggle_display('freeze_account', frm.doc.__onload
&& frm.doc.__onload.can_freeze_account);
} }
// read-only for root accounts // read-only for root accounts
@@ -37,101 +38,79 @@ frappe.ui.form.on("Account", {
} else { } else {
// credit days and type if customer or supplier // credit days and type if customer or supplier
frm.set_intro(null); frm.set_intro(null);
frm.trigger("account_type"); frm.trigger('account_type');
// show / hide convert buttons // show / hide convert buttons
frm.trigger("add_toolbar_buttons"); frm.trigger('add_toolbar_buttons');
} }
if (frm.has_perm("write")) { if (frm.has_perm('write')) {
frm.add_custom_button( frm.add_custom_button(__('Merge Account'), function () {
__("Merge Account"), frm.trigger("merge_account");
function () { }, __('Actions'));
frm.trigger("merge_account"); frm.add_custom_button(__('Update Account Name / Number'), function () {
}, frm.trigger("update_account_number");
__("Actions") }, __('Actions'));
);
frm.add_custom_button(
__("Update Account Name / Number"),
function () {
frm.trigger("update_account_number");
},
__("Actions")
);
} }
} }
}, },
account_type: function (frm) { account_type: function (frm) {
if (frm.doc.is_group == 0) { if (frm.doc.is_group == 0) {
frm.toggle_display(["tax_rate"], frm.doc.account_type == "Tax"); frm.toggle_display(['tax_rate'], frm.doc.account_type == 'Tax');
frm.toggle_display("warehouse", frm.doc.account_type == "Stock"); frm.toggle_display('warehouse', frm.doc.account_type == 'Stock');
} }
}, },
add_toolbar_buttons: function (frm) { add_toolbar_buttons: function(frm) {
frm.add_custom_button( frm.add_custom_button(__('Chart of Accounts'), () => {
__("Chart of Accounts"), frappe.set_route("Tree", "Account");
() => { }, __('View'));
frappe.set_route("Tree", "Account");
},
__("View")
);
if (frm.doc.is_group == 1) { if (frm.doc.is_group == 1) {
frm.add_custom_button( frm.add_custom_button(__('Convert to Non-Group'), function () {
__("Convert to Non-Group"), return frappe.call({
function () { doc: frm.doc,
return frappe.call({ method: 'convert_group_to_ledger',
doc: frm.doc, callback: function() {
method: "convert_group_to_ledger", frm.refresh();
callback: function () { }
frm.refresh(); });
}, }, __('Actions'));
});
},
__("Actions")
);
} else if (cint(frm.doc.is_group) == 0 && frappe.boot.user.can_read.indexOf("GL Entry") !== -1) {
frm.add_custom_button(
__("General Ledger"),
function () {
frappe.route_options = {
account: frm.doc.name,
from_date: frappe.sys_defaults.year_start_date,
to_date: frappe.sys_defaults.year_end_date,
company: frm.doc.company,
};
frappe.set_route("query-report", "General Ledger");
},
__("View")
);
frm.add_custom_button( } else if (cint(frm.doc.is_group) == 0
__("Convert to Group"), && frappe.boot.user.can_read.indexOf("GL Entry") !== -1) {
function () { frm.add_custom_button(__('General Ledger'), function () {
return frappe.call({ frappe.route_options = {
doc: frm.doc, "account": frm.doc.name,
method: "convert_ledger_to_group", "from_date": frappe.sys_defaults.year_start_date,
callback: function () { "to_date": frappe.sys_defaults.year_end_date,
frm.refresh(); "company": frm.doc.company
}, };
}); frappe.set_route("query-report", "General Ledger");
}, }, __('View'));
__("Actions")
); frm.add_custom_button(__('Convert to Group'), function () {
return frappe.call({
doc: frm.doc,
method: 'convert_ledger_to_group',
callback: function() {
frm.refresh();
}
});
}, __('Actions'));
} }
}, },
merge_account: function (frm) { merge_account: function(frm) {
var d = new frappe.ui.Dialog({ var d = new frappe.ui.Dialog({
title: __("Merge with Existing Account"), title: __('Merge with Existing Account'),
fields: [ fields: [
{ {
label: "Name", "label" : "Name",
fieldname: "name", "fieldname": "name",
fieldtype: "Data", "fieldtype": "Data",
reqd: 1, "reqd": 1,
default: frm.doc.name, "default": frm.doc.name
}, }
], ],
primary_action: function () { primary_action: function() {
var data = d.get_values(); var data = d.get_values();
frappe.call({ frappe.call({
method: "erpnext.accounts.doctype.account.account.merge_account", method: "erpnext.accounts.doctype.account.account.merge_account",
@@ -139,45 +118,42 @@ frappe.ui.form.on("Account", {
old: frm.doc.name, old: frm.doc.name,
new: data.name, new: data.name,
}, },
callback: function (r) { callback: function(r) {
if (!r.exc) { if(!r.exc) {
if (r.message) { if(r.message) {
frappe.set_route("Form", "Account", r.message); frappe.set_route("Form", "Account", r.message);
} }
d.hide(); d.hide();
} }
}, }
}); });
}, },
primary_action_label: __("Merge"), primary_action_label: __('Merge')
}); });
d.show(); d.show();
}, },
update_account_number: function (frm) { update_account_number: function(frm) {
var d = new frappe.ui.Dialog({ var d = new frappe.ui.Dialog({
title: __("Update Account Number / Name"), title: __('Update Account Number / Name'),
fields: [ fields: [
{ {
label: "Account Name", "label": "Account Name",
fieldname: "account_name", "fieldname": "account_name",
fieldtype: "Data", "fieldtype": "Data",
reqd: 1, "reqd": 1,
default: frm.doc.account_name, "default": frm.doc.account_name
}, },
{ {
label: "Account Number", "label": "Account Number",
fieldname: "account_number", "fieldname": "account_number",
fieldtype: "Data", "fieldtype": "Data",
default: frm.doc.account_number, "default": frm.doc.account_number
}, }
], ],
primary_action: function () { primary_action: function() {
var data = d.get_values(); var data = d.get_values();
if ( if(data.account_number === frm.doc.account_number && data.account_name === frm.doc.account_name) {
data.account_number === frm.doc.account_number &&
data.account_name === frm.doc.account_name
) {
d.hide(); d.hide();
return; return;
} }
@@ -187,11 +163,11 @@ frappe.ui.form.on("Account", {
args: { args: {
account_number: data.account_number, account_number: data.account_number,
account_name: data.account_name, account_name: data.account_name,
name: frm.doc.name, name: frm.doc.name
}, },
callback: function (r) { callback: function(r) {
if (!r.exc) { if(!r.exc) {
if (r.message) { if(r.message) {
frappe.set_route("Form", "Account", r.message); frappe.set_route("Form", "Account", r.message);
} else { } else {
frm.set_value("account_number", data.account_number); frm.set_value("account_number", data.account_number);
@@ -199,11 +175,11 @@ frappe.ui.form.on("Account", {
} }
d.hide(); d.hide();
} }
}, }
}); });
}, },
primary_action_label: __("Update"), primary_action_label: __('Update')
}); });
d.show(); d.show();
}, }
}); });

View File

@@ -29,7 +29,7 @@ class Account(NestedSet):
if frappe.local.flags.ignore_update_nsm: if frappe.local.flags.ignore_update_nsm:
return return
else: else:
super().on_update() super(Account, self).on_update()
def onload(self): def onload(self):
frozen_accounts_modifier = frappe.db.get_value( frozen_accounts_modifier = frappe.db.get_value(
@@ -58,7 +58,6 @@ class Account(NestedSet):
self.validate_balance_must_be_debit_or_credit() self.validate_balance_must_be_debit_or_credit()
self.validate_account_currency() self.validate_account_currency()
self.validate_root_company_and_sync_account_to_children() self.validate_root_company_and_sync_account_to_children()
self.validate_receivable_payable_account_type()
def validate_parent(self): def validate_parent(self):
"""Fetch Parent Details and validate parent account""" """Fetch Parent Details and validate parent account"""
@@ -87,7 +86,9 @@ class Account(NestedSet):
def set_root_and_report_type(self): def set_root_and_report_type(self):
if self.parent_account: if self.parent_account:
par = frappe.db.get_value("Account", self.parent_account, ["report_type", "root_type"], as_dict=1) par = frappe.db.get_value(
"Account", self.parent_account, ["report_type", "root_type"], as_dict=1
)
if par.report_type: if par.report_type:
self.report_type = par.report_type self.report_type = par.report_type
@@ -113,24 +114,6 @@ class Account(NestedSet):
"Balance Sheet" if self.root_type in ("Asset", "Liability", "Equity") else "Profit and Loss" "Balance Sheet" if self.root_type in ("Asset", "Liability", "Equity") else "Profit and Loss"
) )
def validate_receivable_payable_account_type(self):
doc_before_save = self.get_doc_before_save()
receivable_payable_types = ["Receivable", "Payable"]
if (
doc_before_save
and doc_before_save.account_type in receivable_payable_types
and doc_before_save.account_type != self.account_type
):
# check for ledger entries
if frappe.db.get_all("GL Entry", filters={"account": self.name, "is_cancelled": 0}, limit=1):
msg = _(
"There are ledger entries against this account. Changing {0} to non-{1} in live system will cause incorrect output in 'Accounts {2}' report"
).format(
frappe.bold("Account Type"), doc_before_save.account_type, doc_before_save.account_type
)
frappe.msgprint(msg)
self.add_comment("Comment", msg)
def validate_root_details(self): def validate_root_details(self):
# does not exists parent # does not exists parent
if frappe.db.exists("Account", self.name): if frappe.db.exists("Account", self.name):
@@ -142,7 +125,9 @@ class Account(NestedSet):
def validate_root_company_and_sync_account_to_children(self): def validate_root_company_and_sync_account_to_children(self):
# ignore validation while creating new compnay or while syncing to child companies # 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 return
ancestors = get_root_company(self.company) ancestors = get_root_company(self.company)
if ancestors: if ancestors:
@@ -337,7 +322,7 @@ class Account(NestedSet):
if self.check_gle_exists(): if self.check_gle_exists():
throw(_("Account with existing transaction can not be deleted")) throw(_("Account with existing transaction can not be deleted"))
super().on_trash(True) super(Account, self).on_trash(True)
@frappe.whitelist() @frappe.whitelist()
@@ -345,8 +330,9 @@ class Account(NestedSet):
def get_parent_account(doctype, txt, searchfield, start, page_len, filters): def get_parent_account(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql( return frappe.db.sql(
"""select name from tabAccount """select name from tabAccount
where is_group = 1 and docstatus != 2 and company = {} where is_group = 1 and docstatus != 2 and company = %s
and {} like {} order by name limit {} offset {}""".format("%s", searchfield, "%s", "%s", "%s"), and %s like %s order by name limit %s offset %s"""
% ("%s", searchfield, "%s", "%s", "%s"),
(filters["company"], "%%%s%%" % txt, page_len, start), (filters["company"], "%%%s%%" % txt, page_len, start),
as_list=1, as_list=1,
) )
@@ -404,7 +390,9 @@ def update_account_number(name, account_name, account_number=None, from_descenda
if not account: if not account:
return return
old_acc_name, old_acc_number = frappe.db.get_value("Account", name, ["account_name", "account_number"]) old_acc_name, old_acc_number = frappe.db.get_value(
"Account", name, ["account_name", "account_number"]
)
# check if account exists in parent company # check if account exists in parent company
ancestors = get_ancestors_of("Company", account.company) ancestors = get_ancestors_of("Company", account.company)
@@ -512,5 +500,7 @@ def sync_update_account_number_in_child(
if old_acc_number: if old_acc_number:
filters["account_number"] = 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) update_account_number(d["name"], account_name, account_number, from_descendant=True)

View File

@@ -1,4 +1,4 @@
frappe.provide("frappe.treeview_settings"); frappe.provide("frappe.treeview_settings")
frappe.treeview_settings["Account"] = { frappe.treeview_settings["Account"] = {
breadcrumb: "Accounts", breadcrumb: "Accounts",
@@ -7,12 +7,12 @@ frappe.treeview_settings["Account"] = {
filters: [ filters: [
{ {
fieldname: "company", fieldname: "company",
fieldtype: "Select", fieldtype:"Select",
options: erpnext.utils.get_tree_options("company"), options: erpnext.utils.get_tree_options("company"),
label: __("Company"), label: __("Company"),
default: erpnext.utils.get_tree_default("company"), default: erpnext.utils.get_tree_default("company"),
on_change: function () { on_change: function() {
var me = frappe.treeview_settings["Account"].treeview; var me = frappe.treeview_settings['Account'].treeview;
var company = me.page.fields_dict.company.get_value(); var company = me.page.fields_dict.company.get_value();
if (!company) { if (!company) {
frappe.throw(__("Please set a Company")); frappe.throw(__("Please set a Company"));
@@ -22,36 +22,30 @@ frappe.treeview_settings["Account"] = {
args: { args: {
company: company, company: company,
}, },
callback: function (r) { callback: function(r) {
if (r.message) { if(r.message) {
let root_company = r.message.length ? r.message[0] : ""; let root_company = r.message.length ? r.message[0] : "";
me.page.fields_dict.root_company.set_value(root_company); me.page.fields_dict.root_company.set_value(root_company);
frappe.db.get_value( frappe.db.get_value("Company", {"name": company}, "allow_account_creation_against_child_company", (r) => {
"Company", frappe.flags.ignore_root_company_validation = r.allow_account_creation_against_child_company;
{ name: company }, });
"allow_account_creation_against_child_company",
(r) => {
frappe.flags.ignore_root_company_validation =
r.allow_account_creation_against_child_company;
}
);
} }
}, }
}); });
}, }
}, },
{ {
fieldname: "root_company", fieldname: "root_company",
fieldtype: "Data", fieldtype:"Data",
label: __("Root Company"), label: __("Root Company"),
hidden: true, hidden: true,
disable_onchange: true, disable_onchange: true
}, }
], ],
root_label: "Accounts", root_label: "Accounts",
get_tree_nodes: "erpnext.accounts.utils.get_children", get_tree_nodes: 'erpnext.accounts.utils.get_children',
on_get_node: function (nodes, deep = false) { on_get_node: function(nodes, deep=false) {
if (frappe.boot.user.can_read.indexOf("GL Entry") == -1) return; if (frappe.boot.user.can_read.indexOf("GL Entry") == -1) return;
let accounts = []; let accounts = [];
@@ -63,231 +57,151 @@ frappe.treeview_settings["Account"] = {
} }
frappe.db.get_single_value("Accounts Settings", "show_balance_in_coa").then((value) => { frappe.db.get_single_value("Accounts Settings", "show_balance_in_coa").then((value) => {
if (value) { if(value) {
const get_balances = frappe.call({ const get_balances = frappe.call({
method: "erpnext.accounts.utils.get_account_balances", method: 'erpnext.accounts.utils.get_account_balances',
args: { args: {
accounts: accounts, accounts: accounts,
company: cur_tree.args.company, company: cur_tree.args.company
}, },
}); });
get_balances.then((r) => { get_balances.then(r => {
if (!r.message || r.message.length == 0) return; if (!r.message || r.message.length == 0) return;
for (let account of r.message) { for (let account of r.message) {
const node = cur_tree.nodes && cur_tree.nodes[account.value]; const node = cur_tree.nodes && cur_tree.nodes[account.value];
if (!node || node.is_root) continue; if (!node || node.is_root) continue;
// show Dr if positive since balance is calculated as debit - credit else show Cr // show Dr if positive since balance is calculated as debit - credit else show Cr
const balance = account.balance_in_account_currency || account.balance; const balance = account.balance_in_account_currency || account.balance;
const dr_or_cr = balance > 0 ? "Dr" : "Cr"; const dr_or_cr = balance > 0 ? "Dr": "Cr";
const format = (value, currency) => format_currency(Math.abs(value), currency); const format = (value, currency) => format_currency(Math.abs(value), currency);
if (account.balance !== undefined) { if (account.balance!==undefined) {
node.parent && node.parent.find(".balance-area").remove(); node.parent && node.parent.find('.balance-area').remove();
$( $('<span class="balance-area pull-right">'
'<span class="balance-area pull-right">' + + (account.balance_in_account_currency ?
(account.balance_in_account_currency (format(account.balance_in_account_currency, account.account_currency) + " / ") : "")
? format( + format(account.balance, account.company_currency)
account.balance_in_account_currency, + " " + dr_or_cr
account.account_currency + '</span>').insertBefore(node.$ul);
) + " / "
: "") +
format(account.balance, account.company_currency) +
" " +
dr_or_cr +
"</span>"
).insertBefore(node.$ul);
} }
} }
}); });
} }
}); });
}, },
add_tree_node: "erpnext.accounts.utils.add_ac", add_tree_node: 'erpnext.accounts.utils.add_ac',
menu_items: [ menu_items:[
{ {
label: __("New Company"), label: __('New Company'),
action: function () { action: function() { frappe.new_doc("Company", true) },
frappe.new_doc("Company", true); condition: 'frappe.boot.user.can_create.indexOf("Company") !== -1'
}, }
condition: 'frappe.boot.user.can_create.indexOf("Company") !== -1',
},
], ],
fields: [ fields: [
{ {fieldtype:'Data', fieldname:'account_name', label:__('New Account Name'), reqd:true,
fieldtype: "Data", description: __("Name of new Account. Note: Please don't create accounts for Customers and Suppliers")},
fieldname: "account_name", {fieldtype:'Data', fieldname:'account_number', label:__('Account Number'),
label: __("New Account Name"), description: __("Number of new Account, it will be included in the account name as a prefix")},
reqd: true, {fieldtype:'Check', fieldname:'is_group', label:__('Is Group'),
description: __( description: __('Further accounts can be made under Groups, but entries can be made against non-Groups')},
"Name of new Account. Note: Please don't create accounts for Customers and Suppliers" {fieldtype:'Select', fieldname:'root_type', label:__('Root Type'),
), options: ['Asset', 'Liability', 'Equity', 'Income', 'Expense'].join('\n'),
}, depends_on: 'eval:doc.is_group && !doc.parent_account'},
{ {fieldtype:'Select', fieldname:'account_type', label:__('Account Type'),
fieldtype: "Data", options: frappe.get_meta("Account").fields.filter(d => d.fieldname=='account_type')[0].options,
fieldname: "account_number", description: __("Optional. This setting will be used to filter in various transactions.")
label: __("Account Number"),
description: __("Number of new Account, it will be included in the account name as a prefix"),
},
{
fieldtype: "Check",
fieldname: "is_group",
label: __("Is Group"),
description: __(
"Further accounts can be made under Groups, but entries can be made against non-Groups"
),
},
{
fieldtype: "Select",
fieldname: "root_type",
label: __("Root Type"),
options: ["Asset", "Liability", "Equity", "Income", "Expense"].join("\n"),
depends_on: "eval:doc.is_group && !doc.parent_account",
},
{
fieldtype: "Select",
fieldname: "account_type",
label: __("Account Type"),
options: frappe.get_meta("Account").fields.filter((d) => d.fieldname == "account_type")[0]
.options,
description: __("Optional. This setting will be used to filter in various transactions."),
},
{
fieldtype: "Float",
fieldname: "tax_rate",
label: __("Tax Rate"),
depends_on: 'eval:doc.is_group==0&&doc.account_type=="Tax"',
},
{
fieldtype: "Link",
fieldname: "account_currency",
label: __("Currency"),
options: "Currency",
description: __("Optional. Sets company's default currency, if not specified."),
}, },
{fieldtype:'Float', fieldname:'tax_rate', label:__('Tax Rate'),
depends_on: 'eval:doc.is_group==0&&doc.account_type=="Tax"'},
{fieldtype:'Link', fieldname:'account_currency', label:__('Currency'), options:"Currency",
description: __("Optional. Sets company's default currency, if not specified.")}
], ],
ignore_fields: ["parent_account"], ignore_fields:["parent_account"],
onload: function (treeview) { onload: function(treeview) {
frappe.treeview_settings["Account"].treeview = {}; frappe.treeview_settings['Account'].treeview = {};
$.extend(frappe.treeview_settings["Account"].treeview, treeview); $.extend(frappe.treeview_settings['Account'].treeview, treeview);
function get_company() { function get_company() {
return treeview.page.fields_dict.company.get_value(); return treeview.page.fields_dict.company.get_value();
} }
// tools // tools
treeview.page.add_inner_button( treeview.page.add_inner_button(__("Chart of Cost Centers"), function() {
__("Chart of Cost Centers"), frappe.set_route('Tree', 'Cost Center', {company: get_company()});
function () { }, __('View'));
frappe.set_route("Tree", "Cost Center", { company: get_company() });
},
__("View")
);
treeview.page.add_inner_button( treeview.page.add_inner_button(__("Opening Invoice Creation Tool"), function() {
__("Opening Invoice Creation Tool"), frappe.set_route('Form', 'Opening Invoice Creation Tool', {company: get_company()});
function () { }, __('View'));
frappe.set_route("Form", "Opening Invoice Creation Tool", { company: get_company() });
},
__("View")
);
treeview.page.add_inner_button( treeview.page.add_inner_button(__("Period Closing Voucher"), function() {
__("Period Closing Voucher"), frappe.set_route('List', 'Period Closing Voucher', {company: get_company()});
function () { }, __('View'));
frappe.set_route("List", "Period Closing Voucher", { company: get_company() });
},
__("View")
);
treeview.page.add_inner_button(
__("Journal Entry"), treeview.page.add_inner_button(__("Journal Entry"), function() {
function () { frappe.new_doc('Journal Entry', {company: get_company()});
frappe.new_doc("Journal Entry", { company: get_company() }); }, __('Create'));
}, treeview.page.add_inner_button(__("Company"), function() {
__("Create") frappe.new_doc('Company');
); }, __('Create'));
treeview.page.add_inner_button(
__("Company"),
function () {
frappe.new_doc("Company");
},
__("Create")
);
// financial statements // financial statements
for (let report of [ for (let report of ['Trial Balance', 'General Ledger', 'Balance Sheet',
"Trial Balance", 'Profit and Loss Statement', 'Cash Flow Statement', 'Accounts Payable', 'Accounts Receivable']) {
"General Ledger", treeview.page.add_inner_button(__(report), function() {
"Balance Sheet", frappe.set_route('query-report', report, {company: get_company()});
"Profit and Loss Statement", }, __('Financial Statements'));
"Cash Flow Statement",
"Accounts Payable",
"Accounts Receivable",
]) {
treeview.page.add_inner_button(
__(report),
function () {
frappe.set_route("query-report", report, { company: get_company() });
},
__("Financial Statements")
);
} }
},
post_render: function (treeview) {
frappe.treeview_settings["Account"].treeview["tree"] = treeview.tree;
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]); post_render: function(treeview) {
} else { frappe.treeview_settings['Account'].treeview["tree"] = treeview.tree;
treeview.new_node(); treeview.page.set_primary_action(__("New"), function() {
} let root_company = treeview.page.fields_dict.root_company.get_value();
},
"add" if(root_company) {
); frappe.throw(__("Please add the account to root level Company - {0}"), [root_company]);
} else {
treeview.new_node();
}
}, "add");
}, },
toolbar: [ toolbar: [
{ {
label: __("Add Child"), label:__("Add Child"),
condition: function (node) { condition: function(node) {
return ( return frappe.boot.user.can_create.indexOf("Account") !== -1
frappe.boot.user.can_create.indexOf("Account") !== -1 && && (!frappe.treeview_settings['Account'].treeview.page.fields_dict.root_company.get_value()
(!frappe.treeview_settings[ || frappe.flags.ignore_root_company_validation)
"Account" && node.expandable && !node.hide_add;
].treeview.page.fields_dict.root_company.get_value() ||
frappe.flags.ignore_root_company_validation) &&
node.expandable &&
!node.hide_add
);
}, },
click: function () { click: function() {
var me = frappe.views.trees["Account"]; var me = frappe.views.trees['Account'];
me.new_node(); me.new_node();
}, },
btnClass: "hidden-xs", btnClass: "hidden-xs"
}, },
{ {
condition: function (node) { condition: function(node) {
return !node.root && frappe.boot.user.can_read.indexOf("GL Entry") !== -1; return !node.root && frappe.boot.user.can_read.indexOf("GL Entry") !== -1
}, },
label: __("View Ledger"), label: __("View Ledger"),
click: function (node, btn) { click: function(node, btn) {
frappe.route_options = { frappe.route_options = {
account: node.label, "account": node.label,
from_date: frappe.sys_defaults.year_start_date, "from_date": frappe.sys_defaults.year_start_date,
to_date: frappe.sys_defaults.year_end_date, "to_date": frappe.sys_defaults.year_end_date,
company: "company": frappe.treeview_settings['Account'].treeview.page.fields_dict.company.get_value()
frappe.treeview_settings["Account"].treeview.page.fields_dict.company.get_value(),
}; };
frappe.set_route("query-report", "General Ledger"); frappe.set_route("query-report", "General Ledger");
}, },
btnClass: "hidden-xs", btnClass: "hidden-xs"
}, }
], ],
extend_toolbar: true, extend_toolbar: true
}; }

View File

@@ -31,6 +31,7 @@ def create_charts(
"tax_rate", "tax_rate",
"account_currency", "account_currency",
]: ]:
account_number = cstr(child.get("account_number")).strip() account_number = cstr(child.get("account_number")).strip()
account_name, account_name_in_db = add_suffix_if_duplicate( account_name, account_name_in_db = add_suffix_if_duplicate(
account_name, account_number, accounts account_name, account_number, accounts
@@ -38,9 +39,7 @@ def create_charts(
is_group = identify_is_group(child) is_group = identify_is_group(child)
report_type = ( report_type = (
"Balance Sheet" "Balance Sheet" if root_type in ["Asset", "Liability", "Equity"] else "Profit and Loss"
if root_type in ["Asset", "Liability", "Equity"]
else "Profit and Loss"
) )
account = frappe.get_doc( account = frappe.get_doc(
@@ -142,7 +141,7 @@ def get_chart(chart_template, existing_company=None):
for fname in os.listdir(path): for fname in os.listdir(path):
fname = frappe.as_unicode(fname) fname = frappe.as_unicode(fname)
if fname.endswith(".json"): 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() chart = f.read()
if chart and json.loads(chart).get("name") == chart_template: if chart and json.loads(chart).get("name") == chart_template:
return json.loads(chart).get("tree") 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): for fname in os.listdir(path):
fname = frappe.as_unicode(fname) fname = frappe.as_unicode(fname)
if (fname.startswith(country_code) or fname.startswith(country)) and fname.endswith(".json"): 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()) _get_chart_name(f.read())
# if more than one charts, returned then add the standard # 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): def _get_account_names(account_master):
for account_name, child in account_master.items(): for account_name, child in account_master.items():
if account_name not in [ if account_name not in ["account_number", "account_type", "root_type", "is_group", "tax_rate"]:
"account_number",
"account_type",
"root_type",
"is_group",
"tax_rate",
]:
accounts.append(account_name) accounts.append(account_name)
_get_account_names(child) _get_account_names(child)

View File

@@ -26,7 +26,7 @@ def go():
default_account_types = get_default_account_types() default_account_types = get_default_account_types()
country_dirs = [] country_dirs = []
for basepath, _folders, _files in os.walk(path): for basepath, folders, files in os.walk(path):
basename = os.path.basename(basepath) basename = os.path.basename(basepath)
if basename.startswith("l10n_"): if basename.startswith("l10n_"):
country_dirs.append(basename) country_dirs.append(basename)
@@ -35,7 +35,9 @@ def go():
accounts, charts = {}, {} accounts, charts = {}, {}
country_path = os.path.join(path, country_dir) country_path = os.path.join(path, country_dir)
manifest = ast.literal_eval(open(os.path.join(country_path, "__openerp__.py")).read()) manifest = ast.literal_eval(open(os.path.join(country_path, "__openerp__.py")).read())
data_files = manifest.get("data", []) + manifest.get("init_xml", []) + manifest.get("update_xml", []) data_files = (
manifest.get("data", []) + manifest.get("init_xml", []) + manifest.get("update_xml", [])
)
files_path = [os.path.join(country_path, d) for d in data_files] files_path = [os.path.join(country_path, d) for d in data_files]
xml_roots = get_xml_roots(files_path) xml_roots = get_xml_roots(files_path)
csv_content = get_csv_contents(files_path) csv_content = get_csv_contents(files_path)
@@ -88,10 +90,10 @@ def get_csv_contents(files_path):
fname = os.path.basename(filepath) fname = os.path.basename(filepath)
for file_type in ["account.account.template", "account.account.type", "account.chart.template"]: for file_type in ["account.account.template", "account.account.type", "account.chart.template"]:
if fname.startswith(file_type) and fname.endswith(".csv"): if fname.startswith(file_type) and fname.endswith(".csv"):
with open(filepath) as csvfile: with open(filepath, "r") as csvfile:
try: try:
csv_content.setdefault(file_type, []).append(read_csv_content(csvfile.read())) csv_content.setdefault(file_type, []).append(read_csv_content(csvfile.read()))
except Exception: except Exception as e:
continue continue
return csv_content return csv_content
@@ -136,7 +138,7 @@ def get_account_types(root_list, csv_content, prefix=None):
if csv_content and csv_content[0][0] == "id": if csv_content and csv_content[0][0] == "id":
for row in csv_content[1:]: for row in csv_content[1:]:
row_dict = dict(zip(csv_content[0], row, strict=False)) row_dict = dict(zip(csv_content[0], row))
data = {} data = {}
if row_dict.get("code") and account_type_map.get(row_dict["code"]): if row_dict.get("code") and account_type_map.get(row_dict["code"]):
data["account_type"] = account_type_map[row_dict["code"]] data["account_type"] = account_type_map[row_dict["code"]]
@@ -148,7 +150,7 @@ def get_account_types(root_list, csv_content, prefix=None):
def make_maps_for_xml(xml_roots, account_types, country_dir): def make_maps_for_xml(xml_roots, account_types, country_dir):
"""make maps for `charts` and `accounts`""" """make maps for `charts` and `accounts`"""
for _model, root_list in xml_roots.items(): for model, root_list in xml_roots.items():
for root in root_list: for root in root_list:
for node in root[0].findall("record"): for node in root[0].findall("record"):
if node.get("model") == "account.account.template": if node.get("model") == "account.account.template":
@@ -184,7 +186,7 @@ def make_maps_for_xml(xml_roots, account_types, country_dir):
def make_maps_for_csv(csv_content, account_types, country_dir): def make_maps_for_csv(csv_content, account_types, country_dir):
for content in csv_content.get("account.account.template", []): for content in csv_content.get("account.account.template", []):
for row in content[1:]: for row in content[1:]:
data = dict(zip(content[0], row, strict=False)) data = dict(zip(content[0], row))
account = { account = {
"name": data.get("name"), "name": data.get("name"),
"parent_id": data.get("parent_id:id") or data.get("parent_id/id"), "parent_id": data.get("parent_id:id") or data.get("parent_id/id"),
@@ -204,7 +206,7 @@ def make_maps_for_csv(csv_content, account_types, country_dir):
for content in csv_content.get("account.chart.template", []): for content in csv_content.get("account.chart.template", []):
for row in content[1:]: for row in content[1:]:
if row: if row:
data = dict(zip(content[0], row, strict=False)) data = dict(zip(content[0], row))
charts.setdefault(data.get("id"), {}).update( charts.setdefault(data.get("id"), {}).update(
{ {
"account_root_id": data.get("account_root_id:id") or data.get("account_root_id/id"), "account_root_id": data.get("account_root_id:id") or data.get("account_root_id/id"),
@@ -239,7 +241,7 @@ def make_charts():
if not src.get("name") or not src.get("account_root_id"): if not src.get("name") or not src.get("account_root_id"):
continue continue
if src["account_root_id"] not in accounts: if not src["account_root_id"] in accounts:
continue continue
filename = src["id"][5:] + "_" + chart_id filename = src["id"][5:] + "_" + chart_id
@@ -253,20 +255,14 @@ def make_charts():
for key, val in chart["tree"].items(): for key, val in chart["tree"].items():
if key in ["name", "parent_id"]: if key in ["name", "parent_id"]:
chart["tree"].pop(key) chart["tree"].pop(key)
if isinstance(val, dict): if type(val) == dict:
val["root_type"] = "" val["root_type"] = ""
if chart: if chart:
fpath = os.path.join( fpath = os.path.join(
"erpnext", "erpnext", "erpnext", "accounts", "doctype", "account", "chart_of_accounts", filename + ".json"
"erpnext",
"accounts",
"doctype",
"account",
"chart_of_accounts",
filename + ".json",
) )
with open(fpath) as chartfile: with open(fpath, "r") as chartfile:
old_content = chartfile.read() old_content = chartfile.read()
if not old_content or ( if not old_content or (
json.loads(old_content).get("is_active", "No") == "No" json.loads(old_content).get("is_active", "No") == "No"

View File

@@ -53,13 +53,8 @@
}, },
"II. Forderungen und sonstige Vermögensgegenstände": { "II. Forderungen und sonstige Vermögensgegenstände": {
"is_group": 1, "is_group": 1,
"Forderungen aus Lieferungen und Leistungen mit Kontokorrent": { "Ford. a. Lieferungen und Leistungen": {
"account_number": "1400", "account_number": "1400",
"account_type": "Receivable",
"is_group": 1
},
"Forderungen aus Lieferungen und Leistungen ohne Kontokorrent": {
"account_number": "1410",
"account_type": "Receivable" "account_type": "Receivable"
}, },
"Durchlaufende Posten": { "Durchlaufende Posten": {
@@ -185,13 +180,8 @@
}, },
"IV. Verbindlichkeiten aus Lieferungen und Leistungen": { "IV. Verbindlichkeiten aus Lieferungen und Leistungen": {
"is_group": 1, "is_group": 1,
"Verbindlichkeiten aus Lieferungen und Leistungen mit Kontokorrent": { "Verbindlichkeiten aus Lieferungen u. Leistungen": {
"account_number": "1600", "account_number": "1600",
"account_type": "Payable",
"is_group": 1
},
"Verbindlichkeiten aus Lieferungen und Leistungen ohne Kontokorrent": {
"account_number": "1610",
"account_type": "Payable" "account_type": "Payable"
} }
}, },

View File

@@ -407,10 +407,13 @@
"Bewertungskorrektur zu Forderungen aus Lieferungen und Leistungen": { "Bewertungskorrektur zu Forderungen aus Lieferungen und Leistungen": {
"account_number": "9960" "account_number": "9960"
}, },
"Forderungen aus Lieferungen und Leistungen mit Kontokorrent": { "Debitoren": {
"is_group": 1,
"account_number": "10000"
},
"Forderungen aus Lieferungen und Leistungen": {
"account_number": "1200", "account_number": "1200",
"account_type": "Receivable", "account_type": "Receivable"
"is_group": 1
}, },
"Forderungen aus Lieferungen und Leistungen ohne Kontokorrent": { "Forderungen aus Lieferungen und Leistungen ohne Kontokorrent": {
"account_number": "1210" "account_number": "1210"
@@ -1135,15 +1138,18 @@
"Bewertungskorrektur zu Verb. aus Lieferungen und Leistungen": { "Bewertungskorrektur zu Verb. aus Lieferungen und Leistungen": {
"account_number": "9964" "account_number": "9964"
}, },
"Verb. aus Lieferungen und Leistungen mit Kontokorrent": { "Kreditoren": {
"account_number": "3300", "account_number": "70000",
"account_type": "Payable",
"is_group": 1, "is_group": 1,
"Wareneingangs-Verrechnungskonto" : { "Wareneingangs-­Verrechnungskonto" : {
"account_number": "70001", "account_number": "70001",
"account_type": "Stock Received But Not Billed" "account_type": "Stock Received But Not Billed"
} }
}, },
"Verb. aus Lieferungen und Leistungen": {
"account_number": "3300",
"account_type": "Payable"
},
"Verb. aus Lieferungen und Leistungen ohne Kontokorrent": { "Verb. aus Lieferungen und Leistungen ohne Kontokorrent": {
"account_number": "3310" "account_number": "3310"
}, },

View File

@@ -260,20 +260,28 @@ class TestAccount(unittest.TestCase):
acc.insert() acc.insert()
self.assertTrue( 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( 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 # Try renaming child company account
acc_tc_5 = frappe.db.get_value( acc_tc_5 = frappe.db.get_value(
"Account", {"account_name": "Test Group Account", "company": "_Test Company 5"} "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 # 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") update_account_number(acc_tc_5, "Test Modified Account")
self.assertTrue( self.assertTrue(
@@ -282,7 +290,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 = [ to_delete = [
"Test Group Account - _TC3", "Test Group Account - _TC3",
@@ -307,7 +317,9 @@ class TestAccount(unittest.TestCase):
self.assertEqual(acc.account_currency, "INR") self.assertEqual(acc.account_currency, "INR")
# Make a JV against this account # 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" acc.account_currency = "USD"
self.assertRaises(frappe.ValidationError, acc.save) self.assertRaises(frappe.ValidationError, acc.save)

View File

@@ -17,12 +17,16 @@ class AccountClosingBalance(Document):
def make_closing_entries(closing_entries, voucher_name, company, closing_date): def make_closing_entries(closing_entries, voucher_name, company, closing_date):
accounting_dimensions = get_accounting_dimensions() 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 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 = frappe.new_doc("Account Closing Balance")
cle.update(value) cle.update(value)
cle.update(value["dimensions"]) cle.update(value["dimensions"])
@@ -33,7 +37,6 @@ def make_closing_entries(closing_entries, voucher_name, company, closing_date):
} }
) )
cle.flags.ignore_permissions = True cle.flags.ignore_permissions = True
cle.flags.ignore_links = True
cle.submit() cle.submit()

View File

@@ -1,86 +1,74 @@
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on("Accounting Dimension", { frappe.ui.form.on('Accounting Dimension', {
refresh: function (frm) { refresh: function(frm) {
frm.set_query("document_type", () => { frm.set_query('document_type', () => {
let invalid_doctypes = frappe.model.core_doctypes_list; let invalid_doctypes = frappe.model.core_doctypes_list;
invalid_doctypes.push( invalid_doctypes.push('Accounting Dimension', 'Project',
"Accounting Dimension", 'Cost Center', 'Accounting Dimension Detail', 'Company');
"Project",
"Cost Center",
"Accounting Dimension Detail",
"Company"
);
return { return {
filters: { filters: {
name: ["not in", invalid_doctypes], name: ['not in', invalid_doctypes]
}, }
}; };
}); });
frm.set_query("offsetting_account", "dimension_defaults", function (doc, cdt, cdn) { frm.set_query("offsetting_account", "dimension_defaults", function(doc, cdt, cdn) {
let d = locals[cdt][cdn]; let d = locals[cdt][cdn];
return { return {
filters: { filters: {
company: d.company, company: d.company,
root_type: ["in", ["Asset", "Liability"]], root_type: ["in", ["Asset", "Liability"]],
is_group: 0, is_group: 0
}, }
}; }
}); });
if (!frm.is_new()) { if (!frm.is_new()) {
frm.add_custom_button(__("Show {0}", [frm.doc.document_type]), function () { frm.add_custom_button(__('Show {0}', [frm.doc.document_type]), function () {
frappe.set_route("List", frm.doc.document_type); frappe.set_route("List", frm.doc.document_type);
}); });
let button = frm.doc.disabled ? "Enable" : "Disable"; let button = frm.doc.disabled ? "Enable" : "Disable";
frm.add_custom_button(__(button), function () { frm.add_custom_button(__(button), function() {
frm.set_value("disabled", 1 - frm.doc.disabled);
frm.set_value('disabled', 1 - frm.doc.disabled);
frappe.call({ frappe.call({
method: "erpnext.accounts.doctype.accounting_dimension.accounting_dimension.disable_dimension", method: "erpnext.accounts.doctype.accounting_dimension.accounting_dimension.disable_dimension",
args: { args: {
doc: frm.doc, doc: frm.doc
}, },
freeze: true, freeze: true,
callback: function (r) { callback: function(r) {
let message = frm.doc.disabled ? "Dimension Disabled" : "Dimension Enabled"; let message = frm.doc.disabled ? "Dimension Disabled" : "Dimension Enabled";
frm.save(); frm.save();
frappe.show_alert({ message: __(message), indicator: "green" }); frappe.show_alert({message:__(message), indicator:'green'});
}, }
}); });
}); });
} }
}, },
document_type: function (frm) { 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( frm.set_value('label', frm.doc.document_type);
"Accounting Dimension", frm.set_value('fieldname', frappe.model.scrub(frm.doc.document_type));
{ document_type: frm.doc.document_type },
"document_type", frappe.db.get_value('Accounting Dimension', {'document_type': frm.doc.document_type}, 'document_type', (r) => {
(r) => { if (r && r.document_type) {
if (r && r.document_type) { frm.set_df_property('document_type', 'description', "Document type is already set as dimension");
frm.set_df_property(
"document_type",
"description",
"Document type is already set as dimension"
);
}
} }
); });
}, },
}); });
frappe.ui.form.on("Accounting Dimension Detail", { frappe.ui.form.on('Accounting Dimension Detail', {
dimension_defaults_add: function (frm, cdt, cdn) { dimension_defaults_add: function(frm, cdt, cdn) {
let row = locals[cdt][cdn]; let row = locals[cdt][cdn];
row.reference_document = frm.doc.document_type; row.reference_document = frm.doc.document_type;
}, }
}); });

View File

@@ -17,8 +17,7 @@ class AccountingDimension(Document):
self.set_fieldname_and_label() self.set_fieldname_and_label()
def validate(self): def validate(self):
if self.document_type in ( if self.document_type in core_doctypes_list + (
*core_doctypes_list,
"Accounting Dimension", "Accounting Dimension",
"Project", "Project",
"Cost Center", "Cost Center",
@@ -26,10 +25,13 @@ class AccountingDimension(Document):
"Company", "Company",
"Account", "Account",
): ):
msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type) msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type)
frappe.throw(msg) 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(): if exists and self.is_new():
frappe.throw(_("Document Type already used as a dimension")) frappe.throw(_("Document Type already used as a dimension"))
@@ -87,6 +89,7 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None):
count = 0 count = 0
for doctype in doclist: for doctype in doclist:
if (doc_count + 1) % 2 == 0: if (doc_count + 1) % 2 == 0:
insert_after_field = "dimension_col_break" insert_after_field = "dimension_col_break"
else: else:
@@ -120,7 +123,7 @@ def add_dimension_to_budget_doctype(df, doc):
df.update( df.update(
{ {
"insert_after": "cost_center", "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),
} }
) )
@@ -154,17 +157,19 @@ def delete_accounting_dimension(doc):
frappe.db.sql( frappe.db.sql(
""" """
DELETE FROM `tabCustom Field` DELETE FROM `tabCustom Field`
WHERE fieldname = {} WHERE fieldname = %s
AND dt IN ({})""".format("%s", ", ".join(["%s"] * len(doclist))), # nosec AND dt IN (%s)"""
tuple([doc.fieldname, *doclist]), % ("%s", ", ".join(["%s"] * len(doclist))), # nosec
tuple([doc.fieldname] + doclist),
) )
frappe.db.sql( frappe.db.sql(
""" """
DELETE FROM `tabProperty Setter` DELETE FROM `tabProperty Setter`
WHERE field_name = {} WHERE field_name = %s
AND doc_type IN ({})""".format("%s", ", ".join(["%s"] * len(doclist))), # nosec AND doc_type IN (%s)"""
tuple([doc.fieldname, *doclist]), % ("%s", ", ".join(["%s"] * len(doclist))), # nosec
tuple([doc.fieldname] + doclist),
) )
budget_against_property = frappe.get_doc("Property Setter", "Budget-budget_against-options") budget_against_property = frappe.get_doc("Property Setter", "Budget-budget_against-options")
@@ -213,6 +218,7 @@ def get_doctypes_with_dimensions():
def get_accounting_dimensions(as_list=True, filters=None): def get_accounting_dimensions(as_list=True, filters=None):
if not filters: if not filters:
filters = {"disabled": 0} filters = {"disabled": 0}
@@ -230,19 +236,18 @@ def get_accounting_dimensions(as_list=True, filters=None):
def get_checks_for_pl_and_bs_accounts(): def get_checks_for_pl_and_bs_accounts():
if frappe.flags.accounting_dimensions_details is None: dimensions = frappe.db.sql(
# nosemgrep """SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
frappe.flags.accounting_dimensions_details = frappe.db.sql( FROM `tabAccounting Dimension`p ,`tabAccounting Dimension Detail` c
"""SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs WHERE p.name = c.parent""",
FROM `tabAccounting Dimension`p ,`tabAccounting Dimension Detail` c as_dict=1,
WHERE p.name = c.parent""", )
as_dict=1,
)
return frappe.flags.accounting_dimensions_details return dimensions
def get_dimension_with_children(doctype, dimensions): def get_dimension_with_children(doctype, dimensions):
if isinstance(dimensions, str): if isinstance(dimensions, str):
dimensions = [dimensions] dimensions = [dimensions]
@@ -250,7 +255,9 @@ def get_dimension_with_children(doctype, dimensions):
for dimension in dimensions: for dimension in dimensions:
lft, rgt = frappe.db.get_value(doctype, dimension, ["lft", "rgt"]) 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] all_dimensions += [c.name for c in children]
return all_dimensions return all_dimensions
@@ -294,30 +301,3 @@ def get_dimensions(with_cost_center_and_project=False):
default_dimensions_map[dimension.company][dimension.fieldname] = dimension.default_dimension default_dimensions_map[dimension.company][dimension.fieldname] = dimension.default_dimension
return dimension_filters, default_dimensions_map return dimension_filters, default_dimensions_map
def create_accounting_dimensions_for_doctype(doctype):
accounting_dimensions = frappe.db.get_all(
"Accounting Dimension", fields=["fieldname", "label", "document_type", "disabled"]
)
if not accounting_dimensions:
return
for d in accounting_dimensions:
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname})
if field:
continue
df = {
"fieldname": d.fieldname,
"label": d.label,
"fieldtype": "Link",
"options": d.document_type,
"insert_after": "accounting_dimensions_section",
}
create_custom_field(doctype, df, ignore_validate=True)
frappe.clear_cache(doctype=doctype)

View File

@@ -78,8 +78,6 @@ class TestAccountingDimension(unittest.TestCase):
def tearDown(self): def tearDown(self):
disable_dimension() disable_dimension()
frappe.flags.accounting_dimensions_details = None
frappe.flags.dimension_filter_map = None
def create_dimension(): def create_dimension():

View File

@@ -1,9 +1,10 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on("Accounting Dimension Filter", { frappe.ui.form.on('Accounting Dimension Filter', {
refresh: function (frm, cdt, cdn) { refresh: function(frm, cdt, cdn) {
let help_content = `<table class="table table-bordered" style="background-color: var(--scrollbar-track-color);"> let help_content =
`<table class="table table-bordered" style="background-color: var(--scrollbar-track-color);">
<tr><td> <tr><td>
<p> <p>
<i class="fa fa-hand-right"></i> <i class="fa fa-hand-right"></i>
@@ -12,70 +13,67 @@ frappe.ui.form.on("Accounting Dimension Filter", {
</td></tr> </td></tr>
</table>`; </table>`;
frm.set_df_property("dimension_filter_help", "options", help_content); frm.set_df_property('dimension_filter_help', 'options', help_content);
}, },
onload: function (frm) { onload: function(frm) {
frm.set_query("applicable_on_account", "accounts", function () { frm.set_query('applicable_on_account', 'accounts', function() {
return { return {
filters: { filters: {
company: frm.doc.company, 'company': frm.doc.company
}, }
}; };
}); });
frappe.db.get_list("Accounting Dimension", { fields: ["document_type"] }).then((res) => { frappe.db.get_list('Accounting Dimension',
let options = ["Cost Center", "Project"]; {fields: ['document_type']}).then((res) => {
let options = ['Cost Center', 'Project'];
res.forEach((dimension) => { res.forEach((dimension) => {
options.push(dimension.document_type); options.push(dimension.document_type);
}); });
frm.set_df_property("accounting_dimension", "options", options); frm.set_df_property('accounting_dimension', 'options', options);
}); });
frm.trigger("setup_filters"); frm.trigger('setup_filters');
}, },
setup_filters: function (frm) { setup_filters: function(frm) {
let filters = {}; let filters = {};
if (frm.doc.accounting_dimension) { if (frm.doc.accounting_dimension) {
frappe.model.with_doctype(frm.doc.accounting_dimension, function () { frappe.model.with_doctype(frm.doc.accounting_dimension, function() {
if (frappe.model.is_tree(frm.doc.accounting_dimension)) { if (frappe.model.is_tree(frm.doc.accounting_dimension)) {
filters["is_group"] = 0; filters['is_group'] = 0;
} }
if (frappe.meta.has_field(frm.doc.accounting_dimension, "company")) { if (frappe.meta.has_field(frm.doc.accounting_dimension, 'company')) {
filters["company"] = frm.doc.company; filters['company'] = frm.doc.company;
} }
frm.set_query("dimension_value", "dimensions", function () { frm.set_query('dimension_value', 'dimensions', function() {
return { return {
filters: filters, filters: filters
}; };
}); });
}); });
} }
}, },
accounting_dimension: function (frm) { accounting_dimension: function(frm) {
frm.clear_table("dimensions"); frm.clear_table("dimensions");
let row = frm.add_child("dimensions"); let row = frm.add_child("dimensions");
row.accounting_dimension = frm.doc.accounting_dimension; row.accounting_dimension = frm.doc.accounting_dimension;
frm.fields_dict["dimensions"].grid.update_docfield_property( frm.fields_dict["dimensions"].grid.update_docfield_property("dimension_value", "label", frm.doc.accounting_dimension);
"dimension_value",
"label",
frm.doc.accounting_dimension
);
frm.refresh_field("dimensions"); frm.refresh_field("dimensions");
frm.trigger("setup_filters"); frm.trigger('setup_filters');
}, },
}); });
frappe.ui.form.on("Allowed Dimension", { frappe.ui.form.on('Allowed Dimension', {
dimensions_add: function (frm, cdt, cdn) { dimensions_add: function(frm, cdt, cdn) {
let row = locals[cdt][cdn]; let row = locals[cdt][cdn];
row.accounting_dimension = frm.doc.accounting_dimension; row.accounting_dimension = frm.doc.accounting_dimension;
frm.refresh_field("dimensions"); frm.refresh_field("dimensions");
}, }
}); });

View File

@@ -38,41 +38,37 @@ class AccountingDimensionFilter(Document):
def get_dimension_filter_map(): def get_dimension_filter_map():
if not frappe.flags.get("dimension_filter_map"): filters = frappe.db.sql(
# nosemgrep """
filters = frappe.db.sql( SELECT
""" a.applicable_on_account, d.dimension_value, p.accounting_dimension,
SELECT p.allow_or_restrict, a.is_mandatory
a.applicable_on_account, d.dimension_value, p.accounting_dimension, FROM
p.allow_or_restrict, a.is_mandatory `tabApplicable On Account` a, `tabAllowed Dimension` d,
FROM `tabAccounting Dimension Filter` p
`tabApplicable On Account` a, `tabAllowed Dimension` d, WHERE
`tabAccounting Dimension Filter` p p.name = a.parent
WHERE AND p.disabled = 0
p.name = a.parent AND p.name = d.parent
AND p.disabled = 0 """,
AND p.name = d.parent as_dict=1,
""", )
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 = {} return 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
def build_map(map_object, dimension, account, filter_value, allow_or_restrict, is_mandatory): 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): def tearDown(self):
disable_dimension_filter() disable_dimension_filter()
disable_dimension() disable_dimension()
frappe.flags.accounting_dimensions_details = None
frappe.flags.dimension_filter_map = None
for si in self.invoice_list: for si in self.invoice_list:
si.load_from_db() si.load_from_db()
@@ -57,7 +55,9 @@ class TestAccountingDimensionFilter(unittest.TestCase):
def create_accounting_dimension_filter(): 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( frappe.get_doc(
{ {
"doctype": "Accounting Dimension Filter", "doctype": "Accounting Dimension Filter",

View File

@@ -1,33 +1,30 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on("Accounting Period", { frappe.ui.form.on('Accounting Period', {
onload: function (frm) { onload: function(frm) {
if ( if(frm.doc.closed_documents.length === 0 || (frm.doc.closed_documents.length === 1 && frm.doc.closed_documents[0].document_type == undefined)) {
frm.doc.closed_documents.length === 0 ||
(frm.doc.closed_documents.length === 1 && frm.doc.closed_documents[0].document_type == undefined)
) {
frappe.call({ frappe.call({
method: "get_doctypes_for_closing", method: "get_doctypes_for_closing",
doc: frm.doc, doc:frm.doc,
callback: function (r) { callback: function(r) {
if (r.message) { if(r.message) {
cur_frm.clear_table("closed_documents"); cur_frm.clear_table("closed_documents");
r.message.forEach(function (element) { r.message.forEach(function(element) {
var c = frm.add_child("closed_documents"); var c = frm.add_child("closed_documents");
c.document_type = element.document_type; c.document_type = element.document_type;
c.closed = element.closed; c.closed = element.closed;
}); });
refresh_field("closed_documents"); refresh_field("closed_documents");
} }
}, }
}); });
} }
frm.set_query("document_type", "closed_documents", () => { frm.set_query("document_type", "closed_documents", () => {
return { return {
query: "erpnext.controllers.queries.get_doctypes_for_closing", query: "erpnext.controllers.queries.get_doctypes_for_closing",
}; }
}); });
}, }
}); });

View File

@@ -67,10 +67,7 @@ class AccountingPeriod(Document):
for doctype_for_closing in self.get_doctypes_for_closing(): for doctype_for_closing in self.get_doctypes_for_closing():
self.append( self.append(
"closed_documents", "closed_documents",
{ {"document_type": doctype_for_closing.document_type, "closed": doctype_for_closing.closed},
"document_type": doctype_for_closing.document_type,
"closed": doctype_for_closing.closed,
},
) )

View File

@@ -34,7 +34,9 @@ class TestAccountingPeriod(unittest.TestCase):
ap1 = create_accounting_period(period_name="Test Accounting Period 2") ap1 = create_accounting_period(period_name="Test Accounting Period 2")
ap1.save() 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) self.assertRaises(ClosedAccountingPeriod, doc.save)
def tearDown(self): def tearDown(self):

View File

@@ -1,6 +1,8 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on("Accounts Settings", { frappe.ui.form.on('Accounts Settings', {
refresh: function (frm) {}, refresh: function(frm) {
}
}); });

View File

@@ -68,12 +68,7 @@
"enable_party_matching", "enable_party_matching",
"enable_fuzzy_matching", "enable_fuzzy_matching",
"tab_break_dpet", "tab_break_dpet",
"show_balance_in_coa", "show_balance_in_coa"
"reports_tab",
"remarks_section",
"general_ledger_remarks_length",
"column_break_lvjk",
"receivable_payable_remarks_length"
], ],
"fields": [ "fields": [
{ {
@@ -434,34 +429,6 @@
"fieldname": "show_balance_in_coa", "fieldname": "show_balance_in_coa",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Show Balances in Chart Of Accounts" "label": "Show Balances in Chart Of Accounts"
},
{
"fieldname": "reports_tab",
"fieldtype": "Tab Break",
"label": "Reports"
},
{
"default": "0",
"description": "Truncates 'Remarks' column to set character length",
"fieldname": "general_ledger_remarks_length",
"fieldtype": "Int",
"label": "General Ledger"
},
{
"default": "0",
"description": "Truncates 'Remarks' column to set character length",
"fieldname": "receivable_payable_remarks_length",
"fieldtype": "Int",
"label": "Accounts Receivable/Payable"
},
{
"fieldname": "column_break_lvjk",
"fieldtype": "Column Break"
},
{
"fieldname": "remarks_section",
"fieldtype": "Section Break",
"label": "Remarks Column Length"
} }
], ],
"icon": "icon-cog", "icon": "icon-cog",
@@ -469,7 +436,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2024-01-22 12:10:10.151819", "modified": "2023-07-27 15:05:34.000264",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounts Settings", "name": "Accounts Settings",

View File

@@ -1,11 +1,8 @@
frappe.ui.form.on("Accounts Settings", {
refresh: function (frm) { frappe.ui.form.on('Accounts Settings', {
refresh: function(frm) {
frm.set_df_property("acc_frozen_upto", "label", "Books Closed Through"); frm.set_df_property("acc_frozen_upto", "label", "Books Closed Through");
frm.set_df_property( frm.set_df_property("frozen_accounts_modifier", "label", "Role Allowed to Close Books & Make Changes to Closed Periods");
"frozen_accounts_modifier",
"label",
"Role Allowed to Close Books & Make Changes to Closed Periods"
);
frm.set_df_property("credit_controller", "label", "Credit Manager"); frm.set_df_property("credit_controller", "label", "Credit Manager");
}, }
}); });

View File

@@ -11,7 +11,6 @@
{ {
"fieldname": "company", "fieldname": "company",
"fieldtype": "Link", "fieldtype": "Link",
"ignore_user_permissions": 1,
"in_list_view": 1, "in_list_view": 1,
"label": "Company", "label": "Company",
"options": "Company", "options": "Company",
@@ -20,7 +19,7 @@
], ],
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2024-01-03 11:13:02.669632", "modified": "2020-05-01 12:32:34.044911",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Allowed To Transact With", "name": "Allowed To Transact With",
@@ -29,6 +28,5 @@
"quick_entry": 1, "quick_entry": 1,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"states": [],
"track_changes": 1 "track_changes": 1
} }

View File

@@ -1,39 +1,41 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.provide("erpnext.integrations"); frappe.provide('erpnext.integrations');
frappe.ui.form.on("Bank", { frappe.ui.form.on('Bank', {
onload: function (frm) { onload: function(frm) {
add_fields_to_mapping_table(frm); add_fields_to_mapping_table(frm);
}, },
refresh: function (frm) { refresh: function(frm) {
add_fields_to_mapping_table(frm); add_fields_to_mapping_table(frm);
frappe.dynamic_link = { doc: frm.doc, fieldname: "name", doctype: "Bank" }; frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Bank' };
frm.toggle_display(["address_html", "contact_html"], !frm.doc.__islocal); frm.toggle_display(['address_html','contact_html'], !frm.doc.__islocal);
if (frm.doc.__islocal) { if (frm.doc.__islocal) {
frm.set_df_property("address_and_contact", "hidden", 1); frm.set_df_property('address_and_contact', 'hidden', 1);
frappe.contacts.clear_address_and_contact(frm); frappe.contacts.clear_address_and_contact(frm);
} else { }
frm.set_df_property("address_and_contact", "hidden", 0); else {
frm.set_df_property('address_and_contact', 'hidden', 0);
frappe.contacts.render_address_and_contact(frm); frappe.contacts.render_address_and_contact(frm);
} }
if (frm.doc.plaid_access_token) { if (frm.doc.plaid_access_token) {
frm.add_custom_button(__("Refresh Plaid Link"), () => { frm.add_custom_button(__('Refresh Plaid Link'), () => {
new erpnext.integrations.refreshPlaidLink(frm.doc.plaid_access_token); new erpnext.integrations.refreshPlaidLink(frm.doc.plaid_access_token);
}); });
} }
}, }
}); });
let add_fields_to_mapping_table = function (frm) { let add_fields_to_mapping_table = function (frm) {
let options = []; let options = [];
frappe.model.with_doctype("Bank Transaction", function () { frappe.model.with_doctype("Bank Transaction", function() {
let meta = frappe.get_meta("Bank Transaction"); let meta = frappe.get_meta("Bank Transaction");
meta.fields.forEach((value) => { meta.fields.forEach(value => {
if (!["Section Break", "Column Break"].includes(value.fieldtype)) { if (!["Section Break", "Column Break"].includes(value.fieldtype)) {
options.push(value.fieldname); options.push(value.fieldname);
} }
@@ -41,32 +43,30 @@ let add_fields_to_mapping_table = function (frm) {
}); });
frm.fields_dict.bank_transaction_mapping.grid.update_docfield_property( frm.fields_dict.bank_transaction_mapping.grid.update_docfield_property(
"bank_transaction_field", 'bank_transaction_field', 'options', options
"options",
options
); );
}; };
erpnext.integrations.refreshPlaidLink = class refreshPlaidLink { erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
constructor(access_token) { constructor(access_token) {
this.access_token = access_token; this.access_token = access_token;
this.plaidUrl = "https://cdn.plaid.com/link/v2/stable/link-initialize.js"; this.plaidUrl = 'https://cdn.plaid.com/link/v2/stable/link-initialize.js';
this.init_config(); this.init_config();
} }
async init_config() { async init_config() {
this.plaid_env = await frappe.db.get_single_value("Plaid Settings", "plaid_env"); this.plaid_env = await frappe.db.get_single_value('Plaid Settings', 'plaid_env');
this.token = await this.get_link_token_for_update(); this.token = await this.get_link_token_for_update();
this.init_plaid(); this.init_plaid();
} }
async get_link_token_for_update() { async get_link_token_for_update() {
const token = frappe.xcall( const token = frappe.xcall(
"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.get_link_token_for_update", 'erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.get_link_token_for_update',
{ access_token: this.access_token } { access_token: this.access_token }
); )
if (!token) { if (!token) {
frappe.throw(__("Cannot retrieve link token for update. Check Error Log for more information")); frappe.throw(__('Cannot retrieve link token for update. Check Error Log for more information'));
} }
return token; return token;
} }
@@ -93,13 +93,13 @@ erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
resolve(); resolve();
return; return;
} }
const el = document.createElement("script"); const el = document.createElement('script');
el.type = "text/javascript"; el.type = 'text/javascript';
el.async = true; el.async = true;
el.src = src; el.src = src;
el.addEventListener("load", resolve); el.addEventListener('load', resolve);
el.addEventListener("error", reject); el.addEventListener('error', reject);
el.addEventListener("abort", reject); el.addEventListener('abort', reject);
document.head.appendChild(el); document.head.appendChild(el);
}); });
} }
@@ -108,29 +108,20 @@ erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
me.linkHandler = Plaid.create({ me.linkHandler = Plaid.create({
env: me.plaid_env, env: me.plaid_env,
token: me.token, token: me.token,
onSuccess: me.plaid_success, onSuccess: me.plaid_success
}); });
} }
onScriptError(error) { onScriptError(error) {
frappe.msgprint( frappe.msgprint(__("There was an issue connecting to Plaid's authentication server. Check browser console for more information"));
__(
"There was an issue connecting to Plaid's authentication server. Check browser console for more information"
)
);
console.log(error); console.log(error);
} }
plaid_success(token, response) { plaid_success(token, response) {
frappe frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.update_bank_account_ids', {
.xcall( response: response,
"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.update_bank_account_ids", }).then(() => {
{ frappe.show_alert({ message: __('Plaid Link Updated'), indicator: 'green' });
response: response, });
}
)
.then(() => {
frappe.show_alert({ message: __("Plaid Link Updated"), indicator: "green" });
});
} }
}; };

View File

@@ -1,49 +1,45 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on("Bank Account", { frappe.ui.form.on('Bank Account', {
setup: function (frm) { setup: function(frm) {
frm.set_query("account", function () { frm.set_query("account", function() {
return { return {
filters: { filters: {
account_type: "Bank", 'account_type': 'Bank',
company: frm.doc.company, 'company': frm.doc.company,
is_group: 0, 'is_group': 0
}, }
}; };
}); });
frm.set_query("party_type", function () { frm.set_query("party_type", function() {
return { return {
query: "erpnext.setup.doctype.party_type.party_type.get_party_type", query: "erpnext.setup.doctype.party_type.party_type.get_party_type",
}; };
}); });
}, },
refresh: function (frm) { refresh: function(frm) {
frappe.dynamic_link = { doc: frm.doc, fieldname: "name", doctype: "Bank Account" }; frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Bank Account' }
frm.toggle_display(["address_html", "contact_html"], !frm.doc.__islocal); frm.toggle_display(['address_html','contact_html'], !frm.doc.__islocal);
if (frm.doc.__islocal) { if (frm.doc.__islocal) {
frappe.contacts.clear_address_and_contact(frm); frappe.contacts.clear_address_and_contact(frm);
} else { }
else {
frappe.contacts.render_address_and_contact(frm); frappe.contacts.render_address_and_contact(frm);
} }
if (frm.doc.integration_id) { if (frm.doc.integration_id) {
frm.add_custom_button(__("Unlink external integrations"), function () { frm.add_custom_button(__("Unlink external integrations"), function() {
frappe.confirm( frappe.confirm(__("This action will unlink this account from any external service integrating ERPNext with your bank accounts. It cannot be undone. Are you certain ?"), function() {
__( frm.set_value("integration_id", "");
"This action will unlink this account from any external service integrating ERPNext with your bank accounts. It cannot be undone. Are you certain ?" });
),
function () {
frm.set_value("integration_id", "");
}
);
}); });
} }
}, },
is_company_account: function (frm) { is_company_account: function(frm) {
frm.set_df_property("account", "reqd", frm.doc.is_company_account); frm.set_df_property('account', 'reqd', frm.doc.is_company_account);
}, }
}); });

View File

@@ -13,7 +13,6 @@
"account_type", "account_type",
"account_subtype", "account_subtype",
"column_break_7", "column_break_7",
"disabled",
"is_default", "is_default",
"is_company_account", "is_company_account",
"company", "company",
@@ -200,16 +199,10 @@
"fieldtype": "Data", "fieldtype": "Data",
"in_global_search": 1, "in_global_search": 1,
"label": "Branch Code" "label": "Branch Code"
},
{
"default": "0",
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled"
} }
], ],
"links": [], "links": [],
"modified": "2024-02-02 17:50:09.768835", "modified": "2022-05-04 15:49:42.620630",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Bank Account", "name": "Bank Account",

View File

@@ -9,7 +9,6 @@ from frappe.contacts.address_and_contact import (
load_address_and_contact, load_address_and_contact,
) )
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import comma_and, get_link_to_form
class BankAccount(Document): class BankAccount(Document):
@@ -26,19 +25,6 @@ class BankAccount(Document):
def validate(self): def validate(self):
self.validate_company() self.validate_company()
self.validate_iban() self.validate_iban()
self.validate_account()
def validate_account(self):
if self.account:
if accounts := frappe.db.get_all(
"Bank Account", filters={"account": self.account, "name": ["!=", self.name]}, as_list=1
):
frappe.throw(
_("'{0}' account is already used by {1}. Use another account.").format(
frappe.bold(self.account),
frappe.bold(comma_and([get_link_to_form(self.doctype, x[0]) for x in accounts])),
)
)
def validate_company(self): def validate_company(self):
if self.is_company_account and not self.company: if self.is_company_account and not self.company:

View File

@@ -37,11 +37,11 @@ class TestBankAccount(unittest.TestCase):
try: try:
bank_account.validate_iban() bank_account.validate_iban()
except ValidationError: 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) self.fail(msg=msg)
for not_iban in invalid_ibans: for not_iban in invalid_ibans:
bank_account.iban = not_iban 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): with self.assertRaises(ValidationError, msg=msg):
bank_account.validate_iban() bank_account.validate_iban()

View File

@@ -1,6 +1,8 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on("Bank Account Subtype", { frappe.ui.form.on('Bank Account Subtype', {
refresh: function () {}, refresh: function() {
}
}); });

View File

@@ -1,7 +1,8 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on("Bank Account Type", { frappe.ui.form.on('Bank Account Type', {
// refresh: function(frm) { // refresh: function(frm) {
// } // }
}); });

View File

@@ -2,65 +2,80 @@
// License: GNU General Public License v3. See license.txt // License: GNU General Public License v3. See license.txt
frappe.ui.form.on("Bank Clearance", { frappe.ui.form.on("Bank Clearance", {
setup: function (frm) { setup: function(frm) {
frm.add_fetch("account", "account_currency", "account_currency"); frm.add_fetch("account", "account_currency", "account_currency");
frm.set_query("account", function () { frm.set_query("account", function() {
return { return {
filters: { "filters": {
account_type: ["in", ["Bank", "Cash"]], "account_type": ["in",["Bank","Cash"]],
is_group: 0, "is_group": 0,
}, }
}; };
}); });
frm.set_query("bank_account", function () { frm.set_query("bank_account", function () {
return { return {
filters: { filters: {
is_company_account: 1, 'is_company_account': 1
}, },
}; };
}); });
}, },
onload: function (frm) { onload: function(frm) {
let default_bank_account = frappe.defaults.get_user_default("Company")
? locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"] let default_bank_account = frappe.defaults.get_user_default("Company")?
: ""; locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]: "";
frm.set_value("account", default_bank_account); frm.set_value("account", default_bank_account);
frm.set_value("from_date", frappe.datetime.month_start()); frm.set_value("from_date", frappe.datetime.month_start());
frm.set_value("to_date", frappe.datetime.month_end()); frm.set_value("to_date", frappe.datetime.month_end());
}, },
refresh: function (frm) { refresh: function(frm) {
frm.disable_save(); frm.disable_save();
frm.add_custom_button(__("Get Payment Entries"), () => frm.trigger("get_payment_entries")); frm.add_custom_button(__('Get Payment Entries'), () =>
frm.change_custom_button_type(__("Get Payment Entries"), null, "primary"); frm.trigger("get_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('Get Payment Entries', null, 'primary');
frm.change_custom_button_type(__("Update Clearance Date"), null, "primary");
}
}, },
update_clearance_date: function (frm) { update_clearance_date: function(frm) {
return frappe.call({ return frappe.call({
method: "update_clearance_date", method: "update_clearance_date",
doc: frm.doc, doc: frm.doc,
callback: function (r, rt) { 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');
}
}
}); });
}, },
get_payment_entries: function (frm) { get_payment_entries: function(frm) {
return frappe.call({ return frappe.call({
method: "get_payment_entries", method: "get_payment_entries",
doc: frm.doc, doc: frm.doc,
callback: function () { callback: function(r, rt) {
frm.refresh(); 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 import _, msgprint
from frappe.model.document import Document from frappe.model.document import Document
from frappe.query_builder.custom import ConstantColumn from frappe.query_builder.custom import ConstantColumn
from frappe.utils import flt, fmt_money, get_link_to_form, getdate from frappe.utils import flt, fmt_money, getdate
import erpnext import erpnext
@@ -27,7 +27,7 @@ class BankClearance(Document):
condition = "and (clearance_date IS NULL or clearance_date='0000-00-00')" condition = "and (clearance_date IS NULL or clearance_date='0000-00-00')"
journal_entries = frappe.db.sql( journal_entries = frappe.db.sql(
f""" """
select select
"Journal Entry" as payment_document, t1.name as payment_entry, "Journal Entry" as payment_document, t1.name as payment_entry,
t1.cheque_no as cheque_number, t1.cheque_date, t1.cheque_no as cheque_number, t1.cheque_date,
@@ -41,7 +41,9 @@ class BankClearance(Document):
and ifnull(t1.is_opening, 'No') = 'No' {condition} and ifnull(t1.is_opening, 'No') = 'No' {condition}
group by t2.account, t1.name group by t2.account, t1.name
order by t1.posting_date ASC, t1.name DESC order by t1.posting_date ASC, t1.name DESC
""", """.format(
condition=condition
),
{"account": self.account, "from": self.from_date, "to": self.to_date}, {"account": self.account, "from": self.from_date, "to": self.to_date},
as_dict=1, as_dict=1,
) )
@@ -50,7 +52,7 @@ class BankClearance(Document):
condition += "and bank_account = %(bank_account)s" condition += "and bank_account = %(bank_account)s"
payment_entries = frappe.db.sql( payment_entries = frappe.db.sql(
f""" """
select select
"Payment Entry" as payment_document, name as payment_entry, "Payment Entry" as payment_document, name as payment_entry,
reference_no as cheque_number, reference_date as cheque_date, reference_no as cheque_number, reference_date as cheque_date,
@@ -65,7 +67,9 @@ class BankClearance(Document):
{condition} {condition}
order by order by
posting_date ASC, name DESC posting_date ASC, name DESC
""", """.format(
condition=condition
),
{ {
"account": self.account, "account": self.account,
"from": self.from_date, "from": self.from_date,
@@ -128,9 +132,11 @@ class BankClearance(Document):
query = query.where(loan_repayment.clearance_date.isnull()) query = query.where(loan_repayment.clearance_date.isnull())
if frappe.db.has_column("Loan Repayment", "repay_from_salary"): if frappe.db.has_column("Loan Repayment", "repay_from_salary"):
query = query.where(loan_repayment.repay_from_salary == 0) query = query.where((loan_repayment.repay_from_salary == 0))
query = query.orderby(loan_repayment.posting_date).orderby(loan_repayment.name, order=frappe.qb.desc) query = query.orderby(loan_repayment.posting_date).orderby(
loan_repayment.name, order=frappe.qb.desc
)
loan_repayments = query.run(as_dict=True) loan_repayments = query.run(as_dict=True)
@@ -210,11 +216,8 @@ class BankClearance(Document):
if d.cheque_date and getdate(d.clearance_date) < getdate(d.cheque_date): if d.cheque_date and getdate(d.clearance_date) < getdate(d.cheque_date):
frappe.throw( frappe.throw(
_("Row #{0}: For {1} Clearance date {2} cannot be before Cheque Date {3}").format( _("Row #{0}: Clearance date {1} cannot be before Cheque Date {2}").format(
d.idx, d.idx, d.clearance_date, d.cheque_date
get_link_to_form(d.payment_document, d.payment_entry),
d.clearance_date,
d.cheque_date,
) )
) )

View File

@@ -1,39 +1,39 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
cur_frm.add_fetch("bank_account", "account", "account"); cur_frm.add_fetch('bank_account','account','account');
cur_frm.add_fetch("bank_account", "bank_account_no", "bank_account_no"); cur_frm.add_fetch('bank_account','bank_account_no','bank_account_no');
cur_frm.add_fetch("bank_account", "iban", "iban"); cur_frm.add_fetch('bank_account','iban','iban');
cur_frm.add_fetch("bank_account", "branch_code", "branch_code"); cur_frm.add_fetch('bank_account','branch_code','branch_code');
cur_frm.add_fetch("bank", "swift_number", "swift_number"); cur_frm.add_fetch('bank','swift_number','swift_number');
frappe.ui.form.on("Bank Guarantee", { frappe.ui.form.on('Bank Guarantee', {
setup: function (frm) { setup: function(frm) {
frm.set_query("bank", function () { frm.set_query("bank", function() {
return {
filters: {
company: frm.doc.company
}
};
});
frm.set_query("bank_account", function() {
return { return {
filters: { filters: {
company: frm.doc.company, company: frm.doc.company,
}, bank: frm.doc.bank
}; }
}
}); });
frm.set_query("bank_account", function () { frm.set_query("project", function() {
return { return {
filters: { filters: {
company: frm.doc.company, customer: frm.doc.customer
bank: frm.doc.bank, }
},
};
});
frm.set_query("project", function () {
return {
filters: {
customer: frm.doc.customer,
},
}; };
}); });
}, },
bg_type: function (frm) { bg_type: function(frm) {
if (frm.doc.bg_type == "Receiving") { if (frm.doc.bg_type == "Receiving") {
frm.set_value("reference_doctype", "Sales Order"); frm.set_value("reference_doctype", "Sales Order");
} else if (frm.doc.bg_type == "Providing") { } else if (frm.doc.bg_type == "Providing") {
@@ -41,33 +41,34 @@ frappe.ui.form.on("Bank Guarantee", {
} }
}, },
reference_docname: function (frm) { reference_docname: function(frm) {
if (frm.doc.reference_docname && frm.doc.reference_doctype) { if (frm.doc.reference_docname && frm.doc.reference_doctype) {
let party_field = frm.doc.reference_doctype == "Sales Order" ? "customer" : "supplier"; let party_field = frm.doc.reference_doctype == "Sales Order" ? "customer" : "supplier";
frappe.call({ frappe.call({
method: "erpnext.accounts.doctype.bank_guarantee.bank_guarantee.get_voucher_details", method: "erpnext.accounts.doctype.bank_guarantee.bank_guarantee.get_voucher_details",
args: { args: {
bank_guarantee_type: frm.doc.bg_type, "bank_guarantee_type": frm.doc.bg_type,
reference_name: frm.doc.reference_docname, "reference_name": frm.doc.reference_docname
}, },
callback: function (r) { callback: function(r) {
if (r.message) { if (r.message) {
if (r.message[party_field]) frm.set_value(party_field, r.message[party_field]); if (r.message[party_field]) frm.set_value(party_field, r.message[party_field]);
if (r.message.project) frm.set_value("project", r.message.project); if (r.message.project) frm.set_value("project", r.message.project);
if (r.message.grand_total) frm.set_value("amount", r.message.grand_total); if (r.message.grand_total) frm.set_value("amount", r.message.grand_total);
} }
}, }
}); });
} }
}, },
start_date: function (frm) { start_date: function(frm) {
var end_date = frappe.datetime.add_days(cur_frm.doc.start_date, cur_frm.doc.validity - 1); var end_date = frappe.datetime.add_days(cur_frm.doc.start_date, cur_frm.doc.validity - 1);
cur_frm.set_value("end_date", end_date); cur_frm.set_value("end_date", end_date);
}, },
validity: function (frm) { validity: function(frm) {
var end_date = frappe.datetime.add_days(cur_frm.doc.start_date, cur_frm.doc.validity - 1); var end_date = frappe.datetime.add_days(cur_frm.doc.start_date, cur_frm.doc.validity - 1);
cur_frm.set_value("end_date", end_date); cur_frm.set_value("end_date", end_date);
}, }
}); });

View File

@@ -8,22 +8,21 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
return { return {
filters: { filters: {
company: frm.doc.company, company: frm.doc.company,
is_company_account: 1, 'is_company_account': 1
}, },
}; };
}); });
let no_bank_transactions_text = `<div class="text-muted text-center">${__( let no_bank_transactions_text =
"No Matching Bank Transactions Found" `<div class="text-muted text-center">${__("No Matching Bank Transactions Found")}</div>`
)}</div>`;
set_field_options("no_bank_transactions", no_bank_transactions_text); set_field_options("no_bank_transactions", no_bank_transactions_text);
}, },
onload: function (frm) { onload: function (frm) {
// Set default filter dates // Set default filter dates
let today = frappe.datetime.get_today(); let today = frappe.datetime.get_today()
frm.doc.bank_statement_from_date = frappe.datetime.add_months(today, -1); frm.doc.bank_statement_from_date = frappe.datetime.add_months(today, -1);
frm.doc.bank_statement_to_date = today; frm.doc.bank_statement_to_date = today;
frm.trigger("bank_account"); frm.trigger('bank_account');
}, },
filter_by_reference_date: function (frm) { filter_by_reference_date: function (frm) {
@@ -38,27 +37,34 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
refresh: function (frm) { refresh: function (frm) {
frm.disable_save(); frm.disable_save();
frappe.require("bank-reconciliation-tool.bundle.js", () => frm.trigger("make_reconciliation_tool")); frappe.require("bank-reconciliation-tool.bundle.js", () =>
frm.trigger("make_reconciliation_tool")
frm.add_custom_button(__("Upload Bank Statement"), () =>
frappe.call({
method: "erpnext.accounts.doctype.bank_statement_import.bank_statement_import.upload_bank_statement",
args: {
dt: frm.doc.doctype,
dn: frm.doc.name,
company: frm.doc.company,
bank_account: frm.doc.bank_account,
},
callback: function (r) {
if (!r.exc) {
var doc = frappe.model.sync(r.message);
frappe.set_route("Form", doc[0].doctype, doc[0].name);
}
},
})
); );
frm.add_custom_button(__("Auto Reconcile"), function () { frm.add_custom_button(__("Upload Bank Statement"), () =>
frappe.call({
method:
"erpnext.accounts.doctype.bank_statement_import.bank_statement_import.upload_bank_statement",
args: {
dt: frm.doc.doctype,
dn: frm.doc.name,
company: frm.doc.company,
bank_account: frm.doc.bank_account,
},
callback: function (r) {
if (!r.exc) {
var doc = frappe.model.sync(r.message);
frappe.set_route(
"Form",
doc[0].doctype,
doc[0].name
);
}
},
})
);
frm.add_custom_button(__('Auto Reconcile'), function() {
frappe.call({ frappe.call({
method: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.auto_reconcile_vouchers", method: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.auto_reconcile_vouchers",
args: { args: {
@@ -69,22 +75,33 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
from_reference_date: frm.doc.from_reference_date, from_reference_date: frm.doc.from_reference_date,
to_reference_date: frm.doc.to_reference_date, to_reference_date: frm.doc.to_reference_date,
}, },
}); })
}); });
frm.add_custom_button(__("Get Unreconciled Entries"), function () { frm.add_custom_button(__('Get Unreconciled Entries'), function() {
frm.trigger("make_reconciliation_tool"); frm.trigger("make_reconciliation_tool");
}); });
frm.change_custom_button_type("Get Unreconciled Entries", null, "primary"); frm.change_custom_button_type('Get Unreconciled Entries', null, 'primary');
}, },
bank_account: function (frm) { bank_account: function (frm) {
frappe.db.get_value("Bank Account", frm.doc.bank_account, "account", (r) => { frappe.db.get_value(
frappe.db.get_value("Account", r.account, "account_currency", (r) => { "Bank Account",
frm.doc.account_currency = r.account_currency; frm.doc.bank_account,
frm.trigger("render_chart"); "account",
}); (r) => {
}); frappe.db.get_value(
"Account",
r.account,
"account_currency",
(r) => {
frm.doc.account_currency = r.account_currency;
frm.trigger("render_chart");
}
);
}
);
frm.trigger("get_account_opening_balance"); frm.trigger("get_account_opening_balance");
}, },
@@ -103,7 +120,11 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
) { ) {
frm.trigger("render_chart"); frm.trigger("render_chart");
frm.trigger("render"); frm.trigger("render");
frappe.utils.scroll_to(frm.get_field("reconciliation_tool_cards").$wrapper, true, 30); frappe.utils.scroll_to(
frm.get_field("reconciliation_tool_cards").$wrapper,
true,
30
);
} }
}); });
} }
@@ -112,10 +133,11 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
get_account_opening_balance(frm) { get_account_opening_balance(frm) {
if (frm.doc.bank_account && frm.doc.bank_statement_from_date) { if (frm.doc.bank_account && frm.doc.bank_statement_from_date) {
frappe.call({ frappe.call({
method: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance", method:
"erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance",
args: { args: {
bank_account: frm.doc.bank_account, bank_account: frm.doc.bank_account,
till_date: frappe.datetime.add_days(frm.doc.bank_statement_from_date, -1), till_date: frm.doc.bank_statement_from_date,
}, },
callback: (response) => { callback: (response) => {
frm.set_value("account_opening_balance", response.message); frm.set_value("account_opening_balance", response.message);
@@ -127,7 +149,8 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
get_cleared_balance(frm) { get_cleared_balance(frm) {
if (frm.doc.bank_account && frm.doc.bank_statement_to_date) { if (frm.doc.bank_account && frm.doc.bank_statement_to_date) {
return frappe.call({ return frappe.call({
method: "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance", method:
"erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance",
args: { args: {
bank_account: frm.doc.bank_account, bank_account: frm.doc.bank_account,
till_date: frm.doc.bank_statement_to_date, till_date: frm.doc.bank_statement_to_date,
@@ -140,30 +163,41 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
}, },
render_chart(frm) { render_chart(frm) {
frm.cards_manager = new erpnext.accounts.bank_reconciliation.NumberCardManager({ frm.cards_manager = new erpnext.accounts.bank_reconciliation.NumberCardManager(
$reconciliation_tool_cards: frm.get_field("reconciliation_tool_cards").$wrapper, {
bank_statement_closing_balance: frm.doc.bank_statement_closing_balance, $reconciliation_tool_cards: frm.get_field(
cleared_balance: frm.cleared_balance, "reconciliation_tool_cards"
currency: frm.doc.account_currency, ).$wrapper,
}); bank_statement_closing_balance:
frm.doc.bank_statement_closing_balance,
cleared_balance: frm.cleared_balance,
currency: frm.doc.account_currency,
}
);
}, },
render(frm) { render(frm) {
if (frm.doc.bank_account) { if (frm.doc.bank_account) {
frm.bank_reconciliation_data_table_manager = frm.bank_reconciliation_data_table_manager = new erpnext.accounts.bank_reconciliation.DataTableManager(
new erpnext.accounts.bank_reconciliation.DataTableManager({ {
company: frm.doc.company, company: frm.doc.company,
bank_account: frm.doc.bank_account, bank_account: frm.doc.bank_account,
$reconciliation_tool_dt: frm.get_field("reconciliation_tool_dt").$wrapper, $reconciliation_tool_dt: frm.get_field(
$no_bank_transactions: frm.get_field("no_bank_transactions").$wrapper, "reconciliation_tool_dt"
).$wrapper,
$no_bank_transactions: frm.get_field(
"no_bank_transactions"
).$wrapper,
bank_statement_from_date: frm.doc.bank_statement_from_date, bank_statement_from_date: frm.doc.bank_statement_from_date,
bank_statement_to_date: frm.doc.bank_statement_to_date, bank_statement_to_date: frm.doc.bank_statement_to_date,
filter_by_reference_date: frm.doc.filter_by_reference_date, filter_by_reference_date: frm.doc.filter_by_reference_date,
from_reference_date: frm.doc.from_reference_date, from_reference_date: frm.doc.from_reference_date,
to_reference_date: frm.doc.to_reference_date, to_reference_date: frm.doc.to_reference_date,
bank_statement_closing_balance: frm.doc.bank_statement_closing_balance, bank_statement_closing_balance:
frm.doc.bank_statement_closing_balance,
cards_manager: frm.cards_manager, cards_manager: frm.cards_manager,
}); }
);
} }
}, },
}); });

View File

@@ -17,7 +17,6 @@ from erpnext.accounts.report.bank_reconciliation_statement.bank_reconciliation_s
get_entries, get_entries,
) )
from erpnext.accounts.utils import get_balance_on from erpnext.accounts.utils import get_balance_on
from erpnext.setup.utils import get_exchange_rate
class BankReconciliationTool(Document): class BankReconciliationTool(Document):
@@ -61,7 +60,9 @@ def get_bank_transactions(bank_account, from_date=None, to_date=None):
def get_account_balance(bank_account, till_date): def get_account_balance(bank_account, till_date):
# returns account balance till the specified date # returns account balance till the specified date
account = frappe.db.get_value("Bank Account", bank_account, "account") account = frappe.db.get_value("Bank Account", bank_account, "account")
filters = frappe._dict({"account": account, "report_date": till_date, "include_pos_transactions": 1}) filters = frappe._dict(
{"account": account, "report_date": till_date, "include_pos_transactions": 1}
)
data = get_entries(filters) data = get_entries(filters)
balance_as_per_system = get_balance_on(filters["account"], filters["report_date"]) balance_as_per_system = get_balance_on(filters["account"], filters["report_date"])
@@ -74,7 +75,10 @@ def get_account_balance(bank_account, till_date):
amounts_not_reflected_in_system = get_amounts_not_reflected_in_system(filters) amounts_not_reflected_in_system = get_amounts_not_reflected_in_system(filters)
bank_bal = ( bank_bal = (
flt(balance_as_per_system) - flt(total_debit) + flt(total_credit) + amounts_not_reflected_in_system flt(balance_as_per_system)
- flt(total_debit)
+ flt(total_credit)
+ amounts_not_reflected_in_system
) )
return bank_bal return bank_bal
@@ -125,7 +129,7 @@ def create_journal_entry_bts(
bank_transaction = frappe.db.get_values( bank_transaction = frappe.db.get_values(
"Bank Transaction", "Bank Transaction",
bank_transaction_name, bank_transaction_name,
fieldname=["name", "deposit", "withdrawal", "bank_account", "currency"], fieldname=["name", "deposit", "withdrawal", "bank_account"],
as_dict=True, as_dict=True,
)[0] )[0]
company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account") company_account = frappe.get_value("Bank Account", bank_transaction.bank_account, "account")
@@ -139,94 +143,29 @@ def create_journal_entry_bts(
) )
company = frappe.get_value("Account", company_account, "company") company = frappe.get_value("Account", company_account, "company")
company_default_currency = frappe.get_cached_value("Company", company, "default_currency")
company_account_currency = frappe.get_cached_value("Account", company_account, "account_currency")
second_account_currency = frappe.get_cached_value("Account", second_account, "account_currency")
# determine if multi-currency Journal or not
is_multi_currency = (
True
if company_default_currency != company_account_currency
or company_default_currency != second_account_currency
or company_default_currency != bank_transaction.currency
else False
)
accounts = [] accounts = []
second_account_dict = { # Multi Currency?
"account": second_account, accounts.append(
"account_currency": second_account_currency, {
"credit_in_account_currency": bank_transaction.deposit, "account": second_account,
"debit_in_account_currency": bank_transaction.withdrawal, "credit_in_account_currency": bank_transaction.deposit,
"party_type": party_type, "debit_in_account_currency": bank_transaction.withdrawal,
"party": party, "party_type": party_type,
"cost_center": get_default_cost_center(company), "party": party,
} "cost_center": get_default_cost_center(company),
}
)
company_account_dict = { accounts.append(
"account": company_account, {
"account_currency": company_account_currency, "account": company_account,
"bank_account": bank_transaction.bank_account, "bank_account": bank_transaction.bank_account,
"credit_in_account_currency": bank_transaction.withdrawal, "credit_in_account_currency": bank_transaction.withdrawal,
"debit_in_account_currency": bank_transaction.deposit, "debit_in_account_currency": bank_transaction.deposit,
"cost_center": get_default_cost_center(company), "cost_center": get_default_cost_center(company),
} }
)
# convert transaction amount to company currency
if is_multi_currency:
exc_rate = get_exchange_rate(bank_transaction.currency, company_default_currency, posting_date)
withdrawal_in_company_currency = flt(exc_rate * abs(bank_transaction.withdrawal))
deposit_in_company_currency = flt(exc_rate * abs(bank_transaction.deposit))
else:
withdrawal_in_company_currency = bank_transaction.withdrawal
deposit_in_company_currency = bank_transaction.deposit
# if second account is of foreign currency, convert and set debit and credit fields.
if second_account_currency != company_default_currency:
exc_rate = get_exchange_rate(second_account_currency, company_default_currency, posting_date)
second_account_dict.update(
{
"exchange_rate": exc_rate,
"credit": deposit_in_company_currency,
"debit": withdrawal_in_company_currency,
"credit_in_account_currency": flt(deposit_in_company_currency / exc_rate) or 0,
"debit_in_account_currency": flt(withdrawal_in_company_currency / exc_rate) or 0,
}
)
else:
second_account_dict.update(
{
"exchange_rate": 1,
"credit": deposit_in_company_currency,
"debit": withdrawal_in_company_currency,
"credit_in_account_currency": deposit_in_company_currency,
"debit_in_account_currency": withdrawal_in_company_currency,
}
)
# if company account is of foreign currency, convert and set debit and credit fields.
if company_account_currency != company_default_currency:
exc_rate = get_exchange_rate(company_account_currency, company_default_currency, posting_date)
company_account_dict.update(
{
"exchange_rate": exc_rate,
"credit": withdrawal_in_company_currency,
"debit": deposit_in_company_currency,
}
)
else:
company_account_dict.update(
{
"exchange_rate": 1,
"credit": withdrawal_in_company_currency,
"debit": deposit_in_company_currency,
"credit_in_account_currency": withdrawal_in_company_currency,
"debit_in_account_currency": deposit_in_company_currency,
}
)
accounts.append(second_account_dict)
accounts.append(company_account_dict)
journal_entry_dict = { journal_entry_dict = {
"voucher_type": entry_type, "voucher_type": entry_type,
@@ -236,9 +175,6 @@ def create_journal_entry_bts(
"cheque_no": reference_number, "cheque_no": reference_number,
"mode_of_payment": mode_of_payment, "mode_of_payment": mode_of_payment,
} }
if is_multi_currency:
journal_entry_dict.update({"multi_currency": True})
journal_entry = frappe.new_doc("Journal Entry") journal_entry = frappe.new_doc("Journal Entry")
journal_entry.update(journal_entry_dict) journal_entry.update(journal_entry_dict)
journal_entry.set("accounts", accounts) journal_entry.set("accounts", accounts)
@@ -372,13 +308,12 @@ def auto_reconcile_vouchers(
) )
transaction = frappe.get_doc("Bank Transaction", transaction.name) transaction = frappe.get_doc("Bank Transaction", transaction.name)
account = frappe.db.get_value("Bank Account", transaction.bank_account, "account") account = frappe.db.get_value("Bank Account", transaction.bank_account, "account")
matched_trans = 0
for voucher in vouchers: for voucher in vouchers:
gl_entry = frappe.db.get_value( gl_entry = frappe.db.get_value(
"GL Entry", "GL Entry",
dict( dict(
account=account, account=account, voucher_type=voucher["payment_doctype"], voucher_no=voucher["payment_name"]
voucher_type=voucher["payment_doctype"],
voucher_no=voucher["payment_name"],
), ),
["credit", "debit"], ["credit", "debit"],
as_dict=1, as_dict=1,
@@ -727,7 +662,7 @@ def get_lr_matching_query(bank_account, exact_match, filters):
) )
if frappe.db.has_column("Loan Repayment", "repay_from_salary"): if frappe.db.has_column("Loan Repayment", "repay_from_salary"):
query = query.where(loan_repayment.repay_from_salary == 0) query = query.where((loan_repayment.repay_from_salary == 0))
if exact_match: if exact_match:
query.where(loan_repayment.amount_paid == filters.get("amount")) query.where(loan_repayment.amount_paid == filters.get("amount"))
@@ -760,7 +695,7 @@ def get_pe_matching_query(
if cint(filter_by_reference_date): if cint(filter_by_reference_date):
filter_by_date = f"AND reference_date between '{from_reference_date}' and '{to_reference_date}'" filter_by_date = f"AND reference_date between '{from_reference_date}' and '{to_reference_date}'"
order_by = " reference_date" order_by = " reference_date"
if frappe.flags.auto_reconcile_vouchers is True: if frappe.flags.auto_reconcile_vouchers == True:
filter_by_reference_no = f"AND reference_no = '{transaction.reference_number}'" filter_by_reference_no = f"AND reference_no = '{transaction.reference_number}'"
return f""" return f"""
SELECT SELECT
@@ -811,7 +746,7 @@ def get_je_matching_query(
if cint(filter_by_reference_date): if cint(filter_by_reference_date):
filter_by_date = f"AND je.cheque_date between '{from_reference_date}' and '{to_reference_date}'" filter_by_date = f"AND je.cheque_date between '{from_reference_date}' and '{to_reference_date}'"
order_by = " je.cheque_date" order_by = " je.cheque_date"
if frappe.flags.auto_reconcile_vouchers is True: if frappe.flags.auto_reconcile_vouchers == True:
filter_by_reference_no = f"AND je.cheque_no = '{transaction.reference_number}'" filter_by_reference_no = f"AND je.cheque_no = '{transaction.reference_number}'"
return f""" return f"""
SELECT SELECT

View File

@@ -2,24 +2,16 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on("Bank Statement Import", { frappe.ui.form.on("Bank Statement Import", {
onload(frm) {
frm.set_query("bank_account", function (doc) {
return {
filters: {
company: doc.company,
},
};
});
},
setup(frm) { setup(frm) {
frappe.realtime.on("data_import_refresh", ({ data_import }) => { frappe.realtime.on("data_import_refresh", ({ data_import }) => {
frm.import_in_progress = false; frm.import_in_progress = false;
if (data_import !== frm.doc.name) return; if (data_import !== frm.doc.name) return;
frappe.model.clear_doc("Bank Statement Import", frm.doc.name); frappe.model.clear_doc("Bank Statement Import", frm.doc.name);
frappe.model.with_doc("Bank Statement Import", frm.doc.name).then(() => { frappe.model
frm.refresh(); .with_doc("Bank Statement Import", frm.doc.name)
}); .then(() => {
frm.refresh();
});
}); });
frappe.realtime.on("data_import_progress", (data) => { frappe.realtime.on("data_import_progress", (data) => {
frm.import_in_progress = true; frm.import_in_progress = true;
@@ -46,9 +38,20 @@ frappe.ui.form.on("Bank Statement Import", {
: __("Updating {0} of {1}, {2}", message_args); : __("Updating {0} of {1}, {2}", message_args);
} }
if (data.skipping) { if (data.skipping) {
message = __("Skipping {0} of {1}, {2}", [data.current, data.total, eta_message]); message = __(
"Skipping {0} of {1}, {2}",
[
data.current,
data.total,
eta_message,
]
);
} }
frm.dashboard.show_progress(__("Import Progress"), percent, message); frm.dashboard.show_progress(
__("Import Progress"),
percent,
message
);
frm.page.set_indicator(__("In Progress"), "orange"); frm.page.set_indicator(__("In Progress"), "orange");
// hide progress when complete // hide progress when complete
@@ -90,12 +93,15 @@ frappe.ui.form.on("Bank Statement Import", {
frm.trigger("show_report_error_button"); frm.trigger("show_report_error_button");
if (frm.doc.status === "Partial Success") { if (frm.doc.status === "Partial Success") {
frm.add_custom_button(__("Export Errored Rows"), () => frm.trigger("export_errored_rows")); frm.add_custom_button(__("Export Errored Rows"), () =>
frm.trigger("export_errored_rows")
);
} }
if (frm.doc.status.includes("Success")) { if (frm.doc.status.includes("Success")) {
frm.add_custom_button(__("Go to {0} List", [__(frm.doc.reference_doctype)]), () => frm.add_custom_button(
frappe.set_route("List", frm.doc.reference_doctype) __("Go to {0} List", [__(frm.doc.reference_doctype)]),
() => frappe.set_route("List", frm.doc.reference_doctype)
); );
} }
}, },
@@ -112,8 +118,13 @@ frappe.ui.form.on("Bank Statement Import", {
frm.disable_save(); frm.disable_save();
if (frm.doc.status !== "Success") { if (frm.doc.status !== "Success") {
if (!frm.is_new() && frm.has_import_file()) { if (!frm.is_new() && frm.has_import_file()) {
let label = frm.doc.status === "Pending" ? __("Start Import") : __("Retry"); let label =
frm.page.set_primary_action(label, () => frm.events.start_import(frm)); frm.doc.status === "Pending"
? __("Start Import")
: __("Retry");
frm.page.set_primary_action(label, () =>
frm.events.start_import(frm)
);
} else { } else {
frm.page.set_primary_action(__("Save"), () => frm.save()); frm.page.set_primary_action(__("Save"), () => frm.save());
} }
@@ -155,24 +166,24 @@ frappe.ui.form.on("Bank Statement Import", {
message = message =
successful_records.length > 1 successful_records.length > 1
? __( ? __(
"Successfully imported {0} records out of {1}. Click on Export Errored Rows, fix the errors and import again.", "Successfully imported {0} records out of {1}. Click on Export Errored Rows, fix the errors and import again.",
message_args message_args
) )
: __( : __(
"Successfully imported {0} record out of {1}. Click on Export Errored Rows, fix the errors and import again.", "Successfully imported {0} record out of {1}. Click on Export Errored Rows, fix the errors and import again.",
message_args message_args
); );
} else { } else {
message = message =
successful_records.length > 1 successful_records.length > 1
? __( ? __(
"Successfully updated {0} records out of {1}. Click on Export Errored Rows, fix the errors and import again.", "Successfully updated {0} records out of {1}. Click on Export Errored Rows, fix the errors and import again.",
message_args message_args
) )
: __( : __(
"Successfully updated {0} record out of {1}. Click on Export Errored Rows, fix the errors and import again.", "Successfully updated {0} record out of {1}. Click on Export Errored Rows, fix the errors and import again.",
message_args message_args
); );
} }
} }
frm.dashboard.set_headline(message); frm.dashboard.set_headline(message);
@@ -215,7 +226,8 @@ frappe.ui.form.on("Bank Statement Import", {
}, },
download_template() { download_template() {
let method = "/api/method/frappe.core.doctype.data_import.data_import.download_template"; let method =
"/api/method/frappe.core.doctype.data_import.data_import.download_template";
open_url_post(method, { open_url_post(method, {
doctype: "Bank Transaction", doctype: "Bank Transaction",
@@ -228,7 +240,7 @@ frappe.ui.form.on("Bank Statement Import", {
"description", "description",
"reference_number", "reference_number",
"bank_account", "bank_account",
"currency", "currency"
], ],
}, },
}); });
@@ -299,7 +311,10 @@ frappe.ui.form.on("Bank Statement Import", {
show_import_preview(frm, preview_data) { show_import_preview(frm, preview_data) {
let import_log = JSON.parse(frm.doc.statement_import_log || "[]"); let import_log = JSON.parse(frm.doc.statement_import_log || "[]");
if (frm.import_preview && frm.import_preview.doctype === frm.doc.reference_doctype) { if (
frm.import_preview &&
frm.import_preview.doctype === frm.doc.reference_doctype
) {
frm.import_preview.preview_data = preview_data; frm.import_preview.preview_data = preview_data;
frm.import_preview.import_log = import_log; frm.import_preview.import_log = import_log;
frm.import_preview.refresh(); frm.import_preview.refresh();
@@ -315,10 +330,19 @@ frappe.ui.form.on("Bank Statement Import", {
frm, frm,
events: { events: {
remap_column(changed_map) { remap_column(changed_map) {
let template_options = JSON.parse(frm.doc.template_options || "{}"); let template_options = JSON.parse(
template_options.column_to_field_map = template_options.column_to_field_map || {}; frm.doc.template_options || "{}"
Object.assign(template_options.column_to_field_map, changed_map); );
frm.set_value("template_options", JSON.stringify(template_options)); template_options.column_to_field_map =
template_options.column_to_field_map || {};
Object.assign(
template_options.column_to_field_map,
changed_map
);
frm.set_value(
"template_options",
JSON.stringify(template_options)
);
frm.save().then(() => frm.trigger("import_file")); frm.save().then(() => frm.trigger("import_file"));
}, },
}, },
@@ -328,11 +352,10 @@ frappe.ui.form.on("Bank Statement Import", {
export_errored_rows(frm) { export_errored_rows(frm) {
open_url_post( open_url_post(
"/api/method/erpnext.accounts.doctype.bank_statement_import.bank_statement_import.download_errored_template", "/api/method/frappe.core.doctype.data_import.data_import.download_errored_template",
{ {
data_import_name: frm.doc.name, data_import_name: frm.doc.name,
}, }
true
); );
}, },
@@ -352,7 +375,8 @@ frappe.ui.form.on("Bank Statement Import", {
let other_warnings = []; let other_warnings = [];
for (let warning of warnings) { for (let warning of warnings) {
if (warning.row) { if (warning.row) {
warnings_by_row[warning.row] = warnings_by_row[warning.row] || []; warnings_by_row[warning.row] =
warnings_by_row[warning.row] || [];
warnings_by_row[warning.row].push(warning); warnings_by_row[warning.row].push(warning);
} else { } else {
other_warnings.push(warning); other_warnings.push(warning);
@@ -367,7 +391,9 @@ frappe.ui.form.on("Bank Statement Import", {
if (w.field) { if (w.field) {
let label = let label =
w.field.label + w.field.label +
(w.field.parent !== frm.doc.reference_doctype ? ` (${w.field.parent})` : ""); (w.field.parent !== frm.doc.reference_doctype
? ` (${w.field.parent})`
: "");
return `<li>${label}: ${w.message}</li>`; return `<li>${label}: ${w.message}</li>`;
} }
return `<li>${w.message}</li>`; return `<li>${w.message}</li>`;
@@ -386,9 +412,10 @@ frappe.ui.form.on("Bank Statement Import", {
.map((warning) => { .map((warning) => {
let header = ""; let header = "";
if (warning.col) { if (warning.col) {
let column_number = `<span class="text-uppercase">${__("Column {0}", [ let column_number = `<span class="text-uppercase">${__(
warning.col, "Column {0}",
])}</span>`; [warning.col]
)}</span>`;
let column_header = columns[warning.col].header_title; let column_header = columns[warning.col].header_title;
header = `${column_number} (${column_header})`; header = `${column_number} (${column_header})`;
} }
@@ -427,28 +454,36 @@ frappe.ui.form.on("Bank Statement Import", {
let html = ""; let html = "";
if (log.success) { if (log.success) {
if (frm.doc.import_type === "Insert New Records") { if (frm.doc.import_type === "Insert New Records") {
html = __("Successfully imported {0}", [ html = __(
`<span class="underline">${frappe.utils.get_form_link( "Successfully imported {0}", [
frm.doc.reference_doctype, `<span class="underline">${frappe.utils.get_form_link(
log.docname, frm.doc.reference_doctype,
true log.docname,
)}<span>`, true
]); )}<span>`,
]
);
} else { } else {
html = __("Successfully updated {0}", [ html = __(
`<span class="underline">${frappe.utils.get_form_link( "Successfully updated {0}", [
frm.doc.reference_doctype, `<span class="underline">${frappe.utils.get_form_link(
log.docname, frm.doc.reference_doctype,
true log.docname,
)}<span>`, true
]); )}<span>`,
]
);
} }
} else { } else {
let messages = log.messages let messages = log.messages
.map(JSON.parse) .map(JSON.parse)
.map((m) => { .map((m) => {
let title = m.title ? `<strong>${m.title}</strong>` : ""; let title = m.title
let message = m.message ? `<div>${m.message}</div>` : ""; ? `<strong>${m.title}</strong>`
: "";
let message = m.message
? `<div>${m.message}</div>`
: "";
return title + message; return title + message;
}) })
.join(""); .join("");

View File

@@ -21,7 +21,7 @@ INVALID_VALUES = ("", None)
class BankStatementImport(DataImport): class BankStatementImport(DataImport):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super(BankStatementImport, self).__init__(*args, **kwargs)
def validate(self): def validate(self):
doc_before_save = self.get_doc_before_save() doc_before_save = self.get_doc_before_save()
@@ -30,6 +30,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.import_file != self.import_file)
or (doc_before_save and doc_before_save.google_sheets_url != self.google_sheets_url) or (doc_before_save and doc_before_save.google_sheets_url != self.google_sheets_url)
): ):
template_options_dict = {} template_options_dict = {}
column_to_field_map = {} column_to_field_map = {}
bank = frappe.get_doc("Bank", self.bank) bank = frappe.get_doc("Bank", self.bank)
@@ -44,6 +45,7 @@ class BankStatementImport(DataImport):
self.validate_google_sheets_url() self.validate_google_sheets_url()
def start_import(self): def start_import(self):
preview = frappe.get_doc("Bank Statement Import", self.name).get_preview_from_template( preview = frappe.get_doc("Bank Statement Import", self.name).get_preview_from_template(
self.import_file, self.google_sheets_url self.import_file, self.google_sheets_url
) )
@@ -100,7 +102,7 @@ def download_errored_template(data_import_name):
def parse_data_from_template(raw_data): def parse_data_from_template(raw_data):
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): if all(v in INVALID_VALUES for v in row):
# empty row # empty row
continue continue
@@ -110,7 +112,9 @@ def parse_data_from_template(raw_data):
return 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""" """This method runs in background job"""
update_mapping_db(bank, template_options) update_mapping_db(bank, template_options)

View File

@@ -1,34 +1,36 @@
let imports_in_progress = []; let imports_in_progress = [];
frappe.listview_settings["Bank Statement Import"] = { frappe.listview_settings['Bank Statement Import'] = {
onload(listview) { onload(listview) {
frappe.realtime.on("data_import_progress", (data) => { frappe.realtime.on('data_import_progress', data => {
if (!imports_in_progress.includes(data.data_import)) { if (!imports_in_progress.includes(data.data_import)) {
imports_in_progress.push(data.data_import); imports_in_progress.push(data.data_import);
} }
}); });
frappe.realtime.on("data_import_refresh", (data) => { frappe.realtime.on('data_import_refresh', data => {
imports_in_progress = imports_in_progress.filter((d) => d !== data.data_import); imports_in_progress = imports_in_progress.filter(
d => d !== data.data_import
);
listview.refresh(); listview.refresh();
}); });
}, },
get_indicator: function (doc) { get_indicator: function(doc) {
var colors = { var colors = {
Pending: "orange", 'Pending': 'orange',
"Not Started": "orange", 'Not Started': 'orange',
"Partial Success": "orange", 'Partial Success': 'orange',
Success: "green", 'Success': 'green',
"In Progress": "orange", 'In Progress': 'orange',
Error: "red", 'Error': 'red'
}; };
let status = doc.status; let status = doc.status;
if (imports_in_progress.includes(doc.name)) { if (imports_in_progress.includes(doc.name)) {
status = "In Progress"; status = 'In Progress';
} }
if (status == "Pending") { if (status == 'Pending') {
status = "Not Started"; status = 'Not Started';
} }
return [__(status), colors[status], "status,=," + doc.status]; return [__(status), colors[status], 'status,=,' + doc.status];
}, },
hide_name_column: true, hide_name_column: true
}; };

View File

@@ -1,3 +1,5 @@
from typing import Tuple, Union
import frappe import frappe
from frappe.utils import flt from frappe.utils import flt
from rapidfuzz import fuzz, process from rapidfuzz import fuzz, process
@@ -17,7 +19,7 @@ class AutoMatchParty:
def get(self, key): def get(self, key):
return self.__dict__.get(key, None) return self.__dict__.get(key, None)
def match(self) -> tuple | None: def match(self) -> Union[Tuple, None]:
result = None result = None
result = AutoMatchbyAccountIBAN( result = AutoMatchbyAccountIBAN(
bank_party_account_number=self.bank_party_account_number, bank_party_account_number=self.bank_party_account_number,
@@ -48,7 +50,7 @@ class AutoMatchbyAccountIBAN:
result = self.match_account_in_party() result = self.match_account_in_party()
return result return result
def match_account_in_party(self) -> tuple | None: def match_account_in_party(self) -> Union[Tuple, None]:
"""Check if there is a IBAN/Account No. match in Customer/Supplier/Employee""" """Check if there is a IBAN/Account No. match in Customer/Supplier/Employee"""
result = None result = None
parties = get_parties_in_order(self.deposit) parties = get_parties_in_order(self.deposit)
@@ -95,7 +97,7 @@ class AutoMatchbyPartyNameDescription:
def get(self, key): def get(self, key):
return self.__dict__.get(key, None) return self.__dict__.get(key, None)
def match(self) -> tuple | None: def match(self) -> Union[Tuple, None]:
# fuzzy search by customer/supplier & employee # fuzzy search by customer/supplier & employee
if not (self.bank_party_name or self.description): if not (self.bank_party_name or self.description):
return None return None
@@ -103,15 +105,14 @@ class AutoMatchbyPartyNameDescription:
result = self.match_party_name_desc_in_party() result = self.match_party_name_desc_in_party()
return result 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""" """Fuzzy search party name and/or description against parties in the system"""
result = None result = None
parties = get_parties_in_order(self.deposit) parties = get_parties_in_order(self.deposit)
for party in parties: for party in parties:
filters = {"status": "Active"} if party == "Employee" else {"disabled": 0} filters = {"status": "Active"} if party == "Employee" else {"disabled": 0}
field = party.lower() + "_name" names = frappe.get_all(party, filters=filters, pluck=party.lower() + "_name")
names = frappe.get_all(party, filters=filters, fields=[f"{field} as party_name", "name"])
for field in ["bank_party_name", "description"]: for field in ["bank_party_name", "description"]:
if not self.get(field): if not self.get(field):
@@ -128,13 +129,9 @@ class AutoMatchbyPartyNameDescription:
return result 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 skip = False
result = process.extract( result = process.extract(query=self.get(field), choices=names, scorer=fuzz.token_set_ratio)
query=self.get(field),
choices={row.get("name"): row.get("party_name") for row in names},
scorer=fuzz.token_set_ratio,
)
party_name, skip = self.process_fuzzy_result(result) party_name, skip = self.process_fuzzy_result(result)
if not party_name: if not party_name:
@@ -145,21 +142,21 @@ class AutoMatchbyPartyNameDescription:
party_name, party_name,
), skip ), skip
def process_fuzzy_result(self, result: list | None): def process_fuzzy_result(self, result: Union[list, None]):
""" """
If there are multiple valid close matches return None as result may be faulty. If there are multiple valid close matches return None as result may be faulty.
Return the result only if one accurate match stands out. Return the result only if one accurate match stands out.
Returns: Result, Skip (whether or not to discontinue matching) 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): if not result or not len(result):
return None, False return None, False
first_result = result[0] first_result = result[0]
if len(result) == 1: 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] second_result = result[1]
if first_result[SCORE] > CUTOFF: if first_result[SCORE] > CUTOFF:
@@ -168,7 +165,7 @@ class AutoMatchbyPartyNameDescription:
if first_result[SCORE] == second_result[SCORE]: if first_result[SCORE] == second_result[SCORE]:
return None, True return None, True
return first_result[PARTY_ID], True return first_result[PARTY], True
else: else:
return None, False return None, False

View File

@@ -3,7 +3,7 @@
frappe.ui.form.on("Bank Transaction", { frappe.ui.form.on("Bank Transaction", {
onload(frm) { onload(frm) {
frm.set_query("payment_document", "payment_entries", function () { frm.set_query("payment_document", "payment_entries", function() {
const payment_doctypes = frm.events.get_payment_doctypes(frm); const payment_doctypes = frm.events.get_payment_doctypes(frm);
return { return {
filters: { filters: {
@@ -23,7 +23,7 @@ frappe.ui.form.on("Bank Transaction", {
set_bank_statement_filter(frm); set_bank_statement_filter(frm);
}, },
setup: function (frm) { setup: function(frm) {
frm.set_query("party_type", function () { frm.set_query("party_type", function () {
return { return {
filters: { filters: {
@@ -33,10 +33,16 @@ frappe.ui.form.on("Bank Transaction", {
}); });
}, },
get_payment_doctypes: function () { get_payment_doctypes: function() {
// get payment doctypes from all the apps // get payment doctypes from all the apps
return ["Payment Entry", "Journal Entry", "Sales Invoice", "Purchase Invoice", "Bank Transaction"]; return [
}, "Payment Entry",
"Journal Entry",
"Sales Invoice",
"Purchase Invoice",
"Bank Transaction",
];
}
}); });
frappe.ui.form.on("Bank Transaction Payments", { frappe.ui.form.on("Bank Transaction Payments", {
@@ -48,11 +54,10 @@ frappe.ui.form.on("Bank Transaction Payments", {
const update_clearance_date = (frm, cdt, cdn) => { const update_clearance_date = (frm, cdt, cdn) => {
if (frm.doc.docstatus === 1) { if (frm.doc.docstatus === 1) {
frappe frappe
.xcall("erpnext.accounts.doctype.bank_transaction.bank_transaction.unclear_reference_payment", { .xcall(
doctype: cdt, "erpnext.accounts.doctype.bank_transaction.bank_transaction.unclear_reference_payment",
docname: cdn, { doctype: cdt, docname: cdn, bt_name: frm.doc.name }
bt_name: frm.doc.name, )
})
.then((e) => { .then((e) => {
if (e == "success") { if (e == "success") {
frappe.show_alert({ frappe.show_alert({

View File

@@ -2,7 +2,6 @@
# For license information, please see license.txt # For license information, please see license.txt
import frappe import frappe
from frappe.model.docstatus import DocStatus
from frappe.utils import flt from frappe.utils import flt
from erpnext.controllers.status_updater import StatusUpdater from erpnext.controllers.status_updater import StatusUpdater
@@ -41,10 +40,9 @@ class BankTransaction(StatusUpdater):
else: else:
allocated_amount = 0.0 allocated_amount = 0.0
unallocated_amount = abs(flt(self.withdrawal) - flt(self.deposit)) - allocated_amount amount = abs(flt(self.withdrawal) - flt(self.deposit))
self.db_set("allocated_amount", flt(allocated_amount))
self.db_set("allocated_amount", flt(allocated_amount, self.precision("allocated_amount"))) self.db_set("unallocated_amount", amount - flt(allocated_amount))
self.db_set("unallocated_amount", flt(unallocated_amount, self.precision("unallocated_amount")))
self.reload() self.reload()
self.set_status(update=True) self.set_status(update=True)
@@ -70,7 +68,7 @@ class BankTransaction(StatusUpdater):
"payment_entry": voucher["payment_name"], "payment_entry": voucher["payment_name"],
"allocated_amount": 0.0, # Temporary "allocated_amount": 0.0, # Temporary
} }
self.append("payment_entries", pe) child = self.append("payment_entries", pe)
added = True added = True
# runs on_update_after_submit # runs on_update_after_submit
@@ -91,6 +89,7 @@ class BankTransaction(StatusUpdater):
- 0 > a: Error: already over-allocated - 0 > a: Error: already over-allocated
- clear means: set the latest transaction date as clearance date - clear means: set the latest transaction date as clearance date
""" """
gl_bank_account = frappe.db.get_value("Bank Account", self.bank_account, "account")
remaining_amount = self.unallocated_amount remaining_amount = self.unallocated_amount
for payment_entry in self.payment_entries: for payment_entry in self.payment_entries:
if payment_entry.allocated_amount == 0.0: if payment_entry.allocated_amount == 0.0:
@@ -186,7 +185,9 @@ def get_clearance_details(transaction, payment_entry):
""" """
gl_bank_account = frappe.db.get_value("Bank Account", transaction.bank_account, "account") 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) gles = get_related_bank_gl_entries(payment_entry.payment_document, payment_entry.payment_entry)
bt_allocations = get_total_allocated_amount(payment_entry.payment_document, payment_entry.payment_entry) bt_allocations = get_total_allocated_amount(
payment_entry.payment_document, payment_entry.payment_entry
)
unallocated_amount = min( unallocated_amount = min(
transaction.unallocated_amount, transaction.unallocated_amount,
@@ -284,6 +285,7 @@ def get_total_allocated_amount(doctype, docname):
def get_paid_amount(payment_entry, currency, gl_bank_account): def get_paid_amount(payment_entry, currency, gl_bank_account):
if payment_entry.payment_document in ["Payment Entry", "Sales Invoice", "Purchase Invoice"]: if payment_entry.payment_document in ["Payment Entry", "Sales Invoice", "Purchase Invoice"]:
paid_amount_field = "paid_amount" paid_amount_field = "paid_amount"
if payment_entry.payment_document == "Payment Entry": if payment_entry.payment_document == "Payment Entry":
doc = frappe.get_doc("Payment Entry", payment_entry.payment_entry) doc = frappe.get_doc("Payment Entry", payment_entry.payment_entry)
@@ -322,7 +324,9 @@ def get_paid_amount(payment_entry, currency, gl_bank_account):
) )
elif payment_entry.payment_document == "Loan Repayment": 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": elif payment_entry.payment_document == "Bank Transaction":
dep, wth = frappe.db.get_value( dep, wth = frappe.db.get_value(
@@ -332,7 +336,9 @@ def get_paid_amount(payment_entry, currency, gl_bank_account):
else: else:
frappe.throw( 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
)
) )
@@ -388,21 +394,3 @@ def unclear_reference_payment(doctype, docname, bt_name):
bt = frappe.get_doc("Bank Transaction", bt_name) bt = frappe.get_doc("Bank Transaction", bt_name)
set_voucher_clearance(doctype, docname, None, bt) set_voucher_clearance(doctype, docname, None, bt)
return docname return docname
def remove_from_bank_transaction(doctype, docname):
"""Remove a (cancelled) voucher from all Bank Transactions."""
for bt_name in get_reconciled_bank_transactions(doctype, docname):
bt = frappe.get_doc("Bank Transaction", bt_name)
if bt.docstatus == DocStatus.cancelled():
continue
modified = False
for pe in bt.payment_entries:
if pe.payment_document == doctype and pe.payment_entry == docname:
bt.remove(pe)
modified = True
if modified:
bt.save()

View File

@@ -1,15 +1,15 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt // License: GNU General Public License v3. See license.txt
frappe.listview_settings["Bank Transaction"] = { frappe.listview_settings['Bank Transaction'] = {
add_fields: ["unallocated_amount"], add_fields: ["unallocated_amount"],
get_indicator: function (doc) { get_indicator: function(doc) {
if (doc.docstatus == 2) { if(doc.docstatus == 2) {
return [__("Cancelled"), "red", "docstatus,=,2"]; return [__("Cancelled"), "red", "docstatus,=,2"];
} else if (flt(doc.unallocated_amount) <= 0) { } else if(flt(doc.unallocated_amount)<=0) {
return [__("Reconciled"), "green", "unallocated_amount,=,0"]; return [__("Reconciled"), "green", "unallocated_amount,=,0"];
} else if (flt(doc.unallocated_amount) > 0) { } else if(flt(doc.unallocated_amount)>0) {
return [__("Unreconciled"), "orange", "unallocated_amount,>,0"]; return [__("Unreconciled"), "orange", "unallocated_amount,>,0"];
} }
}, }
}; };

View File

@@ -18,12 +18,12 @@ def upload_bank_statement():
fcontent = frappe.local.uploaded_file fcontent = frappe.local.uploaded_file
fname = frappe.local.uploaded_filename 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 from frappe.utils.csvutils import read_csv_content
rows = read_csv_content(fcontent, False) 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 from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file
rows = read_xlsx_file_from_attached_file(fcontent=fcontent) rows = read_xlsx_file_from_attached_file(fcontent=fcontent)

View File

@@ -2,10 +2,10 @@
# See license.txt # See license.txt
import json import json
import unittest
import frappe import frappe
from frappe import utils from frappe import utils
from frappe.model.docstatus import DocStatus
from frappe.tests.utils import FrappeTestCase from frappe.tests.utils import FrappeTestCase
from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import ( from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import (
@@ -32,16 +32,8 @@ class TestBankTransaction(FrappeTestCase):
frappe.db.delete(dt) frappe.db.delete(dt)
make_pos_profile() make_pos_profile()
add_transactions()
# generate and use a uniq hash identifier for 'Bank Account' and it's linked GL 'Account' to avoid validation error add_vouchers()
uniq_identifier = frappe.generate_hash(length=10)
gl_account = create_gl_account("_Test Bank " + uniq_identifier)
bank_account = create_bank_account(
gl_account=gl_account, bank_account_name="Checking Account " + uniq_identifier
)
add_transactions(bank_account=bank_account)
add_vouchers(gl_account=gl_account)
# This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction. # This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction.
def test_linked_payments(self): def test_linked_payments(self):
@@ -89,29 +81,6 @@ class TestBankTransaction(FrappeTestCase):
clearance_date = frappe.db.get_value("Payment Entry", payment.name, "clearance_date") clearance_date = frappe.db.get_value("Payment Entry", payment.name, "clearance_date")
self.assertFalse(clearance_date) self.assertFalse(clearance_date)
def test_cancel_voucher(self):
bank_transaction = frappe.get_doc(
"Bank Transaction",
dict(description="1512567 BG/000003025 OPSKATTUZWXXX AT776000000098709849 Herr G"),
)
payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1700))
vouchers = json.dumps(
[
{
"payment_doctype": "Payment Entry",
"payment_name": payment.name,
"amount": bank_transaction.unallocated_amount,
}
]
)
reconcile_vouchers(bank_transaction.name, vouchers)
payment.reload()
payment.cancel()
bank_transaction.reload()
self.assertEqual(bank_transaction.docstatus, DocStatus.submitted())
self.assertEqual(bank_transaction.unallocated_amount, 1700)
self.assertEqual(bank_transaction.payment_entries, [])
# Check if ERPNext can correctly filter a linked payments based on the debit/credit amount # Check if ERPNext can correctly filter a linked payments based on the debit/credit amount
def test_debit_credit_output(self): def test_debit_credit_output(self):
bank_transaction = frappe.get_doc( bank_transaction = frappe.get_doc(
@@ -221,9 +190,7 @@ class TestBankTransaction(FrappeTestCase):
self.assertEqual(linked_payments[0][2], repayment_entry.name) self.assertEqual(linked_payments[0][2], repayment_entry.name)
def create_bank_account( def create_bank_account(bank_name="Citi Bank", account_name="_Test Bank - _TC"):
bank_name="Citi Bank", gl_account="_Test Bank - _TC", bank_account_name="Checking Account"
):
try: try:
frappe.get_doc( frappe.get_doc(
{ {
@@ -235,35 +202,21 @@ def create_bank_account(
pass pass
try: try:
bank_account = frappe.get_doc( frappe.get_doc(
{ {
"doctype": "Bank Account", "doctype": "Bank Account",
"account_name": bank_account_name, "account_name": "Checking Account",
"bank": bank_name, "bank": bank_name,
"account": gl_account, "account": account_name,
} }
).insert(ignore_if_duplicate=True) ).insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError: except frappe.DuplicateEntryError:
pass pass
return bank_account.name
def add_transactions():
create_bank_account()
def create_gl_account(gl_account_name="_Test Bank - _TC"):
gl_account = frappe.get_doc(
{
"doctype": "Account",
"company": "_Test Company",
"parent_account": "Current Assets - _TC",
"account_type": "Bank",
"is_group": 0,
"account_name": gl_account_name,
}
).insert()
return gl_account.name
def add_transactions(bank_account="_Test Bank - _TC"):
doc = frappe.get_doc( doc = frappe.get_doc(
{ {
"doctype": "Bank Transaction", "doctype": "Bank Transaction",
@@ -271,7 +224,7 @@ def add_transactions(bank_account="_Test Bank - _TC"):
"date": "2018-10-23", "date": "2018-10-23",
"deposit": 1200, "deposit": 1200,
"currency": "INR", "currency": "INR",
"bank_account": bank_account, "bank_account": "Checking Account - Citi Bank",
} }
).insert() ).insert()
doc.submit() doc.submit()
@@ -283,7 +236,7 @@ def add_transactions(bank_account="_Test Bank - _TC"):
"date": "2018-10-23", "date": "2018-10-23",
"deposit": 1700, "deposit": 1700,
"currency": "INR", "currency": "INR",
"bank_account": bank_account, "bank_account": "Checking Account - Citi Bank",
} }
).insert() ).insert()
doc.submit() doc.submit()
@@ -295,7 +248,7 @@ def add_transactions(bank_account="_Test Bank - _TC"):
"date": "2018-10-26", "date": "2018-10-26",
"withdrawal": 690, "withdrawal": 690,
"currency": "INR", "currency": "INR",
"bank_account": bank_account, "bank_account": "Checking Account - Citi Bank",
} }
).insert() ).insert()
doc.submit() doc.submit()
@@ -307,7 +260,7 @@ def add_transactions(bank_account="_Test Bank - _TC"):
"date": "2018-10-27", "date": "2018-10-27",
"deposit": 3900, "deposit": 3900,
"currency": "INR", "currency": "INR",
"bank_account": bank_account, "bank_account": "Checking Account - Citi Bank",
} }
).insert() ).insert()
doc.submit() doc.submit()
@@ -319,13 +272,13 @@ def add_transactions(bank_account="_Test Bank - _TC"):
"date": "2018-10-27", "date": "2018-10-27",
"withdrawal": 109080, "withdrawal": 109080,
"currency": "INR", "currency": "INR",
"bank_account": bank_account, "bank_account": "Checking Account - Citi Bank",
} }
).insert() ).insert()
doc.submit() doc.submit()
def add_vouchers(gl_account="_Test Bank - _TC"): def add_vouchers():
try: try:
frappe.get_doc( frappe.get_doc(
{ {
@@ -341,7 +294,7 @@ def add_vouchers(gl_account="_Test Bank - _TC"):
pi = make_purchase_invoice(supplier="Conrad Electronic", qty=1, rate=690) pi = make_purchase_invoice(supplier="Conrad Electronic", qty=1, rate=690)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account) pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
pe.reference_no = "Conrad Oct 18" pe.reference_no = "Conrad Oct 18"
pe.reference_date = "2018-10-24" pe.reference_date = "2018-10-24"
pe.insert() pe.insert()
@@ -360,14 +313,14 @@ def add_vouchers(gl_account="_Test Bank - _TC"):
pass pass
pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1200) pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1200)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account) pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
pe.reference_no = "Herr G Oct 18" pe.reference_no = "Herr G Oct 18"
pe.reference_date = "2018-10-24" pe.reference_date = "2018-10-24"
pe.insert() pe.insert()
pe.submit() pe.submit()
pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1700) pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1700)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account) pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
pe.reference_no = "Herr G Nov 18" pe.reference_no = "Herr G Nov 18"
pe.reference_date = "2018-11-01" pe.reference_date = "2018-11-01"
pe.insert() pe.insert()
@@ -398,10 +351,10 @@ def add_vouchers(gl_account="_Test Bank - _TC"):
pass pass
pi = make_purchase_invoice(supplier="Poore Simon's", qty=1, rate=3900, is_paid=1, do_not_save=1) pi = make_purchase_invoice(supplier="Poore Simon's", qty=1, rate=3900, is_paid=1, do_not_save=1)
pi.cash_bank_account = gl_account pi.cash_bank_account = "_Test Bank - _TC"
pi.insert() pi.insert()
pi.submit() pi.submit()
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account) pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
pe.reference_no = "Poore Simon's Oct 18" pe.reference_no = "Poore Simon's Oct 18"
pe.reference_date = "2018-10-28" pe.reference_date = "2018-10-28"
pe.paid_amount = 690 pe.paid_amount = 690
@@ -410,7 +363,7 @@ def add_vouchers(gl_account="_Test Bank - _TC"):
pe.submit() pe.submit()
si = create_sales_invoice(customer="Poore Simon's", qty=1, rate=3900) si = create_sales_invoice(customer="Poore Simon's", qty=1, rate=3900)
pe = get_payment_entry("Sales Invoice", si.name, bank_account=gl_account) pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
pe.reference_no = "Poore Simon's Oct 18" pe.reference_no = "Poore Simon's Oct 18"
pe.reference_date = "2018-10-28" pe.reference_date = "2018-10-28"
pe.insert() pe.insert()
@@ -430,13 +383,19 @@ def add_vouchers(gl_account="_Test Bank - _TC"):
mode_of_payment = frappe.get_doc({"doctype": "Mode of Payment", "name": "Cash"}) 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.append("accounts", {"company": "_Test Company", "default_account": gl_account}) "Mode of Payment Account", {"company": "_Test Company", "parent": "Cash"}
):
mode_of_payment.append(
"accounts", {"company": "_Test Company", "default_account": "_Test Bank - _TC"}
)
mode_of_payment.save() mode_of_payment.save()
si = create_sales_invoice(customer="Fayva", qty=1, rate=109080, do_not_save=1) si = create_sales_invoice(customer="Fayva", qty=1, rate=109080, do_not_save=1)
si.is_pos = 1 si.is_pos = 1
si.append("payments", {"mode_of_payment": "Cash", "account": gl_account, "amount": 109080}) si.append(
"payments", {"mode_of_payment": "Cash", "account": "_Test Bank - _TC", "amount": 109080}
)
si.insert() si.insert()
si.submit() si.submit()

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,206 +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):
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"))

View File

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

View File

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

View File

@@ -1,97 +0,0 @@
{
"actions": [],
"autoname": "autoincrement",
"creation": "2023-09-27 14:56:38.112462",
"default_view": "List",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"root",
"left_child",
"right_child",
"period_from_date",
"period_to_date",
"difference",
"balance_sheet_summary",
"profit_loss_summary",
"generated"
],
"fields": [
{
"fieldname": "root",
"fieldtype": "Link",
"label": "Root",
"options": "Bisect Nodes"
},
{
"fieldname": "left_child",
"fieldtype": "Link",
"label": "Left Child",
"options": "Bisect Nodes"
},
{
"fieldname": "right_child",
"fieldtype": "Link",
"label": "Right Child",
"options": "Bisect Nodes"
},
{
"fieldname": "period_from_date",
"fieldtype": "Datetime",
"label": "Period_from_date"
},
{
"fieldname": "period_to_date",
"fieldtype": "Datetime",
"label": "Period To Date"
},
{
"fieldname": "difference",
"fieldtype": "Float",
"label": "Difference"
},
{
"fieldname": "balance_sheet_summary",
"fieldtype": "Float",
"label": "Balance Sheet Summary"
},
{
"fieldname": "profit_loss_summary",
"fieldtype": "Float",
"label": "Profit and Loss Summary"
},
{
"default": "0",
"fieldname": "generated",
"fieldtype": "Check",
"label": "Generated"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2023-12-01 17:46:12.437996",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bisect Nodes",
"naming_rule": "Autoincrement",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Administrator",
"share": 1,
"write": 1
}
],
"read_only": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}

View File

@@ -1,29 +0,0 @@
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class BisectNodes(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
balance_sheet_summary: DF.Float
difference: DF.Float
generated: DF.Check
left_child: DF.Link | None
name: DF.Int | None
period_from_date: DF.Datetime | None
period_to_date: DF.Datetime | None
profit_loss_summary: DF.Float
right_child: DF.Link | None
root: DF.Link | None
# end: auto-generated types
pass

View File

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

View File

@@ -2,48 +2,48 @@
// For license information, please see license.txt // For license information, please see license.txt
frappe.provide("erpnext.accounts.dimensions"); frappe.provide("erpnext.accounts.dimensions");
frappe.ui.form.on("Budget", { frappe.ui.form.on('Budget', {
onload: function (frm) { onload: function(frm) {
frm.set_query("account", "accounts", function () { frm.set_query("account", "accounts", function() {
return { return {
filters: { filters: {
company: frm.doc.company, company: frm.doc.company,
report_type: "Profit and Loss", report_type: "Profit and Loss",
is_group: 0, is_group: 0
}, }
}; };
}); });
frm.set_query("monthly_distribution", function () { frm.set_query("monthly_distribution", function() {
return { return {
filters: { filters: {
fiscal_year: frm.doc.fiscal_year, fiscal_year: frm.doc.fiscal_year
}, }
}; };
}); });
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype); erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
}, },
refresh: function (frm) { refresh: function(frm) {
frm.trigger("toggle_reqd_fields"); frm.trigger("toggle_reqd_fields")
}, },
budget_against: function (frm) { budget_against: function(frm) {
frm.trigger("set_null_value"); frm.trigger("set_null_value")
frm.trigger("toggle_reqd_fields"); frm.trigger("toggle_reqd_fields")
}, },
set_null_value: function (frm) { set_null_value: function(frm) {
if (frm.doc.budget_against == "Cost Center") { if(frm.doc.budget_against == 'Cost Center') {
frm.set_value("project", null); frm.set_value('project', null)
} else { } else {
frm.set_value("cost_center", null); frm.set_value('cost_center', null)
} }
}, },
toggle_reqd_fields: function (frm) { toggle_reqd_fields: function(frm) {
frm.toggle_reqd("cost_center", frm.doc.budget_against == "Cost Center"); frm.toggle_reqd("cost_center", frm.doc.budget_against=="Cost Center");
frm.toggle_reqd("project", frm.doc.budget_against == "Project"); frm.toggle_reqd("project", frm.doc.budget_against=="Project");
}, }
}); });

View File

@@ -40,11 +40,10 @@ class Budget(Document):
select select
b.name, ba.account from `tabBudget` b, `tabBudget Account` ba b.name, ba.account from `tabBudget` b, `tabBudget Account` ba
where where
ba.parent = b.name and b.docstatus < 2 and b.company = {} and {}={} and ba.parent = b.name and b.docstatus < 2 and b.company = %s and %s=%s and
b.fiscal_year={} and b.name != {} and ba.account in ({}) """.format( b.fiscal_year=%s and b.name != %s and ba.account in (%s) """
"%s", budget_against_field, "%s", "%s", "%s", ",".join(["%s"] * len(accounts)) % ("%s", budget_against_field, "%s", "%s", "%s", ",".join(["%s"] * len(accounts))),
), (self.company, budget_against, self.fiscal_year, self.name) + tuple(accounts),
(self.company, budget_against, self.fiscal_year, self.name, *tuple(accounts)),
as_dict=1, as_dict=1,
) )
@@ -67,14 +66,12 @@ class Budget(Document):
if account_details.is_group: if account_details.is_group:
frappe.throw(_("Budget cannot be assigned against Group Account {0}").format(d.account)) frappe.throw(_("Budget cannot be assigned against Group Account {0}").format(d.account))
elif account_details.company != self.company: elif account_details.company != self.company:
frappe.throw( frappe.throw(_("Account {0} does not belongs to company {1}").format(d.account, self.company))
_("Account {0} does not belongs to company {1}").format(d.account, self.company)
)
elif account_details.report_type != "Profit and Loss": elif account_details.report_type != "Profit and Loss":
frappe.throw( frappe.throw(
_( _("Budget cannot be assigned against {0}, as it's not an Income or Expense account").format(
"Budget cannot be assigned against {0}, as it's not an Income or Expense account" d.account
).format(d.account) )
) )
if d.account in account_list: if d.account in account_list:
@@ -112,8 +109,6 @@ class Budget(Document):
def validate_expense_against_budget(args, expense_amount=0): def validate_expense_against_budget(args, expense_amount=0):
args = frappe._dict(args) args = frappe._dict(args)
if not frappe.get_all("Budget", limit=1):
return
if args.get("company") and not args.fiscal_year: if args.get("company") and not args.fiscal_year:
args.fiscal_year = get_fiscal_year(args.get("posting_date"), company=args.get("company"))[0] args.fiscal_year = get_fiscal_year(args.get("posting_date"), company=args.get("company"))[0]
@@ -121,9 +116,6 @@ def validate_expense_against_budget(args, expense_amount=0):
"Company", args.get("company"), "exception_budget_approver_role" "Company", args.get("company"), "exception_budget_approver_role"
) )
if not frappe.get_cached_value("Budget", {"fiscal_year": args.fiscal_year, "company": args.company}): # nosec
return
if not args.account: if not args.account:
args.account = args.get("expense_account") args.account = args.get("expense_account")
@@ -150,26 +142,32 @@ def validate_expense_against_budget(args, expense_amount=0):
if ( if (
args.get(budget_against) args.get(budget_against)
and args.account and args.account
and (frappe.get_cached_value("Account", args.account, "root_type") == "Expense") and frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"})
): ):
doctype = dimension.get("document_type") doctype = dimension.get("document_type")
if frappe.get_cached_value("DocType", doctype, "is_tree"): if frappe.get_cached_value("DocType", doctype, "is_tree"):
lft, rgt = frappe.get_cached_value(doctype, args.get(budget_against), ["lft", "rgt"]) lft, rgt = frappe.db.get_value(doctype, args.get(budget_against), ["lft", "rgt"])
condition = f"""and exists(select name from `tab{doctype}` condition = """and exists(select name from `tab%s`
where lft<={lft} and rgt>={rgt} and name=b.{budget_against})""" # nosec where lft<=%s and rgt>=%s and name=b.%s)""" % (
doctype,
lft,
rgt,
budget_against,
) # nosec
args.is_tree = True args.is_tree = True
else: else:
condition = f"and b.{budget_against}={frappe.db.escape(args.get(budget_against))}" condition = "and b.%s=%s" % (budget_against, frappe.db.escape(args.get(budget_against)))
args.is_tree = False args.is_tree = False
args.budget_against_field = budget_against args.budget_against_field = budget_against
args.budget_against_doctype = doctype args.budget_against_doctype = doctype
budget_records = frappe.db.sql( budget_records = frappe.db.sql(
f""" """
select select
b.{budget_against} as budget_against, ba.budget_amount, b.monthly_distribution, b.{budget_against_field} as budget_against, ba.budget_amount, b.monthly_distribution,
ifnull(b.applicable_on_material_request, 0) as for_material_request, ifnull(b.applicable_on_material_request, 0) as for_material_request,
ifnull(applicable_on_purchase_order, 0) as for_purchase_order, ifnull(applicable_on_purchase_order, 0) as for_purchase_order,
ifnull(applicable_on_booking_actual_expenses,0) as for_actual_expenses, ifnull(applicable_on_booking_actual_expenses,0) as for_actual_expenses,
@@ -182,7 +180,9 @@ def validate_expense_against_budget(args, expense_amount=0):
b.name=ba.parent and b.fiscal_year=%s b.name=ba.parent and b.fiscal_year=%s
and ba.account=%s and b.docstatus=1 and ba.account=%s and b.docstatus=1
{condition} {condition}
""", """.format(
condition=condition, budget_against_field=budget_against
),
(args.fiscal_year, args.account), (args.fiscal_year, args.account),
as_dict=True, as_dict=True,
) # nosec ) # nosec
@@ -194,18 +194,12 @@ def validate_expense_against_budget(args, expense_amount=0):
def validate_budget_records(args, budget_records, expense_amount): def validate_budget_records(args, budget_records, expense_amount):
for budget in budget_records: for budget in budget_records:
if flt(budget.budget_amount): if flt(budget.budget_amount):
amount = expense_amount or get_amount(args, budget)
yearly_action, monthly_action = get_actions(args, budget) yearly_action, monthly_action = get_actions(args, budget)
args["for_material_request"] = budget.for_material_request
args["for_purchase_order"] = budget.for_purchase_order
if yearly_action in ("Stop", "Warn"): if yearly_action in ("Stop", "Warn"):
compare_expense_with_budget( compare_expense_with_budget(
args, args, flt(budget.budget_amount), _("Annual"), yearly_action, budget.budget_against, amount
flt(budget.budget_amount),
_("Annual"),
yearly_action,
budget.budget_against,
expense_amount,
) )
if monthly_action in ["Stop", "Warn"]: if monthly_action in ["Stop", "Warn"]:
@@ -216,32 +210,18 @@ def validate_budget_records(args, budget_records, expense_amount):
args["month_end_date"] = get_last_day(args.posting_date) args["month_end_date"] = get_last_day(args.posting_date)
compare_expense_with_budget( compare_expense_with_budget(
args, args, budget_amount, _("Accumulated Monthly"), monthly_action, budget.budget_against, amount
budget_amount,
_("Accumulated Monthly"),
monthly_action,
budget.budget_against,
expense_amount,
) )
def compare_expense_with_budget(args, budget_amount, action_for, action, budget_against, amount=0): def compare_expense_with_budget(args, budget_amount, action_for, action, budget_against, amount=0):
args.actual_expense, args.requested_amount, args.ordered_amount = get_actual_expense(args), 0, 0 actual_expense = get_actual_expense(args)
if not amount: total_expense = actual_expense + amount
args.requested_amount, args.ordered_amount = get_requested_amount(args), get_ordered_amount(args)
if args.get("doctype") == "Material Request" and args.for_material_request:
amount = args.requested_amount + args.ordered_amount
elif args.get("doctype") == "Purchase Order" and args.for_purchase_order:
amount = args.ordered_amount
total_expense = args.actual_expense + amount
if total_expense > budget_amount: if total_expense > budget_amount:
if args.actual_expense > budget_amount: if actual_expense > budget_amount:
error_tense = _("is already") error_tense = _("is already")
diff = args.actual_expense - budget_amount diff = actual_expense - budget_amount
else: else:
error_tense = _("will be") error_tense = _("will be")
diff = total_expense - budget_amount diff = total_expense - budget_amount
@@ -258,10 +238,9 @@ def compare_expense_with_budget(args, budget_amount, action_for, action, budget_
frappe.bold(fmt_money(diff, currency=currency)), frappe.bold(fmt_money(diff, currency=currency)),
) )
msg += get_expense_breakup(args, currency, budget_against) if (
frappe.flags.exception_approver_role
if frappe.flags.exception_approver_role and frappe.flags.exception_approver_role in frappe.get_roles( and frappe.flags.exception_approver_role in frappe.get_roles(frappe.session.user)
frappe.session.user
): ):
action = "Warn" action = "Warn"
@@ -271,83 +250,6 @@ def compare_expense_with_budget(args, budget_amount, action_for, action, budget_
frappe.msgprint(msg, indicator="orange", title=_("Budget Exceeded")) frappe.msgprint(msg, indicator="orange", title=_("Budget Exceeded"))
def get_expense_breakup(args, currency, budget_against):
msg = "<hr>Total Expenses booked through - <ul>"
common_filters = frappe._dict(
{
args.budget_against_field: budget_against,
"account": args.account,
"company": args.company,
}
)
msg += (
"<li>"
+ frappe.utils.get_link_to_report(
"General Ledger",
label="Actual Expenses",
filters=common_filters.copy().update(
{
"from_date": frappe.get_cached_value("Fiscal Year", args.fiscal_year, "year_start_date"),
"to_date": frappe.get_cached_value("Fiscal Year", args.fiscal_year, "year_end_date"),
"is_cancelled": 0,
}
),
)
+ " - "
+ frappe.bold(fmt_money(args.actual_expense, currency=currency))
+ "</li>"
)
msg += (
"<li>"
+ frappe.utils.get_link_to_report(
"Material Request",
label="Material Requests",
report_type="Report Builder",
doctype="Material Request",
filters=common_filters.copy().update(
{
"status": [["!=", "Stopped"]],
"docstatus": 1,
"material_request_type": "Purchase",
"schedule_date": [["fiscal year", "2023-2024"]],
"item_code": args.item_code,
"per_ordered": [["<", 100]],
}
),
)
+ " - "
+ frappe.bold(fmt_money(args.requested_amount, currency=currency))
+ "</li>"
)
msg += (
"<li>"
+ frappe.utils.get_link_to_report(
"Purchase Order",
label="Unbilled Orders",
report_type="Report Builder",
doctype="Purchase Order",
filters=common_filters.copy().update(
{
"status": [["!=", "Closed"]],
"docstatus": 1,
"transaction_date": [["fiscal year", "2023-2024"]],
"item_code": args.item_code,
"per_billed": [["<", 100]],
}
),
)
+ " - "
+ frappe.bold(fmt_money(args.ordered_amount, currency=currency))
+ "</li></ul>"
)
return msg
def get_actions(args, budget): def get_actions(args, budget):
yearly_action = budget.action_if_annual_budget_exceeded yearly_action = budget.action_if_annual_budget_exceeded
monthly_action = budget.action_if_accumulated_monthly_budget_exceeded monthly_action = budget.action_if_accumulated_monthly_budget_exceeded
@@ -363,15 +265,31 @@ def get_actions(args, budget):
return yearly_action, monthly_action return yearly_action, monthly_action
def get_requested_amount(args): def get_amount(args, budget):
amount = 0
if args.get("doctype") == "Material Request" and budget.for_material_request:
amount = (
get_requested_amount(args, budget) + get_ordered_amount(args, budget) + get_actual_expense(args)
)
elif args.get("doctype") == "Purchase Order" and budget.for_purchase_order:
amount = get_ordered_amount(args, budget) + get_actual_expense(args)
return amount
def get_requested_amount(args, budget):
item_code = args.get("item_code") item_code = args.get("item_code")
condition = get_other_condition(args, "Material Request") condition = get_other_condition(args, budget, "Material Request")
data = frappe.db.sql( data = frappe.db.sql(
""" select ifnull((sum(child.stock_qty - child.ordered_qty) * rate), 0) as amount """ select ifnull((sum(child.stock_qty - child.ordered_qty) * rate), 0) as amount
from `tabMaterial Request Item` child, `tabMaterial Request` parent where parent.name = child.parent and from `tabMaterial Request Item` child, `tabMaterial Request` parent where parent.name = child.parent and
child.item_code = %s and parent.docstatus = 1 and child.stock_qty > child.ordered_qty and {} and child.item_code = %s and parent.docstatus = 1 and child.stock_qty > child.ordered_qty and {0} and
parent.material_request_type = 'Purchase' and parent.status != 'Stopped'""".format(condition), parent.material_request_type = 'Purchase' and parent.status != 'Stopped'""".format(
condition
),
item_code, item_code,
as_list=1, as_list=1,
) )
@@ -379,15 +297,17 @@ def get_requested_amount(args):
return data[0][0] if data else 0 return data[0][0] if data else 0
def get_ordered_amount(args): def get_ordered_amount(args, budget):
item_code = args.get("item_code") item_code = args.get("item_code")
condition = get_other_condition(args, "Purchase Order") condition = get_other_condition(args, budget, "Purchase Order")
data = frappe.db.sql( data = frappe.db.sql(
f""" select ifnull(sum(child.amount - child.billed_amt), 0) as amount """ select ifnull(sum(child.amount - child.billed_amt), 0) as amount
from `tabPurchase Order Item` child, `tabPurchase Order` parent where from `tabPurchase Order Item` child, `tabPurchase Order` parent where
parent.name = child.parent and child.item_code = %s and parent.docstatus = 1 and child.amount > child.billed_amt parent.name = child.parent and child.item_code = %s and parent.docstatus = 1 and child.amount > child.billed_amt
and parent.status != 'Closed' and {condition}""", and parent.status != 'Closed' and {0}""".format(
condition
),
item_code, item_code,
as_list=1, as_list=1,
) )
@@ -395,12 +315,12 @@ def get_ordered_amount(args):
return data[0][0] if data else 0 return data[0][0] if data else 0
def get_other_condition(args, for_doc): def get_other_condition(args, budget, for_doc):
condition = "expense_account = '%s'" % (args.expense_account) condition = "expense_account = '%s'" % (args.expense_account)
budget_against_field = args.get("budget_against_field") budget_against_field = args.get("budget_against_field")
if budget_against_field and args.get(budget_against_field): if budget_against_field and args.get(budget_against_field):
condition += f" and child.{budget_against_field} = '{args.get(budget_against_field)}'" condition += " and child.%s = '%s'" % (budget_against_field, args.get(budget_against_field))
if args.get("fiscal_year"): if args.get("fiscal_year"):
date_field = "schedule_date" if for_doc == "Material Request" else "transaction_date" date_field = "schedule_date" if for_doc == "Material Request" else "transaction_date"
@@ -408,8 +328,12 @@ def get_other_condition(args, for_doc):
"Fiscal Year", args.get("fiscal_year"), ["year_start_date", "year_end_date"] "Fiscal Year", args.get("fiscal_year"), ["year_start_date", "year_end_date"]
) )
condition += f""" and parent.{date_field} condition += """ and parent.%s
between '{start_date}' and '{end_date}' """ between '%s' and '%s' """ % (
date_field,
start_date,
end_date,
)
return condition return condition
@@ -428,17 +352,21 @@ def get_actual_expense(args):
args.update(lft_rgt) args.update(lft_rgt)
condition2 = f"""and exists(select name from `tab{args.budget_against_doctype}` condition2 = """and exists(select name from `tab{doctype}`
where lft>=%(lft)s and rgt<=%(rgt)s where lft>=%(lft)s and rgt<=%(rgt)s
and name=gle.{budget_against_field})""" and name=gle.{budget_against_field})""".format(
doctype=args.budget_against_doctype, budget_against_field=budget_against_field # nosec
)
else: else:
condition2 = f"""and exists(select name from `tab{args.budget_against_doctype}` condition2 = """and exists(select name from `tab{doctype}`
where name=gle.{budget_against_field} and where name=gle.{budget_against} and
gle.{budget_against_field} = %({budget_against_field})s)""" gle.{budget_against} = %({budget_against})s)""".format(
doctype=args.budget_against_doctype, budget_against=budget_against_field
)
amount = flt( amount = flt(
frappe.db.sql( frappe.db.sql(
f""" """
select sum(gle.debit) - sum(gle.credit) select sum(gle.debit) - sum(gle.credit)
from `tabGL Entry` gle from `tabGL Entry` gle
where where
@@ -449,7 +377,9 @@ def get_actual_expense(args):
and gle.company=%(company)s and gle.company=%(company)s
and gle.docstatus=1 and gle.docstatus=1
{condition2} {condition2}
""", """.format(
condition1=condition1, condition2=condition2
),
(args), (args),
)[0][0] )[0][0]
) # nosec ) # nosec

View File

@@ -41,7 +41,9 @@ class TestBudget(unittest.TestCase):
budget = make_budget(budget_against="Cost Center") budget = make_budget(budget_against="Cost Center")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
jv = make_journal_entry( jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC", "_Test Account Cost for Goods Sold - _TC",
@@ -61,7 +63,9 @@ class TestBudget(unittest.TestCase):
budget = make_budget(budget_against="Cost Center") budget = make_budget(budget_against="Cost Center")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
jv = make_journal_entry( jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC", "_Test Account Cost for Goods Sold - _TC",
@@ -93,7 +97,9 @@ class TestBudget(unittest.TestCase):
) )
fiscal_year = get_fiscal_year(nowdate())[0] fiscal_year = get_fiscal_year(nowdate())[0]
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
frappe.db.set_value("Budget", budget.name, "fiscal_year", fiscal_year) frappe.db.set_value("Budget", budget.name, "fiscal_year", fiscal_year)
mr = frappe.get_doc( mr = frappe.get_doc(
@@ -132,7 +138,9 @@ class TestBudget(unittest.TestCase):
) )
fiscal_year = get_fiscal_year(nowdate())[0] fiscal_year = get_fiscal_year(nowdate())[0]
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
frappe.db.set_value("Budget", budget.name, "fiscal_year", fiscal_year) frappe.db.set_value("Budget", budget.name, "fiscal_year", fiscal_year)
po = create_purchase_order(transaction_date=nowdate(), do_not_submit=True) po = create_purchase_order(transaction_date=nowdate(), do_not_submit=True)
@@ -150,7 +158,9 @@ class TestBudget(unittest.TestCase):
budget = make_budget(budget_against="Project") budget = make_budget(budget_against="Project")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
project = frappe.get_value("Project", {"project_name": "_Test Project"}) project = frappe.get_value("Project", {"project_name": "_Test Project"})
@@ -213,7 +223,7 @@ class TestBudget(unittest.TestCase):
if month > 9: if month > 9:
month = 9 month = 9
for _i in range(month + 1): for i in range(month + 1):
jv = make_journal_entry( jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC", "_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", "_Test Bank - _TC",
@@ -227,7 +237,9 @@ class TestBudget(unittest.TestCase):
frappe.db.get_value("GL Entry", {"voucher_type": "Journal Entry", "voucher_no": jv.name}) frappe.db.get_value("GL Entry", {"voucher_type": "Journal Entry", "voucher_no": jv.name})
) )
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
self.assertRaises(BudgetError, jv.cancel) self.assertRaises(BudgetError, jv.cancel)
@@ -243,7 +255,7 @@ class TestBudget(unittest.TestCase):
month = 9 month = 9
project = frappe.get_value("Project", {"project_name": "_Test Project"}) project = frappe.get_value("Project", {"project_name": "_Test Project"})
for _i in range(month + 1): for i in range(month + 1):
jv = make_journal_entry( jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC", "_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", "_Test Bank - _TC",
@@ -258,7 +270,9 @@ class TestBudget(unittest.TestCase):
frappe.db.get_value("GL Entry", {"voucher_type": "Journal Entry", "voucher_no": jv.name}) frappe.db.get_value("GL Entry", {"voucher_type": "Journal Entry", "voucher_no": jv.name})
) )
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
self.assertRaises(BudgetError, jv.cancel) self.assertRaises(BudgetError, jv.cancel)
@@ -270,7 +284,9 @@ class TestBudget(unittest.TestCase):
set_total_expense_zero(nowdate(), "cost_center", "_Test Cost Center 2 - _TC") set_total_expense_zero(nowdate(), "cost_center", "_Test Cost Center 2 - _TC")
budget = make_budget(budget_against="Cost Center", cost_center="_Test Company - _TC") budget = make_budget(budget_against="Cost Center", cost_center="_Test Company - _TC")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
jv = make_journal_entry( jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC", "_Test Account Cost for Goods Sold - _TC",
@@ -300,7 +316,9 @@ class TestBudget(unittest.TestCase):
).insert(ignore_permissions=True) ).insert(ignore_permissions=True)
budget = make_budget(budget_against="Cost Center", cost_center=cost_center) budget = make_budget(budget_against="Cost Center", cost_center=cost_center)
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") frappe.db.set_value(
"Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop"
)
jv = make_journal_entry( jv = make_journal_entry(
"_Test Account Cost for Goods Sold - _TC", "_Test Account Cost for Goods Sold - _TC",
@@ -405,11 +423,13 @@ def make_budget(**args):
fiscal_year = get_fiscal_year(nowdate())[0] fiscal_year = get_fiscal_year(nowdate())[0]
if budget_against == "Project": if budget_against == "Project":
project_name = "{}%".format("_Test Project/" + fiscal_year) project_name = "{0}%".format("_Test Project/" + fiscal_year)
budget_list = frappe.get_all("Budget", fields=["name"], filters={"name": ("like", project_name)}) budget_list = frappe.get_all("Budget", fields=["name"], filters={"name": ("like", project_name)})
else: else:
cost_center_name = "{}%".format(cost_center or "_Test Cost Center - _TC/" + fiscal_year) cost_center_name = "{0}%".format(cost_center or "_Test Cost Center - _TC/" + fiscal_year)
budget_list = frappe.get_all("Budget", fields=["name"], filters={"name": ("like", cost_center_name)}) budget_list = frappe.get_all(
"Budget", fields=["name"], filters={"name": ("like", cost_center_name)}
)
for d in budget_list: for d in budget_list:
frappe.db.sql("delete from `tabBudget` where name = %(name)s", d) frappe.db.sql("delete from `tabBudget` where name = %(name)s", d)
frappe.db.sql("delete from `tabBudget Account` where parent = %(name)s", d) frappe.db.sql("delete from `tabBudget Account` where parent = %(name)s", d)
@@ -431,18 +451,24 @@ def make_budget(**args):
budget.action_if_annual_budget_exceeded = "Stop" budget.action_if_annual_budget_exceeded = "Stop"
budget.action_if_accumulated_monthly_budget_exceeded = "Ignore" budget.action_if_accumulated_monthly_budget_exceeded = "Ignore"
budget.budget_against = budget_against budget.budget_against = budget_against
budget.append("accounts", {"account": "_Test Account Cost for Goods Sold - _TC", "budget_amount": 200000}) budget.append(
"accounts", {"account": "_Test Account Cost for Goods Sold - _TC", "budget_amount": 200000}
)
if args.applicable_on_material_request: if args.applicable_on_material_request:
budget.applicable_on_material_request = 1 budget.applicable_on_material_request = 1
budget.action_if_annual_budget_exceeded_on_mr = args.action_if_annual_budget_exceeded_on_mr or "Warn" budget.action_if_annual_budget_exceeded_on_mr = (
args.action_if_annual_budget_exceeded_on_mr or "Warn"
)
budget.action_if_accumulated_monthly_budget_exceeded_on_mr = ( budget.action_if_accumulated_monthly_budget_exceeded_on_mr = (
args.action_if_accumulated_monthly_budget_exceeded_on_mr or "Warn" args.action_if_accumulated_monthly_budget_exceeded_on_mr or "Warn"
) )
if args.applicable_on_purchase_order: if args.applicable_on_purchase_order:
budget.applicable_on_purchase_order = 1 budget.applicable_on_purchase_order = 1
budget.action_if_annual_budget_exceeded_on_po = args.action_if_annual_budget_exceeded_on_po or "Warn" budget.action_if_annual_budget_exceeded_on_po = (
args.action_if_annual_budget_exceeded_on_po or "Warn"
)
budget.action_if_accumulated_monthly_budget_exceeded_on_po = ( budget.action_if_accumulated_monthly_budget_exceeded_on_po = (
args.action_if_accumulated_monthly_budget_exceeded_on_po or "Warn" args.action_if_accumulated_monthly_budget_exceeded_on_po or "Warn"
) )

View File

@@ -1,42 +1,94 @@
{ {
"actions": [], "allow_copy": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2016-05-16 11:54:09.286135", "creation": "2016-05-16 11:54:09.286135",
"custom": 0,
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [
"account",
"budget_amount"
],
"fields": [ "fields": [
{ {
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "account", "fieldname": "account",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1, "in_list_view": 1,
"label": "Account", "label": "Account",
"length": 0,
"no_copy": 0,
"options": "Account", "options": "Account",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1, "reqd": 1,
"search_index": 1 "search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "budget_amount", "fieldname": "budget_amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1, "in_list_view": 1,
"label": "Budget Amount", "label": "Budget Amount",
"length": 0,
"no_copy": 0,
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
"reqd": 1 "permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
} }
], ],
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1, "istable": 1,
"links": [], "max_attachments": 0,
"modified": "2024-03-04 15:43:27.016947", "modified": "2017-01-02 17:02:53.339420",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Budget Account", "name": "Budget Account",
"name_case": "",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],
"quick_entry": 1, "quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"states": [] "track_seen": 0
} }

View File

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

View File

@@ -1,45 +1,43 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on("Cash Flow Mapping", { frappe.ui.form.on('Cash Flow Mapping', {
refresh: function (frm) { refresh: function(frm) {
frm.events.disable_unchecked_fields(frm); frm.events.disable_unchecked_fields(frm);
}, },
reset_check_fields: function (frm) { reset_check_fields: function(frm) {
frm.fields frm.fields.filter(field => field.df.fieldtype === 'Check')
.filter((field) => field.df.fieldtype === "Check") .map(field => frm.set_df_property(field.df.fieldname, 'read_only', 0));
.map((field) => frm.set_df_property(field.df.fieldname, "read_only", 0));
}, },
has_checked_field(frm) { has_checked_field(frm) {
const val = frm.fields.filter((field) => field.value === 1); const val = frm.fields.filter(field => field.value === 1);
return val.length ? 1 : 0; return val.length ? 1 : 0;
}, },
_disable_unchecked_fields: function (frm) { _disable_unchecked_fields: function(frm) {
// get value of clicked field // get value of clicked field
frm.fields frm.fields.filter(field => field.value === 0)
.filter((field) => field.value === 0) .map(field => frm.set_df_property(field.df.fieldname, 'read_only', 1));
.map((field) => frm.set_df_property(field.df.fieldname, "read_only", 1));
}, },
disable_unchecked_fields: function (frm) { disable_unchecked_fields: function(frm) {
frm.events.reset_check_fields(frm); frm.events.reset_check_fields(frm);
const checked = frm.events.has_checked_field(frm); const checked = frm.events.has_checked_field(frm);
if (checked) { if (checked) {
frm.events._disable_unchecked_fields(frm); frm.events._disable_unchecked_fields(frm);
} }
}, },
is_working_capital: function (frm) { is_working_capital: function(frm) {
frm.events.disable_unchecked_fields(frm); frm.events.disable_unchecked_fields(frm);
}, },
is_finance_cost: function (frm) { is_finance_cost: function(frm) {
frm.events.disable_unchecked_fields(frm); frm.events.disable_unchecked_fields(frm);
}, },
is_income_tax_liability: function (frm) { is_income_tax_liability: function(frm) {
frm.events.disable_unchecked_fields(frm); frm.events.disable_unchecked_fields(frm);
}, },
is_income_tax_expense: function (frm) { is_income_tax_expense: function(frm) {
frm.events.disable_unchecked_fields(frm); frm.events.disable_unchecked_fields(frm);
}, },
is_finance_cost_adjustment: function (frm) { is_finance_cost_adjustment: function(frm) {
frm.events.disable_unchecked_fields(frm); frm.events.disable_unchecked_fields(frm);
}, }
}); });

View File

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

View File

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

View File

@@ -1,10 +1,11 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt // License: GNU General Public License v3. See license.txt
frappe.ui.form.on("Cashier Closing", { frappe.ui.form.on('Cashier Closing', {
setup: function (frm) {
setup: function(frm){
if (frm.doc.user == "" || frm.doc.user == null) { if (frm.doc.user == "" || frm.doc.user == null) {
frm.doc.user = frappe.session.user; frm.doc.user = frappe.session.user;
} }
}, }
}); });

View File

@@ -1,152 +1,457 @@
{ {
"actions": [], "allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "naming_series:", "autoname": "naming_series:",
"beta": 0,
"creation": "2018-06-18 16:51:49.994750", "creation": "2018-06-18 16:51:49.994750",
"custom": 0,
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [
"naming_series",
"user",
"date",
"from_time",
"time",
"expense",
"custody",
"returns",
"outstanding_amount",
"payments",
"net_amount",
"amended_from"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "POS-CLO-", "default": "POS-CLO-",
"fieldname": "naming_series", "fieldname": "naming_series",
"fieldtype": "Select", "fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 1, "in_filter": 1,
"in_global_search": 1, "in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Series", "label": "Series",
"length": 0,
"no_copy": 0,
"options": "POS-CLO-", "options": "POS-CLO-",
"read_only": 1 "permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "user", "fieldname": "user",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 1, "in_filter": 1,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "User", "label": "User",
"length": 0,
"no_copy": 0,
"options": "User", "options": "User",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1, "read_only": 1,
"reqd": 1 "remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Today", "default": "Today",
"fieldname": "date", "fieldname": "date",
"fieldtype": "Date", "fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 1, "in_filter": 1,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Date", "label": "Date",
"read_only": 1 "length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "from_time", "fieldname": "from_time",
"fieldtype": "Time", "fieldtype": "Time",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 1, "in_filter": 1,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "From Time", "label": "From Time",
"reqd": 1 "length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "time", "fieldname": "time",
"fieldtype": "Time", "fieldtype": "Time",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 1, "in_filter": 1,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "To Time", "label": "To Time",
"reqd": 1 "length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0.00", "default": "0.00",
"fieldname": "expense", "fieldname": "expense",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 1, "in_filter": 1,
"label": "Expense" "in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Expense",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0.00", "default": "0.00",
"fieldname": "custody", "fieldname": "custody",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 1, "in_filter": 1,
"label": "Custody" "in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Custody",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0.00", "default": "0.00",
"fieldname": "returns", "fieldname": "returns",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 1, "in_filter": 1,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Returns", "label": "Returns",
"precision": "2" "length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "2",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0.00", "default": "0.00",
"fieldname": "outstanding_amount", "fieldname": "outstanding_amount",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Outstanding Amount", "label": "Outstanding Amount",
"read_only": 1 "length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "0.0",
"fieldname": "payments", "fieldname": "payments",
"fieldtype": "Table", "fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 1, "in_filter": 1,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Payments", "label": "Payments",
"options": "Cashier Closing Payments" "length": 0,
"no_copy": 0,
"options": "Cashier Closing Payments",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "net_amount", "fieldname": "net_amount",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 1, "in_filter": 1,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Net Amount", "label": "Net Amount",
"read_only": 1 "length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "amended_from", "fieldname": "amended_from",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Amended From", "label": "Amended From",
"length": 0,
"no_copy": 1, "no_copy": 1,
"options": "Cashier Closing", "options": "Cashier Closing",
"permlevel": 0,
"print_hide": 1, "print_hide": 1,
"read_only": 1 "print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 1, "is_submittable": 1,
"links": [], "issingle": 0,
"modified": "2023-12-28 13:15:46.858427", "istable": 0,
"max_attachments": 0,
"modified": "2019-02-19 08:35:24.157327",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Cashier Closing", "name": "Cashier Closing",
"naming_rule": "By \"Naming Series\" field", "name_case": "",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "System Manager", "role": "System Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 1, "submit": 1,
"write": 1 "write": 1
} }
], ],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"states": [], "track_changes": 1,
"track_changes": 1 "track_seen": 0,
"track_views": 0
} }

View File

@@ -1,4 +1,4 @@
frappe.ui.form.on("Chart of Accounts Importer", { frappe.ui.form.on('Chart of Accounts Importer', {
onload: function (frm) { onload: function (frm) {
frm.set_value("company", ""); frm.set_value("company", "");
frm.set_value("import_file", ""); frm.set_value("import_file", "");
@@ -8,34 +8,31 @@ frappe.ui.form.on("Chart of Accounts Importer", {
frm.disable_save(); frm.disable_save();
// make company mandatory // make company mandatory
frm.set_df_property("company", "reqd", frm.doc.company ? 0 : 1); frm.set_df_property('company', 'reqd', frm.doc.company ? 0 : 1);
frm.set_df_property("import_file_section", "hidden", frm.doc.company ? 0 : 1); frm.set_df_property('import_file_section', 'hidden', frm.doc.company ? 0 : 1);
if (frm.doc.import_file) { if (frm.doc.import_file) {
frappe.run_serially([ frappe.run_serially([
() => generate_tree_preview(frm), () => generate_tree_preview(frm),
() => create_import_button(frm), () => create_import_button(frm),
() => frm.set_df_property("chart_preview", "hidden", 0), () => frm.set_df_property('chart_preview', 'hidden', 0)
]); ]);
} }
frm.set_df_property( frm.set_df_property('chart_preview', 'hidden',
"chart_preview", $(frm.fields_dict['chart_tree'].wrapper).html()!="" ? 0 : 1);
"hidden",
$(frm.fields_dict["chart_tree"].wrapper).html() != "" ? 0 : 1
);
}, },
download_template: function (frm) { download_template: function(frm) {
var d = new frappe.ui.Dialog({ var d = new frappe.ui.Dialog({
title: __("Download Template"), title: __("Download Template"),
fields: [ fields: [
{ {
label: "File Type", label : "File Type",
fieldname: "file_type", fieldname: "file_type",
fieldtype: "Select", fieldtype: "Select",
reqd: 1, reqd: 1,
options: ["Excel", "CSV"], options: ["Excel", "CSV"]
}, },
{ {
label: "Template Type", label: "Template Type",
@@ -44,53 +41,38 @@ frappe.ui.form.on("Chart of Accounts Importer", {
reqd: 1, reqd: 1,
options: ["Sample Template", "Blank Template"], options: ["Sample Template", "Blank Template"],
change: () => { change: () => {
let template_type = d.get_value("template_type"); let template_type = d.get_value('template_type');
if (template_type === "Sample Template") { if (template_type === "Sample Template") {
d.set_df_property( d.set_df_property('template_type', 'description',
"template_type",
"description",
`The Sample Template contains all the required accounts pre filled in the template. `The Sample Template contains all the required accounts pre filled in the template.
You can add more accounts or change existing accounts in the template as per your choice.` You can add more accounts or change existing accounts in the template as per your choice.`);
);
} else { } else {
d.set_df_property( d.set_df_property('template_type', 'description',
"template_type",
"description",
`The Blank Template contains just the account type and root type required to build the Chart `The Blank Template contains just the account type and root type required to build the Chart
of Accounts. Please enter the account names and add more rows as per your requirement.` of Accounts. Please enter the account names and add more rows as per your requirement.`);
);
} }
}, }
}, }
{
label: "Company",
fieldname: "company",
fieldtype: "Link",
reqd: 1,
hidden: 1,
default: frm.doc.company,
},
], ],
primary_action: function () { primary_action: function() {
let data = d.get_values(); var data = d.get_values();
if (!data.template_type) { if (!data.template_type) {
frappe.throw(__("Please select <b>Template Type</b> to download template")); frappe.throw(__('Please select <b>Template Type</b> to download template'));
} }
open_url_post( open_url_post(
"/api/method/erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.download_template", '/api/method/erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.download_template',
{ {
file_type: data.file_type, file_type: data.file_type,
template_type: data.template_type, template_type: data.template_type
company: data.company,
} }
); );
d.hide(); d.hide();
}, },
primary_action_label: __("Download"), primary_action_label: __('Download')
}); });
d.show(); d.show();
}, },
@@ -98,7 +80,7 @@ frappe.ui.form.on("Chart of Accounts Importer", {
import_file: function (frm) { import_file: function (frm) {
if (!frm.doc.import_file) { if (!frm.doc.import_file) {
frm.page.set_indicator(""); frm.page.set_indicator("");
$(frm.fields_dict["chart_tree"].wrapper).empty(); // empty wrapper on removing file $(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper on removing file
} }
}, },
@@ -108,97 +90,89 @@ frappe.ui.form.on("Chart of Accounts Importer", {
frappe.call({ frappe.call({
method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.validate_company", method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.validate_company",
args: { args: {
company: frm.doc.company, company: frm.doc.company
}, },
callback: function (r) { callback: function(r) {
if (r.message === false) { if(r.message===false) {
frm.set_value("company", ""); frm.set_value("company", "");
frappe.throw( frappe.throw(__("Transactions against the Company already exist! Chart of Accounts can only be imported for a Company with no transactions."));
__(
"Transactions against the Company already exist! Chart of Accounts can only be imported for a Company with no transactions."
)
);
} else { } else {
frm.trigger("refresh"); frm.trigger("refresh");
} }
}, }
}); });
} }
}, }
}); });
var create_import_button = function (frm) { var create_import_button = function(frm) {
frm.page frm.page.set_primary_action(__("Import"), function () {
.set_primary_action(__("Import"), function () {
return frappe.call({
method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.import_coa",
args: {
file_name: frm.doc.import_file,
company: frm.doc.company,
},
freeze: true,
freeze_message: __("Creating Accounts..."),
callback: function (r) {
if (!r.exc) {
clearInterval(frm.page["interval"]);
frm.page.set_indicator(__("Import Successful"), "blue");
create_reset_button(frm);
}
},
});
})
.addClass("btn btn-primary");
};
var create_reset_button = function (frm) {
frm.page
.set_primary_action(__("Reset"), function () {
frm.page.clear_primary_action();
delete frm.page["show_import_button"];
frm.reload_doc();
})
.addClass("btn btn-primary");
};
var validate_coa = function (frm) {
if (frm.doc.import_file) {
let parent = __("All Accounts");
return frappe.call({ return frappe.call({
method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.get_coa", method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.import_coa",
args: { args: {
file_name: frm.doc.import_file, file_name: frm.doc.import_file,
parent: parent, company: frm.doc.company
doctype: "Chart of Accounts Importer",
file_type: frm.doc.file_type,
for_validate: 1,
}, },
callback: function (r) { freeze: true,
if (r.message["show_import_button"]) { freeze_message: __("Creating Accounts..."),
frm.page["show_import_button"] = Boolean(r.message["show_import_button"]); callback: function(r) {
if (!r.exc) {
clearInterval(frm.page["interval"]);
frm.page.set_indicator(__('Import Successful'), 'blue');
create_reset_button(frm);
} }
}
});
}).addClass('btn btn-primary');
};
var create_reset_button = function(frm) {
frm.page.set_primary_action(__("Reset"), function () {
frm.page.clear_primary_action();
delete frm.page["show_import_button"];
frm.reload_doc();
}).addClass('btn btn-primary');
};
var validate_coa = function(frm) {
if (frm.doc.import_file) {
let parent = __('All Accounts');
return frappe.call({
'method': 'erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.get_coa',
'args': {
file_name: frm.doc.import_file,
parent: parent,
doctype: 'Chart of Accounts Importer',
file_type: frm.doc.file_type,
for_validate: 1
}, },
callback: function(r) {
if (r.message['show_import_button']) {
frm.page['show_import_button'] = Boolean(r.message['show_import_button']);
}
}
}); });
} }
}; };
var generate_tree_preview = function (frm) { var generate_tree_preview = function(frm) {
let parent = __("All Accounts"); let parent = __('All Accounts');
$(frm.fields_dict["chart_tree"].wrapper).empty(); // empty wrapper to load new data $(frm.fields_dict['chart_tree'].wrapper).empty(); // empty wrapper to load new data
// generate tree structure based on the csv data // generate tree structure based on the csv data
return new frappe.ui.Tree({ return new frappe.ui.Tree({
parent: $(frm.fields_dict["chart_tree"].wrapper), parent: $(frm.fields_dict['chart_tree'].wrapper),
label: parent, label: parent,
expandable: true, expandable: true,
method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.get_coa", method: 'erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.get_coa',
args: { args: {
file_name: frm.doc.import_file, file_name: frm.doc.import_file,
parent: parent, parent: parent,
doctype: "Chart of Accounts Importer", doctype: 'Chart of Accounts Importer',
file_type: frm.doc.file_type, file_type: frm.doc.file_type
}, },
onclick: function (node) { onclick: function(node) {
parent = node.value; parent = node.value;
}, }
}); });
}; };

View File

@@ -8,7 +8,6 @@ from functools import reduce
import frappe import frappe
from frappe import _ from frappe import _
from frappe.desk.form.linked_with import get_linked_fields
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils import cint, cstr from frappe.utils import cint, cstr
from frappe.utils.csvutils import UnicodeWriter from frappe.utils.csvutils import UnicodeWriter
@@ -26,7 +25,9 @@ from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import
class ChartofAccountsImporter(Document): class ChartofAccountsImporter(Document):
def validate(self): def validate(self):
if self.import_file: if self.import_file:
get_coa("Chart of Accounts Importer", "All Accounts", file_name=self.import_file, for_validate=1) get_coa(
"Chart of Accounts Importer", "All Accounts", file_name=self.import_file, for_validate=1
)
def validate_columns(data): def validate_columns(data):
@@ -102,7 +103,7 @@ def generate_data_from_csv(file_doc, as_dict=False):
file_path = file_doc.get_full_path() file_path = file_doc.get_full_path()
data = [] data = []
with open(file_path) as in_file: with open(file_path, "r") as in_file:
csv_reader = list(csv.reader(in_file)) csv_reader = list(csv.reader(in_file))
headers = csv_reader[0] headers = csv_reader[0]
del csv_reader[0] # delete top row and headers row del csv_reader[0] # delete top row and headers row
@@ -111,7 +112,7 @@ def generate_data_from_csv(file_doc, as_dict=False):
if as_dict: if as_dict:
data.append({frappe.scrub(header): row[index] for index, header in enumerate(headers)}) data.append({frappe.scrub(header): row[index] for index, header in enumerate(headers)})
else: else:
if not row[1] and len(row) > 1: if not row[1]:
row[1] = row[0] row[1] = row[0]
row[3] = row[2] row[3] = row[2]
data.append(row) data.append(row)
@@ -201,10 +202,10 @@ def build_forest(data):
for row in data: for row in data:
account_name, parent_account, account_number, parent_account_number = row[0:4] account_name, parent_account, account_number, parent_account_number = row[0:4]
if account_number: if account_number:
account_name = f"{account_number} - {account_name}" account_name = "{} - {}".format(account_number, account_name)
if parent_account_number: if parent_account_number:
parent_account_number = cstr(parent_account_number).strip() parent_account_number = cstr(parent_account_number).strip()
parent_account = f"{parent_account_number} - {parent_account}" parent_account = "{} - {}".format(parent_account_number, parent_account)
if parent_account == account_name == child: if parent_account == account_name == child:
return [parent_account] return [parent_account]
@@ -216,7 +217,7 @@ def build_forest(data):
frappe.bold(parent_account) frappe.bold(parent_account)
) )
) )
return [child, *parent_account_list] return [child] + parent_account_list
charts_map, paths = {}, [] charts_map, paths = {}, []
@@ -236,12 +237,12 @@ def build_forest(data):
) = i ) = i
if not account_name: if not account_name:
error_messages.append(f"Row {line_no}: Please enter Account Name") error_messages.append("Row {0}: Please enter Account Name".format(line_no))
name = account_name name = account_name
if account_number: if account_number:
account_number = cstr(account_number).strip() account_number = cstr(account_number).strip()
account_name = f"{account_number} - {account_name}" account_name = "{} - {}".format(account_number, account_name)
charts_map[account_name] = {} charts_map[account_name] = {}
charts_map[account_name]["account_name"] = name charts_map[account_name]["account_name"] = name
@@ -293,8 +294,10 @@ def build_response_as_excel(writer):
@frappe.whitelist() @frappe.whitelist()
def download_template(file_type, template_type, company): def download_template(file_type, template_type):
writer = get_template(template_type, company) data = frappe._dict(frappe.local.form_dict)
writer = get_template(template_type)
if file_type == "CSV": if file_type == "CSV":
# download csv file # download csv file
@@ -305,7 +308,8 @@ def download_template(file_type, template_type, company):
build_response_as_excel(writer) build_response_as_excel(writer)
def get_template(template_type, company): def get_template(template_type):
fields = [ fields = [
"Account Name", "Account Name",
"Parent Account", "Parent Account",
@@ -331,17 +335,34 @@ def get_template(template_type, company):
["", "", "", "", 0, account_type.get("account_type"), account_type.get("root_type")] ["", "", "", "", 0, account_type.get("account_type"), account_type.get("root_type")]
) )
else: else:
writer = get_sample_template(writer, company) writer = get_sample_template(writer)
return writer return writer
def get_sample_template(writer, company): def get_sample_template(writer):
currency = frappe.db.get_value("Company", company, "default_currency") template = [
with open(os.path.join(os.path.dirname(__file__), "coa_sample_template.csv")) as f: ["Application Of Funds(Assets)", "", "", "", 1, "", "Asset"],
for row in f: ["Sources Of Funds(Liabilities)", "", "", "", 1, "", "Liability"],
row = [*row.strip().split(","), currency] ["Equity", "", "", "", 1, "", "Equity"],
writer.writerow(row) ["Expenses", "", "", "", 1, "", "Expense"],
["Income", "", "", "", 1, "", "Income"],
["Bank Accounts", "Application Of Funds(Assets)", "", "", 1, "Bank", "Asset"],
["Cash In Hand", "Application Of Funds(Assets)", "", "", 1, "Cash", "Asset"],
["Stock Assets", "Application Of Funds(Assets)", "", "", 1, "Stock", "Asset"],
["Cost Of Goods Sold", "Expenses", "", "", 0, "Cost of Goods Sold", "Expense"],
["Asset Depreciation", "Expenses", "", "", 0, "Depreciation", "Expense"],
["Fixed Assets", "Application Of Funds(Assets)", "", "", 0, "Fixed Asset", "Asset"],
["Accounts Payable", "Sources Of Funds(Liabilities)", "", "", 0, "Payable", "Liability"],
["Accounts Receivable", "Application Of Funds(Assets)", "", "", 1, "Receivable", "Asset"],
["Stock Expenses", "Expenses", "", "", 0, "Stock Adjustment", "Expense"],
["Sample Bank", "Bank Accounts", "", "", 0, "Bank", "Asset"],
["Cash", "Cash In Hand", "", "", 0, "Cash", "Asset"],
["Stores", "Stock Assets", "", "", 0, "Stock", "Asset"],
]
for row in template:
writer.writerow(row)
return writer return writer
@@ -432,11 +453,14 @@ def get_mandatory_account_types():
def unset_existing_data(company): def unset_existing_data(company):
# remove accounts data from company linked = frappe.db.sql(
'''select fieldname from tabDocField
where fieldtype="Link" and options="Account" and parent="Company"''',
as_dict=True,
)
fieldnames = get_linked_fields("Account").get("Company", {}).get("fieldname", []) # remove accounts data from company
linked = [{"fieldname": name} for name in fieldnames] update_values = {d.fieldname: "" for d in linked}
update_values = {d.get("fieldname"): "" for d in linked}
frappe.db.set_value("Company", company, update_values, update_values) frappe.db.set_value("Company", company, update_values, update_values)
# remove accounts data from various doctypes # remove accounts data from various doctypes
@@ -449,7 +473,7 @@ def unset_existing_data(company):
"Purchase Taxes and Charges Template", "Purchase Taxes and Charges Template",
]: ]:
frappe.db.sql( frappe.db.sql(
f'''delete from `tab{doctype}` where `company`="%s"''' % (company) # nosec '''delete from `tab{0}` where `company`="%s"'''.format(doctype) % (company) # nosec
) )

View File

@@ -1,17 +0,0 @@
Application Of Funds(Assets),,,,1,,Asset
Sources Of Funds(Liabilities),,,,1,,Liability
Equity,,,,1,,Equity
Expenses,,,,1,Expense Account,Expense
Income,,,,1,Income Account,Income
Bank Accounts,Application Of Funds(Assets),,,1,Bank,Asset
Cash In Hand,Application Of Funds(Assets),,,1,Cash,Asset
Stock Assets,Application Of Funds(Assets),,,1,Stock,Asset
Cost Of Goods Sold,Expenses,,,0,Cost of Goods Sold,Expense
Asset Depreciation,Expenses,,,0,Depreciation,Expense
Fixed Assets,Application Of Funds(Assets),,,0,Fixed Asset,Asset
Accounts Payable,Sources Of Funds(Liabilities),,,0,Payable,Liability
Accounts Receivable,Application Of Funds(Assets),,,1,Receivable,Asset
Stock Expenses,Expenses,,,0,Stock Adjustment,Expense
Sample Bank,Bank Accounts,,,0,Bank,Asset
Cash,Cash In Hand,,,0,Cash,Asset
Stores,Stock Assets,,,0,Stock,Asset
1 Application Of Funds(Assets) 1 Asset
2 Sources Of Funds(Liabilities) 1 Liability
3 Equity 1 Equity
4 Expenses 1 Expense Account Expense
5 Income 1 Income Account Income
6 Bank Accounts Application Of Funds(Assets) 1 Bank Asset
7 Cash In Hand Application Of Funds(Assets) 1 Cash Asset
8 Stock Assets Application Of Funds(Assets) 1 Stock Asset
9 Cost Of Goods Sold Expenses 0 Cost of Goods Sold Expense
10 Asset Depreciation Expenses 0 Depreciation Expense
11 Fixed Assets Application Of Funds(Assets) 0 Fixed Asset Asset
12 Accounts Payable Sources Of Funds(Liabilities) 0 Payable Liability
13 Accounts Receivable Application Of Funds(Assets) 1 Receivable Asset
14 Stock Expenses Expenses 0 Stock Adjustment Expense
15 Sample Bank Bank Accounts 0 Bank Asset
16 Cash Cash In Hand 0 Cash Asset
17 Stores Stock Assets 0 Stock Asset

View File

@@ -3,20 +3,18 @@
frappe.provide("erpnext.cheque_print"); frappe.provide("erpnext.cheque_print");
frappe.ui.form.on("Cheque Print Template", { frappe.ui.form.on('Cheque Print Template', {
refresh: function (frm) { refresh: function(frm) {
if (!frm.doc.__islocal) { if(!frm.doc.__islocal) {
frm.add_custom_button( frm.add_custom_button(frm.doc.has_print_format?__("Update Print Format"):__("Create Print Format"),
frm.doc.has_print_format ? __("Update Print Format") : __("Create Print Format"), function() {
function () {
erpnext.cheque_print.view_cheque_print(frm); erpnext.cheque_print.view_cheque_print(frm);
} }).addClass("btn-primary");
).addClass("btn-primary");
$(frm.fields_dict.cheque_print_preview.wrapper).empty(); $(frm.fields_dict.cheque_print_preview.wrapper).empty()
var template =
'<div style="position: relative; overflow-x: scroll;">\ var template = '<div style="position: relative; overflow-x: scroll;">\
<div id="cheque_preview" style="width: {{ cheque_width }}cm; \ <div id="cheque_preview" style="width: {{ cheque_width }}cm; \
height: {{ cheque_height }}cm;\ height: {{ cheque_height }}cm;\
background-repeat: no-repeat;\ background-repeat: no-repeat;\
@@ -50,30 +48,30 @@ frappe.ui.form.on("Cheque Print Template", {
</div>\ </div>\
</div>'; </div>';
$(frappe.render(template, frm.doc)).appendTo(frm.fields_dict.cheque_print_preview.wrapper); $(frappe.render(template, frm.doc)).appendTo(frm.fields_dict.cheque_print_preview.wrapper)
if (frm.doc.scanned_cheque) { if (frm.doc.scanned_cheque) {
$(frm.fields_dict.cheque_print_preview.wrapper) $(frm.fields_dict.cheque_print_preview.wrapper).find("#cheque_preview").css('background-image', 'url(' + frm.doc.scanned_cheque + ')');
.find("#cheque_preview")
.css("background-image", "url(" + frm.doc.scanned_cheque + ")");
} }
} }
}, }
}); });
erpnext.cheque_print.view_cheque_print = function (frm) {
erpnext.cheque_print.view_cheque_print = function(frm) {
frappe.call({ frappe.call({
method: "erpnext.accounts.doctype.cheque_print_template.cheque_print_template.create_or_update_cheque_print_format", method: "erpnext.accounts.doctype.cheque_print_template.cheque_print_template.create_or_update_cheque_print_format",
args: { args:{
template_name: frm.doc.name, "template_name": frm.doc.name
}, },
callback: function (r) { callback: function(r) {
if (!r.exe && !frm.doc.has_print_format) { if (!r.exe && !frm.doc.has_print_format) {
var doc = frappe.model.sync(r.message); var doc = frappe.model.sync(r.message);
frappe.set_route("Form", r.message.doctype, r.message.name); frappe.set_route("Form", r.message.doctype, r.message.name);
} else {
frappe.msgprint(__("Print settings updated in respective print format"));
} }
}, else {
}); frappe.msgprint(__("Print settings updated in respective print format"))
}; }
}
})
}

View File

@@ -31,71 +31,71 @@ def create_or_update_cheque_print_format(template_name):
cheque_print.html = """ cheque_print.html = """
<style> <style>
.print-format {{ .print-format {
padding: 0px; padding: 0px;
}} }
@media screen {{ @media screen {
.print-format {{ .print-format {
padding: 0in; padding: 0in;
}} }
}} }
</style> </style>
<div style="position: relative; top:{starting_position_from_top_edge}cm"> <div style="position: relative; top:%(starting_position_from_top_edge)scm">
<div style="width:{cheque_width}cm;height:{cheque_height}cm;"> <div style="width:%(cheque_width)scm;height:%(cheque_height)scm;">
<span style="top:{acc_pay_dist_from_top_edge}cm; left:{acc_pay_dist_from_left_edge}cm; <span style="top:%(acc_pay_dist_from_top_edge)scm; left:%(acc_pay_dist_from_left_edge)scm;
border-bottom: solid 1px;border-top:solid 1px; width:2cm;text-align: center; position: absolute;"> border-bottom: solid 1px;border-top:solid 1px; width:2cm;text-align: center; position: absolute;">
{message_to_show} %(message_to_show)s
</span> </span>
<span style="top:{date_dist_from_top_edge}cm; left:{date_dist_from_left_edge}cm; <span style="top:%(date_dist_from_top_edge)scm; left:%(date_dist_from_left_edge)scm;
position: absolute;"> position: absolute;">
{{{{ frappe.utils.formatdate(doc.reference_date) or '' }}}} {{ frappe.utils.formatdate(doc.reference_date) or '' }}
</span> </span>
<span style="top:{acc_no_dist_from_top_edge}cm;left:{acc_no_dist_from_left_edge}cm; <span style="top:%(acc_no_dist_from_top_edge)scm;left:%(acc_no_dist_from_left_edge)scm;
position: absolute; min-width: 6cm;"> position: absolute; min-width: 6cm;">
{{{{ doc.account_no or '' }}}} {{ doc.account_no or '' }}
</span> </span>
<span style="top:{payer_name_from_top_edge}cm;left: {payer_name_from_left_edge}cm; <span style="top:%(payer_name_from_top_edge)scm;left: %(payer_name_from_left_edge)scm;
position: absolute; min-width: 6cm;"> position: absolute; min-width: 6cm;">
{{{{doc.party_name}}}} {{doc.party_name}}
</span> </span>
<span style="top:{amt_in_words_from_top_edge}cm; left:{amt_in_words_from_left_edge}cm; <span style="top:%(amt_in_words_from_top_edge)scm; left:%(amt_in_words_from_left_edge)scm;
position: absolute; display: block; width: {amt_in_word_width}cm; position: absolute; display: block; width: %(amt_in_word_width)scm;
line-height:{amt_in_words_line_spacing}cm; word-wrap: break-word;"> line-height:%(amt_in_words_line_spacing)scm; word-wrap: break-word;">
{{{{frappe.utils.money_in_words(doc.base_paid_amount or doc.base_received_amount)}}}} {{frappe.utils.money_in_words(doc.base_paid_amount or doc.base_received_amount)}}
</span> </span>
<span style="top:{amt_in_figures_from_top_edge}cm;left: {amt_in_figures_from_left_edge}cm; <span style="top:%(amt_in_figures_from_top_edge)scm;left: %(amt_in_figures_from_left_edge)scm;
position: absolute; min-width: 4cm;"> position: absolute; min-width: 4cm;">
{{{{doc.get_formatted("base_paid_amount") or doc.get_formatted("base_received_amount")}}}} {{doc.get_formatted("base_paid_amount") or doc.get_formatted("base_received_amount")}}
</span> </span>
<span style="top:{signatory_from_top_edge}cm;left: {signatory_from_left_edge}cm; <span style="top:%(signatory_from_top_edge)scm;left: %(signatory_from_left_edge)scm;
position: absolute; min-width: 6cm;"> position: absolute; min-width: 6cm;">
{{{{doc.company}}}} {{doc.company}}
</span> </span>
</div> </div>
</div>""".format( </div>""" % {
starting_position_from_top_edge=doc.starting_position_from_top_edge "starting_position_from_top_edge": doc.starting_position_from_top_edge
if doc.cheque_size == "A4" if doc.cheque_size == "A4"
else 0.0, else 0.0,
cheque_width=doc.cheque_width, "cheque_width": doc.cheque_width,
cheque_height=doc.cheque_height, "cheque_height": doc.cheque_height,
acc_pay_dist_from_top_edge=doc.acc_pay_dist_from_top_edge, "acc_pay_dist_from_top_edge": doc.acc_pay_dist_from_top_edge,
acc_pay_dist_from_left_edge=doc.acc_pay_dist_from_left_edge, "acc_pay_dist_from_left_edge": doc.acc_pay_dist_from_left_edge,
message_to_show=doc.message_to_show if doc.message_to_show else _("Account Pay Only"), "message_to_show": doc.message_to_show if doc.message_to_show else _("Account Pay Only"),
date_dist_from_top_edge=doc.date_dist_from_top_edge, "date_dist_from_top_edge": doc.date_dist_from_top_edge,
date_dist_from_left_edge=doc.date_dist_from_left_edge, "date_dist_from_left_edge": doc.date_dist_from_left_edge,
acc_no_dist_from_top_edge=doc.acc_no_dist_from_top_edge, "acc_no_dist_from_top_edge": doc.acc_no_dist_from_top_edge,
acc_no_dist_from_left_edge=doc.acc_no_dist_from_left_edge, "acc_no_dist_from_left_edge": doc.acc_no_dist_from_left_edge,
payer_name_from_top_edge=doc.payer_name_from_top_edge, "payer_name_from_top_edge": doc.payer_name_from_top_edge,
payer_name_from_left_edge=doc.payer_name_from_left_edge, "payer_name_from_left_edge": doc.payer_name_from_left_edge,
amt_in_words_from_top_edge=doc.amt_in_words_from_top_edge, "amt_in_words_from_top_edge": doc.amt_in_words_from_top_edge,
amt_in_words_from_left_edge=doc.amt_in_words_from_left_edge, "amt_in_words_from_left_edge": doc.amt_in_words_from_left_edge,
amt_in_word_width=doc.amt_in_word_width, "amt_in_word_width": doc.amt_in_word_width,
amt_in_words_line_spacing=doc.amt_in_words_line_spacing, "amt_in_words_line_spacing": doc.amt_in_words_line_spacing,
amt_in_figures_from_top_edge=doc.amt_in_figures_from_top_edge, "amt_in_figures_from_top_edge": doc.amt_in_figures_from_top_edge,
amt_in_figures_from_left_edge=doc.amt_in_figures_from_left_edge, "amt_in_figures_from_left_edge": doc.amt_in_figures_from_left_edge,
signatory_from_top_edge=doc.signatory_from_top_edge, "signatory_from_top_edge": doc.signatory_from_top_edge,
signatory_from_left_edge=doc.signatory_from_left_edge, "signatory_from_left_edge": doc.signatory_from_left_edge,
) }
cheque_print.save(ignore_permissions=True) cheque_print.save(ignore_permissions=True)

View File

@@ -3,80 +3,75 @@
frappe.provide("erpnext.accounts"); frappe.provide("erpnext.accounts");
frappe.ui.form.on("Cost Center", {
onload: function (frm) {
frm.set_query("parent_cost_center", function () { frappe.ui.form.on('Cost Center', {
onload: function(frm) {
frm.set_query("parent_cost_center", function() {
return { return {
filters: { filters: {
company: frm.doc.company, company: frm.doc.company,
is_group: 1, is_group: 1
}, }
}; }
}); });
}, },
refresh: function (frm) { refresh: function(frm) {
if (!frm.is_new()) { if (!frm.is_new()) {
frm.add_custom_button(__("Update Cost Center Name / Number"), function () { frm.add_custom_button(__('Update Cost Center Name / Number'), function () {
frm.trigger("update_cost_center_number"); frm.trigger("update_cost_center_number");
}); });
} }
let intro_txt = ""; let intro_txt = '';
let doc = frm.doc; let doc = frm.doc;
frm.toggle_display("cost_center_name", doc.__islocal); frm.toggle_display('cost_center_name', doc.__islocal);
frm.toggle_enable(["is_group", "company"], doc.__islocal); frm.toggle_enable(['is_group', 'company'], doc.__islocal);
if (!doc.__islocal && doc.is_group == 1) { if(!doc.__islocal && doc.is_group==1) {
intro_txt += __( intro_txt += __('Note: This Cost Center is a Group. Cannot make accounting entries against groups.');
"Note: This Cost Center is a Group. Cannot make accounting entries against groups."
);
} }
frm.events.hide_unhide_group_ledger(frm); frm.events.hide_unhide_group_ledger(frm);
frm.toggle_display("sb1", doc.is_group == 0); frm.toggle_display('sb1', doc.is_group==0);
frm.set_intro(intro_txt); frm.set_intro(intro_txt);
if (!frm.doc.__islocal) { if(!frm.doc.__islocal) {
frm.add_custom_button(__("Chart of Cost Centers"), function () { frm.add_custom_button(__('Chart of Cost Centers'),
frappe.set_route("Tree", "Cost Center"); function() { frappe.set_route("Tree", "Cost Center"); });
});
frm.add_custom_button(__("Budget"), function () { frm.add_custom_button(__('Budget'),
frappe.set_route("List", "Budget", { cost_center: frm.doc.name }); function() { frappe.set_route("List", "Budget", {'cost_center': frm.doc.name}); });
});
} }
}, },
update_cost_center_number: function (frm) { update_cost_center_number: function(frm) {
var d = new frappe.ui.Dialog({ var d = new frappe.ui.Dialog({
title: __("Update Cost Center Name / Number"), title: __('Update Cost Center Name / Number'),
fields: [ fields: [
{ {
label: "Cost Center Name", "label": "Cost Center Name",
fieldname: "cost_center_name", "fieldname": "cost_center_name",
fieldtype: "Data", "fieldtype": "Data",
reqd: 1, "reqd": 1,
default: frm.doc.cost_center_name, "default": frm.doc.cost_center_name
}, },
{ {
label: "Cost Center Number", "label": "Cost Center Number",
fieldname: "cost_center_number", "fieldname": "cost_center_number",
fieldtype: "Data", "fieldtype": "Data",
default: frm.doc.cost_center_number, "default": frm.doc.cost_center_number
}, },
{ {
label: __("Merge with existing"), "label": __("Merge with existing"),
fieldname: "merge", "fieldname": "merge",
fieldtype: "Check", "fieldtype": "Check",
default: 0, "default": 0
}, }
], ],
primary_action: function () { primary_action: function() {
var data = d.get_values(); var data = d.get_values();
if ( if(data.cost_center_name === frm.doc.cost_center_name && data.cost_center_number === frm.doc.cost_center_number) {
data.cost_center_name === frm.doc.cost_center_name &&
data.cost_center_number === frm.doc.cost_center_number
) {
d.hide(); d.hide();
return; return;
} }
@@ -88,12 +83,12 @@ frappe.ui.form.on("Cost Center", {
cost_center_name: data.cost_center_name, cost_center_name: data.cost_center_name,
cost_center_number: cstr(data.cost_center_number), cost_center_number: cstr(data.cost_center_number),
company: frm.doc.company, company: frm.doc.company,
merge: data.merge, merge: data.merge
}, },
callback: function (r) { callback: function(r) {
frappe.dom.unfreeze(); frappe.dom.unfreeze();
if (!r.exc) { if(!r.exc) {
if (r.message) { if(r.message) {
frappe.set_route("Form", "Cost Center", r.message); frappe.set_route("Form", "Cost Center", r.message);
} else { } else {
me.frm.set_value("cost_center_name", data.cost_center_name); me.frm.set_value("cost_center_name", data.cost_center_name);
@@ -101,42 +96,44 @@ frappe.ui.form.on("Cost Center", {
} }
d.hide(); d.hide();
} }
}, }
}); });
}, },
primary_action_label: __("Update"), primary_action_label: __('Update')
}); });
d.show(); d.show();
}, },
parent_cost_center(frm) { parent_cost_center(frm) {
if (!frm.doc.company) { if(!frm.doc.company) {
frappe.msgprint(__("Please enter company name first")); frappe.msgprint(__('Please enter company name first'));
} }
}, },
hide_unhide_group_ledger(frm) { hide_unhide_group_ledger(frm) {
let doc = frm.doc; let doc = frm.doc;
if (doc.is_group == 1) { if (doc.is_group == 1) {
frm.add_custom_button(__("Convert to Non-Group"), () => frm.events.convert_to_ledger(frm)); frm.add_custom_button(__('Convert to Non-Group'),
() => frm.events.convert_to_ledger(frm));
} else if (doc.is_group == 0) { } else if (doc.is_group == 0) {
frm.add_custom_button(__("Convert to Group"), () => frm.events.convert_to_group(frm)); frm.add_custom_button(__('Convert to Group'),
() => frm.events.convert_to_group(frm));
} }
}, },
convert_to_group(frm) { convert_to_group(frm) {
frm.call("convert_ledger_to_group").then((r) => { frm.call('convert_ledger_to_group').then(r => {
if (r.message === 1) { if(r.message === 1) {
frm.refresh(); frm.refresh();
} }
}); });
}, },
convert_to_ledger(frm) { convert_to_ledger(frm) {
frm.call("convert_group_to_ledger").then((r) => { frm.call('convert_group_to_ledger').then(r => {
if (r.message === 1) { if(r.message === 1) {
frm.refresh(); frm.refresh();
} }
}); });
}, }
}); });

View File

@@ -125,7 +125,7 @@
"idx": 1, "idx": 1,
"is_tree": 1, "is_tree": 1,
"links": [], "links": [],
"modified": "2024-04-24 10:55:54.083042", "modified": "2022-01-31 13:22:58.916273",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Cost Center", "name": "Cost Center",
@@ -163,15 +163,6 @@
{ {
"read": 1, "read": 1,
"role": "Purchase User" "role": "Purchase User"
},
{
"email": 1,
"export": 1,
"print": 1,
"report": 1,
"role": "Employee",
"select": 1,
"share": 1
} }
], ],
"search_fields": "parent_cost_center, is_group", "search_fields": "parent_cost_center, is_group",

View File

@@ -15,7 +15,9 @@ class CostCenter(NestedSet):
def autoname(self): def autoname(self):
from erpnext.accounts.utils import get_autoname_with_number from erpnext.accounts.utils import get_autoname_with_number
self.name = get_autoname_with_number(self.cost_center_number, self.cost_center_name, self.company) self.name = get_autoname_with_number(
self.cost_center_number, self.cost_center_name, self.company
)
def validate(self): def validate(self):
self.validate_mandatory() self.validate_mandatory()
@@ -88,14 +90,14 @@ class CostCenter(NestedSet):
new_cost_center = get_name_with_abbr(newdn, self.company) new_cost_center = get_name_with_abbr(newdn, self.company)
# Validate properties before merging # Validate properties before merging
super().before_rename(olddn, new_cost_center, merge, "is_group") super(CostCenter, self).before_rename(olddn, new_cost_center, merge, "is_group")
if not merge: if not merge:
new_cost_center = get_name_with_number(new_cost_center, self.cost_center_number) new_cost_center = get_name_with_number(new_cost_center, self.cost_center_number)
return new_cost_center return new_cost_center
def after_rename(self, olddn, newdn, merge=False): def after_rename(self, olddn, newdn, merge=False):
super().after_rename(olddn, newdn, merge) super(CostCenter, self).after_rename(olddn, newdn, merge)
if not merge: if not merge:
new_cost_center = frappe.db.get_value( new_cost_center = frappe.db.get_value(

View File

@@ -1,84 +1,54 @@
frappe.treeview_settings["Cost Center"] = { frappe.treeview_settings["Cost Center"] = {
breadcrumb: "Accounts", breadcrumb: "Accounts",
get_tree_root: false, get_tree_root: false,
filters: [ filters: [{
{ fieldname: "company",
fieldname: "company", fieldtype:"Select",
fieldtype: "Select", options: erpnext.utils.get_tree_options("company"),
options: erpnext.utils.get_tree_options("company"), label: __("Company"),
label: __("Company"), default: erpnext.utils.get_tree_default("company")
default: erpnext.utils.get_tree_default("company"), }],
},
],
root_label: "Cost Centers", root_label: "Cost Centers",
get_tree_nodes: "erpnext.accounts.utils.get_children", get_tree_nodes: 'erpnext.accounts.utils.get_children',
add_tree_node: "erpnext.accounts.utils.add_cc", add_tree_node: 'erpnext.accounts.utils.add_cc',
menu_items: [ menu_items:[
{ {
label: __("New Company"), label: __('New Company'),
action: function () { action: function() { frappe.new_doc("Company", true) },
frappe.new_doc("Company", true); condition: 'frappe.boot.user.can_create.indexOf("Company") !== -1'
}, }
condition: 'frappe.boot.user.can_create.indexOf("Company") !== -1',
},
], ],
fields: [ fields:[
{ fieldtype: "Data", fieldname: "cost_center_name", label: __("New Cost Center Name"), reqd: true }, {fieldtype:'Data', fieldname:'cost_center_name', label:__('New Cost Center Name'), reqd:true},
{ {fieldtype:'Check', fieldname:'is_group', label:__('Is Group'),
fieldtype: "Check", description:__('Further cost centers can be made under Groups but entries can be made against non-Groups')},
fieldname: "is_group", {fieldtype:'Data', fieldname:'cost_center_number', label:__('Cost Center Number'),
label: __("Is Group"), description: __("Number of new Cost Center, it will be included in the cost center name as a prefix")}
description: __(
"Further cost centers can be made under Groups but entries can be made against non-Groups"
),
},
{
fieldtype: "Data",
fieldname: "cost_center_number",
label: __("Cost Center Number"),
description: __(
"Number of new Cost Center, it will be included in the cost center name as a prefix"
),
},
], ],
ignore_fields: ["parent_cost_center"], ignore_fields:["parent_cost_center"],
onload: function (treeview) { onload: function(treeview) {
function get_company() { function get_company() {
return treeview.page.fields_dict.company.get_value(); return treeview.page.fields_dict.company.get_value();
} }
// tools // tools
treeview.page.add_inner_button( treeview.page.add_inner_button(__("Chart of Accounts"), function() {
__("Chart of Accounts"), frappe.set_route('Tree', 'Account', {company: get_company()});
function () { }, __('View'));
frappe.set_route("Tree", "Account", { company: get_company() });
},
__("View")
);
// make // make
treeview.page.add_inner_button( treeview.page.add_inner_button(__("Budget List"), function() {
__("Budget List"), frappe.set_route('List', 'Budget', {company: get_company()});
function () { }, __('Budget'));
frappe.set_route("List", "Budget", { company: get_company() });
},
__("Budget")
);
treeview.page.add_inner_button( treeview.page.add_inner_button(__("Monthly Distribution"), function() {
__("Monthly Distribution"), frappe.set_route('List', 'Monthly Distribution', {company: get_company()});
function () { }, __('Budget'));
frappe.set_route("List", "Monthly Distribution", { company: get_company() });
},
__("Budget")
);
treeview.page.add_inner_button( treeview.page.add_inner_button(__("Budget Variance Report"), function() {
__("Budget Variance Report"), frappe.set_route('query-report', 'Budget Variance Report', {company: get_company()});
function () { }, __('Budget'));
frappe.set_route("query-report", "Budget Variance Report", { company: get_company() });
}, }
__("Budget")
); }
},
};

View File

@@ -10,6 +10,7 @@ test_records = frappe.get_test_records("Cost Center")
class TestCostCenter(unittest.TestCase): class TestCostCenter(unittest.TestCase):
def test_cost_center_creation_against_child_node(self): def test_cost_center_creation_against_child_node(self):
if not frappe.db.get_value("Cost Center", {"name": "_Test Cost Center 2 - _TC"}): if not frappe.db.get_value("Cost Center", {"name": "_Test Cost Center 2 - _TC"}):
frappe.get_doc(test_records[1]).insert() frappe.get_doc(test_records[1]).insert()

View File

@@ -1,24 +1,19 @@
// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on("Cost Center Allocation", { frappe.ui.form.on('Cost Center Allocation', {
setup: function (frm) { setup: function(frm) {
frm.set_query("main_cost_center", function () { let filters = {"is_group": 0};
return { if (frm.doc.company) {
filters: { $.extend(filters, {
company: frm.doc.company, "company": frm.doc.company
is_group: 0, });
}, }
};
});
frm.set_query("cost_center", "allocation_percentages", function () { frm.set_query('main_cost_center', function() {
return { return {
filters: { filters: filters
company: frm.doc.company,
is_group: 0,
},
}; };
}); });
}, }
}); });

View File

@@ -29,7 +29,7 @@ class InvalidDateError(frappe.ValidationError):
class CostCenterAllocation(Document): class CostCenterAllocation(Document):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super(CostCenterAllocation, self).__init__(*args, **kwargs)
self._skip_from_date_validation = False self._skip_from_date_validation = False
def validate(self): def validate(self):
@@ -44,7 +44,9 @@ class CostCenterAllocation(Document):
total_percentage = sum([d.percentage for d in self.get("allocation_percentages", [])]) total_percentage = sum([d.percentage for d in self.get("allocation_percentages", [])])
if total_percentage != 100: if total_percentage != 100:
frappe.throw(_("Total percentage against cost centers should be 100"), WrongPercentageAllocation) frappe.throw(
_("Total percentage against cost centers should be 100"), WrongPercentageAllocation
)
def validate_from_date_based_on_existing_gle(self): def validate_from_date_based_on_existing_gle(self):
# Check if GLE exists against the main cost center # Check if GLE exists against the main cost center

View File

@@ -22,10 +22,8 @@ class TestCostCenterAllocation(unittest.TestCase):
cost_centers = [ cost_centers = [
"Main Cost Center 1", "Main Cost Center 1",
"Main Cost Center 2", "Main Cost Center 2",
"Main Cost Center 3",
"Sub Cost Center 1", "Sub Cost Center 1",
"Sub Cost Center 2", "Sub Cost Center 2",
"Sub Cost Center 3",
] ]
for cc in cost_centers: for cc in cost_centers:
create_cost_center(cost_center_name=cc, company="_Test Company") create_cost_center(cost_center_name=cc, company="_Test Company")
@@ -38,7 +36,7 @@ class TestCostCenterAllocation(unittest.TestCase):
) )
jv = make_journal_entry( jv = make_journal_entry(
"Cash - _TC", "Sales - _TC", 100, cost_center="Main Cost Center 1 - _TC", submit=True "_Test Cash - _TC", "Sales - _TC", 100, cost_center="Main Cost Center 1 - _TC", submit=True
) )
expected_values = [["Sub Cost Center 1 - _TC", 0.0, 60], ["Sub Cost Center 2 - _TC", 0.0, 40]] expected_values = [["Sub Cost Center 1 - _TC", 0.0, 60], ["Sub Cost Center 2 - _TC", 0.0, 40]]
@@ -122,7 +120,7 @@ class TestCostCenterAllocation(unittest.TestCase):
def test_valid_from_based_on_existing_gle(self): def test_valid_from_based_on_existing_gle(self):
# GLE posted against Sub Cost Center 1 on today # GLE posted against Sub Cost Center 1 on today
jv = make_journal_entry( jv = make_journal_entry(
"Cash - _TC", "_Test Cash - _TC",
"Sales - _TC", "Sales - _TC",
100, 100,
cost_center="Main Cost Center 1 - _TC", cost_center="Main Cost Center 1 - _TC",
@@ -143,53 +141,6 @@ class TestCostCenterAllocation(unittest.TestCase):
jv.cancel() jv.cancel()
def test_multiple_cost_center_allocation_on_same_main_cost_center(self):
coa1 = create_cost_center_allocation(
"_Test Company",
"Main Cost Center 3 - _TC",
{"Sub Cost Center 1 - _TC": 30, "Sub Cost Center 2 - _TC": 30, "Sub Cost Center 3 - _TC": 40},
valid_from=add_days(today(), -5),
)
coa2 = create_cost_center_allocation(
"_Test Company",
"Main Cost Center 3 - _TC",
{"Sub Cost Center 1 - _TC": 50, "Sub Cost Center 2 - _TC": 50},
valid_from=add_days(today(), -1),
)
jv = make_journal_entry(
"Cash - _TC",
"Sales - _TC",
100,
cost_center="Main Cost Center 3 - _TC",
posting_date=today(),
submit=True,
)
expected_values = {"Sub Cost Center 1 - _TC": 50, "Sub Cost Center 2 - _TC": 50}
gle = frappe.qb.DocType("GL Entry")
gl_entries = (
frappe.qb.from_(gle)
.select(gle.cost_center, gle.debit, gle.credit)
.where(gle.voucher_type == "Journal Entry")
.where(gle.voucher_no == jv.name)
.where(gle.account == "Sales - _TC")
.orderby(gle.cost_center)
).run(as_dict=1)
self.assertTrue(gl_entries)
for gle in gl_entries:
self.assertTrue(gle.cost_center in expected_values)
self.assertEqual(gle.debit, 0)
self.assertEqual(gle.credit, expected_values[gle.cost_center])
coa1.cancel()
coa2.cancel()
jv.cancel()
def create_cost_center_allocation( def create_cost_center_allocation(
company, company,

View File

@@ -1,41 +1,44 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on("Coupon Code", { frappe.ui.form.on('Coupon Code', {
setup: function (frm) { setup: function(frm) {
frm.set_query("pricing_rule", function () { frm.set_query("pricing_rule", function() {
return { return {
filters: [["Pricing Rule", "coupon_code_based", "=", "1"]], filters: [
["Pricing Rule","coupon_code_based", "=", "1"]
]
}; };
}); });
}, },
coupon_name: function (frm) { coupon_name:function(frm){
if (frm.doc.__islocal === 1) { if (frm.doc.__islocal===1) {
frm.trigger("make_coupon_code"); frm.trigger("make_coupon_code");
} }
}, },
coupon_type: function (frm) { coupon_type:function(frm){
if (frm.doc.__islocal === 1) { if (frm.doc.__islocal===1) {
frm.trigger("make_coupon_code"); frm.trigger("make_coupon_code");
} }
}, },
make_coupon_code: function (frm) { make_coupon_code: function(frm) {
var coupon_name = frm.doc.coupon_name; var coupon_name=frm.doc.coupon_name;
var coupon_code; var coupon_code;
if (frm.doc.coupon_type == "Gift Card") { if (frm.doc.coupon_type=='Gift Card') {
coupon_code = Math.random().toString(12).substring(2, 12).toUpperCase(); coupon_code=Math.random().toString(12).substring(2, 12).toUpperCase();
} else if (frm.doc.coupon_type == "Promotional") {
coupon_name = coupon_name.replace(/\s/g, "");
coupon_code = coupon_name.toUpperCase().slice(0, 8);
} }
frm.doc.coupon_code = coupon_code; else if(frm.doc.coupon_type=='Promotional'){
frm.refresh_field("coupon_code"); coupon_name=coupon_name.replace(/\s/g,'');
coupon_code=coupon_name.toUpperCase().slice(0,8);
}
frm.doc.coupon_code=coupon_code;
frm.refresh_field('coupon_code');
}, },
refresh: function (frm) { refresh: function(frm) {
if (frm.doc.pricing_rule) { if (frm.doc.pricing_rule) {
frm.add_custom_button(__("Add/Edit Coupon Conditions"), function () { frm.add_custom_button(__("Add/Edit Coupon Conditions"), function(){
frappe.set_route("Form", "Pricing Rule", frm.doc.pricing_rule); frappe.set_route("Form", "Pricing Rule", frm.doc.pricing_rule);
}); });
} }
}, }
}); });

View File

@@ -1,41 +1,28 @@
// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on("Currency Exchange Settings", { frappe.ui.form.on('Currency Exchange Settings', {
service_provider: function (frm) { service_provider: function(frm) {
frm.call({ if (frm.doc.service_provider == "exchangerate.host") {
method: "erpnext.accounts.doctype.currency_exchange_settings.currency_exchange_settings.get_api_endpoint", let result = ['result'];
args: { let params = {
service_provider: frm.doc.service_provider, date: '{transaction_date}',
use_http: frm.doc.use_http, from: '{from_currency}',
}, to: '{to_currency}'
callback: function (r) { };
if (r && r.message) { add_param(frm, "https://api.exchangerate.host/convert", params, result);
if (frm.doc.service_provider == "exchangerate.host") { } else if (frm.doc.service_provider == "frankfurter.app") {
let result = ["result"]; let result = ['rates', '{to_currency}'];
let params = { let params = {
date: "{transaction_date}", base: '{from_currency}',
from: "{from_currency}", symbols: '{to_currency}'
to: "{to_currency}", };
}; add_param(frm, "https://frankfurter.app/{transaction_date}", params, result);
add_param(frm, r.message, params, result); }
} else if (frm.doc.service_provider == "frankfurter.app") { }
let result = ["rates", "{to_currency}"];
let params = {
base: "{from_currency}",
symbols: "{to_currency}",
};
add_param(frm, r.message, params, result);
}
}
},
});
},
use_http: function (frm) {
frm.trigger("service_provider");
},
}); });
function add_param(frm, api, params, result) { function add_param(frm, api, params, result) {
var row; var row;
frm.clear_table("req_params"); frm.clear_table("req_params");
@@ -43,13 +30,13 @@ function add_param(frm, api, params, result) {
frm.doc.api_endpoint = api; frm.doc.api_endpoint = api;
$.each(params, function (key, value) { $.each(params, function(key, value) {
row = frm.add_child("req_params"); row = frm.add_child("req_params");
row.key = key; row.key = key;
row.value = value; row.value = value;
}); });
$.each(result, function (key, value) { $.each(result, function(key, value) {
row = frm.add_child("result_key"); row = frm.add_child("result_key");
row.key = value; row.key = value;
}); });

View File

@@ -6,11 +6,8 @@
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"api_details_section", "api_details_section",
"disabled",
"service_provider", "service_provider",
"api_endpoint", "api_endpoint",
"use_http",
"access_key",
"url", "url",
"column_break_3", "column_break_3",
"help", "help",
@@ -80,31 +77,12 @@
"label": "Service Provider", "label": "Service Provider",
"options": "frankfurter.app\nexchangerate.host\nCustom", "options": "frankfurter.app\nexchangerate.host\nCustom",
"reqd": 1 "reqd": 1
},
{
"default": "0",
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled"
},
{
"depends_on": "eval:doc.service_provider == 'exchangerate.host';",
"fieldname": "access_key",
"fieldtype": "Data",
"label": "Access Key"
},
{
"default": "0",
"depends_on": "eval: doc.service_provider != \"Custom\"",
"fieldname": "use_http",
"fieldtype": "Check",
"label": "Use HTTP Protocol"
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2024-03-18 08:32:26.895076", "modified": "2022-01-10 15:51:14.521174",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Currency Exchange Settings", "name": "Currency Exchange Settings",

View File

@@ -18,20 +18,11 @@ class CurrencyExchangeSettings(Document):
def set_parameters_and_result(self): def set_parameters_and_result(self):
if self.service_provider == "exchangerate.host": if self.service_provider == "exchangerate.host":
if not self.access_key:
frappe.throw(
_("Access Key is required for Service Provider: {0}").format(
frappe.bold(self.service_provider)
)
)
self.set("result_key", []) self.set("result_key", [])
self.set("req_params", []) self.set("req_params", [])
self.api_endpoint = get_api_endpoint(self.service_provider, self.use_http) self.api_endpoint = "https://api.exchangerate.host/convert"
self.append("result_key", {"key": "result"}) self.append("result_key", {"key": "result"})
self.append("req_params", {"key": "access_key", "value": self.access_key})
self.append("req_params", {"key": "amount", "value": "1"})
self.append("req_params", {"key": "date", "value": "{transaction_date}"}) self.append("req_params", {"key": "date", "value": "{transaction_date}"})
self.append("req_params", {"key": "from", "value": "{from_currency}"}) self.append("req_params", {"key": "from", "value": "{from_currency}"})
self.append("req_params", {"key": "to", "value": "{to_currency}"}) self.append("req_params", {"key": "to", "value": "{to_currency}"})
@@ -39,7 +30,7 @@ class CurrencyExchangeSettings(Document):
self.set("result_key", []) self.set("result_key", [])
self.set("req_params", []) self.set("req_params", [])
self.api_endpoint = get_api_endpoint(self.service_provider, self.use_http) self.api_endpoint = "https://frankfurter.app/{transaction_date}"
self.append("result_key", {"key": "rates"}) self.append("result_key", {"key": "rates"})
self.append("result_key", {"key": "{to_currency}"}) self.append("result_key", {"key": "{to_currency}"})
self.append("req_params", {"key": "base", "value": "{from_currency}"}) self.append("req_params", {"key": "base", "value": "{from_currency}"})
@@ -52,7 +43,9 @@ class CurrencyExchangeSettings(Document):
transaction_date=nowdate(), to_currency="INR", from_currency="USD" transaction_date=nowdate(), to_currency="INR", from_currency="USD"
) )
api_url = self.api_endpoint.format(transaction_date=nowdate(), to_currency="INR", from_currency="USD") api_url = self.api_endpoint.format(
transaction_date=nowdate(), to_currency="INR", from_currency="USD"
)
try: try:
response = requests.get(api_url, params=params) response = requests.get(api_url, params=params)
@@ -72,23 +65,7 @@ class CurrencyExchangeSettings(Document):
] ]
except Exception: except Exception:
frappe.throw(_("Invalid result key. Response:") + " " + response.text) frappe.throw(_("Invalid result key. Response:") + " " + response.text)
if not isinstance(value, int | float): if not isinstance(value, (int, float)):
frappe.throw(_("Returned exchange rate is neither integer not float.")) frappe.throw(_("Returned exchange rate is neither integer not float."))
self.url = response.url self.url = response.url
@frappe.whitelist()
def get_api_endpoint(service_provider: str | None = None, use_http: bool = False):
if service_provider and service_provider in ["exchangerate.host", "frankfurter.app"]:
if service_provider == "exchangerate.host":
api = "api.exchangerate.host/convert"
elif service_provider == "frankfurter.app":
api = "frankfurter.app/{transaction_date}"
protocol = "https://"
if use_http:
protocol = "http://"
return protocol + api
return None

View File

@@ -9,7 +9,7 @@ frappe.ui.form.on("Dunning", {
docstatus: 1, docstatus: 1,
company: frm.doc.company, company: frm.doc.company,
outstanding_amount: [">", 0], outstanding_amount: [">", 0],
status: "Overdue", status: "Overdue"
}, },
}; };
}); });
@@ -18,14 +18,18 @@ frappe.ui.form.on("Dunning", {
filters: { filters: {
company: frm.doc.company, company: frm.doc.company,
root_type: "Income", root_type: "Income",
is_group: 0, is_group: 0
}, }
}; };
}); });
}, },
refresh: function (frm) { refresh: function (frm) {
frm.set_df_property("company", "read_only", frm.doc.__islocal ? 0 : 1); frm.set_df_property("company", "read_only", frm.doc.__islocal ? 0 : 1);
frm.set_df_property("sales_invoice", "read_only", frm.doc.__islocal ? 0 : 1); frm.set_df_property(
"sales_invoice",
"read_only",
frm.doc.__islocal ? 0 : 1
);
if (frm.doc.docstatus === 1 && frm.doc.status === "Unresolved") { if (frm.doc.docstatus === 1 && frm.doc.status === "Unresolved") {
frm.add_custom_button(__("Resolve"), () => { frm.add_custom_button(__("Resolve"), () => {
frm.set_value("status", "Resolved"); frm.set_value("status", "Resolved");
@@ -36,27 +40,22 @@ frappe.ui.form.on("Dunning", {
__("Payment"), __("Payment"),
function () { function () {
frm.events.make_payment_entry(frm); frm.events.make_payment_entry(frm);
}, },__("Create")
__("Create")
); );
frm.page.set_inner_btn_group_as_primary(__("Create")); frm.page.set_inner_btn_group_as_primary(__("Create"));
} }
if (frm.doc.docstatus > 0) { if(frm.doc.docstatus > 0) {
frm.add_custom_button( frm.add_custom_button(__('Ledger'), function() {
__("Ledger"), frappe.route_options = {
function () { "voucher_no": frm.doc.name,
frappe.route_options = { "from_date": frm.doc.posting_date,
voucher_no: frm.doc.name, "to_date": frm.doc.posting_date,
from_date: frm.doc.posting_date, "company": frm.doc.company,
to_date: frm.doc.posting_date, "show_cancelled_entries": frm.doc.docstatus === 2
company: frm.doc.company, };
show_cancelled_entries: frm.doc.docstatus === 2, frappe.set_route("query-report", "General Ledger");
}; }, __('View'));
frappe.set_route("query-report", "General Ledger");
},
__("View")
);
} }
}, },
overdue_days: function (frm) { overdue_days: function (frm) {
@@ -87,7 +86,8 @@ frappe.ui.form.on("Dunning", {
get_dunning_letter_text: function (frm) { get_dunning_letter_text: function (frm) {
if (frm.doc.dunning_type) { if (frm.doc.dunning_type) {
frappe.call({ frappe.call({
method: "erpnext.accounts.doctype.dunning.dunning.get_dunning_letter_text", method:
"erpnext.accounts.doctype.dunning.dunning.get_dunning_letter_text",
args: { args: {
dunning_type: frm.doc.dunning_type, dunning_type: frm.doc.dunning_type,
language: frm.doc.language, language: frm.doc.language,
@@ -129,25 +129,26 @@ frappe.ui.form.on("Dunning", {
}, },
calculate_overdue_days: function (frm) { calculate_overdue_days: function (frm) {
if (frm.doc.posting_date && frm.doc.due_date) { if (frm.doc.posting_date && frm.doc.due_date) {
const overdue_days = moment(frm.doc.posting_date).diff(frm.doc.due_date, "days"); const overdue_days = moment(frm.doc.posting_date).diff(
frm.doc.due_date,
"days"
);
frm.set_value("overdue_days", overdue_days); frm.set_value("overdue_days", overdue_days);
} }
}, },
calculate_interest_and_amount: function (frm) { calculate_interest_and_amount: function (frm) {
const interest_per_year = (frm.doc.outstanding_amount * frm.doc.rate_of_interest) / 100; const interest_per_year = frm.doc.outstanding_amount * frm.doc.rate_of_interest / 100;
const interest_amount = flt( const interest_amount = flt((interest_per_year * cint(frm.doc.overdue_days)) / 365 || 0, precision('interest_amount'));
(interest_per_year * cint(frm.doc.overdue_days)) / 365 || 0, const dunning_amount = flt(interest_amount + frm.doc.dunning_fee, precision('dunning_amount'));
precision("interest_amount") const grand_total = flt(frm.doc.outstanding_amount + dunning_amount, precision('grand_total'));
);
const dunning_amount = flt(interest_amount + frm.doc.dunning_fee, precision("dunning_amount"));
const grand_total = flt(frm.doc.outstanding_amount + dunning_amount, precision("grand_total"));
frm.set_value("interest_amount", interest_amount); frm.set_value("interest_amount", interest_amount);
frm.set_value("dunning_amount", dunning_amount); frm.set_value("dunning_amount", dunning_amount);
frm.set_value("grand_total", grand_total); frm.set_value("grand_total", grand_total);
}, },
make_payment_entry: function (frm) { make_payment_entry: function (frm) {
return frappe.call({ return frappe.call({
method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry", method:
"erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry",
args: { args: {
dt: frm.doc.doctype, dt: frm.doc.doctype,
dn: frm.doc.name, dn: frm.doc.name,

View File

@@ -1,7 +1,8 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on("Dunning Type", { frappe.ui.form.on('Dunning Type', {
// refresh: function(frm) { // refresh: function(frm) {
// } // }
}); });

View File

@@ -1,79 +1,75 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on("Exchange Rate Revaluation", { frappe.ui.form.on('Exchange Rate Revaluation', {
setup: function (frm) { setup: function(frm) {
frm.set_query("party_type", "accounts", function () { frm.set_query("party_type", "accounts", function() {
return { return {
filters: { "filters": {
name: ["in", Object.keys(frappe.boot.party_account_types)], "name": ["in", Object.keys(frappe.boot.party_account_types)],
}, }
}; };
}); });
frm.set_query("account", "accounts", function (doc) { frm.set_query("account", "accounts", function(doc) {
return { return {
filters: { "filters": {
company: doc.company, "company": doc.company
}, }
}; };
}); });
}, },
refresh: function (frm) { refresh: function(frm) {
if (frm.doc.docstatus == 1) { if(frm.doc.docstatus==1) {
frappe.call({ frappe.call({
method: "check_journal_entry_condition", method: 'check_journal_entry_condition',
doc: frm.doc, doc: frm.doc,
callback: function (r) { callback: function(r) {
if (r.message) { if (r.message) {
frm.add_custom_button( frm.add_custom_button(__('Journal Entries'), function() {
__("Journal Entries"), return frm.events.make_jv(frm);
function () { }, __('Create'));
return frm.events.make_jv(frm);
},
__("Create")
);
} }
}, }
}); });
} }
}, },
validate_rounding_loss: function (frm) { validate_rounding_loss: function(frm) {
let allowance = frm.doc.rounding_loss_allowance; let allowance = frm.doc.rounding_loss_allowance;
if (!(allowance >= 0 && allowance < 1)) { if (!(allowance >= 0 && allowance < 1)) {
frappe.throw(__("Rounding Loss Allowance should be between 0 and 1")); frappe.throw(__("Rounding Loss Allowance should be between 0 and 1"));
} }
}, },
rounding_loss_allowance: function (frm) { rounding_loss_allowance: function(frm) {
frm.events.validate_rounding_loss(frm); frm.events.validate_rounding_loss(frm);
}, },
validate: function (frm) { validate: function(frm) {
frm.events.validate_rounding_loss(frm); frm.events.validate_rounding_loss(frm);
}, },
get_entries: function (frm, account) { get_entries: function(frm, account) {
frappe.call({ frappe.call({
method: "get_accounts_data", method: "get_accounts_data",
doc: cur_frm.doc, doc: cur_frm.doc,
account: account, account: account,
callback: function (r) { callback: function(r){
frappe.model.clear_table(frm.doc, "accounts"); frappe.model.clear_table(frm.doc, "accounts");
if (r.message) { if(r.message) {
r.message.forEach((d) => { r.message.forEach((d) => {
cur_frm.add_child("accounts", d); cur_frm.add_child("accounts",d);
}); });
frm.events.get_total_gain_loss(frm); frm.events.get_total_gain_loss(frm);
refresh_field("accounts"); refresh_field("accounts");
} }
}, }
}); });
}, },
get_total_gain_loss: function (frm) { get_total_gain_loss: function(frm) {
if (!(frm.doc.accounts && frm.doc.accounts.length)) return; if(!(frm.doc.accounts && frm.doc.accounts.length)) return;
let total_gain_loss = 0; let total_gain_loss = 0;
frm.doc.accounts.forEach((d) => { frm.doc.accounts.forEach((d) => {
@@ -84,7 +80,7 @@ frappe.ui.form.on("Exchange Rate Revaluation", {
frm.refresh_fields(); frm.refresh_fields();
}, },
make_jv: function (frm) { make_jv : function(frm) {
let revaluation_journal = null; let revaluation_journal = null;
let zero_balance_journal = null; let zero_balance_journal = null;
frappe.call({ frappe.call({
@@ -92,68 +88,66 @@ frappe.ui.form.on("Exchange Rate Revaluation", {
doc: frm.doc, doc: frm.doc,
freeze: true, freeze: true,
freeze_message: "Making Journal Entries...", freeze_message: "Making Journal Entries...",
callback: function (r) { callback: function(r){
if (r.message) { if (r.message) {
let response = r.message; let response = r.message;
if (response["revaluation_jv"] || response["zero_balance_jv"]) { if(response['revaluation_jv'] || response['zero_balance_jv']) {
frappe.msgprint(__("Journals have been created")); frappe.msgprint(__("Journals have been created"));
} }
} }
}, }
}); });
}, }
}); });
frappe.ui.form.on("Exchange Rate Revaluation Account", { frappe.ui.form.on("Exchange Rate Revaluation Account", {
new_exchange_rate: function (frm, cdt, cdn) { new_exchange_rate: function(frm, cdt, cdn) {
var row = frappe.get_doc(cdt, cdn); var row = frappe.get_doc(cdt, cdn);
row.new_balance_in_base_currency = flt( row.new_balance_in_base_currency = flt(row.new_exchange_rate * flt(row.balance_in_account_currency),
row.new_exchange_rate * flt(row.balance_in_account_currency), precision("new_balance_in_base_currency", row));
precision("new_balance_in_base_currency", row)
);
row.gain_loss = row.new_balance_in_base_currency - flt(row.balance_in_base_currency); row.gain_loss = row.new_balance_in_base_currency - flt(row.balance_in_base_currency);
refresh_field("accounts"); refresh_field("accounts");
frm.events.get_total_gain_loss(frm); frm.events.get_total_gain_loss(frm);
}, },
account: function (frm, cdt, cdn) { account: function(frm, cdt, cdn) {
var row = locals[cdt][cdn]; var row = locals[cdt][cdn];
if (row.account) { if (row.account) {
get_account_details(frm, cdt, cdn); get_account_details(frm, cdt, cdn);
} }
}, },
party: function (frm, cdt, cdn) { party: function(frm, cdt, cdn) {
var row = locals[cdt][cdn]; var row = locals[cdt][cdn];
if (row.party && row.account) { if (row.party && row.account) {
get_account_details(frm, cdt, cdn); get_account_details(frm, cdt, cdn);
} }
}, },
accounts_remove: function (frm) { accounts_remove: function(frm) {
frm.events.get_total_gain_loss(frm); frm.events.get_total_gain_loss(frm);
}, }
}); });
var get_account_details = function (frm, cdt, cdn) { var get_account_details = function(frm, cdt, cdn) {
var row = frappe.get_doc(cdt, cdn); var row = frappe.get_doc(cdt, cdn);
if (!frm.doc.company || !frm.doc.posting_date) { if(!frm.doc.company || !frm.doc.posting_date) {
frappe.throw(__("Please select Company and Posting Date to getting entries")); frappe.throw(__("Please select Company and Posting Date to getting entries"));
} }
frappe.call({ frappe.call({
method: "erpnext.accounts.doctype.exchange_rate_revaluation.exchange_rate_revaluation.get_account_details", method: "erpnext.accounts.doctype.exchange_rate_revaluation.exchange_rate_revaluation.get_account_details",
args: { args:{
account: row.account, account: row.account,
company: frm.doc.company, company: frm.doc.company,
posting_date: frm.doc.posting_date, posting_date: frm.doc.posting_date,
party_type: row.party_type, party_type: row.party_type,
party: row.party, party: row.party,
rounding_loss_allowance: frm.doc.rounding_loss_allowance, rounding_loss_allowance: frm.doc.rounding_loss_allowance
}, },
callback: function (r) { callback: function(r){
$.extend(row, r.message); $.extend(row, r.message);
refresh_field("accounts"); refresh_field("accounts");
frm.events.get_total_gain_loss(frm); frm.events.get_total_gain_loss(frm);
}, }
}); });
}; };

View File

@@ -192,7 +192,7 @@ class ExchangeRateRevaluation(Document):
# round off balance based on currency precision # round off balance based on currency precision
# and consider debit-credit difference allowance # and consider debit-credit difference allowance
currency_precision = get_currency_precision() currency_precision = get_currency_precision()
rounding_loss_allowance = float(rounding_loss_allowance) rounding_loss_allowance = float(rounding_loss_allowance) or 0.05
for acc in account_details: for acc in account_details:
acc.balance_in_account_currency = flt(acc.balance_in_account_currency, currency_precision) acc.balance_in_account_currency = flt(acc.balance_in_account_currency, currency_precision)
if abs(acc.balance_in_account_currency) <= rounding_loss_allowance: if abs(acc.balance_in_account_currency) <= rounding_loss_allowance:
@@ -246,6 +246,7 @@ class ExchangeRateRevaluation(Document):
# Handle Accounts with '0' balance in Account/Base Currency # Handle Accounts with '0' balance in Account/Base Currency
for d in [x for x in account_details if x.zero_balance]: for d in [x for x in account_details if x.zero_balance]:
if d.balance != 0: if d.balance != 0:
current_exchange_rate = new_exchange_rate = 0 current_exchange_rate = new_exchange_rate = 0
@@ -258,8 +259,7 @@ class ExchangeRateRevaluation(Document):
new_balance_in_account_currency = 0 new_balance_in_account_currency = 0
current_exchange_rate = ( current_exchange_rate = (
calculate_exchange_rate_using_last_gle(company, d.account, d.party_type, d.party) calculate_exchange_rate_using_last_gle(company, d.account, d.party_type, d.party) or 0.0
or 0.0
) )
gain_loss = new_balance_in_account_currency - ( gain_loss = new_balance_in_account_currency - (
@@ -313,7 +313,9 @@ class ExchangeRateRevaluation(Document):
revaluation_jv = self.make_jv_for_revaluation() revaluation_jv = self.make_jv_for_revaluation()
if revaluation_jv: if revaluation_jv:
frappe.msgprint(f"Revaluation Journal: {get_link_to_form('Journal Entry', revaluation_jv.name)}") frappe.msgprint(
f"Revaluation Journal: {get_link_to_form('Journal Entry', revaluation_jv.name)}"
)
return { return {
"revaluation_jv": revaluation_jv.name if revaluation_jv else None, "revaluation_jv": revaluation_jv.name if revaluation_jv else None,
@@ -370,8 +372,7 @@ class ExchangeRateRevaluation(Document):
journal_account.update( journal_account.update(
{ {
dr_or_cr: flt( dr_or_cr: flt(
abs(d.get("balance_in_account_currency")), abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")
d.precision("balance_in_account_currency"),
), ),
reverse_dr_or_cr: 0, reverse_dr_or_cr: 0,
"debit": 0, "debit": 0,
@@ -497,9 +498,7 @@ class ExchangeRateRevaluation(Document):
abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency") abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")
), ),
"cost_center": erpnext.get_default_cost_center(self.company), "cost_center": erpnext.get_default_cost_center(self.company),
"exchange_rate": flt( "exchange_rate": flt(d.get("current_exchange_rate"), d.precision("current_exchange_rate")),
d.get("current_exchange_rate"), d.precision("current_exchange_rate")
),
"reference_type": "Exchange Rate Revaluation", "reference_type": "Exchange Rate Revaluation",
"reference_name": self.name, "reference_name": self.name,
} }
@@ -577,7 +576,7 @@ def calculate_exchange_rate_using_last_gle(company, account, party_type, party):
@frappe.whitelist() @frappe.whitelist()
def get_account_details( def get_account_details(
company, posting_date, account, party_type=None, party=None, rounding_loss_allowance: float | None = None company, posting_date, account, party_type=None, party=None, rounding_loss_allowance: float = None
): ):
if not (company and posting_date): if not (company and posting_date):
frappe.throw(_("Company and Posting Date is mandatory")) frappe.throw(_("Company and Posting Date is mandatory"))
@@ -590,7 +589,7 @@ def get_account_details(
frappe.throw(_("Party Type and Party is mandatory for {0} account").format(account_type)) frappe.throw(_("Party Type and Party is mandatory for {0} account").format(account_type))
account_details = {} account_details = {}
erpnext.get_company_currency(company) company_currency = erpnext.get_company_currency(company)
account_details = { account_details = {
"account_currency": account_currency, "account_currency": account_currency,
@@ -604,22 +603,24 @@ def get_account_details(
rounding_loss_allowance=rounding_loss_allowance, rounding_loss_allowance=rounding_loss_allowance,
) )
if account_balance and (account_balance[0].balance or account_balance[0].balance_in_account_currency): if account_balance and (
if account_with_new_balance := ExchangeRateRevaluation.calculate_new_account_balance( account_balance[0].balance or account_balance[0].balance_in_account_currency
):
account_with_new_balance = ExchangeRateRevaluation.calculate_new_account_balance(
company, posting_date, account_balance company, posting_date, account_balance
): )
row = account_with_new_balance[0] row = account_with_new_balance[0]
account_details.update( account_details.update(
{ {
"balance_in_base_currency": row["balance_in_base_currency"], "balance_in_base_currency": row["balance_in_base_currency"],
"balance_in_account_currency": row["balance_in_account_currency"], "balance_in_account_currency": row["balance_in_account_currency"],
"current_exchange_rate": row["current_exchange_rate"], "current_exchange_rate": row["current_exchange_rate"],
"new_exchange_rate": row["new_exchange_rate"], "new_exchange_rate": row["new_exchange_rate"],
"new_balance_in_base_currency": row["new_balance_in_base_currency"], "new_balance_in_base_currency": row["new_balance_in_base_currency"],
"new_balance_in_account_currency": row["new_balance_in_account_currency"], "new_balance_in_account_currency": row["new_balance_in_account_currency"],
"zero_balance": row["zero_balance"], "zero_balance": row["zero_balance"],
"gain_loss": row["gain_loss"], "gain_loss": row["gain_loss"],
} }
) )
return account_details return account_details

View File

@@ -1,14 +1,21 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt # See license.txt
import unittest
import frappe import frappe
from frappe import qb
from frappe.tests.utils import FrappeTestCase, change_settings from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, flt, today from frappe.utils import add_days, flt, today
from erpnext import get_default_cost_center
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.party import get_party_account
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
from erpnext.stock.doctype.item.test_item import create_item
class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase): class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase):
@@ -66,7 +73,9 @@ class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase):
err.extend("accounts", accounts) err.extend("accounts", accounts)
row = err.accounts[0] row = err.accounts[0]
row.new_exchange_rate = 85 row.new_exchange_rate = 85
row.new_balance_in_base_currency = flt(row.new_exchange_rate * flt(row.balance_in_account_currency)) row.new_balance_in_base_currency = flt(
row.new_exchange_rate * flt(row.balance_in_account_currency)
)
row.gain_loss = row.new_balance_in_base_currency - flt(row.balance_in_base_currency) row.gain_loss = row.new_balance_in_base_currency - flt(row.balance_in_base_currency)
err.set_total_gain_loss() err.set_total_gain_loss()
err = err.save().submit() err = err.save().submit()
@@ -118,9 +127,9 @@ class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase):
pe.save().submit() pe.save().submit()
# Cancel the auto created gain/loss JE to simulate balance only in base currency # Cancel the auto created gain/loss JE to simulate balance only in base currency
je = frappe.db.get_all("Journal Entry Account", filters={"reference_name": si.name}, pluck="parent")[ je = frappe.db.get_all(
0 "Journal Entry Account", filters={"reference_name": si.name}, pluck="parent"
] )[0]
frappe.get_doc("Journal Entry", je).cancel() frappe.get_doc("Journal Entry", je).cancel()
err = frappe.new_doc("Exchange Rate Revaluation") err = frappe.new_doc("Exchange Rate Revaluation")
@@ -226,9 +235,9 @@ class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase):
self.assertEqual(flt(acc.debit, precision), 0.0) self.assertEqual(flt(acc.debit, precision), 0.0)
self.assertEqual(flt(acc.credit, precision), 0.0) self.assertEqual(flt(acc.credit, precision), 0.0)
row = next(x for x in je.accounts if x.account == self.debtors_usd) row = [x for x in je.accounts if x.account == self.debtors_usd][0]
self.assertEqual(flt(row.credit_in_account_currency, precision), 5.0) # in USD self.assertEqual(flt(row.credit_in_account_currency, precision), 5.0) # in USD
row = next(x for x in je.accounts if x.account != self.debtors_usd) row = [x for x in je.accounts if x.account != self.debtors_usd][0]
self.assertEqual(flt(row.debit_in_account_currency, precision), 421.06) # in INR self.assertEqual(flt(row.debit_in_account_currency, precision), 421.06) # in INR
# total_debit and total_credit will be 0.0, as JV is posting only to account currency fields # total_debit and total_credit will be 0.0, as JV is posting only to account currency fields
@@ -285,5 +294,5 @@ class TestExchangeRateRevaluation(AccountsTestMixin, FrappeTestCase):
"new_balance_in_account_currency": 100.0, "new_balance_in_account_currency": 100.0,
} }
for key, _val in expected_data.items(): for key, val in expected_data.items():
self.assertEqual(expected_data.get(key), account_details.get(key)) self.assertEqual(expected_data.get(key), account_details.get(key))

View File

@@ -1,6 +1,8 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors // Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt // For license information, please see license.txt
frappe.ui.form.on("Finance Book", { frappe.ui.form.on('Finance Book', {
refresh: function (frm) {}, refresh: function(frm) {
}
}); });

View File

@@ -1,21 +1,17 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt // License: GNU General Public License v3. See license.txt
frappe.ui.form.on("Fiscal Year", { frappe.ui.form.on('Fiscal Year', {
onload: function (frm) { onload: function(frm) {
if (frm.doc.__islocal) { if(frm.doc.__islocal) {
frm.set_value( frm.set_value("year_start_date",
"year_start_date", frappe.datetime.add_days(frappe.defaults.get_default("year_end_date"), 1));
frappe.datetime.add_days(frappe.defaults.get_default("year_end_date"), 1)
);
} }
}, },
year_start_date: function (frm) { year_start_date: function(frm) {
if (!frm.doc.is_short_year) { if (!frm.doc.is_short_year) {
let year_end_date = frappe.datetime.add_days( let year_end_date =
frappe.datetime.add_months(frm.doc.year_start_date, 12), frappe.datetime.add_days(frappe.datetime.add_months(frm.doc.year_start_date, 12), -1);
-1
);
frm.set_value("year_end_date", year_end_date); frm.set_value("year_end_date", year_end_date);
} }
}, },

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