mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-25 09:38:31 +00:00
Merge branch 'develop' into so-mr-po
This commit is contained in:
@@ -52,7 +52,7 @@ class ERPNextAddress(Address):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_shipping_address(company, address=None):
|
||||
def get_shipping_address(company: str, address: str | None = None):
|
||||
filters = [
|
||||
["Dynamic Link", "link_doctype", "=", "Company"],
|
||||
["Dynamic Link", "link_name", "=", company],
|
||||
|
||||
@@ -13,15 +13,15 @@ from frappe.utils.nestedset import get_descendants_of
|
||||
@frappe.whitelist()
|
||||
@cache_source
|
||||
def get(
|
||||
chart_name=None,
|
||||
chart=None,
|
||||
no_cache=None,
|
||||
filters=None,
|
||||
from_date=None,
|
||||
to_date=None,
|
||||
timespan=None,
|
||||
time_interval=None,
|
||||
heatmap_year=None,
|
||||
chart_name: str | None = None,
|
||||
chart: str | dict | None = None,
|
||||
no_cache: bool | None = None,
|
||||
filters: str | dict | None = None,
|
||||
from_date: str | None = None,
|
||||
to_date: str | None = None,
|
||||
timespan: str | None = None,
|
||||
time_interval: str | None = None,
|
||||
heatmap_year: str | None = None,
|
||||
):
|
||||
if chart_name:
|
||||
chart = frappe.get_doc("Dashboard Chart", chart_name)
|
||||
|
||||
@@ -471,7 +471,7 @@ class Account(NestedSet):
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_parent_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
def get_parent_account(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict):
|
||||
return frappe.db.sql(
|
||||
"""select name from tabAccount
|
||||
where is_group = 1 and docstatus != 2 and company = {}
|
||||
@@ -515,7 +515,9 @@ def get_account_autoname(account_number, account_name, company):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_account_number(name, account_name, account_number=None, from_descendant=False):
|
||||
def update_account_number(
|
||||
name: str, account_name: str, account_number: str | None = None, from_descendant: bool = False
|
||||
):
|
||||
_ensure_idle_system()
|
||||
account = frappe.get_cached_doc("Account", name)
|
||||
if not account:
|
||||
@@ -577,7 +579,7 @@ def update_account_number(name, account_name, account_number=None, from_descenda
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def merge_account(old, new):
|
||||
def merge_account(old: str, new: str):
|
||||
_ensure_idle_system()
|
||||
# Validate properties before merging
|
||||
new_account = frappe.get_cached_doc("Account", new)
|
||||
@@ -614,7 +616,7 @@ def merge_account(old, new):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_root_company(company):
|
||||
def get_root_company(company: str):
|
||||
# return the topmost company in the hierarchy
|
||||
ancestors = get_ancestors_of("Company", company, "lft asc")
|
||||
return [ancestors[0]] if ancestors else []
|
||||
|
||||
@@ -99,7 +99,7 @@ def identify_is_group(child):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_chart(chart_template, existing_company=None):
|
||||
def get_chart(chart_template: str | None, existing_company: str | None = None):
|
||||
chart = {}
|
||||
if existing_company:
|
||||
return get_account_tree_from_existing_company(existing_company)
|
||||
@@ -132,7 +132,7 @@ def get_chart(chart_template, existing_company=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_charts_for_country(country, with_standard=False):
|
||||
def get_charts_for_country(country: str, with_standard: bool = False):
|
||||
charts = []
|
||||
|
||||
def _get_chart_name(content):
|
||||
@@ -225,7 +225,7 @@ def build_account_tree(tree, parent, all_accounts):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def validate_bank_account(coa, bank_account):
|
||||
def validate_bank_account(coa: str, bank_account: str):
|
||||
accounts = []
|
||||
chart = get_chart(coa)
|
||||
|
||||
@@ -244,7 +244,9 @@ def validate_bank_account(coa, bank_account):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def build_tree_from_json(chart_template, chart_data=None, from_coa_importer=False):
|
||||
def build_tree_from_json(
|
||||
chart_template: str, chart_data: dict | None = None, from_coa_importer: bool = False
|
||||
):
|
||||
"""get chart template from its folder and parse the json to be rendered as tree"""
|
||||
chart = chart_data or get_chart(chart_template)
|
||||
|
||||
|
||||
@@ -103,10 +103,6 @@ class AccountingDimension(Document):
|
||||
if not self.fieldname:
|
||||
self.fieldname = scrub(self.label)
|
||||
|
||||
def on_update(self):
|
||||
frappe.flags.accounting_dimensions = None
|
||||
frappe.flags.accounting_dimensions_details = None
|
||||
|
||||
|
||||
def make_dimension_in_accounting_doctypes(doc, doclist=None):
|
||||
if not doclist:
|
||||
@@ -210,7 +206,7 @@ def delete_accounting_dimension(doc):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def disable_dimension(doc):
|
||||
def disable_dimension(doc: str):
|
||||
if frappe.in_test:
|
||||
toggle_disabling(doc=doc)
|
||||
else:
|
||||
@@ -241,34 +237,26 @@ def get_doctypes_with_dimensions():
|
||||
return frappe.get_hooks("accounting_dimension_doctypes")
|
||||
|
||||
|
||||
def get_accounting_dimensions(as_list=True, filters=None):
|
||||
if not filters:
|
||||
filters = {"disabled": 0}
|
||||
|
||||
if frappe.flags.accounting_dimensions is None:
|
||||
frappe.flags.accounting_dimensions = frappe.get_all(
|
||||
"Accounting Dimension",
|
||||
fields=["label", "fieldname", "disabled", "document_type"],
|
||||
filters=filters,
|
||||
)
|
||||
def get_accounting_dimensions(as_list=True):
|
||||
accounting_dimensions = frappe.get_all(
|
||||
"Accounting Dimension",
|
||||
fields=["label", "fieldname", "disabled", "document_type"],
|
||||
filters={"disabled": 0},
|
||||
)
|
||||
|
||||
if as_list:
|
||||
return [d.fieldname for d in frappe.flags.accounting_dimensions]
|
||||
return [d.fieldname for d in accounting_dimensions]
|
||||
else:
|
||||
return frappe.flags.accounting_dimensions
|
||||
return accounting_dimensions
|
||||
|
||||
|
||||
def get_checks_for_pl_and_bs_accounts():
|
||||
if frappe.flags.accounting_dimensions_details is None:
|
||||
# nosemgrep
|
||||
frappe.flags.accounting_dimensions_details = frappe.db.sql(
|
||||
"""SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
|
||||
return frappe.db.sql(
|
||||
"""SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
|
||||
FROM `tabAccounting Dimension`p ,`tabAccounting Dimension Detail` c
|
||||
WHERE p.name = c.parent AND p.disabled = 0""",
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
return frappe.flags.accounting_dimensions_details
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
|
||||
def get_dimension_with_children(doctype, dimensions):
|
||||
@@ -286,7 +274,7 @@ def get_dimension_with_children(doctype, dimensions):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_dimensions(with_cost_center_and_project=False):
|
||||
def get_dimensions(with_cost_center_and_project: str | bool = False):
|
||||
c = frappe.qb.DocType("Accounting Dimension Detail")
|
||||
p = frappe.qb.DocType("Accounting Dimension")
|
||||
dimension_filters = (
|
||||
|
||||
@@ -69,37 +69,34 @@ class AccountingDimensionFilter(Document):
|
||||
|
||||
|
||||
def get_dimension_filter_map():
|
||||
if not frappe.flags.get("dimension_filter_map"):
|
||||
filters = frappe.db.sql(
|
||||
"""
|
||||
SELECT
|
||||
a.applicable_on_account, d.dimension_value, p.accounting_dimension,
|
||||
p.allow_or_restrict, p.fieldname, a.is_mandatory
|
||||
FROM
|
||||
`tabApplicable On Account` a,
|
||||
`tabAccounting Dimension Filter` p
|
||||
LEFT JOIN `tabAllowed Dimension` d ON d.parent = p.name
|
||||
WHERE
|
||||
p.name = a.parent
|
||||
AND p.disabled = 0
|
||||
""",
|
||||
as_dict=1,
|
||||
filters = frappe.db.sql(
|
||||
"""
|
||||
SELECT
|
||||
a.applicable_on_account, d.dimension_value, p.accounting_dimension,
|
||||
p.allow_or_restrict, p.fieldname, a.is_mandatory
|
||||
FROM
|
||||
`tabApplicable On Account` a,
|
||||
`tabAccounting Dimension Filter` p
|
||||
LEFT JOIN `tabAllowed Dimension` d ON d.parent = p.name
|
||||
WHERE
|
||||
p.name = a.parent
|
||||
AND p.disabled = 0
|
||||
""",
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
dimension_filter_map = {}
|
||||
|
||||
for f in filters:
|
||||
build_map(
|
||||
dimension_filter_map,
|
||||
f.fieldname,
|
||||
f.applicable_on_account,
|
||||
f.dimension_value,
|
||||
f.allow_or_restrict,
|
||||
f.is_mandatory,
|
||||
)
|
||||
|
||||
dimension_filter_map = {}
|
||||
|
||||
for f in filters:
|
||||
build_map(
|
||||
dimension_filter_map,
|
||||
f.fieldname,
|
||||
f.applicable_on_account,
|
||||
f.dimension_value,
|
||||
f.allow_or_restrict,
|
||||
f.is_mandatory,
|
||||
)
|
||||
frappe.flags.dimension_filter_map = dimension_filter_map
|
||||
|
||||
return frappe.flags.dimension_filter_map
|
||||
return dimension_filter_map
|
||||
|
||||
|
||||
def build_map(map_object, dimension, account, filter_value, allow_or_restrict, is_mandatory):
|
||||
|
||||
@@ -115,7 +115,7 @@ def get_default_company_bank_account(company, party_type, party):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_bank_account_details(bank_account):
|
||||
def get_bank_account_details(bank_account: str):
|
||||
return frappe.get_cached_value(
|
||||
"Bank Account", bank_account, ["account", "bank", "bank_account_no"], as_dict=1
|
||||
)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
import json
|
||||
from datetime import date
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
@@ -47,7 +47,9 @@ class BankReconciliationTool(Document):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_bank_transactions(bank_account, from_date=None, to_date=None):
|
||||
def get_bank_transactions(
|
||||
bank_account: str, from_date: str | date | None = None, to_date: str | date | None = None
|
||||
):
|
||||
# returns bank transactions for a bank account
|
||||
filters = []
|
||||
filters.append(["bank_account", "=", bank_account])
|
||||
@@ -80,7 +82,7 @@ def get_bank_transactions(bank_account, from_date=None, to_date=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_account_balance(bank_account, till_date, company):
|
||||
def get_account_balance(bank_account: str, till_date: str | date, company: str):
|
||||
# returns account balance till the specified date
|
||||
account = frappe.db.get_value("Bank Account", bank_account, "account")
|
||||
filters = frappe._dict(
|
||||
@@ -106,7 +108,9 @@ def get_account_balance(bank_account, till_date, company):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_bank_transaction(bank_transaction_name, reference_number, party_type=None, party=None):
|
||||
def update_bank_transaction(
|
||||
bank_transaction_name: str, reference_number: str, party_type: str | None = None, party: str | None = None
|
||||
):
|
||||
# updates bank transaction based on the new parameters provided by the user from Vouchers
|
||||
bank_transaction = frappe.get_doc("Bank Transaction", bank_transaction_name)
|
||||
bank_transaction.reference_number = reference_number
|
||||
@@ -135,16 +139,16 @@ def update_bank_transaction(bank_transaction_name, reference_number, party_type=
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_journal_entry_bts(
|
||||
bank_transaction_name,
|
||||
reference_number=None,
|
||||
reference_date=None,
|
||||
posting_date=None,
|
||||
entry_type=None,
|
||||
second_account=None,
|
||||
mode_of_payment=None,
|
||||
party_type=None,
|
||||
party=None,
|
||||
allow_edit=None,
|
||||
bank_transaction_name: str,
|
||||
reference_number: str | None = None,
|
||||
reference_date: str | None = None,
|
||||
posting_date: str | date | None = None,
|
||||
entry_type: str | None = None,
|
||||
second_account: str | None = None,
|
||||
mode_of_payment: str | None = None,
|
||||
party_type: str | None = None,
|
||||
party: str | None = None,
|
||||
allow_edit: bool | None = None,
|
||||
):
|
||||
# Create a new journal entry based on the bank transaction
|
||||
bank_transaction = frappe.db.get_values(
|
||||
@@ -294,17 +298,17 @@ def create_journal_entry_bts(
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_payment_entry_bts(
|
||||
bank_transaction_name,
|
||||
reference_number=None,
|
||||
reference_date=None,
|
||||
party_type=None,
|
||||
party=None,
|
||||
posting_date=None,
|
||||
mode_of_payment=None,
|
||||
project=None,
|
||||
cost_center=None,
|
||||
allow_edit=None,
|
||||
company_bank_account=None,
|
||||
bank_transaction_name: str,
|
||||
reference_number: str | None = None,
|
||||
reference_date: str | None = None,
|
||||
party_type: str | None = None,
|
||||
party: str | None = None,
|
||||
posting_date: str | None = None,
|
||||
mode_of_payment: str | None = None,
|
||||
project: str | None = None,
|
||||
cost_center: str | None = None,
|
||||
allow_edit: bool | None = None,
|
||||
company_bank_account: str | None = None,
|
||||
):
|
||||
# Create a new payment entry based on the bank transaction
|
||||
bank_transaction = frappe.db.get_values(
|
||||
@@ -371,12 +375,12 @@ def create_payment_entry_bts(
|
||||
|
||||
@frappe.whitelist()
|
||||
def auto_reconcile_vouchers(
|
||||
bank_account,
|
||||
from_date=None,
|
||||
to_date=None,
|
||||
filter_by_reference_date=None,
|
||||
from_reference_date=None,
|
||||
to_reference_date=None,
|
||||
bank_account: str,
|
||||
from_date: str | date | None = None,
|
||||
to_date: str | date | None = None,
|
||||
filter_by_reference_date: bool | None = None,
|
||||
from_reference_date: bool | None = None,
|
||||
to_reference_date: str | None = None,
|
||||
):
|
||||
bank_transactions = get_bank_transactions(bank_account)
|
||||
|
||||
@@ -471,7 +475,7 @@ def get_auto_reconcile_message(partially_reconciled, reconciled):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def reconcile_vouchers(bank_transaction_name, vouchers):
|
||||
def reconcile_vouchers(bank_transaction_name: str, vouchers: str):
|
||||
# updated clear date of all the vouchers based on the bank transaction
|
||||
vouchers = json.loads(vouchers)
|
||||
transaction = frappe.get_doc("Bank Transaction", bank_transaction_name)
|
||||
@@ -487,13 +491,13 @@ def reconcile_vouchers(bank_transaction_name, vouchers):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_linked_payments(
|
||||
bank_transaction_name,
|
||||
document_types=None,
|
||||
from_date=None,
|
||||
to_date=None,
|
||||
filter_by_reference_date=None,
|
||||
from_reference_date=None,
|
||||
to_reference_date=None,
|
||||
bank_transaction_name: str,
|
||||
document_types: str | list[str] | None = None,
|
||||
from_date: str | date | None = None,
|
||||
to_date: str | date | None = None,
|
||||
filter_by_reference_date: bool | None = None,
|
||||
from_reference_date: bool | None = None,
|
||||
to_reference_date: str | None = None,
|
||||
):
|
||||
# get all matching payments for a bank transaction
|
||||
transaction = frappe.get_doc("Bank Transaction", bank_transaction_name)
|
||||
|
||||
@@ -143,7 +143,7 @@ def preprocess_mt940_content(content: str) -> str:
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def convert_mt940_to_csv(data_import, mt940_file_path):
|
||||
def convert_mt940_to_csv(data_import: str, mt940_file_path: str):
|
||||
doc = frappe.get_doc("Bank Statement Import", data_import)
|
||||
|
||||
_file_doc, content = get_file(mt940_file_path)
|
||||
@@ -208,26 +208,28 @@ def convert_mt940_to_csv(data_import, mt940_file_path):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_preview_from_template(data_import, import_file=None, google_sheets_url=None):
|
||||
def get_preview_from_template(
|
||||
data_import: str, import_file: str | None = None, google_sheets_url: str | None = None
|
||||
):
|
||||
return frappe.get_doc("Bank Statement Import", data_import).get_preview_from_template(
|
||||
import_file, google_sheets_url
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def form_start_import(data_import):
|
||||
def form_start_import(data_import: str):
|
||||
job_id = frappe.get_doc("Bank Statement Import", data_import).start_import()
|
||||
return job_id is not None
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def download_errored_template(data_import_name):
|
||||
def download_errored_template(data_import_name: str):
|
||||
data_import = frappe.get_doc("Bank Statement Import", data_import_name)
|
||||
data_import.export_errored_rows()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def download_import_log(data_import_name):
|
||||
def download_import_log(data_import_name: str):
|
||||
return frappe.get_doc("Bank Statement Import", data_import_name).download_import_log()
|
||||
|
||||
|
||||
@@ -363,7 +365,7 @@ def write_xlsx(data, sheet_name, wb=None, column_widths=None, file_path=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_import_status(docname):
|
||||
def get_import_status(docname: str):
|
||||
import_status = {}
|
||||
|
||||
data_import = frappe.get_doc("Bank Statement Import", docname)
|
||||
|
||||
@@ -375,11 +375,12 @@ def get_clearance_details(transaction, payment_entry, bt_allocations, gl_entries
|
||||
("unallocated_amount", "bank_account"),
|
||||
as_dict=True,
|
||||
)
|
||||
bt_bank_account = frappe.db.get_value("Bank Account", bt.bank_account, "account")
|
||||
|
||||
if bt.bank_account != gl_bank_account:
|
||||
if bt_bank_account != gl_bank_account:
|
||||
frappe.throw(
|
||||
_("Bank Account {} in Bank Transaction {} is not matching with Bank Account {}").format(
|
||||
bt.bank_account, payment_entry.payment_entry, gl_bank_account
|
||||
bt_bank_account, payment_entry.payment_entry, gl_bank_account
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ def upload_bank_statement():
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_bank_entries(columns, data, bank_account):
|
||||
def create_bank_entries(columns: str, data: str, bank_account: str):
|
||||
header_map = get_header_mapping(columns, bank_account)
|
||||
|
||||
success = 0
|
||||
|
||||
@@ -845,7 +845,7 @@ def get_fiscal_year_date_range(from_fiscal_year, to_fiscal_year):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def revise_budget(budget_name):
|
||||
def revise_budget(budget_name: str):
|
||||
old_budget = frappe.get_doc("Budget", budget_name)
|
||||
|
||||
if old_budget.docstatus == 1:
|
||||
|
||||
@@ -57,7 +57,7 @@ def validate_columns(data):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def validate_company(company):
|
||||
def validate_company(company: str):
|
||||
parent_company, allow_account_creation_against_child_company = frappe.get_cached_value(
|
||||
"Company", company, ["parent_company", "allow_account_creation_against_child_company"]
|
||||
)
|
||||
@@ -74,7 +74,7 @@ def validate_company(company):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def import_coa(file_name, company):
|
||||
def import_coa(file_name: str, company: str):
|
||||
# delete existing data for accounts
|
||||
unset_existing_data(company)
|
||||
|
||||
@@ -159,7 +159,9 @@ def generate_data_from_excel(file_doc, extension, as_dict=False):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_coa(doctype, parent, is_root=False, file_name=None, for_validate=0):
|
||||
def get_coa(
|
||||
doctype: str, parent: str, is_root: bool = False, file_name: str | None = None, for_validate: int = 0
|
||||
):
|
||||
"""called by tree view (to fetch node's children)"""
|
||||
|
||||
file_doc, extension = get_file(file_name)
|
||||
@@ -307,7 +309,7 @@ def build_response_as_excel(writer):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def download_template(file_type, template_type, company):
|
||||
def download_template(file_type: str, template_type: str, company: str):
|
||||
writer = get_template(template_type, company)
|
||||
|
||||
if file_type == "CSV":
|
||||
@@ -361,7 +363,7 @@ def get_sample_template(writer, company):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def validate_accounts(file_doc, extension):
|
||||
def validate_accounts(file_doc: Document, extension: str):
|
||||
if extension == "csv":
|
||||
accounts = generate_data_from_csv(file_doc, as_dict=True)
|
||||
else:
|
||||
|
||||
@@ -47,7 +47,7 @@ class ChequePrintTemplate(Document):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_or_update_cheque_print_format(template_name):
|
||||
def create_or_update_cheque_print_format(template_name: str):
|
||||
if not frappe.db.exists("Print Format", template_name):
|
||||
cheque_print = frappe.new_doc("Print Format")
|
||||
cheque_print.update(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from datetime import date
|
||||
|
||||
import frappe
|
||||
from frappe import _, qb
|
||||
@@ -614,7 +615,12 @@ def calculate_exchange_rate_using_last_gle(company, account, party_type, party):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_account_details(
|
||||
company, posting_date, account, party_type=None, party=None, rounding_loss_allowance: float | None = None
|
||||
company: str,
|
||||
posting_date: str | date,
|
||||
account: str,
|
||||
party_type: str | None = None,
|
||||
party: str | None = None,
|
||||
rounding_loss_allowance: float = 0.0,
|
||||
):
|
||||
if not (company and posting_date):
|
||||
frappe.throw(_("Company and Posting Date is mandatory"))
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import frappe
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from frappe import _
|
||||
from frappe import _, cint
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import add_days, add_years, cstr, getdate
|
||||
|
||||
@@ -33,23 +33,11 @@ class FiscalYear(Document):
|
||||
self.validate_dates()
|
||||
self.validate_overlap()
|
||||
|
||||
if not self.is_new():
|
||||
year_start_end_dates = frappe.db.sql(
|
||||
"""select year_start_date, year_end_date
|
||||
from `tabFiscal Year` where name=%s""",
|
||||
(self.name),
|
||||
)
|
||||
def on_update(self):
|
||||
frappe.cache().delete_key("fiscal_years")
|
||||
|
||||
if year_start_end_dates:
|
||||
if (
|
||||
getdate(self.year_start_date) != year_start_end_dates[0][0]
|
||||
or getdate(self.year_end_date) != year_start_end_dates[0][1]
|
||||
):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Cannot change Fiscal Year Start Date and Fiscal Year End Date once the Fiscal Year is saved."
|
||||
)
|
||||
)
|
||||
def on_trash(self):
|
||||
frappe.cache().delete_key("fiscal_years")
|
||||
|
||||
def validate_dates(self):
|
||||
self.validate_from_to_dates("year_start_date", "year_end_date")
|
||||
@@ -66,28 +54,20 @@ class FiscalYear(Document):
|
||||
frappe.exceptions.InvalidDates,
|
||||
)
|
||||
|
||||
def on_update(self):
|
||||
check_duplicate_fiscal_year(self)
|
||||
frappe.cache().delete_value("fiscal_years")
|
||||
|
||||
def on_trash(self):
|
||||
frappe.cache().delete_value("fiscal_years")
|
||||
|
||||
def validate_overlap(self):
|
||||
existing_fiscal_years = frappe.db.sql(
|
||||
"""select name from `tabFiscal Year`
|
||||
where (
|
||||
(%(year_start_date)s between year_start_date and year_end_date)
|
||||
or (%(year_end_date)s between year_start_date and year_end_date)
|
||||
or (year_start_date between %(year_start_date)s and %(year_end_date)s)
|
||||
or (year_end_date between %(year_start_date)s and %(year_end_date)s)
|
||||
) and name!=%(name)s""",
|
||||
{
|
||||
"year_start_date": self.year_start_date,
|
||||
"year_end_date": self.year_end_date,
|
||||
"name": self.name or "No Name",
|
||||
},
|
||||
as_dict=True,
|
||||
fy = frappe.qb.DocType("Fiscal Year")
|
||||
|
||||
name = self.name or self.year
|
||||
|
||||
existing_fiscal_years = (
|
||||
frappe.qb.from_(fy)
|
||||
.select(fy.name)
|
||||
.where(
|
||||
(fy.year_start_date <= self.year_end_date)
|
||||
& (fy.year_end_date >= self.year_start_date)
|
||||
& (fy.name != name)
|
||||
)
|
||||
.run(as_dict=True)
|
||||
)
|
||||
|
||||
if existing_fiscal_years:
|
||||
@@ -110,37 +90,30 @@ class FiscalYear(Document):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Year start date or end date is overlapping with {0}. To avoid please set company"
|
||||
).format(existing.name),
|
||||
).format(frappe.get_desk_link("Fiscal Year", existing.name, open_in_new_tab=True)),
|
||||
frappe.NameError,
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def check_duplicate_fiscal_year(doc):
|
||||
year_start_end_dates = frappe.db.sql(
|
||||
"""select name, year_start_date, year_end_date from `tabFiscal Year` where name!=%s""",
|
||||
(doc.name),
|
||||
)
|
||||
for fiscal_year, ysd, yed in year_start_end_dates:
|
||||
if (getdate(doc.year_start_date) == ysd and getdate(doc.year_end_date) == yed) and (
|
||||
not frappe.in_test
|
||||
):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Fiscal Year Start Date and Fiscal Year End Date are already set in Fiscal Year {0}"
|
||||
).format(fiscal_year)
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def auto_create_fiscal_year():
|
||||
for d in frappe.db.sql(
|
||||
"""select name from `tabFiscal Year` where year_end_date = date_add(current_date, interval 3 day)"""
|
||||
):
|
||||
fy = frappe.qb.DocType("Fiscal Year")
|
||||
|
||||
# Skipped auto-creating Short Year, as it has very rare use case.
|
||||
# Reference: https://www.irs.gov/businesses/small-businesses-self-employed/tax-years (US)
|
||||
follow_up_date = add_days(getdate(), days=3)
|
||||
fiscal_year = (
|
||||
frappe.qb.from_(fy)
|
||||
.select(fy.name)
|
||||
.where((fy.year_end_date == follow_up_date) & (fy.is_short_year == 0))
|
||||
.run()
|
||||
)
|
||||
|
||||
for d in fiscal_year:
|
||||
try:
|
||||
current_fy = frappe.get_doc("Fiscal Year", d[0])
|
||||
|
||||
new_fy = frappe.copy_doc(current_fy, ignore_no_copy=False)
|
||||
new_fy = frappe.new_doc("Fiscal Year")
|
||||
new_fy.disabled = cint(current_fy.disabled)
|
||||
|
||||
new_fy.year_start_date = add_days(current_fy.year_end_date, 1)
|
||||
new_fy.year_end_date = add_years(current_fy.year_end_date, 1)
|
||||
@@ -148,6 +121,10 @@ def auto_create_fiscal_year():
|
||||
start_year = cstr(new_fy.year_start_date.year)
|
||||
end_year = cstr(new_fy.year_end_date.year)
|
||||
new_fy.year = start_year if start_year == end_year else (start_year + "-" + end_year)
|
||||
|
||||
for row in current_fy.companies:
|
||||
new_fy.append("companies", {"company": row.company})
|
||||
|
||||
new_fy.auto_created = 1
|
||||
|
||||
new_fy.insert(ignore_permissions=True)
|
||||
|
||||
@@ -15,20 +15,22 @@
|
||||
"ignore_user_permissions": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"options": "Company"
|
||||
"options": "Company",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:09:44.659251",
|
||||
"modified": "2026-02-20 23:02:26.193606",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Fiscal Year Company",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ class FiscalYearCompany(Document):
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
company: DF.Link | None
|
||||
company: DF.Link
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
|
||||
@@ -317,7 +317,7 @@ class InvoiceDiscounting(AccountsController):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_invoices(filters):
|
||||
def get_invoices(filters: str):
|
||||
filters = frappe._dict(json.loads(filters))
|
||||
cond = []
|
||||
if filters.customer:
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
|
||||
import json
|
||||
from datetime import date
|
||||
|
||||
import frappe
|
||||
from frappe import _, msgprint, scrub
|
||||
from frappe.core.doctype.submission_queue.submission_queue import queue_submission
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import comma_and, cstr, flt, fmt_money, formatdate, get_link_to_form, nowdate
|
||||
|
||||
import erpnext
|
||||
@@ -1215,7 +1216,7 @@ class JournalEntry(AccountsController):
|
||||
cancel_exchange_gain_loss_journal(frappe._dict(doctype=self.doctype, name=self.name))
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_balance(self, difference_account=None):
|
||||
def get_balance(self, difference_account: str | None = None):
|
||||
if not self.get("accounts"):
|
||||
msgprint(_("'Entries' cannot be empty"), raise_exception=True)
|
||||
else:
|
||||
@@ -1321,7 +1322,12 @@ class JournalEntry(AccountsController):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_default_bank_cash_account(
|
||||
company, account_type=None, mode_of_payment=None, account=None, *, fetch_balance=True
|
||||
company: str,
|
||||
account_type: str | None = None,
|
||||
mode_of_payment: str | None = None,
|
||||
account: str | None = None,
|
||||
*,
|
||||
fetch_balance: bool = True,
|
||||
):
|
||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account
|
||||
|
||||
@@ -1370,7 +1376,12 @@ def get_default_bank_cash_account(
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_payment_entry_against_order(
|
||||
dt, dn, amount=None, debit_in_account_currency=None, journal_entry=False, bank_account=None
|
||||
dt: str,
|
||||
dn: str,
|
||||
amount: float | None = None,
|
||||
debit_in_account_currency: str | float | None = None,
|
||||
journal_entry: bool = False,
|
||||
bank_account: str | None = None,
|
||||
):
|
||||
ref_doc = frappe.get_doc(dt, dn)
|
||||
|
||||
@@ -1415,7 +1426,12 @@ def get_payment_entry_against_order(
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_payment_entry_against_invoice(
|
||||
dt, dn, amount=None, debit_in_account_currency=None, journal_entry=False, bank_account=None
|
||||
dt: str,
|
||||
dn: str,
|
||||
amount: float | None = None,
|
||||
debit_in_account_currency: str | None = None,
|
||||
journal_entry: bool = False,
|
||||
bank_account: str | None = None,
|
||||
):
|
||||
ref_doc = frappe.get_doc(dt, dn)
|
||||
if dt == "Sales Invoice":
|
||||
@@ -1528,7 +1544,7 @@ def get_payment_entry(ref_doc, args):
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_against_jv(doctype, txt, searchfield, start, page_len, filters):
|
||||
def get_against_jv(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict):
|
||||
if not frappe.db.has_column("Journal Entry", searchfield):
|
||||
return []
|
||||
|
||||
@@ -1559,7 +1575,7 @@ def get_against_jv(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_outstanding(args):
|
||||
def get_outstanding(args: str | dict):
|
||||
if not frappe.has_permission("Account"):
|
||||
frappe.msgprint(_("No Permission"), raise_exception=1)
|
||||
|
||||
@@ -1619,7 +1635,7 @@ def get_outstanding(args):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_party_account_and_currency(company, party_type, party):
|
||||
def get_party_account_and_currency(company: str, party_type: str, party: str):
|
||||
if not frappe.has_permission("Account"):
|
||||
frappe.msgprint(_("No Permission"), raise_exception=1)
|
||||
|
||||
@@ -1632,7 +1648,14 @@ def get_party_account_and_currency(company, party_type, party):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_account_details_and_party_type(account, date, company, debit=None, credit=None, exchange_rate=None):
|
||||
def get_account_details_and_party_type(
|
||||
account: str,
|
||||
date: str,
|
||||
company: str,
|
||||
debit: float | str | None = None,
|
||||
credit: float | str | None = None,
|
||||
exchange_rate: float | str | None = None,
|
||||
):
|
||||
"""Returns dict of account details and party type to be set in Journal Entry on selection of account."""
|
||||
if not frappe.has_permission("Account"):
|
||||
frappe.msgprint(_("No Permission"), raise_exception=1)
|
||||
@@ -1681,15 +1704,15 @@ def get_account_details_and_party_type(account, date, company, debit=None, credi
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_exchange_rate(
|
||||
posting_date,
|
||||
account=None,
|
||||
account_currency=None,
|
||||
company=None,
|
||||
reference_type=None,
|
||||
reference_name=None,
|
||||
debit=None,
|
||||
credit=None,
|
||||
exchange_rate=None,
|
||||
posting_date: str | date,
|
||||
account: str | None = None,
|
||||
account_currency: str | None = None,
|
||||
company: str | None = None,
|
||||
reference_type: str | None = None,
|
||||
reference_name: str | None = None,
|
||||
debit: float | str | None = None,
|
||||
credit: float | str | None = None,
|
||||
exchange_rate: str | float | None = None,
|
||||
):
|
||||
# Ensure exchange_rate is always numeric to avoid calculation errors
|
||||
if isinstance(exchange_rate, str):
|
||||
@@ -1726,7 +1749,7 @@ def get_exchange_rate(
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_average_exchange_rate(account):
|
||||
def get_average_exchange_rate(account: str):
|
||||
exchange_rate = 0
|
||||
bank_balance_in_account_currency = get_balance_on(account)
|
||||
if bank_balance_in_account_currency:
|
||||
@@ -1737,7 +1760,7 @@ def get_average_exchange_rate(account):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_inter_company_journal_entry(name, voucher_type, company):
|
||||
def make_inter_company_journal_entry(name: str, voucher_type: str, company: str):
|
||||
journal_entry = frappe.new_doc("Journal Entry")
|
||||
journal_entry.voucher_type = voucher_type
|
||||
journal_entry.company = company
|
||||
@@ -1747,7 +1770,7 @@ def make_inter_company_journal_entry(name, voucher_type, company):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_reverse_journal_entry(source_name, target_doc=None):
|
||||
def make_reverse_journal_entry(source_name: str, target_doc: str | Document | None = None):
|
||||
existing_reverse = frappe.db.exists("Journal Entry", {"reversal_of": source_name, "docstatus": 1})
|
||||
if existing_reverse:
|
||||
frappe.throw(
|
||||
|
||||
@@ -55,7 +55,7 @@ class LedgerMerge(Document):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def form_start_merge(docname):
|
||||
def form_start_merge(docname: str):
|
||||
return frappe.get_doc("Ledger Merge", docname).start_merge()
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from datetime import date
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
@@ -88,13 +89,13 @@ def get_loyalty_details(
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_loyalty_program_details_with_points(
|
||||
customer,
|
||||
loyalty_program=None,
|
||||
expiry_date=None,
|
||||
company=None,
|
||||
silent=False,
|
||||
include_expired_entry=False,
|
||||
current_transaction_amount=0,
|
||||
customer: str,
|
||||
loyalty_program: str | None = None,
|
||||
expiry_date: str | date | None = None,
|
||||
company: str | None = None,
|
||||
silent: bool = False,
|
||||
include_expired_entry: bool = False,
|
||||
current_transaction_amount: int | float = 0,
|
||||
):
|
||||
lp_details = get_loyalty_program_details(customer, loyalty_program, company=company, silent=silent)
|
||||
loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program)
|
||||
@@ -119,12 +120,12 @@ def get_loyalty_program_details_with_points(
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_loyalty_program_details(
|
||||
customer,
|
||||
loyalty_program=None,
|
||||
expiry_date=None,
|
||||
company=None,
|
||||
silent=False,
|
||||
include_expired_entry=False,
|
||||
customer: str,
|
||||
loyalty_program: str | None = None,
|
||||
expiry_date: str | date | None = None,
|
||||
company: str | None = None,
|
||||
silent: bool = False,
|
||||
include_expired_entry: bool = False,
|
||||
):
|
||||
lp_details = frappe._dict()
|
||||
|
||||
@@ -146,7 +147,7 @@ def get_loyalty_program_details(
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_redeemption_factor(loyalty_program=None, customer=None):
|
||||
def get_redeemption_factor(loyalty_program: str | None = None, customer: str | None = None):
|
||||
customer_loyalty_program = None
|
||||
if not loyalty_program:
|
||||
customer_loyalty_program = frappe.db.get_value("Customer", customer, "loyalty_program")
|
||||
|
||||
@@ -293,7 +293,7 @@ def publish(index, total, doctype):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_temporary_opening_account(company=None):
|
||||
def get_temporary_opening_account(company: str | None = None):
|
||||
if not company:
|
||||
return
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ class PartyLink(Document):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_party_link(primary_role, primary_party, secondary_party):
|
||||
def create_party_link(primary_role: str, primary_party: str, secondary_party: str):
|
||||
party_link = frappe.new_doc("Party Link")
|
||||
party_link.primary_role = primary_role
|
||||
party_link.primary_party = primary_party
|
||||
|
||||
@@ -512,12 +512,16 @@ frappe.ui.form.on("Payment Entry", {
|
||||
frm.set_value("contact_email", "");
|
||||
frm.set_value("contact_person", "");
|
||||
}
|
||||
|
||||
if (frm.doc.payment_type && frm.doc.party_type && frm.doc.party && frm.doc.company) {
|
||||
if (!frm.doc.posting_date) {
|
||||
frappe.msgprint(__("Please select Posting Date before selecting Party"));
|
||||
frm.set_value("party", "");
|
||||
return;
|
||||
}
|
||||
|
||||
erpnext.utils.get_employee_contact_details(frm);
|
||||
|
||||
frm.set_party_account_based_on_party = true;
|
||||
|
||||
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
|
||||
|
||||
@@ -701,7 +701,6 @@
|
||||
"fetch_from": "company.book_advance_payments_in_separate_party_account",
|
||||
"fieldname": "book_advance_payments_in_separate_party_account",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Book Advance Payments in Separate Party Account",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
@@ -793,7 +792,7 @@
|
||||
"table_fieldname": "payment_entries"
|
||||
}
|
||||
],
|
||||
"modified": "2025-12-18 13:56:40.206038",
|
||||
"modified": "2026-02-03 16:08:49.800381",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Entry",
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
import json
|
||||
from datetime import date
|
||||
from functools import reduce
|
||||
|
||||
import frappe
|
||||
from frappe import ValidationError, _, qb, scrub, throw
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.meta import get_field_precision
|
||||
from frappe.query_builder import Tuple
|
||||
from frappe.query_builder.functions import Count
|
||||
@@ -1064,8 +1065,12 @@ class PaymentEntry(AccountsController):
|
||||
total_allocated_amount += flt(d.allocated_amount)
|
||||
base_total_allocated_amount += self.calculate_base_allocated_amount_for_reference(d)
|
||||
|
||||
self.total_allocated_amount = abs(total_allocated_amount)
|
||||
self.base_total_allocated_amount = abs(base_total_allocated_amount)
|
||||
self.total_allocated_amount = flt(
|
||||
abs(total_allocated_amount), self.precision("total_allocated_amount")
|
||||
)
|
||||
self.base_total_allocated_amount = flt(
|
||||
abs(base_total_allocated_amount), self.precision("base_total_allocated_amount")
|
||||
)
|
||||
|
||||
def set_unallocated_amount(self):
|
||||
self.unallocated_amount = 0
|
||||
@@ -1081,20 +1086,32 @@ class PaymentEntry(AccountsController):
|
||||
self.base_paid_amount + deductions_to_consider
|
||||
):
|
||||
self.unallocated_amount = (
|
||||
self.base_paid_amount
|
||||
+ deductions_to_consider
|
||||
- self.base_total_allocated_amount
|
||||
- included_taxes
|
||||
) / self.source_exchange_rate
|
||||
flt(
|
||||
(
|
||||
self.base_paid_amount
|
||||
+ deductions_to_consider
|
||||
- self.base_total_allocated_amount
|
||||
- included_taxes
|
||||
),
|
||||
self.precision("unallocated_amount"),
|
||||
)
|
||||
/ self.source_exchange_rate
|
||||
)
|
||||
elif self.payment_type == "Pay" and self.base_total_allocated_amount < (
|
||||
self.base_received_amount - deductions_to_consider
|
||||
):
|
||||
self.unallocated_amount = (
|
||||
self.base_received_amount
|
||||
- deductions_to_consider
|
||||
- self.base_total_allocated_amount
|
||||
- included_taxes
|
||||
) / self.target_exchange_rate
|
||||
flt(
|
||||
(
|
||||
self.base_received_amount
|
||||
- deductions_to_consider
|
||||
- self.base_total_allocated_amount
|
||||
- included_taxes
|
||||
),
|
||||
self.precision("unallocated_amount"),
|
||||
)
|
||||
/ self.target_exchange_rate
|
||||
)
|
||||
|
||||
def set_exchange_gain_loss(self):
|
||||
exchange_gain_loss = flt(
|
||||
@@ -1867,7 +1884,9 @@ class PaymentEntry(AccountsController):
|
||||
frappe.response["matched_payment_requests"] = matched_payment_requests
|
||||
|
||||
@frappe.whitelist()
|
||||
def allocate_amount_to_references(self, paid_amount, paid_amount_change, allocate_payment_amount):
|
||||
def allocate_amount_to_references(
|
||||
self, paid_amount: float, paid_amount_change: bool, allocate_payment_amount: bool
|
||||
):
|
||||
"""
|
||||
Allocate `Allocated Amount` and `Payment Request` against `Reference` based on `Paid Amount` and `Outstanding Amount`.\n
|
||||
:param paid_amount: Paid Amount / Received Amount.
|
||||
@@ -2039,7 +2058,7 @@ class PaymentEntry(AccountsController):
|
||||
)
|
||||
|
||||
@frappe.whitelist()
|
||||
def set_matched_payment_requests(self, matched_payment_requests):
|
||||
def set_matched_payment_requests(self, matched_payment_requests: str | list | None):
|
||||
"""
|
||||
Set `Payment Request` against `Reference` based on `matched_payment_requests`.\n
|
||||
:param matched_payment_requests: List of tuple of matched Payment Requests.
|
||||
@@ -2255,7 +2274,7 @@ def validate_inclusive_tax(tax, doc):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_outstanding_reference_documents(args, validate=False):
|
||||
def get_outstanding_reference_documents(args: str | dict, validate: bool = False):
|
||||
if isinstance(args, str):
|
||||
args = json.loads(args)
|
||||
|
||||
@@ -2670,7 +2689,7 @@ def get_negative_outstanding_invoices(
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_party_details(company, party_type, party, date, cost_center=None):
|
||||
def get_party_details(company: str, party_type: str, party: str, date: str, cost_center: str | None = None):
|
||||
bank_account = ""
|
||||
party_bank_account = ""
|
||||
|
||||
@@ -2696,7 +2715,7 @@ def get_party_details(company, party_type, party, date, cost_center=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_account_details(account, date, cost_center=None):
|
||||
def get_account_details(account: str, date: str | date, cost_center: str | None = None):
|
||||
frappe.has_permission("Payment Entry", throw=True)
|
||||
|
||||
# to check if the passed account is accessible under reference doctype Payment Entry
|
||||
@@ -2716,7 +2735,7 @@ def get_account_details(account, date, cost_center=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_company_defaults(company):
|
||||
def get_company_defaults(company: str):
|
||||
fields = ["write_off_account", "exchange_gain_loss_account", "cost_center"]
|
||||
return frappe.get_cached_value("Company", company, fields, as_dict=1)
|
||||
|
||||
@@ -2755,7 +2774,11 @@ def get_outstanding_on_journal_entry(voucher_no, party_type, party):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_reference_details(
|
||||
reference_doctype, reference_name, party_account_currency, party_type=None, party=None
|
||||
reference_doctype: str,
|
||||
reference_name: str,
|
||||
party_account_currency: str,
|
||||
party_type: str | None = None,
|
||||
party: str | None = None,
|
||||
):
|
||||
total_amount = outstanding_amount = exchange_rate = account = None
|
||||
|
||||
@@ -2846,15 +2869,15 @@ def get_reference_details(
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_payment_entry(
|
||||
dt,
|
||||
dn,
|
||||
party_amount=None,
|
||||
bank_account=None,
|
||||
bank_amount=None,
|
||||
party_type=None,
|
||||
payment_type=None,
|
||||
reference_date=None,
|
||||
created_from_payment_request=False,
|
||||
dt: str,
|
||||
dn: str,
|
||||
party_amount: int | float | None = None,
|
||||
bank_account: str | None = None,
|
||||
bank_amount: int | float | None = None,
|
||||
party_type: str | None = None,
|
||||
payment_type: str | None = None,
|
||||
reference_date: str | date | None = None,
|
||||
created_from_payment_request: bool | None = None,
|
||||
):
|
||||
doc = frappe.get_doc(dt, dn)
|
||||
over_billing_allowance = frappe.get_single_value("Accounts Settings", "over_billing_allowance")
|
||||
@@ -3520,7 +3543,7 @@ def get_paid_amount(dt, dn, party_type, party, account, due_date):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_payment_order(source_name, target_doc=None):
|
||||
def make_payment_order(source_name: str, target_doc: str | Document | None = None):
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
|
||||
def set_missing_values(source, target):
|
||||
|
||||
@@ -59,7 +59,7 @@ class PaymentOrder(Document):
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_mop_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
def get_mop_query(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict):
|
||||
return frappe.db.sql(
|
||||
""" select mode_of_payment from `tabPayment Order Reference`
|
||||
where parent = %(parent)s and mode_of_payment like %(txt)s
|
||||
@@ -70,7 +70,7 @@ def get_mop_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_supplier_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
def get_supplier_query(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict):
|
||||
return frappe.db.sql(
|
||||
""" select supplier from `tabPayment Order Reference`
|
||||
where parent = %(parent)s and supplier like %(txt)s and
|
||||
@@ -81,7 +81,7 @@ def get_supplier_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_payment_records(name, supplier, mode_of_payment=None):
|
||||
def make_payment_records(name: str, supplier: str, mode_of_payment: str | None = None):
|
||||
doc = frappe.get_doc("Payment Order", name)
|
||||
make_journal_entry(doc, supplier, mode_of_payment)
|
||||
|
||||
|
||||
@@ -433,7 +433,9 @@ class PaymentReconciliation(Document):
|
||||
return frappe.get_single_value("Accounts Settings", "auto_reconcile_payments")
|
||||
|
||||
@frappe.whitelist()
|
||||
def calculate_difference_on_allocation_change(self, payment_entry, invoice, allocated_amount):
|
||||
def calculate_difference_on_allocation_change(
|
||||
self, payment_entry: list, invoice: list, allocated_amount: float
|
||||
):
|
||||
invoice_exchange_map = self.get_invoice_exchange_map(invoice, payment_entry)
|
||||
invoice[0]["exchange_rate"] = invoice_exchange_map.get(invoice[0].get("invoice_number"))
|
||||
if payment_entry[0].get("reference_type") in ["Sales Invoice", "Purchase Invoice"]:
|
||||
@@ -445,7 +447,7 @@ class PaymentReconciliation(Document):
|
||||
return new_difference_amount
|
||||
|
||||
@frappe.whitelist()
|
||||
def allocate_entries(self, args):
|
||||
def allocate_entries(self, args: dict):
|
||||
self.validate_entries()
|
||||
|
||||
exc_gain_loss_posting_date = frappe.db.get_single_value(
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2025-12-02 17:50:08.648006",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"payment_term",
|
||||
"column_break_lnjp",
|
||||
"payment_schedule",
|
||||
"section_break_fjhh",
|
||||
"description",
|
||||
"section_break_mjlv",
|
||||
"due_date",
|
||||
"column_break_qghl",
|
||||
"amount"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "payment_term",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Payment Term",
|
||||
"options": "Payment Term"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "section_break_fjhh",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_mjlv",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "due_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Due Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_qghl",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Amount",
|
||||
"precision": "2"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_lnjp",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "payment_schedule",
|
||||
"fieldtype": "Link",
|
||||
"label": "Payment Schedule",
|
||||
"options": "Payment Schedule",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-01-19 02:21:36.455830",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Reference",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"row_format": "Dynamic",
|
||||
"rows_threshold_for_grid_search": 20,
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class PaymentReference(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
|
||||
|
||||
amount: DF.Currency
|
||||
description: DF.SmallText | None
|
||||
due_date: DF.Date | None
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
payment_schedule: DF.Link | None
|
||||
payment_term: DF.Link | None
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -105,3 +105,29 @@ frappe.ui.form.on("Payment Request", "is_a_subscription", function (frm) {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.form.on("Payment Request", "calculate_total_amount_by_selected_rows", function (frm) {
|
||||
if (frm.doc.docstatus !== 0) {
|
||||
frappe.msgprint(__("Cannot fetch selected rows for submitted Payment Request"));
|
||||
return;
|
||||
}
|
||||
const selected = frm.get_selected()?.payment_reference || [];
|
||||
if (!selected.length) {
|
||||
frappe.throw(__("No rows selected"));
|
||||
}
|
||||
let total = 0;
|
||||
selected.forEach((name) => {
|
||||
const row = frm.doc.payment_reference.find((d) => d.name === name);
|
||||
if (row) {
|
||||
row.manually_selected = 1;
|
||||
|
||||
total += row.amount;
|
||||
}
|
||||
});
|
||||
frm.doc.payment_reference.forEach((row) => {
|
||||
row.auto_selected = 0;
|
||||
});
|
||||
frm.set_value("grand_total", total);
|
||||
frm.refresh_field("grand_total");
|
||||
frm.save();
|
||||
});
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
"column_break_4",
|
||||
"reference_doctype",
|
||||
"reference_name",
|
||||
"payment_reference_section",
|
||||
"payment_reference",
|
||||
"transaction_details",
|
||||
"grand_total",
|
||||
"currency",
|
||||
@@ -157,6 +159,7 @@
|
||||
"label": "Amount",
|
||||
"non_negative": 1,
|
||||
"options": "currency",
|
||||
"read_only_depends_on": "eval:doc.payment_reference.length>0",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@@ -457,6 +460,17 @@
|
||||
"fieldname": "phone_number",
|
||||
"fieldtype": "Data",
|
||||
"label": "Phone Number"
|
||||
},
|
||||
{
|
||||
"fieldname": "payment_reference_section",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "payment_reference",
|
||||
"fieldtype": "Table",
|
||||
"label": "Payment Reference",
|
||||
"options": "Payment Reference",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
@@ -464,7 +478,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-08-29 11:52:48.555415",
|
||||
"modified": "2026-01-13 12:53:00.963274",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Request",
|
||||
|
||||
@@ -45,6 +45,7 @@ class PaymentRequest(Document):
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
from erpnext.accounts.doctype.payment_reference.payment_reference import PaymentReference
|
||||
from erpnext.accounts.doctype.subscription_plan_detail.subscription_plan_detail import (
|
||||
SubscriptionPlanDetail,
|
||||
)
|
||||
@@ -78,6 +79,7 @@ class PaymentRequest(Document):
|
||||
payment_gateway: DF.ReadOnly | None
|
||||
payment_gateway_account: DF.Link | None
|
||||
payment_order: DF.Link | None
|
||||
payment_reference: DF.Table[PaymentReference]
|
||||
payment_request_type: DF.Literal["Outward", "Inward"]
|
||||
payment_url: DF.Data | None
|
||||
phone_number: DF.Data | None
|
||||
@@ -109,15 +111,36 @@ class PaymentRequest(Document):
|
||||
if self.get("__islocal"):
|
||||
self.status = "Draft"
|
||||
self.validate_reference_document()
|
||||
self.validate_against_payment_reference()
|
||||
self.validate_payment_request_amount()
|
||||
# self.validate_currency()
|
||||
self.validate_subscription_details()
|
||||
|
||||
def validate_against_payment_reference(self):
|
||||
if not self.payment_reference:
|
||||
return
|
||||
|
||||
expected = sum(flt(r.amount) for r in self.payment_reference)
|
||||
if flt(expected, self.precision("grand_total")) != flt(self.grand_total):
|
||||
frappe.throw(_("Grand Total must match sum of Payment References"))
|
||||
|
||||
seen = set()
|
||||
for r in self.payment_reference:
|
||||
if not r.payment_schedule:
|
||||
continue # legacy mode → skip
|
||||
|
||||
if r.payment_schedule in seen:
|
||||
frappe.throw(_("Duplicate Payment Schedule selected"))
|
||||
|
||||
seen.add(r.payment_schedule)
|
||||
|
||||
def validate_reference_document(self):
|
||||
if not self.reference_doctype or not self.reference_name:
|
||||
frappe.throw(_("To create a Payment Request reference document is required"))
|
||||
|
||||
def validate_payment_request_amount(self):
|
||||
if self.payment_reference:
|
||||
return
|
||||
if self.grand_total == 0:
|
||||
frappe.throw(
|
||||
_("{0} cannot be zero").format(self.get_label_from_fieldname("grand_total")),
|
||||
@@ -552,9 +575,63 @@ def make_payment_request(**args):
|
||||
ref_doc = args.ref_doc or frappe.get_doc(args.dt, args.dn)
|
||||
if not args.get("company"):
|
||||
args.company = ref_doc.company
|
||||
|
||||
gateway_account = get_gateway_details(args) or frappe._dict()
|
||||
|
||||
grand_total = get_amount(ref_doc, gateway_account.get("payment_account"))
|
||||
# Schedule-based PRs are allowed only if no Payment Entry exists for this document.
|
||||
# Any existing Payment Entry forces legacy (amount-based) flow.
|
||||
selected_payment_schedules = json.loads(args.get("schedules")) if args.get("schedules") else []
|
||||
|
||||
# Backend guard:
|
||||
# If any Payment Entry exists, schedule-based PRs are not allowed.
|
||||
if selected_payment_schedules and get_existing_payment_entry(ref_doc.name):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Payment Schedule based Payment Requests cannot be created because a Payment Entry already exists for this document."
|
||||
)
|
||||
)
|
||||
|
||||
has_payment_entry = bool(get_existing_payment_entry(ref_doc.name))
|
||||
|
||||
payment_reference = []
|
||||
|
||||
if selected_payment_schedules:
|
||||
existing_payment_references = get_existing_payment_references(ref_doc.name)
|
||||
|
||||
if existing_payment_references:
|
||||
existing_ids = {r["payment_schedule"] for r in existing_payment_references}
|
||||
selected_ids = {r["name"] for r in selected_payment_schedules}
|
||||
duplicate_ids = existing_ids & selected_ids
|
||||
|
||||
if duplicate_ids:
|
||||
duplicate_schedules = []
|
||||
for row in selected_payment_schedules:
|
||||
if row["name"] in duplicate_ids:
|
||||
existing_ref = next(
|
||||
(r for r in existing_payment_references if r["payment_schedule"] == row["name"]),
|
||||
{},
|
||||
)
|
||||
existing_pr = existing_ref.get("parent")
|
||||
duplicate_schedules.append(
|
||||
f"Payment Term: {row.get('payment_term')}, "
|
||||
f"Due Date: {row.get('due_date')}, "
|
||||
f"Amount: {row.get('payment_amount')} "
|
||||
f"(already requested in PR {existing_pr})"
|
||||
)
|
||||
frappe.throw(
|
||||
_("The following payment schedule(s) already exist:\n{0}").format(
|
||||
"\n".join(duplicate_schedules)
|
||||
)
|
||||
)
|
||||
|
||||
payment_reference = set_payment_references(args.get("schedules"))
|
||||
|
||||
# Determine grand_total
|
||||
if selected_payment_schedules and not has_payment_entry:
|
||||
grand_total = sum(row.get("payment_amount") for row in selected_payment_schedules)
|
||||
else:
|
||||
grand_total = get_amount(ref_doc, gateway_account.get("payment_account"))
|
||||
|
||||
if not grand_total:
|
||||
frappe.throw(_("Payment Entry is already created"))
|
||||
|
||||
@@ -564,7 +641,6 @@ def make_payment_request(**args):
|
||||
loyalty_amount = validate_loyalty_points(ref_doc, int(args.loyalty_points)) # sets fields on ref_doc
|
||||
ref_doc.db_update()
|
||||
grand_total = grand_total - loyalty_amount
|
||||
|
||||
# fetches existing payment request `grand_total` amount
|
||||
existing_payment_request_amount = get_existing_payment_request_amount(ref_doc)
|
||||
|
||||
@@ -584,19 +660,20 @@ def make_payment_request(**args):
|
||||
else:
|
||||
# If PR's are processed, cancel all of them.
|
||||
cancel_old_payment_requests(ref_doc.doctype, ref_doc.name)
|
||||
else:
|
||||
elif not selected_payment_schedules:
|
||||
grand_total = validate_and_calculate_grand_total(grand_total, existing_payment_request_amount)
|
||||
|
||||
draft_payment_request = frappe.db.get_value(
|
||||
"Payment Request",
|
||||
{"reference_doctype": ref_doc.doctype, "reference_name": ref_doc.name, "docstatus": 0},
|
||||
)
|
||||
|
||||
if draft_payment_request:
|
||||
frappe.db.set_value(
|
||||
"Payment Request", draft_payment_request, "grand_total", grand_total, update_modified=False
|
||||
)
|
||||
pr = frappe.get_doc("Payment Request", draft_payment_request)
|
||||
|
||||
if selected_payment_schedules:
|
||||
apply_payment_references(pr, payment_reference)
|
||||
pr.save()
|
||||
|
||||
else:
|
||||
bank_account = (
|
||||
get_party_bank_account(args.get("party_type"), args.get("party"))
|
||||
@@ -651,7 +728,10 @@ def make_payment_request(**args):
|
||||
}
|
||||
)
|
||||
|
||||
# Update dimensions
|
||||
if selected_payment_schedules:
|
||||
apply_payment_references(pr, payment_reference)
|
||||
|
||||
# Dimensions
|
||||
pr.update(
|
||||
{
|
||||
"cost_center": ref_doc.get("cost_center"),
|
||||
@@ -680,6 +760,51 @@ def make_payment_request(**args):
|
||||
return pr.as_dict()
|
||||
|
||||
|
||||
def apply_payment_references(pr, payment_reference):
|
||||
existing_refs = pr.get("payment_reference") or []
|
||||
|
||||
existing_ids = {r.get("payment_schedule") for r in existing_refs if r.get("payment_schedule")}
|
||||
new_refs = [r for r in (payment_reference or []) if r.get("payment_schedule") not in existing_ids]
|
||||
pr.set("payment_reference", existing_refs + new_refs)
|
||||
pr.set("grand_total", sum(flt(r.get("amount")) for r in pr.get("payment_reference")))
|
||||
|
||||
|
||||
def set_payment_references(payment_schedules):
|
||||
payment_schedules = json.loads(payment_schedules) if payment_schedules else []
|
||||
payment_reference = []
|
||||
|
||||
for row in payment_schedules:
|
||||
payment_reference.append(
|
||||
{
|
||||
"payment_term": row.get("payment_term"),
|
||||
"payment_schedule": row.get("name"),
|
||||
"description": row.get("description"),
|
||||
"due_date": row.get("due_date"),
|
||||
"amount": row.get("payment_amount"),
|
||||
}
|
||||
)
|
||||
|
||||
return payment_reference
|
||||
|
||||
|
||||
def get_existing_payment_entry(ref_docname):
|
||||
pe = frappe.qb.DocType("Payment Entry")
|
||||
per = frappe.qb.DocType("Payment Entry Reference")
|
||||
|
||||
existing_pe = (
|
||||
frappe.qb.from_(pe)
|
||||
.join(per)
|
||||
.on(per.parent == pe.name)
|
||||
.select(pe.name)
|
||||
.where(pe.docstatus < 2)
|
||||
.where(per.reference_name == ref_docname)
|
||||
.limit(1)
|
||||
.run()
|
||||
)
|
||||
|
||||
return existing_pe
|
||||
|
||||
|
||||
def get_amount(ref_doc, payment_account=None):
|
||||
"""get amount based on doctype"""
|
||||
grand_total = 0
|
||||
@@ -812,7 +937,7 @@ def get_payment_gateway_account(filter):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_print_format_list(ref_doctype):
|
||||
def get_print_format_list(ref_doctype: str):
|
||||
print_format_list = ["Standard"]
|
||||
|
||||
print_format_list.extend(
|
||||
@@ -823,12 +948,12 @@ def get_print_format_list(ref_doctype):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def resend_payment_email(docname):
|
||||
def resend_payment_email(docname: str):
|
||||
return frappe.get_doc("Payment Request", docname).send_email()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_payment_entry(docname):
|
||||
def make_payment_entry(docname: str):
|
||||
doc = frappe.get_doc("Payment Request", docname)
|
||||
return doc.create_payment_entry(submit=False).as_dict()
|
||||
|
||||
@@ -921,7 +1046,7 @@ def get_dummy_message(doc):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_subscription_details(reference_doctype, reference_name):
|
||||
def get_subscription_details(reference_doctype: str, reference_name: str):
|
||||
if reference_doctype == "Sales Invoice":
|
||||
subscriptions = frappe.db.sql(
|
||||
"""SELECT parent as sub_name FROM `tabSubscription Invoice` WHERE invoice=%s""",
|
||||
@@ -937,7 +1062,7 @@ def get_subscription_details(reference_doctype, reference_name):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_payment_order(source_name, target_doc=None):
|
||||
def make_payment_order(source_name: str, target_doc: str | Document | None = None):
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
|
||||
def set_missing_values(source, target):
|
||||
@@ -985,7 +1110,9 @@ def validate_payment(doc, method=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_open_payment_requests_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
def get_open_payment_requests_query(
|
||||
doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict
|
||||
):
|
||||
# permission checks in `get_list()`
|
||||
filters = frappe._dict(filters)
|
||||
|
||||
@@ -1024,3 +1151,44 @@ def get_irequests_of_payment_request(doc: str | None = None) -> list:
|
||||
},
|
||||
)
|
||||
return res
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_available_payment_schedules(reference_doctype, reference_name):
|
||||
ref_doc = frappe.get_doc(reference_doctype, reference_name)
|
||||
|
||||
if not hasattr(ref_doc, "payment_schedule") or not ref_doc.payment_schedule:
|
||||
return []
|
||||
|
||||
if get_existing_payment_entry(reference_name):
|
||||
return []
|
||||
|
||||
existing_refs = get_existing_payment_references(reference_name)
|
||||
existing_ids = {r["payment_schedule"] for r in existing_refs if r.get("payment_schedule")}
|
||||
|
||||
return [r for r in ref_doc.payment_schedule if r.name not in existing_ids]
|
||||
|
||||
|
||||
def get_existing_payment_references(reference_name):
|
||||
PR = frappe.qb.DocType("Payment Request")
|
||||
PRF = frappe.qb.DocType("Payment Reference")
|
||||
|
||||
result = (
|
||||
frappe.qb.from_(PR)
|
||||
.join(PRF)
|
||||
.on(PR.name == PRF.parent)
|
||||
.select(
|
||||
PRF.payment_term,
|
||||
PRF.due_date,
|
||||
PRF.amount.as_("payment_amount"),
|
||||
PRF.payment_schedule,
|
||||
PRF.parent,
|
||||
)
|
||||
.where(PR.reference_name == reference_name)
|
||||
.where(PR.docstatus < 2)
|
||||
.where(
|
||||
PR.status.isin(["Draft", "Requested", "Initiated", "Partially Paid", "Payment Ordered", "Paid"])
|
||||
)
|
||||
).run(as_dict=True)
|
||||
|
||||
return result
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import json
|
||||
import re
|
||||
from unittest.mock import patch
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
from frappe.utils import add_days, nowdate
|
||||
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_terms_template
|
||||
@@ -850,3 +852,130 @@ class TestPaymentRequest(IntegrationTestCase):
|
||||
pr.load_from_db()
|
||||
|
||||
self.assertEqual(pr.grand_total, pi.outstanding_amount)
|
||||
|
||||
def test_payment_request_grand_total_from_selected_schedules(self):
|
||||
po = create_purchase_order(do_not_save=1, currency="INR", qty=1, rate=100)
|
||||
po.payment_schedule = []
|
||||
|
||||
po.append("payment_schedule", {"due_date": nowdate(), "payment_amount": 30})
|
||||
po.append("payment_schedule", {"due_date": add_days(nowdate(), 1), "payment_amount": 30})
|
||||
po.append("payment_schedule", {"due_date": add_days(nowdate(), 2), "payment_amount": 40})
|
||||
|
||||
po.save()
|
||||
po.submit()
|
||||
|
||||
schedules = json.dumps(
|
||||
[
|
||||
{
|
||||
"payment_term": row.payment_term,
|
||||
"name": row.name,
|
||||
"due_date": row.due_date,
|
||||
"payment_amount": row.payment_amount,
|
||||
"description": row.description,
|
||||
}
|
||||
for row in [po.payment_schedule[0], po.payment_schedule[2]]
|
||||
]
|
||||
)
|
||||
pr = make_payment_request(
|
||||
dt="Purchase Order",
|
||||
dn=po.name,
|
||||
mute_email=1,
|
||||
submit_doc=False,
|
||||
return_doc=True,
|
||||
schedules=schedules,
|
||||
)
|
||||
|
||||
pr.submit()
|
||||
|
||||
self.assertEqual(pr.grand_total, 70)
|
||||
self.assertEqual(len(pr.payment_reference), 2)
|
||||
|
||||
def test_draft_pr_reuse_merges_payment_references(self):
|
||||
from frappe.utils import add_days, nowdate
|
||||
|
||||
po = create_purchase_order(do_not_save=1, currency="INR", qty=1, rate=100)
|
||||
po.payment_schedule = []
|
||||
po.append("payment_schedule", {"due_date": nowdate(), "payment_amount": 50})
|
||||
po.append("payment_schedule", {"due_date": add_days(nowdate(), 1), "payment_amount": 50})
|
||||
po.save()
|
||||
po.submit()
|
||||
schedules = json.dumps(
|
||||
[
|
||||
{
|
||||
"payment_term": row.payment_term,
|
||||
"name": row.name,
|
||||
"due_date": row.due_date,
|
||||
"payment_amount": row.payment_amount,
|
||||
"description": row.description,
|
||||
}
|
||||
for row in [po.payment_schedule[0]]
|
||||
]
|
||||
)
|
||||
pr = make_payment_request(
|
||||
dt="Purchase Order",
|
||||
dn=po.name,
|
||||
mute_email=1,
|
||||
submit_doc=False,
|
||||
return_doc=True,
|
||||
schedules=schedules,
|
||||
)
|
||||
|
||||
pr.save()
|
||||
schedules = json.dumps(
|
||||
[
|
||||
{
|
||||
"payment_term": row.payment_term,
|
||||
"name": row.name,
|
||||
"due_date": row.due_date,
|
||||
"payment_amount": row.payment_amount,
|
||||
"description": row.description,
|
||||
}
|
||||
for row in [po.payment_schedule[1]]
|
||||
]
|
||||
)
|
||||
# call make_payment_request again → reuse draft
|
||||
pr_reused = make_payment_request(
|
||||
dt="Purchase Order",
|
||||
dn=po.name,
|
||||
mute_email=1,
|
||||
submit_doc=False,
|
||||
return_doc=True,
|
||||
schedules=schedules,
|
||||
)
|
||||
|
||||
self.assertEqual(pr.name, pr_reused.name)
|
||||
self.assertEqual(pr_reused.grand_total, 100)
|
||||
self.assertEqual(len(pr_reused.payment_reference), 2)
|
||||
|
||||
def test_schedule_pr_not_allowed_if_payment_entry_exists(self):
|
||||
po = create_purchase_order(do_not_save=1, currency="INR", qty=1, rate=100)
|
||||
po.payment_schedule = []
|
||||
row = po.append("payment_schedule", {"due_date": nowdate(), "payment_amount": 100})
|
||||
po.save()
|
||||
po.submit()
|
||||
|
||||
# create PE first
|
||||
pr = make_payment_request(dt="Purchase Order", dn=po.name, mute_email=1, submit_doc=1, return_doc=1)
|
||||
pr.create_payment_entry()
|
||||
|
||||
schedules = json.dumps(
|
||||
[
|
||||
{
|
||||
"name": row.name,
|
||||
"payment_term": row.payment_term,
|
||||
"due_date": row.due_date,
|
||||
"payment_amount": row.payment_amount,
|
||||
"description": row.description,
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
with self.assertRaises(frappe.ValidationError):
|
||||
make_payment_request(
|
||||
dt="Purchase Order",
|
||||
dn=po.name,
|
||||
mute_email=1,
|
||||
submit_doc=False,
|
||||
return_doc=True,
|
||||
schedules=schedules,
|
||||
)
|
||||
|
||||
@@ -515,7 +515,7 @@ def delete_closing_entries(voucher_no):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_period_start_end_date(fiscal_year, company):
|
||||
def get_period_start_end_date(fiscal_year: str, company: str):
|
||||
fy_start_date, fy_end_date = frappe.db.get_value(
|
||||
"Fiscal Year", fiscal_year, ["year_start_date", "year_end_date"]
|
||||
)
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.query_builder import DocType
|
||||
@@ -252,13 +254,13 @@ class POSClosingEntry(StatusUpdater):
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_cashiers(doctype, txt, searchfield, start, page_len, filters):
|
||||
def get_cashiers(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict):
|
||||
cashiers_list = frappe.get_all("POS Profile User", filters=filters, fields=["user"], as_list=1)
|
||||
return [c for c in cashiers_list]
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_invoices(start, end, pos_profile, user):
|
||||
def get_invoices(start: str | datetime, end: str | datetime, pos_profile: str, user: str):
|
||||
invoice_doctype = frappe.db.get_single_value("POS Settings", "invoice_type")
|
||||
|
||||
sales_inv_query = build_invoice_query("Sales Invoice", user, pos_profile, start, end)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _, bold
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.mapper import map_child_doc, map_doc
|
||||
from frappe.query_builder.functions import IfNull, Sum
|
||||
from frappe.utils import cint, flt, get_link_to_form, getdate, nowdate
|
||||
@@ -753,7 +754,7 @@ class POSInvoice(SalesInvoice):
|
||||
return profile
|
||||
|
||||
@frappe.whitelist()
|
||||
def set_missing_values(self, for_validate=False):
|
||||
def set_missing_values(self, for_validate: bool = False):
|
||||
profile = self.set_pos_fields(for_validate)
|
||||
|
||||
if not self.debit_to:
|
||||
@@ -854,7 +855,7 @@ class POSInvoice(SalesInvoice):
|
||||
return frappe.get_doc("Payment Request", pr)
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_payments(self, payments):
|
||||
def update_payments(self, payments: list):
|
||||
if self.status == "Consolidated":
|
||||
frappe.throw(_("Create Payment Entry for Consolidated POS Invoices."))
|
||||
|
||||
@@ -897,7 +898,7 @@ class POSInvoice(SalesInvoice):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_stock_availability(item_code, warehouse):
|
||||
def get_stock_availability(item_code: str | None, warehouse: str):
|
||||
if frappe.db.get_value("Item", item_code, "is_stock_item"):
|
||||
is_stock_item = True
|
||||
bin_qty = get_bin_qty(item_code, warehouse)
|
||||
@@ -1020,14 +1021,14 @@ def get_pos_reserved_qty_from_table(child_table, item_code, warehouse):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_sales_return(source_name, target_doc=None):
|
||||
def make_sales_return(source_name: str, target_doc: Document | None = None):
|
||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||
|
||||
return make_return_doc("POS Invoice", source_name, target_doc)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_merge_log(invoices):
|
||||
def make_merge_log(invoices: str | list):
|
||||
import json
|
||||
|
||||
if isinstance(invoices, str):
|
||||
@@ -1077,7 +1078,15 @@ def add_return_modes(doc, pos_profile):
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False):
|
||||
def item_query(
|
||||
doctype: str,
|
||||
txt: str,
|
||||
searchfield: str,
|
||||
start: int,
|
||||
page_len: int,
|
||||
filters: dict,
|
||||
as_dict: bool = False,
|
||||
):
|
||||
if pos_profile := filters.get("pos_profile")[1]:
|
||||
pos_profile = frappe.get_cached_doc("POS Profile", pos_profile)
|
||||
if item_groups := get_item_group(pos_profile):
|
||||
|
||||
@@ -275,7 +275,7 @@ def get_child_nodes(group_type, root):
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def pos_profile_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
def pos_profile_query(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict):
|
||||
user = frappe.session["user"]
|
||||
company = filters.get("company") or frappe.defaults.get_user_default("company")
|
||||
|
||||
@@ -319,7 +319,7 @@ def pos_profile_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def set_default_profile(pos_profile, company):
|
||||
def set_default_profile(pos_profile: str, company: str):
|
||||
modified = now()
|
||||
user = frappe.session.user
|
||||
|
||||
|
||||
@@ -320,7 +320,7 @@ class PricingRule(Document):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def apply_pricing_rule(args, doc=None):
|
||||
def apply_pricing_rule(args: str | dict, doc: str | dict | Document | None = None):
|
||||
"""
|
||||
args = {
|
||||
"items": [{"doctype": "", "name": "", "item_code": "", "brand": "", "item_group": ""}, ...],
|
||||
@@ -618,7 +618,12 @@ def apply_price_discount_rule(pricing_rule, item_details, args):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None, rate=None):
|
||||
def remove_pricing_rule_for_item(
|
||||
pricing_rules: str | None,
|
||||
item_details: str | frappe._dict,
|
||||
item_code: str | None = None,
|
||||
rate: float | None = None,
|
||||
):
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import (
|
||||
get_applied_pricing_rules,
|
||||
get_pricing_rule_items,
|
||||
@@ -666,7 +671,7 @@ def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None, ra
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def remove_pricing_rules(item_list):
|
||||
def remove_pricing_rules(item_list: str | list):
|
||||
if isinstance(item_list, str):
|
||||
item_list = json.loads(item_list)
|
||||
|
||||
@@ -704,7 +709,7 @@ def set_transaction_type(args):
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_item_uoms(doctype, txt, searchfield, start, page_len, filters):
|
||||
def get_item_uoms(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict):
|
||||
items = [filters.get("value")]
|
||||
if filters.get("apply_on") != "Item Code":
|
||||
field = frappe.scrub(filters.get("apply_on"))
|
||||
|
||||
@@ -420,7 +420,7 @@ def get_context(customer, doc):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def fetch_customers(customer_collection, collection_name, primary_mandatory):
|
||||
def fetch_customers(customer_collection: str, collection_name: str, primary_mandatory: str | int):
|
||||
customer_list = []
|
||||
customers = []
|
||||
|
||||
@@ -460,7 +460,7 @@ def fetch_customers(customer_collection, collection_name, primary_mandatory):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=True):
|
||||
def get_customer_emails(customer_name: str, primary_mandatory: str | int, billing_and_primary: bool = True):
|
||||
"""Returns first email from Contact Email table as a Billing email
|
||||
when Is Billing Contact checked
|
||||
and Primary email- email with Is Primary checked"""
|
||||
@@ -506,7 +506,7 @@ def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=Tr
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def download_statements(document_name):
|
||||
def download_statements(document_name: str):
|
||||
doc = frappe.get_doc("Process Statement Of Accounts", document_name)
|
||||
report = get_report_pdf(doc)
|
||||
if report:
|
||||
@@ -516,7 +516,7 @@ def download_statements(document_name):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def send_emails(document_name, from_scheduler=False, posting_date=None):
|
||||
def send_emails(document_name: str, from_scheduler: bool = False, posting_date: str | None = None):
|
||||
doc = frappe.get_doc("Process Statement Of Accounts", document_name)
|
||||
report = get_report_pdf(doc, consolidated=False)
|
||||
|
||||
|
||||
@@ -18,8 +18,19 @@ class TestProcessStatementOfAccounts(AccountsTestMixin, IntegrationTestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
letterhead = frappe.get_doc("Letter Head", "Company Letterhead - Grey")
|
||||
letterhead.is_default = 0
|
||||
letterhead.save()
|
||||
cls.enterClassContext(cls.change_settings("Selling Settings", validate_selling_price=0))
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
super().tearDownClass()
|
||||
letterhead = frappe.get_doc("Letter Head", "Company Letterhead - Grey")
|
||||
letterhead.is_default = 1
|
||||
letterhead.save()
|
||||
frappe.db.commit() # nosemgrep
|
||||
|
||||
def setUp(self):
|
||||
self.create_company()
|
||||
self.create_customer()
|
||||
|
||||
@@ -134,7 +134,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
||||
this.frm.add_custom_button(
|
||||
__("Payment Request"),
|
||||
function () {
|
||||
me.make_payment_request();
|
||||
me.make_payment_request_with_schedule();
|
||||
},
|
||||
__("Create")
|
||||
);
|
||||
|
||||
@@ -86,18 +86,18 @@
|
||||
"taxes_and_charges_deducted",
|
||||
"total_taxes_and_charges",
|
||||
"totals_section",
|
||||
"use_company_roundoff_cost_center",
|
||||
"grand_total",
|
||||
"in_words",
|
||||
"column_break8",
|
||||
"disable_rounded_total",
|
||||
"rounding_adjustment",
|
||||
"column_break8",
|
||||
"use_company_roundoff_cost_center",
|
||||
"in_words",
|
||||
"rounded_total",
|
||||
"base_totals_section",
|
||||
"base_grand_total",
|
||||
"base_rounding_adjustment",
|
||||
"column_break_hcca",
|
||||
"base_in_words",
|
||||
"column_break_hcca",
|
||||
"base_rounding_adjustment",
|
||||
"base_rounded_total",
|
||||
"section_break_ttrv",
|
||||
"total_advance",
|
||||
@@ -1689,7 +1689,7 @@
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-02-05 20:45:16.964500",
|
||||
"modified": "2026-02-23 13:23:57.269770",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
||||
@@ -6,6 +6,7 @@ import json
|
||||
|
||||
import frappe
|
||||
from frappe import _, qb, throw
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import cint, cstr, flt, formatdate, get_link_to_form, getdate, nowdate
|
||||
@@ -1745,10 +1746,6 @@ class PurchaseInvoice(BuyingController):
|
||||
project_doc.db_update()
|
||||
|
||||
def validate_supplier_invoice(self):
|
||||
if self.bill_date:
|
||||
if getdate(self.bill_date) > getdate(self.posting_date):
|
||||
frappe.throw(_("Supplier Invoice Date cannot be greater than Posting Date"))
|
||||
|
||||
if self.bill_no:
|
||||
if cint(frappe.get_single_value("Accounts Settings", "check_supplier_invoice_uniqueness")):
|
||||
fiscal_year = get_fiscal_year(self.posting_date, company=self.company, as_dict=True)
|
||||
@@ -1945,14 +1942,14 @@ def make_regional_gl_entries(gl_entries, doc):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_debit_note(source_name, target_doc=None):
|
||||
def make_debit_note(source_name: str, target_doc: str | Document | None = None):
|
||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||
|
||||
return make_return_doc("Purchase Invoice", source_name, target_doc)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_stock_entry(source_name, target_doc=None):
|
||||
def make_stock_entry(source_name: str, target_doc: str | Document | None = None):
|
||||
doc = get_mapped_doc(
|
||||
"Purchase Invoice",
|
||||
source_name,
|
||||
@@ -1970,35 +1967,37 @@ def make_stock_entry(source_name, target_doc=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def change_release_date(name, release_date=None):
|
||||
def change_release_date(name: str, release_date: str | None = None):
|
||||
if frappe.db.exists("Purchase Invoice", name):
|
||||
pi = frappe.get_lazy_doc("Purchase Invoice", name)
|
||||
pi.db_set("release_date", release_date)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def unblock_invoice(name):
|
||||
def unblock_invoice(name: str):
|
||||
if frappe.db.exists("Purchase Invoice", name):
|
||||
pi = frappe.get_lazy_doc("Purchase Invoice", name)
|
||||
pi.unblock_invoice()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def block_invoice(name, release_date, hold_comment=None):
|
||||
def block_invoice(name: str, release_date: str, hold_comment: str | None = None):
|
||||
if frappe.db.exists("Purchase Invoice", name):
|
||||
pi = frappe.get_lazy_doc("Purchase Invoice", name)
|
||||
pi.block_invoice(hold_comment, release_date)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_inter_company_sales_invoice(source_name, target_doc=None):
|
||||
def make_inter_company_sales_invoice(source_name: str, target_doc: Document | None = None):
|
||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_transaction
|
||||
|
||||
return make_inter_company_transaction("Purchase Invoice", source_name, target_doc)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_purchase_receipt(source_name, target_doc=None, args=None):
|
||||
def make_purchase_receipt(
|
||||
source_name: str, target_doc: str | Document | None = None, args: str | dict | None = None
|
||||
):
|
||||
if args is None:
|
||||
args = {}
|
||||
if isinstance(args, str):
|
||||
|
||||
@@ -152,7 +152,7 @@ class RepostAccountingLedger(Document):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def start_repost(account_repost_doc=str) -> None:
|
||||
def start_repost(account_repost_doc: str | None = None) -> None:
|
||||
from erpnext.accounts.general_ledger import make_reverse_gl_entries
|
||||
|
||||
frappe.flags.through_repost_accounting_ledger = True
|
||||
@@ -286,7 +286,9 @@ def validate_docs_for_voucher_types(doc_voucher_types):
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_repost_allowed_types(doctype, txt, searchfield, start, page_len, filters):
|
||||
def get_repost_allowed_types(
|
||||
doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict
|
||||
):
|
||||
filters = {"allowed": True}
|
||||
|
||||
if txt:
|
||||
|
||||
@@ -21,7 +21,7 @@ def repost_ple_for_voucher(voucher_type, voucher_no, gle_map=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def start_payment_ledger_repost(docname=None):
|
||||
def start_payment_ledger_repost(docname: str | None = None):
|
||||
"""
|
||||
Repost Payment Ledger Entries for Vouchers through Background Job
|
||||
"""
|
||||
@@ -119,7 +119,7 @@ class RepostPaymentLedger(Document):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def execute_repost_payment_ledger(docname):
|
||||
def execute_repost_payment_ledger(docname: str):
|
||||
"""Repost Payment Ledger Entries by background job."""
|
||||
|
||||
job_name = "payment_ledger_repost_" + docname
|
||||
|
||||
@@ -138,7 +138,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
||||
this.frm.add_custom_button(
|
||||
__("Payment Request"),
|
||||
function () {
|
||||
me.make_payment_request();
|
||||
me.make_payment_request_with_schedule();
|
||||
},
|
||||
__("Create")
|
||||
);
|
||||
|
||||
@@ -78,21 +78,23 @@
|
||||
"column_break_47",
|
||||
"total_taxes_and_charges",
|
||||
"totals_section",
|
||||
"use_company_roundoff_cost_center",
|
||||
"grand_total",
|
||||
"rounding_adjustment",
|
||||
"in_words",
|
||||
"column_break5",
|
||||
"rounded_total",
|
||||
"disable_rounded_total",
|
||||
"total_advance",
|
||||
"outstanding_amount",
|
||||
"use_company_roundoff_cost_center",
|
||||
"rounding_adjustment",
|
||||
"rounded_total",
|
||||
"base_totals_section",
|
||||
"base_grand_total",
|
||||
"base_rounding_adjustment",
|
||||
"base_in_words",
|
||||
"column_break_xjag",
|
||||
"base_rounding_adjustment",
|
||||
"base_rounded_total",
|
||||
"section_break_vacb",
|
||||
"total_advance",
|
||||
"column_break_rdks",
|
||||
"outstanding_amount",
|
||||
"section_tax_withholding_entry",
|
||||
"tax_withholding_group",
|
||||
"ignore_tax_withholding_threshold",
|
||||
@@ -269,6 +271,7 @@
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Customer",
|
||||
"print_hide": 1,
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
@@ -797,8 +800,7 @@
|
||||
"hide_seconds": 1,
|
||||
"label": "Time Sheets",
|
||||
"options": "Sales Invoice Timesheet",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@@ -2307,6 +2309,14 @@
|
||||
"fieldname": "utm_analytics_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "UTM Analytics"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_vacb",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_rdks",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
@@ -2320,7 +2330,7 @@
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2026-02-10 11:59:07.819903",
|
||||
"modified": "2026-02-28 17:58:56.453076",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
||||
@@ -6,6 +6,7 @@ import frappe
|
||||
import frappe.utils
|
||||
from frappe import _, msgprint, throw
|
||||
from frappe.contacts.doctype.address.address import get_address_display
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from frappe.model.utils import get_fetch_values
|
||||
from frappe.query_builder import Case
|
||||
@@ -119,7 +120,7 @@ class SalesInvoice(SellingController):
|
||||
cost_center: DF.Link | None
|
||||
coupon_code: DF.Link | None
|
||||
currency: DF.Link
|
||||
customer: DF.Link | None
|
||||
customer: DF.Link
|
||||
customer_address: DF.Link | None
|
||||
customer_group: DF.Link | None
|
||||
customer_name: DF.SmallText | None
|
||||
@@ -741,7 +742,7 @@ class SalesInvoice(SellingController):
|
||||
pos_invoice_doc.cancel()
|
||||
|
||||
@frappe.whitelist()
|
||||
def set_missing_values(self, for_validate=False):
|
||||
def set_missing_values(self, for_validate: bool = False):
|
||||
pos = self.set_pos_fields(for_validate)
|
||||
|
||||
if not self.debit_to:
|
||||
@@ -2409,7 +2410,7 @@ def get_list_context(context=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_bank_cash_account(mode_of_payment, company):
|
||||
def get_bank_cash_account(mode_of_payment: str, company: str):
|
||||
account = frappe.db.get_value(
|
||||
"Mode of Payment Account", {"parent": mode_of_payment, "company": company}, "default_account"
|
||||
)
|
||||
@@ -2424,7 +2425,7 @@ def get_bank_cash_account(mode_of_payment, company):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_maintenance_schedule(source_name, target_doc=None):
|
||||
def make_maintenance_schedule(source_name: str, target_doc: str | Document | None = None):
|
||||
doclist = get_mapped_doc(
|
||||
"Sales Invoice",
|
||||
source_name,
|
||||
@@ -2441,7 +2442,7 @@ def make_maintenance_schedule(source_name, target_doc=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_delivery_note(source_name, target_doc=None):
|
||||
def make_delivery_note(source_name: str, target_doc: Document | None = None):
|
||||
def set_missing_values(source, target):
|
||||
target.run_method("set_missing_values")
|
||||
target.run_method("set_po_nos")
|
||||
@@ -2490,7 +2491,7 @@ def make_delivery_note(source_name, target_doc=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_sales_return(source_name, target_doc=None):
|
||||
def make_sales_return(source_name: str, target_doc: Document | None = None):
|
||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||
|
||||
return make_return_doc("Sales Invoice", source_name, target_doc)
|
||||
@@ -2584,7 +2585,7 @@ def validate_inter_company_transaction(doc, doctype):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_inter_company_purchase_invoice(source_name, target_doc=None):
|
||||
def make_inter_company_purchase_invoice(source_name: str, target_doc: Document | None = None):
|
||||
return make_inter_company_transaction("Sales Invoice", source_name, target_doc)
|
||||
|
||||
|
||||
@@ -2962,7 +2963,7 @@ def update_address(doc, address_field, address_display_field, address_name):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_loyalty_programs(customer):
|
||||
def get_loyalty_programs(customer: str):
|
||||
"""sets applicable loyalty program to the customer or returns a list of applicable programs"""
|
||||
from erpnext.selling.doctype.customer.customer import get_loyalty_programs
|
||||
|
||||
@@ -2980,7 +2981,7 @@ def get_loyalty_programs(customer):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_invoice_discounting(source_name, target_doc=None):
|
||||
def create_invoice_discounting(source_name: str, target_doc: str | Document | None = None):
|
||||
invoice = frappe.get_doc("Sales Invoice", source_name)
|
||||
invoice_discounting = frappe.new_doc("Invoice Discounting")
|
||||
invoice_discounting.company = invoice.company
|
||||
@@ -3072,7 +3073,9 @@ def get_mode_of_payment_info(mode_of_payment, company):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_dunning(source_name, target_doc=None, ignore_permissions=False):
|
||||
def create_dunning(
|
||||
source_name: str, target_doc: str | Document | None = None, ignore_permissions: bool = False
|
||||
):
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
|
||||
def postprocess_dunning(source, target):
|
||||
|
||||
@@ -843,6 +843,7 @@
|
||||
"fieldtype": "Currency",
|
||||
"label": "Incoming Rate (Costing)",
|
||||
"no_copy": 1,
|
||||
"non_negative": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"print_hide": 1
|
||||
},
|
||||
@@ -1009,7 +1010,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-02-15 21:08:57.341638",
|
||||
"modified": "2026-02-23 14:37:14.853941",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Item",
|
||||
|
||||
@@ -342,14 +342,14 @@ class ShareTransfer(Document):
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_jv_entry(
|
||||
company,
|
||||
account,
|
||||
amount,
|
||||
payment_account,
|
||||
credit_applicant_type,
|
||||
credit_applicant,
|
||||
debit_applicant_type,
|
||||
debit_applicant,
|
||||
company: str,
|
||||
account: str,
|
||||
amount: float,
|
||||
payment_account: str,
|
||||
credit_applicant_type: str,
|
||||
credit_applicant: str,
|
||||
debit_applicant_type: str,
|
||||
debit_applicant: str,
|
||||
):
|
||||
journal_entry = frappe.new_doc("Journal Entry")
|
||||
journal_entry.voucher_type = "Journal Entry"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from datetime import date
|
||||
|
||||
import frappe
|
||||
from dateutil import relativedelta
|
||||
@@ -43,7 +44,13 @@ class SubscriptionPlan(Document):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_plan_rate(
|
||||
plan, quantity=1, customer=None, start_date=None, end_date=None, prorate_factor=1, party=None
|
||||
plan: str,
|
||||
quantity: int = 1,
|
||||
customer: str | None = None,
|
||||
start_date: str | date | None = None,
|
||||
end_date: str | date | None = None,
|
||||
prorate_factor: float = 1,
|
||||
party: str | None = None,
|
||||
):
|
||||
plan = frappe.get_doc("Subscription Plan", plan)
|
||||
if plan.price_determination == "Fixed Rate":
|
||||
|
||||
@@ -8,6 +8,8 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.contacts.doctype.address.address import get_default_address
|
||||
from frappe.model.document import Document
|
||||
from frappe.query_builder import DocType
|
||||
from frappe.query_builder.functions import IfNull
|
||||
from frappe.utils import cstr
|
||||
from frappe.utils.nestedset import get_root_of
|
||||
|
||||
@@ -83,6 +85,8 @@ class TaxRule(Document):
|
||||
frappe.throw(_("Tax Template is mandatory."))
|
||||
|
||||
def validate_filters(self):
|
||||
TaxRule = DocType("Tax Rule")
|
||||
|
||||
filters = {
|
||||
"tax_type": self.tax_type,
|
||||
"customer": self.customer,
|
||||
@@ -105,37 +109,38 @@ class TaxRule(Document):
|
||||
"company": self.company,
|
||||
}
|
||||
|
||||
conds = ""
|
||||
for d in filters:
|
||||
if conds:
|
||||
conds += " and "
|
||||
conds += f"""ifnull({d}, '') = {frappe.db.escape(cstr(filters[d]))}"""
|
||||
|
||||
if self.from_date and self.to_date:
|
||||
conds += f""" and ((from_date > '{self.from_date}' and from_date < '{self.to_date}') or
|
||||
(to_date > '{self.from_date}' and to_date < '{self.to_date}') or
|
||||
('{self.from_date}' > from_date and '{self.from_date}' < to_date) or
|
||||
('{self.from_date}' = from_date and '{self.to_date}' = to_date))"""
|
||||
|
||||
elif self.from_date and not self.to_date:
|
||||
conds += f""" and to_date > '{self.from_date}'"""
|
||||
|
||||
elif self.to_date and not self.from_date:
|
||||
conds += f""" and from_date < '{self.to_date}'"""
|
||||
|
||||
tax_rule = frappe.db.sql(
|
||||
f"select name, priority \
|
||||
from `tabTax Rule` where {conds} and name != '{self.name}'",
|
||||
as_dict=1,
|
||||
query = (
|
||||
frappe.qb.from_(TaxRule).select(TaxRule.name, TaxRule.priority).where(TaxRule.name != self.name)
|
||||
)
|
||||
|
||||
if tax_rule:
|
||||
if tax_rule[0].priority == self.priority:
|
||||
frappe.throw(_("Tax Rule Conflicts with {0}").format(tax_rule[0].name), ConflictingTaxRule)
|
||||
for field, value in filters.items():
|
||||
query = query.where(IfNull(TaxRule[field], "") == cstr(value))
|
||||
|
||||
if self.from_date and self.to_date:
|
||||
query = query.where(
|
||||
((TaxRule.from_date > self.from_date) & (TaxRule.from_date < self.to_date))
|
||||
| ((TaxRule.to_date > self.from_date) & (TaxRule.to_date < self.to_date))
|
||||
| ((self.from_date > TaxRule.from_date) & (self.from_date < TaxRule.to_date))
|
||||
| ((TaxRule.from_date == self.from_date) & (TaxRule.to_date == self.to_date))
|
||||
)
|
||||
|
||||
elif self.from_date:
|
||||
query = query.where(TaxRule.to_date > self.from_date)
|
||||
|
||||
elif self.to_date:
|
||||
query = query.where(TaxRule.from_date < self.to_date)
|
||||
|
||||
tax_rule = query.run(as_dict=True)
|
||||
|
||||
if tax_rule and tax_rule[0].priority == self.priority:
|
||||
frappe.throw(
|
||||
_("Tax Rule Conflicts with {0}").format(tax_rule[0].name),
|
||||
ConflictingTaxRule,
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_party_details(party, party_type, args=None):
|
||||
def get_party_details(party: str | None, party_type: str, args: dict | None = None):
|
||||
out = {}
|
||||
billing_address, shipping_address = None, None
|
||||
if args:
|
||||
|
||||
@@ -194,7 +194,7 @@ def get_linked_advances(company, docname):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_unreconcile_doc_for_selection(selections=None):
|
||||
def create_unreconcile_doc_for_selection(selections: str | None = None):
|
||||
if selections:
|
||||
selections = json.loads(selections)
|
||||
# assuming each row is a unique voucher
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"allow_roles": [
|
||||
{
|
||||
"role": "Accounts Manager"
|
||||
},
|
||||
{
|
||||
"role": "Accounts User"
|
||||
}
|
||||
],
|
||||
"creation": "2026-02-22 18:26:42.015787",
|
||||
"docstatus": 0,
|
||||
"doctype": "Module Onboarding",
|
||||
"idx": 4,
|
||||
"is_complete": 0,
|
||||
"modified": "2026-02-23 22:51:34.267812",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounting Onboarding",
|
||||
"owner": "Administrator",
|
||||
"steps": [
|
||||
{
|
||||
"step": "Chart of Accounts"
|
||||
},
|
||||
{
|
||||
"step": "Setup Sales taxes"
|
||||
},
|
||||
{
|
||||
"step": "Create Sales Invoice"
|
||||
},
|
||||
{
|
||||
"step": "Create Payment Entry"
|
||||
},
|
||||
{
|
||||
"step": "View Balance Sheet"
|
||||
},
|
||||
{
|
||||
"step": "Review Accounts Settings"
|
||||
}
|
||||
],
|
||||
"title": "Accounting Onboarding"
|
||||
}
|
||||
@@ -1,3 +1,43 @@
|
||||
<h3>{{ _("Fiscal Year") }}</h3>
|
||||
<h4>{{ _("New Fiscal Year - {0}").format(doc.name) }}</h4>
|
||||
|
||||
<p>{{ _("New fiscal year created :- ") }} {{ doc.name }}</p>
|
||||
<p>{{ _("A new fiscal year has been automatically created.") }}</p>
|
||||
|
||||
<p>{{ _("Fiscal Year Details") }}</p>
|
||||
|
||||
<table style="margin-bottom: 1rem; width: 70%">
|
||||
<tr>
|
||||
<td style="font-weight:bold; width: 40%">{{ _("Year Name") }}</td>
|
||||
<td>{{ doc.name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-weight:bold; width: 40%">{{ _("Start Date") }}</td>
|
||||
<td>{{ frappe.format_value(doc.year_start_date) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-weight:bold; width: 40%">{{ _("End Date") }}</td>
|
||||
<td>{{ frappe.format_value(doc.year_end_date) }}</td>
|
||||
</tr>
|
||||
{% if doc.companies|length > 0 %}
|
||||
<tr>
|
||||
<td style="vertical-align: top; font-weight: bold; width: 40%" rowspan="{{ doc.companies|length }}">
|
||||
{% if doc.companies|length < 2 %}
|
||||
{{ _("Company") }}
|
||||
{% else %}
|
||||
{{ _("Companies") }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ doc.companies[0].company }}</td>
|
||||
</tr>
|
||||
{% for idx in range(1, doc.companies|length) %}
|
||||
<tr>
|
||||
<td>{{ doc.companies[idx].company }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</table>
|
||||
|
||||
{% if doc.disabled %}
|
||||
<p>{{ _("The fiscal year has been automatically created in a Disabled state to maintain consistency with the previous fiscal year's status.") }}</p>
|
||||
{% endif %}
|
||||
|
||||
<p>{{ _("Please review the {0} configuration and complete any required financial setup activities.").format(frappe.utils.get_link_to_form("Fiscal Year", doc.name, frappe.bold("Fiscal Year"))) }}</p>
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"attach_print": 0,
|
||||
"channel": "Email",
|
||||
"condition": "doc.auto_created",
|
||||
"condition": "doc.auto_created == 1",
|
||||
"condition_type": "Python",
|
||||
"creation": "2018-04-25 14:19:05.440361",
|
||||
"days_in_advance": 0,
|
||||
"docstatus": 0,
|
||||
@@ -11,8 +12,10 @@
|
||||
"event": "New",
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"message": "<h4>{{ _(\"New Fiscal Year - {0}\").format(doc.name) }}</h4>\n\n<p>{{ _(\"A new fiscal year has been automatically created.\") }}</p>\n\n<p>{{ _(\"Fiscal Year Details\") }}</p>\n\n<table style=\"margin-bottom: 1rem; width: 70%\">\n <tr>\n <td style=\"font-weight:bold; width: 40%\">{{ _(\"Year Name\") }}</td>\n <td>{{ doc.name }}</td>\n </tr>\n <tr>\n <td style=\"font-weight:bold; width: 40%\">{{ _(\"Start Date\") }}</td>\n <td>{{ frappe.format_value(doc.year_start_date) }}</td>\n </tr>\n <tr>\n <td style=\"font-weight:bold; width: 40%\">{{ _(\"End Date\") }}</td>\n <td>{{ frappe.format_value(doc.year_end_date) }}</td>\n </tr>\n {% if doc.companies|length > 0 %}\n <tr>\n <td style=\"vertical-align: top; font-weight: bold; width: 40%\" rowspan=\"{{ doc.companies|length }}\">\n {% if doc.companies|length < 2 %}\n {{ _(\"Company\") }}\n {% else %}\n {{ _(\"Companies\") }}\n {% endif %}\n </td>\n <td>{{ doc.companies[0].company }}</td>\n </tr>\n {% for idx in range(1, doc.companies|length) %}\n <tr>\n <td>{{ doc.companies[idx].company }}</td>\n </tr>\n {% endfor %}\n {% endif %}\n</table>\n\n{% if doc.disabled %}\n<p>{{ _(\"The fiscal year has been automatically created in a Disabled state to maintain consistency with the previous fiscal year's status.\") }}</p>\n{% endif %}\n\n<p>{{ _(\"Please review the {0} configuration and complete any required financial setup activities.\").format(frappe.utils.get_link_to_form(\"Fiscal Year\", doc.name, frappe.bold(\"Fiscal Year\"))) }}</p>",
|
||||
"message_type": "HTML",
|
||||
"modified": "2023-11-17 08:54:51.532104",
|
||||
"minutes_offset": 0,
|
||||
"modified": "2026-02-23 17:37:03.755394",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Notification for new fiscal year",
|
||||
@@ -27,5 +30,5 @@
|
||||
],
|
||||
"send_system_notification": 0,
|
||||
"send_to_all_assignees": 0,
|
||||
"subject": "Notification for new fiscal year {{ doc.name }}"
|
||||
"subject": "New Fiscal Year {{ doc.name }} - Review Required"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"action": "Go to Page",
|
||||
"action_label": "Configure Chart of Accounts",
|
||||
"creation": "2026-02-22 18:28:15.401383",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 1,
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2026-02-23 22:44:45.540780",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Chart of Accounts",
|
||||
"owner": "Administrator",
|
||||
"path": "Tree/Account",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 0,
|
||||
"title": "Review Chart of Accounts",
|
||||
"validate_action": 1
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"action": "Create Entry",
|
||||
"action_label": "Create Payment Entry",
|
||||
"creation": "2026-02-23 19:22:12.005360",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 0,
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2026-02-23 20:19:56.482245",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Create Payment Entry",
|
||||
"owner": "Administrator",
|
||||
"reference_document": "Payment Entry",
|
||||
"route_options": "{\n \"payment_type\": \"Receive\",\n \"party_type\": \"Customer\"\n}",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 0,
|
||||
"title": "Create Payment Entry",
|
||||
"validate_action": 1
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"action": "Create Entry",
|
||||
"action_label": "Create Sales Invoice",
|
||||
"creation": "2026-02-20 13:42:38.439574",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 2,
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2026-02-23 22:16:40.931428",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Create Sales Invoice",
|
||||
"owner": "Administrator",
|
||||
"reference_document": "Sales Invoice",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 0,
|
||||
"title": "Create Sales Invoice",
|
||||
"validate_action": 1
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"action": "Update Settings",
|
||||
"action_label": "Review Accounts Settings",
|
||||
"creation": "2026-02-23 19:27:06.055104",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 0,
|
||||
"is_complete": 0,
|
||||
"is_single": 1,
|
||||
"is_skipped": 0,
|
||||
"modified": "2026-02-23 22:16:40.855407",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Review Accounts Settings",
|
||||
"owner": "Administrator",
|
||||
"path": "desk/accounts-settings",
|
||||
"reference_document": "Accounts Settings",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 0,
|
||||
"title": "Review Accounts Settings",
|
||||
"validate_action": 0
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"action": "Go to Page",
|
||||
"action_label": "Setup Sales Taxes",
|
||||
"creation": "2026-02-22 18:30:18.750391",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"form_tour": "",
|
||||
"idx": 1,
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2026-02-23 22:44:42.373227",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Setup Sales taxes",
|
||||
"owner": "Administrator",
|
||||
"path": "/desk/sales-taxes-and-charges-template",
|
||||
"reference_document": "Sales Taxes and Charges Template",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 0,
|
||||
"title": "Setup Sales taxes",
|
||||
"validate_action": 1
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"action": "View Report",
|
||||
"action_label": "View Balance Sheet",
|
||||
"creation": "2026-02-23 19:22:57.651194",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 0,
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2026-02-23 22:44:39.178107",
|
||||
"modified_by": "Administrator",
|
||||
"name": "View Balance Sheet",
|
||||
"owner": "Administrator",
|
||||
"reference_report": "Balance Sheet",
|
||||
"report_description": "View Balance Sheet",
|
||||
"report_reference_doctype": "GL Entry",
|
||||
"report_type": "Script Report",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 0,
|
||||
"title": "View Balance Sheet",
|
||||
"validate_action": 1
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from datetime import date
|
||||
|
||||
import frappe
|
||||
from frappe import _, msgprint, qb, scrub
|
||||
@@ -55,22 +56,22 @@ class DuplicatePartyAccountError(frappe.ValidationError):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_party_details(
|
||||
party=None,
|
||||
account=None,
|
||||
party_type="Customer",
|
||||
company=None,
|
||||
posting_date=None,
|
||||
bill_date=None,
|
||||
price_list=None,
|
||||
currency=None,
|
||||
doctype=None,
|
||||
ignore_permissions=False,
|
||||
fetch_payment_terms_template=True,
|
||||
party_address=None,
|
||||
company_address=None,
|
||||
shipping_address=None,
|
||||
dispatch_address=None,
|
||||
pos_profile=None,
|
||||
party: str | None = None,
|
||||
account: str | None = None,
|
||||
party_type: str = "Customer",
|
||||
company: str | None = None,
|
||||
posting_date: str | None = None,
|
||||
bill_date: str | None = None,
|
||||
price_list: str | None = None,
|
||||
currency: str | None = None,
|
||||
doctype: str | None = None,
|
||||
ignore_permissions: bool | None = False,
|
||||
fetch_payment_terms_template: bool = True,
|
||||
party_address: str | None = None,
|
||||
company_address: str | None = None,
|
||||
shipping_address: str | None = None,
|
||||
dispatch_address: str | None = None,
|
||||
pos_profile: str | None = None,
|
||||
):
|
||||
if not party:
|
||||
return frappe._dict()
|
||||
@@ -296,19 +297,9 @@ def complete_contact_details(party_details):
|
||||
contact_details = frappe._dict()
|
||||
|
||||
if party_details.party_type == "Employee":
|
||||
contact_details = frappe.db.get_value(
|
||||
"Employee",
|
||||
party_details.party,
|
||||
[
|
||||
"employee_name as contact_display",
|
||||
"prefered_email as contact_email",
|
||||
"cell_number as contact_mobile",
|
||||
"designation as contact_designation",
|
||||
"department as contact_department",
|
||||
],
|
||||
as_dict=True,
|
||||
)
|
||||
from erpnext.setup.doctype.employee.employee import _get_contact_details as get_employee_contact
|
||||
|
||||
contact_details = get_employee_contact(party_details.party)
|
||||
contact_details.update({"contact_person": None, "contact_phone": None})
|
||||
elif party_details.contact_person:
|
||||
contact_details = frappe.db.get_value(
|
||||
@@ -416,7 +407,9 @@ def set_account_and_due_date(party, account, party_type, company, posting_date,
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_party_account(party_type, party=None, company=None, include_advance=False):
|
||||
def get_party_account(
|
||||
party_type: str, party: str | None = None, company: str | None = None, include_advance: bool = False
|
||||
):
|
||||
"""Returns the account for the given `party`.
|
||||
Will first search in party (Customer / Supplier) record, if not found,
|
||||
will search in group (Customer Group / Supplier Group),
|
||||
@@ -501,7 +494,7 @@ def get_party_advance_account(party_type, party, company):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_party_bank_account(party_type, party):
|
||||
def get_party_bank_account(party_type: str, party: str):
|
||||
return frappe.db.get_value("Bank Account", {"party_type": party_type, "party": party, "is_default": 1})
|
||||
|
||||
|
||||
@@ -619,7 +612,14 @@ def validate_party_accounts(doc):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_due_date(posting_date, party_type, party, company=None, bill_date=None, template_name=None):
|
||||
def get_due_date(
|
||||
posting_date: str | date | None,
|
||||
party_type: str | None,
|
||||
party: str | None,
|
||||
company: str | None = None,
|
||||
bill_date: str | None = None,
|
||||
template_name: str | None = None,
|
||||
):
|
||||
"""Get due date from `Payment Terms Template`"""
|
||||
due_date = None
|
||||
if (bill_date or posting_date) and party:
|
||||
@@ -701,7 +701,9 @@ def validate_due_date_with_template(posting_date, due_date, bill_date, template_
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_address_tax_category(tax_category=None, billing_address=None, shipping_address=None):
|
||||
def get_address_tax_category(
|
||||
tax_category: str | None = None, billing_address: str | None = None, shipping_address: str | None = None
|
||||
):
|
||||
addr_tax_category_from = frappe.get_single_value(
|
||||
"Accounts Settings", "determine_address_tax_category_from"
|
||||
)
|
||||
@@ -717,16 +719,16 @@ def get_address_tax_category(tax_category=None, billing_address=None, shipping_a
|
||||
|
||||
@frappe.whitelist()
|
||||
def set_taxes(
|
||||
party,
|
||||
party_type,
|
||||
posting_date,
|
||||
company,
|
||||
customer_group=None,
|
||||
supplier_group=None,
|
||||
tax_category=None,
|
||||
billing_address=None,
|
||||
shipping_address=None,
|
||||
use_for_shopping_cart=None,
|
||||
party: str | None,
|
||||
party_type: str,
|
||||
posting_date: str | date | None,
|
||||
company: str | None,
|
||||
customer_group: str | None = None,
|
||||
supplier_group: str | None = None,
|
||||
tax_category: str | None = None,
|
||||
billing_address: str | None = None,
|
||||
shipping_address: str | None = None,
|
||||
use_for_shopping_cart: int | None = None,
|
||||
):
|
||||
from erpnext.accounts.doctype.tax_rule.tax_rule import get_party_details, get_tax_template
|
||||
|
||||
@@ -766,7 +768,7 @@ def set_taxes(
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_payment_terms_template(party_name, party_type, company=None):
|
||||
def get_payment_terms_template(party_name: str, party_type: str, company: str | None = None):
|
||||
if party_type not in ("Customer", "Supplier"):
|
||||
return
|
||||
template = None
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -102,7 +102,7 @@ def execute(filters=None):
|
||||
filters.periodicity, period_list, filters.accumulated_values, company=filters.company
|
||||
)
|
||||
|
||||
chart = get_chart_data(filters, columns, asset, liability, equity, currency)
|
||||
chart = get_chart_data(filters, period_list, asset, liability, equity, currency)
|
||||
|
||||
report_summary, primitive_summary = get_report_summary(
|
||||
period_list, asset, liability, equity, provisional_profit_loss, currency, filters
|
||||
@@ -231,18 +231,19 @@ def get_report_summary(
|
||||
], (net_asset - net_liability + net_equity)
|
||||
|
||||
|
||||
def get_chart_data(filters, columns, asset, liability, equity, currency):
|
||||
labels = [d.get("label") for d in columns[4:]]
|
||||
def get_chart_data(filters, chart_columns, asset, liability, equity, currency):
|
||||
labels = [col.get("label") for col in chart_columns]
|
||||
|
||||
asset_data, liability_data, equity_data = [], [], []
|
||||
|
||||
for p in columns[4:]:
|
||||
for col in chart_columns:
|
||||
key = col.get("key") or col.get("fieldname")
|
||||
if asset:
|
||||
asset_data.append(asset[-2].get(p.get("fieldname")))
|
||||
asset_data.append(asset[-2].get(key))
|
||||
if liability:
|
||||
liability_data.append(liability[-2].get(p.get("fieldname")))
|
||||
liability_data.append(liability[-2].get(key))
|
||||
if equity:
|
||||
equity_data.append(equity[-2].get(p.get("fieldname")))
|
||||
equity_data.append(equity[-2].get(key))
|
||||
|
||||
datasets = []
|
||||
if asset_data:
|
||||
|
||||
@@ -5,6 +5,7 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import add_months, flt, formatdate
|
||||
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
from erpnext.controllers.trends import get_period_date_ranges
|
||||
|
||||
@@ -13,6 +14,8 @@ def execute(filters=None):
|
||||
if not filters:
|
||||
filters = {}
|
||||
|
||||
validate_filters(filters)
|
||||
|
||||
columns = get_columns(filters)
|
||||
if filters.get("budget_against_filter"):
|
||||
dimensions = filters.get("budget_against_filter")
|
||||
@@ -31,6 +34,10 @@ def execute(filters=None):
|
||||
return columns, data, None, chart_data
|
||||
|
||||
|
||||
def validate_filters(filters):
|
||||
validate_budget_dimensions(filters)
|
||||
|
||||
|
||||
def get_budget_records(filters, dimensions):
|
||||
budget_against_field = frappe.scrub(filters["budget_against"])
|
||||
|
||||
@@ -51,7 +58,7 @@ def get_budget_records(filters, dimensions):
|
||||
b.company = %s
|
||||
AND b.docstatus = 1
|
||||
AND b.budget_against = %s
|
||||
AND b.{budget_against_field} IN ({', '.join(['%s'] * len(dimensions))})
|
||||
AND b.{budget_against_field} IN ({", ".join(["%s"] * len(dimensions))})
|
||||
AND (
|
||||
b.from_fiscal_year <= %s
|
||||
AND b.to_fiscal_year >= %s
|
||||
@@ -404,6 +411,17 @@ def get_budget_dimensions(filters):
|
||||
) # nosec
|
||||
|
||||
|
||||
def validate_budget_dimensions(filters):
|
||||
dimensions = [d.get("document_type") for d in get_dimensions(with_cost_center_and_project=True)[0]]
|
||||
if filters.get("budget_against") and filters.get("budget_against") not in dimensions:
|
||||
frappe.throw(
|
||||
title=_("Invalid Accounting Dimension"),
|
||||
msg=_("{0} is not a valid Accounting Dimension.").format(
|
||||
frappe.bold(filters.get("budget_against"))
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def build_comparison_chart_data(filters, columns, data):
|
||||
if not data:
|
||||
return None
|
||||
|
||||
@@ -145,7 +145,7 @@ def execute(filters=None):
|
||||
True,
|
||||
)
|
||||
|
||||
chart = get_chart_data(columns, data, company_currency)
|
||||
chart = get_chart_data(period_list, data, company_currency)
|
||||
|
||||
report_summary = get_report_summary(summary_data, company_currency)
|
||||
|
||||
@@ -417,12 +417,12 @@ def get_report_summary(summary_data, currency):
|
||||
return report_summary
|
||||
|
||||
|
||||
def get_chart_data(columns, data, currency):
|
||||
labels = [d.get("label") for d in columns[2:]]
|
||||
def get_chart_data(period_list, data, currency):
|
||||
labels = [period.get("label") for period in period_list]
|
||||
datasets = [
|
||||
{
|
||||
"name": section.get("section").replace("'", ""),
|
||||
"values": [section.get(d.get("fieldname")) for d in columns[2:]],
|
||||
"values": [section.get(period.get("key")) for period in period_list],
|
||||
}
|
||||
for section in data
|
||||
if section.get("parent_section") is None and section.get("currency")
|
||||
|
||||
@@ -48,22 +48,25 @@ def execute(filters=None):
|
||||
return columns, data, message, chart
|
||||
|
||||
fiscal_year = get_fiscal_year_data(filters.get("from_fiscal_year"), filters.get("to_fiscal_year"))
|
||||
companies_column, companies = get_companies(filters)
|
||||
columns = get_columns(companies_column, filters)
|
||||
company_list, companies = get_companies(filters)
|
||||
company_columns = get_company_columns(company_list, filters)
|
||||
columns = get_columns(company_columns)
|
||||
|
||||
if filters.get("report") == "Balance Sheet":
|
||||
data, message, chart, report_summary = get_balance_sheet_data(
|
||||
fiscal_year, companies, columns, filters
|
||||
fiscal_year, companies, company_columns, filters
|
||||
)
|
||||
elif filters.get("report") == "Profit and Loss Statement":
|
||||
data, message, chart, report_summary = get_profit_loss_data(fiscal_year, companies, columns, filters)
|
||||
data, message, chart, report_summary = get_profit_loss_data(
|
||||
fiscal_year, companies, company_columns, filters
|
||||
)
|
||||
else:
|
||||
data, report_summary = get_cash_flow_data(fiscal_year, companies, filters)
|
||||
|
||||
return columns, data, message, chart, report_summary
|
||||
|
||||
|
||||
def get_balance_sheet_data(fiscal_year, companies, columns, filters):
|
||||
def get_balance_sheet_data(fiscal_year, companies, company_columns, filters):
|
||||
asset = get_data(companies, "Asset", "Debit", fiscal_year, filters=filters)
|
||||
|
||||
liability = get_data(companies, "Liability", "Credit", fiscal_year, filters=filters)
|
||||
@@ -116,7 +119,7 @@ def get_balance_sheet_data(fiscal_year, companies, columns, filters):
|
||||
True,
|
||||
)
|
||||
|
||||
chart = get_chart_data(filters, columns, asset, liability, equity, company_currency)
|
||||
chart = get_chart_data(filters, company_columns, asset, liability, equity, company_currency)
|
||||
|
||||
return data, message, chart, report_summary
|
||||
|
||||
@@ -164,7 +167,7 @@ def get_root_account_name(root_type, company):
|
||||
return root_account[0][0]
|
||||
|
||||
|
||||
def get_profit_loss_data(fiscal_year, companies, columns, filters):
|
||||
def get_profit_loss_data(fiscal_year, companies, company_columns, filters):
|
||||
income, expense, net_profit_loss = get_income_expense_data(companies, fiscal_year, filters)
|
||||
company_currency = get_company_currency(filters)
|
||||
|
||||
@@ -174,7 +177,7 @@ def get_profit_loss_data(fiscal_year, companies, columns, filters):
|
||||
if net_profit_loss:
|
||||
data.append(net_profit_loss)
|
||||
|
||||
chart = get_pl_chart_data(filters, columns, income, expense, net_profit_loss, company_currency)
|
||||
chart = get_pl_chart_data(filters, company_columns, income, expense, net_profit_loss, company_currency)
|
||||
|
||||
report_summary, primitive_summary = get_pl_summary(
|
||||
companies, "", income, expense, net_profit_loss, company_currency, filters, True
|
||||
@@ -280,7 +283,30 @@ def get_account_type_based_data(account_type, companies, fiscal_year, filters):
|
||||
return data
|
||||
|
||||
|
||||
def get_columns(companies, filters):
|
||||
def get_company_columns(companies, filters):
|
||||
company_columns = []
|
||||
for company in companies:
|
||||
apply_currency_formatter = 1 if not filters.presentation_currency else 0
|
||||
currency = filters.presentation_currency
|
||||
if not currency:
|
||||
currency = erpnext.get_company_currency(company)
|
||||
|
||||
company_columns.append(
|
||||
{
|
||||
"fieldname": company,
|
||||
"label": f"{company} ({currency})",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 150,
|
||||
"apply_currency_formatter": apply_currency_formatter,
|
||||
"company_name": company,
|
||||
}
|
||||
)
|
||||
|
||||
return company_columns
|
||||
|
||||
|
||||
def get_columns(company_columns):
|
||||
columns = [
|
||||
{
|
||||
"fieldname": "account",
|
||||
@@ -298,23 +324,7 @@ def get_columns(companies, filters):
|
||||
},
|
||||
]
|
||||
|
||||
for company in companies:
|
||||
apply_currency_formatter = 1 if not filters.presentation_currency else 0
|
||||
currency = filters.presentation_currency
|
||||
if not currency:
|
||||
currency = erpnext.get_company_currency(company)
|
||||
|
||||
columns.append(
|
||||
{
|
||||
"fieldname": company,
|
||||
"label": f"{company} ({currency})",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 150,
|
||||
"apply_currency_formatter": apply_currency_formatter,
|
||||
"company_name": company,
|
||||
}
|
||||
)
|
||||
columns.extend(company_columns)
|
||||
|
||||
return columns
|
||||
|
||||
|
||||
@@ -86,6 +86,12 @@ frappe.query_reports["Consolidated Trial Balance"] = {
|
||||
fieldtype: "Check",
|
||||
default: 1,
|
||||
},
|
||||
{
|
||||
fieldname: "show_net_values",
|
||||
label: __("Show net values in opening and closing columns"),
|
||||
fieldtype: "Check",
|
||||
default: 1,
|
||||
},
|
||||
{
|
||||
fieldname: "show_group_accounts",
|
||||
label: __("Show Group Accounts"),
|
||||
|
||||
@@ -14,6 +14,7 @@ from erpnext.accounts.report.financial_statements import (
|
||||
)
|
||||
from erpnext.accounts.report.trial_balance.trial_balance import (
|
||||
accumulate_values_into_parents,
|
||||
calculate_total_row,
|
||||
calculate_values,
|
||||
get_opening_balances,
|
||||
hide_group_accounts,
|
||||
@@ -44,7 +45,6 @@ def execute(filters: dict | None = None):
|
||||
|
||||
def validate_filters(filters):
|
||||
validate_companies(filters)
|
||||
filters.show_net_values = True
|
||||
tb_validate_filters(filters)
|
||||
|
||||
|
||||
@@ -99,16 +99,20 @@ def get_data(filters) -> list[list]:
|
||||
tb_data = get_company_wise_tb_data(company_filter, reporting_currency, ignore_reporting_currency)
|
||||
consolidate_trial_balance_data(data, tb_data)
|
||||
|
||||
for d in data:
|
||||
prepare_opening_closing(d)
|
||||
|
||||
total_row = calculate_total_row(data, reporting_currency)
|
||||
|
||||
data.extend([{}, total_row])
|
||||
if filters.get("show_net_values"):
|
||||
prepare_opening_closing_for_ctb(data)
|
||||
|
||||
if not filters.get("show_group_accounts"):
|
||||
data = hide_group_accounts(data)
|
||||
|
||||
total_row = calculate_total_row(
|
||||
data, reporting_currency, show_group_accounts=filters.get("show_group_accounts")
|
||||
)
|
||||
|
||||
calculate_foreign_currency_translation_reserve(total_row, data, filters=filters)
|
||||
|
||||
data.extend([total_row])
|
||||
|
||||
if filters.get("presentation_currency"):
|
||||
update_to_presentation_currency(
|
||||
data,
|
||||
@@ -207,10 +211,6 @@ def prepare_companywise_tb_data(accounts, filters, parent_children_map, reportin
|
||||
data = []
|
||||
|
||||
for d in accounts:
|
||||
# Prepare opening closing for group account
|
||||
if parent_children_map.get(d.account) and filters.get("show_net_values"):
|
||||
prepare_opening_closing(d)
|
||||
|
||||
has_value = False
|
||||
row = {
|
||||
"account": d.name,
|
||||
@@ -242,35 +242,9 @@ def prepare_companywise_tb_data(accounts, filters, parent_children_map, reportin
|
||||
return data
|
||||
|
||||
|
||||
def calculate_total_row(data, reporting_currency):
|
||||
total_row = {
|
||||
"account": "'" + _("Total") + "'",
|
||||
"account_name": "'" + _("Total") + "'",
|
||||
"warn_if_negative": True,
|
||||
"opening_debit": 0.0,
|
||||
"opening_credit": 0.0,
|
||||
"debit": 0.0,
|
||||
"credit": 0.0,
|
||||
"closing_debit": 0.0,
|
||||
"closing_credit": 0.0,
|
||||
"parent_account": None,
|
||||
"indent": 0,
|
||||
"has_value": True,
|
||||
"currency": reporting_currency,
|
||||
}
|
||||
|
||||
for d in data:
|
||||
if not d.get("parent_account"):
|
||||
for field in value_fields:
|
||||
total_row[field] += d[field]
|
||||
|
||||
if data:
|
||||
calculate_foreign_currency_translation_reserve(total_row, data)
|
||||
|
||||
return total_row
|
||||
|
||||
|
||||
def calculate_foreign_currency_translation_reserve(total_row, data):
|
||||
def calculate_foreign_currency_translation_reserve(total_row, data, filters):
|
||||
if not data or not total_row:
|
||||
return
|
||||
opening_dr_cr_diff = total_row["opening_debit"] - total_row["opening_credit"]
|
||||
dr_cr_diff = total_row["debit"] - total_row["credit"]
|
||||
|
||||
@@ -289,7 +263,7 @@ def calculate_foreign_currency_translation_reserve(total_row, data):
|
||||
"root_type": data[idx].get("root_type"),
|
||||
"account_type": "Equity",
|
||||
"parent_account": data[idx].get("account"),
|
||||
"indent": data[idx].get("indent") + 1,
|
||||
"indent": data[idx].get("indent") + 1 if filters.get("show_group_accounts") else 0,
|
||||
"has_value": True,
|
||||
"currency": total_row.get("currency"),
|
||||
}
|
||||
@@ -297,7 +271,8 @@ def calculate_foreign_currency_translation_reserve(total_row, data):
|
||||
fctr_row["closing_debit"] = fctr_row["opening_debit"] + fctr_row["debit"]
|
||||
fctr_row["closing_credit"] = fctr_row["opening_credit"] + fctr_row["credit"]
|
||||
|
||||
prepare_opening_closing(fctr_row)
|
||||
if filters.get("show_net_values"):
|
||||
prepare_opening_closing(fctr_row)
|
||||
|
||||
data.insert(idx + 1, fctr_row)
|
||||
|
||||
@@ -396,6 +371,11 @@ def update_to_presentation_currency(data, from_currency, to_currency, date, igno
|
||||
d.update(currency=to_currency)
|
||||
|
||||
|
||||
def prepare_opening_closing_for_ctb(data):
|
||||
for d in data:
|
||||
prepare_opening_closing(d)
|
||||
|
||||
|
||||
def get_columns():
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -444,6 +444,7 @@ class TestGrossProfit(IntegrationTestCase):
|
||||
qty=-1, rate=100, posting_date=nowdate(), do_not_save=True, do_not_submit=True
|
||||
)
|
||||
sinv.is_return = 1
|
||||
sinv.items[0].allow_zero_valuation_rate = 1
|
||||
sinv = sinv.save().submit()
|
||||
|
||||
filters = frappe._dict(
|
||||
|
||||
@@ -68,7 +68,7 @@ def execute(filters=None):
|
||||
currency = filters.presentation_currency or frappe.get_cached_value(
|
||||
"Company", filters.company, "default_currency"
|
||||
)
|
||||
chart = get_chart_data(filters, columns, income, expense, net_profit_loss, currency)
|
||||
chart = get_chart_data(filters, period_list, income, expense, net_profit_loss, currency)
|
||||
|
||||
report_summary, primitive_summary = get_report_summary(
|
||||
period_list, filters.periodicity, income, expense, net_profit_loss, currency, filters
|
||||
@@ -162,18 +162,19 @@ def get_net_profit_loss(income, expense, period_list, company, currency=None, co
|
||||
return net_profit_loss
|
||||
|
||||
|
||||
def get_chart_data(filters, columns, income, expense, net_profit_loss, currency):
|
||||
labels = [d.get("label") for d in columns[4:]]
|
||||
def get_chart_data(filters, chart_columns, income, expense, net_profit_loss, currency):
|
||||
labels = [col.get("label") for col in chart_columns]
|
||||
|
||||
income_data, expense_data, net_profit = [], [], []
|
||||
|
||||
for p in columns[4:]:
|
||||
for col in chart_columns:
|
||||
key = col.get("key") or col.get("fieldname")
|
||||
if income:
|
||||
income_data.append(income[-2].get(p.get("fieldname")))
|
||||
income_data.append(income[-2].get(key))
|
||||
if expense:
|
||||
expense_data.append(expense[-2].get(p.get("fieldname")))
|
||||
expense_data.append(expense[-2].get(key))
|
||||
if net_profit_loss:
|
||||
net_profit.append(net_profit_loss.get(p.get("fieldname")))
|
||||
net_profit.append(net_profit_loss.get(key))
|
||||
|
||||
datasets = []
|
||||
if income_data:
|
||||
|
||||
@@ -390,7 +390,7 @@ def calculate_values(
|
||||
prepare_opening_closing(d)
|
||||
|
||||
|
||||
def calculate_total_row(accounts, company_currency):
|
||||
def calculate_total_row(data, company_currency, show_group_accounts=True):
|
||||
total_row = {
|
||||
"account": "'" + _("Total") + "'",
|
||||
"account_name": "'" + _("Total") + "'",
|
||||
@@ -407,10 +407,16 @@ def calculate_total_row(accounts, company_currency):
|
||||
"currency": company_currency,
|
||||
}
|
||||
|
||||
for d in accounts:
|
||||
if not d.parent_account:
|
||||
for field in value_fields:
|
||||
total_row[field] += d[field]
|
||||
def sum_value_fields(row):
|
||||
for field in value_fields:
|
||||
total_row[field] += row[field]
|
||||
|
||||
for d in data:
|
||||
if not show_group_accounts:
|
||||
sum_value_fields(d)
|
||||
|
||||
elif show_group_accounts and not d.get("parent_account"):
|
||||
sum_value_fields(d)
|
||||
|
||||
return total_row
|
||||
|
||||
@@ -456,11 +462,13 @@ def prepare_data(accounts, filters, parent_children_map, company_currency):
|
||||
row["has_value"] = has_value
|
||||
data.append(row)
|
||||
|
||||
total_row = calculate_total_row(accounts, company_currency)
|
||||
|
||||
if not filters.get("show_group_accounts"):
|
||||
data = hide_group_accounts(data)
|
||||
|
||||
total_row = calculate_total_row(
|
||||
data, company_currency, show_group_accounts=filters.get("show_group_accounts")
|
||||
)
|
||||
|
||||
data.extend([{}, total_row])
|
||||
|
||||
return data
|
||||
|
||||
@@ -147,7 +147,12 @@ def get_appropriate_company(filters):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_invoiced_item_gross_margin(sales_invoice=None, item_code=None, company=None, with_item_data=False):
|
||||
def get_invoiced_item_gross_margin(
|
||||
sales_invoice: str | None = None,
|
||||
item_code: str | None = None,
|
||||
company: str | None = None,
|
||||
with_item_data: bool = False,
|
||||
):
|
||||
from erpnext.accounts.report.gross_profit.gross_profit import GrossProfitGenerator
|
||||
|
||||
sales_invoice = sales_invoice or frappe.form_dict.get("sales_invoice")
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
|
||||
from collections import defaultdict
|
||||
from datetime import date, datetime
|
||||
from json import loads
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
@@ -60,15 +61,15 @@ OUTSTANDING_DOCTYPES = frozenset(["Sales Invoice", "Purchase Invoice", "Fees"])
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_fiscal_year(
|
||||
date=None,
|
||||
fiscal_year=None,
|
||||
label="Date",
|
||||
verbose=1,
|
||||
company=None,
|
||||
as_dict=False,
|
||||
boolean=None,
|
||||
raise_on_missing=True,
|
||||
truncate=False,
|
||||
date: str | datetime | None = None,
|
||||
fiscal_year: str | None = None,
|
||||
label: str = "Date",
|
||||
verbose: int = 1,
|
||||
company: str | None = None,
|
||||
as_dict: bool = False,
|
||||
boolean: str | bool | None = None,
|
||||
raise_on_missing: bool = True,
|
||||
truncate: bool = False,
|
||||
):
|
||||
if isinstance(raise_on_missing, str):
|
||||
raise_on_missing = loads(raise_on_missing)
|
||||
@@ -93,14 +94,14 @@ def get_fiscal_year(
|
||||
|
||||
|
||||
def get_fiscal_years(
|
||||
transaction_date=None,
|
||||
fiscal_year=None,
|
||||
label="Date",
|
||||
verbose=1,
|
||||
company=None,
|
||||
as_dict=False,
|
||||
boolean=None,
|
||||
raise_on_missing=True,
|
||||
transaction_date: str | None = None,
|
||||
fiscal_year: str | None = None,
|
||||
label: str = "Date",
|
||||
verbose: int = 1,
|
||||
company: str | None = None,
|
||||
as_dict: bool = False,
|
||||
boolean: str | None = None,
|
||||
raise_on_missing: bool = True,
|
||||
):
|
||||
if transaction_date:
|
||||
transaction_date = getdate(transaction_date)
|
||||
@@ -171,7 +172,7 @@ def _get_fiscal_years(company=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_fiscal_year_filter_field(company=None):
|
||||
def get_fiscal_year_filter_field(company: str | None = None):
|
||||
field = {"fieldtype": "Select", "options": [], "operator": "Between", "query_value": True}
|
||||
fiscal_years = get_fiscal_years(company=company)
|
||||
for fiscal_year in fiscal_years:
|
||||
@@ -199,18 +200,18 @@ def validate_fiscal_year(date, fiscal_year, company, label="Date", doc=None):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_balance_on(
|
||||
account=None,
|
||||
date=None,
|
||||
party_type=None,
|
||||
party=None,
|
||||
company=None,
|
||||
in_account_currency=True,
|
||||
cost_center=None,
|
||||
ignore_account_permission=False,
|
||||
account_type=None,
|
||||
start_date=None,
|
||||
finance_book=None,
|
||||
include_default_fb_balances=False,
|
||||
account: str | None = None,
|
||||
date: str | date | None = None,
|
||||
party_type: str | None = None,
|
||||
party: str | None = None,
|
||||
company: str | None = None,
|
||||
in_account_currency: bool = True,
|
||||
cost_center: str | None = None,
|
||||
ignore_account_permission: bool = False,
|
||||
account_type: str | None = None,
|
||||
start_date: str | None = None,
|
||||
finance_book: str | None = None,
|
||||
include_default_fb_balances: bool = False,
|
||||
):
|
||||
if not account and frappe.form_dict.get("account"):
|
||||
account = frappe.form_dict.get("account")
|
||||
@@ -437,7 +438,7 @@ def get_count_on(account, fieldname, date):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_ac(args=None):
|
||||
def add_ac(args: frappe._dict | None = None):
|
||||
from frappe.desk.treeview import make_tree_args
|
||||
|
||||
if not args:
|
||||
@@ -469,7 +470,7 @@ def add_ac(args=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def add_cc(args=None):
|
||||
def add_cc(args: frappe._dict | None = None):
|
||||
from frappe.desk.treeview import make_tree_args
|
||||
|
||||
if not args:
|
||||
@@ -500,7 +501,8 @@ def _build_dimensions_dict_for_exc_gain_loss(
|
||||
dimensions_dict = frappe._dict()
|
||||
if entry and active_dimensions:
|
||||
for dim in active_dimensions:
|
||||
dimensions_dict[dim.fieldname] = entry.get(dim.fieldname)
|
||||
if entry_dimension := entry.get(dim.fieldname):
|
||||
dimensions_dict[dim.fieldname] = entry_dimension
|
||||
return dimensions_dict
|
||||
|
||||
|
||||
@@ -1153,7 +1155,7 @@ def remove_ref_doc_link_from_pe(
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_company_default(company, fieldname, ignore_validation=False):
|
||||
def get_company_default(company: str, fieldname: str, ignore_validation: bool = False):
|
||||
value = frappe.get_cached_value("Company", company, fieldname)
|
||||
|
||||
if not ignore_validation and not value:
|
||||
@@ -1338,7 +1340,9 @@ def get_companies():
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_children(doctype, parent, company, is_root=False, include_disabled=False):
|
||||
def get_children(
|
||||
doctype: str, parent: str, company: str, is_root: bool = False, include_disabled: bool = False
|
||||
):
|
||||
if isinstance(include_disabled, str):
|
||||
include_disabled = loads(include_disabled)
|
||||
from erpnext.accounts.report.financial_statements import sort_accounts
|
||||
@@ -1371,7 +1375,12 @@ def get_children(doctype, parent, company, is_root=False, include_disabled=False
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_account_balances(accounts, company, finance_book=None, include_default_fb_balances=False):
|
||||
def get_account_balances(
|
||||
accounts: str | list,
|
||||
company: str,
|
||||
finance_book: str | None = None,
|
||||
include_default_fb_balances: bool = False,
|
||||
):
|
||||
if isinstance(accounts, str):
|
||||
accounts = loads(accounts)
|
||||
|
||||
@@ -1464,7 +1473,9 @@ def create_payment_gateway_account(gateway, payment_channel="Email", company=Non
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_cost_center(docname, cost_center_name, cost_center_number, company, merge):
|
||||
def update_cost_center(
|
||||
docname: str, cost_center_name: str, cost_center_number: str, company: str, merge: bool
|
||||
):
|
||||
"""
|
||||
Renames the document by adding the number as a prefix to the current name and updates
|
||||
all transaction where it was present.
|
||||
@@ -1544,7 +1555,7 @@ def parse_naming_series_variable(doc, variable):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_coa(doctype, parent, is_root=None, chart=None):
|
||||
def get_coa(doctype: str, parent: str, is_root: bool | None = None, chart: str | None = None):
|
||||
from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import (
|
||||
build_tree_from_json,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"allow_roles": [
|
||||
{
|
||||
"role": "Accounts Manager"
|
||||
},
|
||||
{
|
||||
"role": "Accounts User"
|
||||
},
|
||||
{
|
||||
"role": "Quality Manager"
|
||||
}
|
||||
],
|
||||
"creation": "2026-02-23 20:56:50.917521",
|
||||
"docstatus": 0,
|
||||
"doctype": "Module Onboarding",
|
||||
"idx": 0,
|
||||
"is_complete": 0,
|
||||
"modified": "2026-02-26 10:45:47.970714",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Onboarding",
|
||||
"owner": "Administrator",
|
||||
"steps": [
|
||||
{
|
||||
"step": "Learn Asset"
|
||||
},
|
||||
{
|
||||
"step": "Create Asset Category"
|
||||
},
|
||||
{
|
||||
"step": "Create Asset Item"
|
||||
},
|
||||
{
|
||||
"step": "Create Asset Location"
|
||||
},
|
||||
{
|
||||
"step": "Create Existing Asset"
|
||||
},
|
||||
{
|
||||
"step": "View Balance Sheet"
|
||||
}
|
||||
],
|
||||
"title": "Assets Setup"
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"action": "Create Entry",
|
||||
"action_label": "Create Asset Category",
|
||||
"creation": "2026-02-23 20:50:50.211884",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 1,
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2026-02-23 20:50:50.211884",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Create Asset Category",
|
||||
"owner": "Administrator",
|
||||
"reference_document": "Asset Category",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 0,
|
||||
"title": "Create Asset Category",
|
||||
"validate_action": 1
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"action": "Create Entry",
|
||||
"action_label": "Create Asset Item",
|
||||
"creation": "2026-02-23 20:52:40.135614",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 1,
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2026-02-23 22:31:53.211343",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Create Asset Item",
|
||||
"owner": "Administrator",
|
||||
"reference_document": "Item",
|
||||
"route_options": "{\n \"is_fixed_asset\": 1,\n \"is_stock_item\": 0\n}",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 1,
|
||||
"title": "Create Asset Item",
|
||||
"validate_action": 1
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"action": "Create Entry",
|
||||
"action_label": "Create Asset Location",
|
||||
"creation": "2026-02-23 20:53:07.450876",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 0,
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2026-02-23 20:53:07.450876",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Create Asset Location",
|
||||
"owner": "Administrator",
|
||||
"reference_document": "Location",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 0,
|
||||
"title": "Create Asset Location",
|
||||
"validate_action": 1
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"action": "Create Entry",
|
||||
"action_label": "Create Existing Asset",
|
||||
"creation": "2026-02-23 20:54:25.961869",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 3,
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2026-02-23 22:31:48.789836",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Create Existing Asset",
|
||||
"owner": "Administrator",
|
||||
"reference_document": "Asset",
|
||||
"route_options": "{\n \"asset_type\": \"Existing Asset\"\n}",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 1,
|
||||
"title": "Create Existing Asset",
|
||||
"validate_action": 1
|
||||
}
|
||||
20
erpnext/assets/onboarding_step/learn_asset/learn_asset.json
Normal file
20
erpnext/assets/onboarding_step/learn_asset/learn_asset.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"action": "View Docs",
|
||||
"action_label": "Learn Asset",
|
||||
"creation": "2026-02-23 21:00:47.254648",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 0,
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2026-02-26 10:44:59.557156",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Learn Asset",
|
||||
"owner": "Administrator",
|
||||
"path": "https://docs.frappe.io/erpnext/assets/introduction",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 0,
|
||||
"title": "Learn Asset",
|
||||
"validate_action": 1
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"action": "View Report",
|
||||
"action_label": "View Balance Sheet",
|
||||
"creation": "2026-02-23 19:22:57.651194",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 0,
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2026-02-23 22:44:39.178107",
|
||||
"modified_by": "Administrator",
|
||||
"name": "View Balance Sheet",
|
||||
"owner": "Administrator",
|
||||
"reference_report": "Balance Sheet",
|
||||
"report_description": "View Balance Sheet",
|
||||
"report_reference_doctype": "GL Entry",
|
||||
"report_type": "Script Report",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 0,
|
||||
"title": "View Balance Sheet",
|
||||
"validate_action": 1
|
||||
}
|
||||
@@ -428,7 +428,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
this.frm.add_custom_button(
|
||||
__("Payment Request"),
|
||||
function () {
|
||||
me.make_payment_request();
|
||||
me.make_payment_request_with_schedule();
|
||||
},
|
||||
__("Create")
|
||||
);
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"order_confirmation_date",
|
||||
"column_break_7",
|
||||
"transaction_date",
|
||||
"transaction_time",
|
||||
"schedule_date",
|
||||
"column_break1",
|
||||
"is_subcontracted",
|
||||
@@ -77,16 +78,16 @@
|
||||
"total_taxes_and_charges",
|
||||
"totals_section",
|
||||
"grand_total",
|
||||
"in_words",
|
||||
"column_break4",
|
||||
"disable_rounded_total",
|
||||
"rounding_adjustment",
|
||||
"column_break4",
|
||||
"in_words",
|
||||
"rounded_total",
|
||||
"base_totals_section",
|
||||
"base_grand_total",
|
||||
"base_rounding_adjustment",
|
||||
"column_break_jkoz",
|
||||
"base_in_words",
|
||||
"column_break_jkoz",
|
||||
"base_rounding_adjustment",
|
||||
"base_rounded_total",
|
||||
"section_break_tnkm",
|
||||
"advance_paid",
|
||||
@@ -1311,6 +1312,14 @@
|
||||
{
|
||||
"fieldname": "section_break_tnkm",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"default": "Now",
|
||||
"depends_on": "is_internal_supplier",
|
||||
"fieldname": "transaction_time",
|
||||
"fieldtype": "Time",
|
||||
"label": "Time",
|
||||
"mandatory_depends_on": "is_internal_supplier"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
@@ -1318,7 +1327,7 @@
|
||||
"idx": 105,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-02-06 17:07:24.249692",
|
||||
"modified": "2026-03-02 00:40:47.119584",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order",
|
||||
|
||||
@@ -167,6 +167,7 @@ class PurchaseOrder(BuyingController):
|
||||
total_qty: DF.Float
|
||||
total_taxes_and_charges: DF.Currency
|
||||
transaction_date: DF.Date
|
||||
transaction_time: DF.Time | None
|
||||
# end: auto-generated types
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"creation": "2026-02-22 16:46:17.299107",
|
||||
"docstatus": 0,
|
||||
"doctype": "Form Tour",
|
||||
"first_document": 0,
|
||||
"idx": 0,
|
||||
"include_name_field": 0,
|
||||
"is_standard": 1,
|
||||
"list_name": "List",
|
||||
"modified": "2026-02-22 16:46:17.299107",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier Form Tour",
|
||||
"new_document_form": 0,
|
||||
"owner": "Administrator",
|
||||
"reference_doctype": "Supplier",
|
||||
"report_name": "",
|
||||
"save_on_complete": 1,
|
||||
"steps": [
|
||||
{
|
||||
"description": "Enter the Full Name of the Supplier",
|
||||
"fieldname": "supplier_name",
|
||||
"fieldtype": "Data",
|
||||
"has_next_condition": 0,
|
||||
"hide_buttons": 0,
|
||||
"is_table_field": 0,
|
||||
"label": "Supplier Name",
|
||||
"modal_trigger": 0,
|
||||
"next_on_click": 0,
|
||||
"offset_x": 0,
|
||||
"offset_y": 0,
|
||||
"popover_element": 0,
|
||||
"position": "Left",
|
||||
"title": "Full Name",
|
||||
"ui_tour": 0
|
||||
}
|
||||
],
|
||||
"title": "Supplier Form Tour",
|
||||
"track_steps": 0,
|
||||
"ui_tour": 0,
|
||||
"view_name": "Workspaces"
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"allow_roles": [
|
||||
{
|
||||
"role": "Purchase Manager"
|
||||
},
|
||||
{
|
||||
"role": "Purchase User"
|
||||
}
|
||||
],
|
||||
"creation": "2026-02-19 10:53:58.761773",
|
||||
"docstatus": 0,
|
||||
"doctype": "Module Onboarding",
|
||||
"idx": 0,
|
||||
"is_complete": 0,
|
||||
"modified": "2026-02-25 16:59:28.328912",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Buying Onboarding",
|
||||
"owner": "Administrator",
|
||||
"steps": [
|
||||
{
|
||||
"step": "Create Supplier"
|
||||
},
|
||||
{
|
||||
"step": "Create Item"
|
||||
},
|
||||
{
|
||||
"step": "Create Purchase Order"
|
||||
},
|
||||
{
|
||||
"step": "Create Purchase Invoice"
|
||||
},
|
||||
{
|
||||
"step": "View Purchase Order Analysis"
|
||||
},
|
||||
{
|
||||
"step": "Review Buying Settings"
|
||||
}
|
||||
],
|
||||
"title": "Buying Setup"
|
||||
}
|
||||
20
erpnext/buying/onboarding_step/create_item/create_item.json
Normal file
20
erpnext/buying/onboarding_step/create_item/create_item.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"action": "Create Entry",
|
||||
"action_label": "Create Item",
|
||||
"creation": "2026-02-19 12:38:40.865013",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 8,
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2026-02-25 16:58:56.384284",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Create Item",
|
||||
"owner": "Administrator",
|
||||
"reference_document": "Item",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 0,
|
||||
"title": "Create Item",
|
||||
"validate_action": 1
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"action": "Create Entry",
|
||||
"action_label": "Create Purchase Invoice",
|
||||
"creation": "2026-02-19 12:38:14.868162",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 5,
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2026-02-25 16:58:56.386439",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Create Purchase Invoice",
|
||||
"owner": "Administrator",
|
||||
"reference_document": "Purchase Invoice",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 0,
|
||||
"title": "Create Purchase Invoice",
|
||||
"validate_action": 1
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"action": "Create Entry",
|
||||
"action_label": "Create Purchase Order",
|
||||
"creation": "2026-02-19 12:13:44.068135",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"form_tour": "",
|
||||
"idx": 2,
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2026-02-25 16:58:56.379480",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Create Purchase Order",
|
||||
"owner": "Administrator",
|
||||
"reference_document": "Purchase Order",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 1,
|
||||
"title": "Create Purchase Order",
|
||||
"validate_action": 1
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"action": "Create Entry",
|
||||
"action_label": "Create supplier",
|
||||
"creation": "2026-02-19 10:53:56.936107",
|
||||
"description": "",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"form_tour": "Supplier Form Tour",
|
||||
"idx": 2,
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2026-02-25 16:58:56.375824",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Create Supplier",
|
||||
"owner": "Administrator",
|
||||
"reference_document": "Supplier",
|
||||
"show_form_tour": 1,
|
||||
"show_full_form": 1,
|
||||
"title": "Create Supplier",
|
||||
"validate_action": 1
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"action": "Update Settings",
|
||||
"action_label": "Review Buying Settings",
|
||||
"creation": "2026-02-23 20:27:23.664752",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 0,
|
||||
"is_complete": 0,
|
||||
"is_single": 1,
|
||||
"is_skipped": 0,
|
||||
"modified": "2026-02-25 16:58:56.388794",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Review Buying Settings",
|
||||
"owner": "Administrator",
|
||||
"path": "desk/buying-settings",
|
||||
"reference_document": "Buying Settings",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 0,
|
||||
"title": "Review Buying Settings",
|
||||
"validate_action": 0
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"action": "View Report",
|
||||
"action_label": "View Purchase Order Analysis",
|
||||
"creation": "2026-02-23 20:26:29.245112",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 0,
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2026-02-25 10:33:43.731928",
|
||||
"modified_by": "Administrator",
|
||||
"name": "View Purchase Order Analysis",
|
||||
"owner": "Administrator",
|
||||
"reference_report": "Purchase Order Analysis",
|
||||
"report_description": "View Purchase Order Analysis",
|
||||
"report_reference_doctype": "Purchase Order",
|
||||
"report_type": "Script Report",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 0,
|
||||
"title": "View Purchase Order Analysis",
|
||||
"validate_action": 1
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user