Compare commits

..

1 Commits

Author SHA1 Message Date
coderabbitai[bot]
f6c8d5e038 📝 Add docstrings to fix/email-campaign-timeout
Docstrings generation was requested by @pratikb64.

* https://github.com/frappe/erpnext/pull/51994#issuecomment-3783742931

The following files were modified:

* `erpnext/crm/doctype/email_campaign/email_campaign.py`
2026-01-22 10:55:07 +00:00
567 changed files with 131535 additions and 179866 deletions

View File

@@ -60,7 +60,7 @@ body:
description: Share exact version number of Frappe and ERPNext you are using.
placeholder: |
Frappe version -
ERPNext version -
ERPNext Verion -
validations:
required: true

View File

@@ -1,39 +0,0 @@
module.exports = async ({ github, context, localeChanged }) => {
const pr = context.payload.pull_request.number
const { owner, repo } = context.repo
const { data: me } = await github.rest.users.getAuthenticated()
const botUser = me.login
const reviews = await github.rest.pulls.listReviews({
owner,
repo,
pull_number: pr
})
const botReview = reviews.data
.reverse()
.find(r => r.user.login === botUser && r.state === "CHANGES_REQUESTED")
if (localeChanged) {
if (!botReview) {
await github.rest.pulls.createReview({
owner,
repo,
pull_number: pr,
event: "REQUEST_CHANGES",
body: "Translations are managed through [Crowdin](https://crowdin.com/project/frappe/). Consider contributing through Crowdin."
})
}
} else {
if (botReview) {
await github.rest.pulls.dismissReview({
owner,
repo,
pull_number: pr,
review_id: botReview.id,
message: ""
})
}
}
}

View File

@@ -1,44 +0,0 @@
name: Prevent Locale Changes
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
check-locale:
permissions:
contents: read
runs-on: ubuntu-latest
if: github.actor != 'frappe-pr-bot'
# Exemption for the bot used for Crowdin Sync and POT File generation.
concurrency:
group: prevent-locale-changes-${{ github.event.pull_request.number }}
cancel-in-progress: true
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Detect locale changes
id: check
run: |
git fetch origin ${{ github.base_ref }}
if git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -q '^erpnext/locale/'; then
echo "locale_changed=true" >> $GITHUB_OUTPUT
else
echo "locale_changed=false" >> $GITHUB_OUTPUT
fi
- name: Manage locale review
uses: actions/github-script@v7
with:
github-token: ${{ secrets.RELEASE_TOKEN }}
script: |
const localeChanged = "${{ steps.check.outputs.locale_changed }}" === "true"
const handler = require('./.github/helper/locale_review.js')
await handler({ github, context, localeChanged })

View File

@@ -7,7 +7,6 @@ on:
paths:
- "**.js"
- "**.css"
- "**.svg"
- "**.md"
- "**.html"
- 'crowdin.yml'

View File

@@ -1,4 +1,3 @@
<div align="center">
<a href="https://frappe.io/erpnext">
<img src="./erpnext/public/images/v16/erpnext.svg" alt="ERPNext Logo" height="80px" width="80xp"/>

View File

@@ -1,7 +1,7 @@
# Security Policy
The ERPNext team and community take security issues seriously. To report a security issue, please go through the information mentioned [here](https://frappe.io/security).
The ERPNext team and community take security issues seriously. To report a security issue, fill out the form at [https://erpnext.com/security/report](https://erpnext.com/security/report).
You can help us make ERPNext and all its users more secure by following the [Reporting guidelines](https://frappe.io/security).
You can help us make ERPNext and all it's users more secure by following the [Reporting guidelines](https://erpnext.com/security).
We appreciate your efforts to responsibly disclose your findings. We'll endeavor to respond quickly, and will keep you updated throughout the process.
We appreciate your efforts to responsibly disclose your findings. We'll endeavor to respond quickly, and will keep you updated throughout the process.

View File

@@ -1,50 +0,0 @@
{
"cards": [
{
"card": "Total Outgoing Bills"
},
{
"card": "Total Incoming Bills"
},
{
"card": "Total Incoming Payment"
},
{
"card": "Total Outgoing Payment"
}
],
"charts": [
{
"chart": "Incoming Bills (Purchase Invoice)",
"width": "Half"
},
{
"chart": "Outgoing Bills (Sales Invoice)",
"width": "Half"
},
{
"chart": "Accounts Receivable Ageing",
"width": "Half"
},
{
"chart": "Accounts Payable Ageing",
"width": "Half"
},
{
"chart": "Bank Balance",
"width": "Full"
}
],
"creation": "2026-01-26 21:25:12.793893",
"dashboard_name": "Payments",
"docstatus": 0,
"doctype": "Dashboard",
"idx": 0,
"is_default": 0,
"is_standard": 1,
"modified": "2026-01-26 21:25:12.793893",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payments",
"owner": "Administrator"
}

View File

@@ -52,7 +52,7 @@ class ERPNextAddress(Address):
@frappe.whitelist()
def get_shipping_address(company: str, address: str | None = None):
def get_shipping_address(company, address=None):
filters = [
["Dynamic Link", "link_doctype", "=", "Company"],
["Dynamic Link", "link_name", "=", company],

View File

@@ -13,15 +13,15 @@ from frappe.utils.nestedset import get_descendants_of
@frappe.whitelist()
@cache_source
def get(
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,
chart_name=None,
chart=None,
no_cache=None,
filters=None,
from_date=None,
to_date=None,
timespan=None,
time_interval=None,
heatmap_year=None,
):
if chart_name:
chart = frappe.get_doc("Dashboard Chart", chart_name)

View File

@@ -471,7 +471,7 @@ class Account(NestedSet):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_parent_account(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict):
def get_parent_account(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql(
"""select name from tabAccount
where is_group = 1 and docstatus != 2 and company = {}
@@ -515,9 +515,7 @@ def get_account_autoname(account_number, account_name, company):
@frappe.whitelist()
def update_account_number(
name: str, account_name: str, account_number: str | None = None, from_descendant: bool = False
):
def update_account_number(name, account_name, account_number=None, from_descendant=False):
_ensure_idle_system()
account = frappe.get_cached_doc("Account", name)
if not account:
@@ -579,7 +577,7 @@ def update_account_number(
@frappe.whitelist()
def merge_account(old: str, new: str):
def merge_account(old, new):
_ensure_idle_system()
# Validate properties before merging
new_account = frappe.get_cached_doc("Account", new)
@@ -616,7 +614,7 @@ def merge_account(old: str, new: str):
@frappe.whitelist()
def get_root_company(company: str):
def get_root_company(company):
# return the topmost company in the hierarchy
ancestors = get_ancestors_of("Company", company, "lft asc")
return [ancestors[0]] if ancestors else []

View File

@@ -99,7 +99,7 @@ def identify_is_group(child):
@frappe.whitelist()
def get_chart(chart_template: str | None, existing_company: str | None = None):
def get_chart(chart_template, existing_company=None):
chart = {}
if existing_company:
return get_account_tree_from_existing_company(existing_company)
@@ -132,7 +132,7 @@ def get_chart(chart_template: str | None, existing_company: str | None = None):
@frappe.whitelist()
def get_charts_for_country(country: str, with_standard: bool = False):
def get_charts_for_country(country, with_standard=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: str, bank_account: str):
def validate_bank_account(coa, bank_account):
accounts = []
chart = get_chart(coa)
@@ -244,9 +244,7 @@ def validate_bank_account(coa: str, bank_account: str):
@frappe.whitelist()
def build_tree_from_json(
chart_template: str, chart_data: dict | None = None, from_coa_importer: bool = False
):
def build_tree_from_json(chart_template, chart_data=None, from_coa_importer=False):
"""get chart template from its folder and parse the json to be rendered as tree"""
chart = chart_data or get_chart(chart_template)

View File

@@ -33,17 +33,6 @@
},
"account_number": "1151.000"
},
"Pajak Dibayar di Muka": {
"PPN Masukan": {
"account_number": "1152.001",
"account_type": "Tax"
},
"PPh 23 Dibayar di Muka": {
"account_number": "1152.002",
"account_type": "Tax"
},
"account_number": "1152.000"
},
"account_number": "1150.000"
},
"Kas": {
@@ -108,6 +97,17 @@
},
"account_number": "1130.000"
},
"Pajak Dibayar di Muka": {
"PPN Masukan": {
"account_number": "1151.001",
"account_type": "Tax"
},
"PPh 23 Dibayar di Muka": {
"account_number": "1152.001",
"account_type": "Tax"
},
"account_number": "1150.000"
},
"account_number": "1100.000"
},

View File

@@ -103,6 +103,10 @@ 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:
@@ -206,7 +210,7 @@ def delete_accounting_dimension(doc):
@frappe.whitelist()
def disable_dimension(doc: str):
def disable_dimension(doc):
if frappe.in_test:
toggle_disabling(doc=doc)
else:
@@ -237,26 +241,34 @@ def get_doctypes_with_dimensions():
return frappe.get_hooks("accounting_dimension_doctypes")
def get_accounting_dimensions(as_list=True):
accounting_dimensions = frappe.get_all(
"Accounting Dimension",
fields=["label", "fieldname", "disabled", "document_type"],
filters={"disabled": 0},
)
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,
)
if as_list:
return [d.fieldname for d in accounting_dimensions]
return [d.fieldname for d in frappe.flags.accounting_dimensions]
else:
return accounting_dimensions
return frappe.flags.accounting_dimensions
def get_checks_for_pl_and_bs_accounts():
return frappe.db.sql(
"""SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
if frappe.flags.accounting_dimensions_details is None:
# nosemgrep
frappe.flags.accounting_dimensions_details = frappe.db.sql(
"""SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
FROM `tabAccounting Dimension`p ,`tabAccounting Dimension Detail` c
WHERE p.name = c.parent AND p.disabled = 0""",
as_dict=1,
)
as_dict=1,
)
return frappe.flags.accounting_dimensions_details
def get_dimension_with_children(doctype, dimensions):
@@ -274,7 +286,7 @@ def get_dimension_with_children(doctype, dimensions):
@frappe.whitelist()
def get_dimensions(with_cost_center_and_project: str | bool = False):
def get_dimensions(with_cost_center_and_project=False):
c = frappe.qb.DocType("Accounting Dimension Detail")
p = frappe.qb.DocType("Accounting Dimension")
dimension_filters = (

View File

@@ -69,34 +69,37 @@ class AccountingDimensionFilter(Document):
def 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,
)
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,
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,
)
return dimension_filter_map
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
def build_map(map_object, dimension, account, filter_value, allow_or_restrict, is_mandatory):

View File

@@ -97,7 +97,7 @@ def validate_accounting_period_on_doc_save(doc, method=None):
if doc.doctype == "Bank Clearance":
return
elif doc.doctype == "Asset":
if doc.asset_type == "Existing Asset":
if doc.is_existing_asset:
return
else:
date = doc.available_for_use_date

View File

@@ -20,10 +20,6 @@
"enable_common_party_accounting",
"allow_multi_currency_invoices_against_single_party_account",
"confirm_before_resetting_posting_date",
"analytics_section",
"enable_accounting_dimensions",
"column_break_vtnr",
"enable_discounts_and_margin",
"journals_section",
"merge_similar_account_heads",
"deferred_accounting_settings_section",
@@ -55,16 +51,12 @@
"allow_pegged_currencies_exchange_rates",
"column_break_yuug",
"stale_days",
"payments_tab",
"section_break_jpd0",
"auto_reconcile_payments",
"auto_reconciliation_job_trigger",
"reconciliation_queue_size",
"column_break_resa",
"exchange_gain_loss_posting_date",
"payment_options_section",
"enable_loyalty_point_program",
"column_break_ctam",
"invoicing_settings_tab",
"accounts_transactions_settings_section",
"over_billing_allowance",
@@ -205,7 +197,7 @@
"description": "Payment Terms from orders will be fetched into the invoices as is",
"fieldname": "automatically_fetch_payment_terms",
"fieldtype": "Check",
"label": "Automatically Fetch Payment Terms from Order/Quotation"
"label": "Automatically Fetch Payment Terms from Order"
},
{
"description": "The percentage you are allowed to bill more against the amount ordered. For example, if the order value is $100 for an item and tolerance is set as 10%, then you are allowed to bill up to $110 ",
@@ -289,7 +281,7 @@
},
{
"default": "0",
"description": "Learn about <a href=\"https://docs.frappe.io/erpnext/user/manual/en/common_party_accounting\" rel=\"noopener noreferrer\">Common Party</a>",
"description": "Learn about <a href=\"https://docs.frappe.io/erpnext/user/manual/en/common_party_accounting\">Common Party</a>",
"fieldname": "enable_common_party_accounting",
"fieldtype": "Check",
"label": "Enable Common Party Accounting"
@@ -645,59 +637,16 @@
"fieldname": "budget_section",
"fieldtype": "Section Break",
"label": "Budget"
},
{
"fieldname": "analytics_section",
"fieldtype": "Section Break",
"label": "Analytical Accounting"
},
{
"fieldname": "column_break_vtnr",
"fieldtype": "Column Break"
},
{
"default": "0",
"description": "Apply discounts and margins on products",
"fieldname": "enable_discounts_and_margin",
"fieldtype": "Check",
"label": "Enable Discounts and Margin"
},
{
"fieldname": "payments_tab",
"fieldtype": "Tab Break",
"label": "Payments"
},
{
"fieldname": "payment_options_section",
"fieldtype": "Section Break",
"label": "Payment Options"
},
{
"default": "0",
"fieldname": "enable_loyalty_point_program",
"fieldtype": "Check",
"label": "Enable Loyalty Point Program"
},
{
"fieldname": "column_break_ctam",
"fieldtype": "Column Break"
},
{
"default": "0",
"description": "Enable cost center, projects and other custom accounting dimensions",
"fieldname": "enable_accounting_dimensions",
"fieldtype": "Check",
"label": "Enable Accounting Dimensions"
}
],
"grid_page_length": 50,
"hide_toolbar": 0,
"hide_toolbar": 1,
"icon": "icon-cog",
"idx": 1,
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2026-02-27 01:04:09.415288",
"modified": "2026-01-11 18:30:45.968531",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",

View File

@@ -12,28 +12,6 @@ from frappe.utils import cint
from erpnext.accounts.utils import sync_auto_reconcile_config
SELLING_DOCTYPES = [
"Sales Invoice",
"Sales Order",
"Delivery Note",
"Quotation",
"Sales Invoice Item",
"Sales Order Item",
"Delivery Note Item",
"Quotation Item",
"POS Invoice",
"POS Invoice Item",
]
BUYING_DOCTYPES = [
"Purchase Invoice",
"Purchase Order",
"Purchase Receipt",
"Purchase Invoice Item",
"Purchase Order Item",
"Purchase Receipt Item",
]
class AccountsSettings(Document):
# begin: auto-generated types
@@ -65,12 +43,9 @@ class AccountsSettings(Document):
default_ageing_range: DF.Data | None
delete_linked_ledger_entries: DF.Check
determine_address_tax_category_from: DF.Literal["Billing Address", "Shipping Address"]
enable_accounting_dimensions: DF.Check
enable_common_party_accounting: DF.Check
enable_discounts_and_margin: DF.Check
enable_fuzzy_matching: DF.Check
enable_immutable_ledger: DF.Check
enable_loyalty_point_program: DF.Check
enable_party_matching: DF.Check
exchange_gain_loss_posting_date: DF.Literal["Invoice", "Payment", "Reconciliation Date"]
fetch_valuation_rate_for_internal_transaction: DF.Check
@@ -123,18 +98,6 @@ class AccountsSettings(Document):
if old_doc.show_payment_schedule_in_print != self.show_payment_schedule_in_print:
self.enable_payment_schedule_in_print()
if old_doc.enable_accounting_dimensions != self.enable_accounting_dimensions:
toggle_accounting_dimension_sections(not self.enable_accounting_dimensions)
clear_cache = True
if old_doc.enable_discounts_and_margin != self.enable_discounts_and_margin:
toggle_sales_discount_section(not self.enable_discounts_and_margin)
clear_cache = True
if old_doc.enable_loyalty_point_program != self.enable_loyalty_point_program:
toggle_loyalty_point_program_section(not self.enable_loyalty_point_program)
clear_cache = True
if clear_cache:
frappe.clear_cache()
@@ -191,36 +154,3 @@ class AccountsSettings(Document):
frappe.db.sql(f"drop procedure if exists {InitSQLProceduresForAR.init_procedure_name}")
frappe.db.sql(f"drop procedure if exists {InitSQLProceduresForAR.allocate_procedure_name}")
def toggle_accounting_dimension_sections(hide):
accounting_dimension_doctypes = frappe.get_hooks("accounting_dimension_doctypes")
for doctype in accounting_dimension_doctypes:
create_property_setter_for_hiding_field(doctype, "accounting_dimensions_section", hide)
def toggle_sales_discount_section(hide):
for doctype in SELLING_DOCTYPES + BUYING_DOCTYPES:
meta = frappe.get_meta(doctype)
if meta.has_field("additional_discount_section"):
create_property_setter_for_hiding_field(doctype, "additional_discount_section", hide)
if meta.has_field("discount_and_margin"):
create_property_setter_for_hiding_field(doctype, "discount_and_margin", hide)
def toggle_loyalty_point_program_section(hide):
for doctype in SELLING_DOCTYPES:
meta = frappe.get_meta(doctype)
if meta.has_field("loyalty_points_redemption"):
create_property_setter_for_hiding_field(doctype, "loyalty_points_redemption", hide)
def create_property_setter_for_hiding_field(doctype, field_name, hide):
make_property_setter(
doctype,
field_name,
"hidden",
hide,
"Check",
validate_fields_for_doctype=False,
)

View File

@@ -50,7 +50,6 @@
"fieldname": "amount",
"fieldtype": "Currency",
"label": "Amount",
"options": "currency",
"read_only": 1
},
{

View File

@@ -3,6 +3,9 @@
frappe.provide("erpnext.integrations");
frappe.ui.form.on("Bank", {
onload: function (frm) {
add_fields_to_mapping_table(frm);
},
refresh: function (frm) {
add_fields_to_mapping_table(frm);
frm.toggle_display(["address_html", "contact_html"], !frm.doc.__islocal);
@@ -34,11 +37,11 @@ let add_fields_to_mapping_table = function (frm) {
});
});
const grid = frm.fields_dict.bank_transaction_mapping?.grid;
if (grid) {
grid.update_docfield_property("bank_transaction_field", "options", options);
}
frm.fields_dict.bank_transaction_mapping.grid.update_docfield_property(
"bank_transaction_field",
"options",
options
);
};
erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
@@ -113,7 +116,7 @@ erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
"There was an issue connecting to Plaid's authentication server. Check browser console for more information"
)
);
console.error(error);
console.log(error);
}
plaid_success(token, response) {

View File

@@ -115,7 +115,7 @@ def get_default_company_bank_account(company, party_type, party):
@frappe.whitelist()
def get_bank_account_details(bank_account: str):
def get_bank_account_details(bank_account):
return frappe.get_cached_value(
"Bank Account", bank_account, ["account", "bank", "bank_account_no"], as_dict=1
)

View File

@@ -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,9 +47,7 @@ class BankReconciliationTool(Document):
@frappe.whitelist()
def get_bank_transactions(
bank_account: str, from_date: str | date | None = None, to_date: str | date | None = None
):
def get_bank_transactions(bank_account, from_date=None, to_date=None):
# returns bank transactions for a bank account
filters = []
filters.append(["bank_account", "=", bank_account])
@@ -82,7 +80,7 @@ def get_bank_transactions(
@frappe.whitelist()
def get_account_balance(bank_account: str, till_date: str | date, company: str):
def get_account_balance(bank_account, till_date, company):
# returns account balance till the specified date
account = frappe.db.get_value("Bank Account", bank_account, "account")
filters = frappe._dict(
@@ -108,9 +106,7 @@ def get_account_balance(bank_account: str, till_date: str | date, company: str):
@frappe.whitelist()
def update_bank_transaction(
bank_transaction_name: str, reference_number: str, party_type: str | None = None, party: str | None = None
):
def update_bank_transaction(bank_transaction_name, reference_number, party_type=None, party=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
@@ -139,16 +135,16 @@ def update_bank_transaction(
@frappe.whitelist()
def create_journal_entry_bts(
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,
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,
):
# Create a new journal entry based on the bank transaction
bank_transaction = frappe.db.get_values(
@@ -298,17 +294,17 @@ def create_journal_entry_bts(
@frappe.whitelist()
def create_payment_entry_bts(
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,
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,
):
# Create a new payment entry based on the bank transaction
bank_transaction = frappe.db.get_values(
@@ -375,12 +371,12 @@ def create_payment_entry_bts(
@frappe.whitelist()
def auto_reconcile_vouchers(
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_account,
from_date=None,
to_date=None,
filter_by_reference_date=None,
from_reference_date=None,
to_reference_date=None,
):
bank_transactions = get_bank_transactions(bank_account)
@@ -475,7 +471,7 @@ def get_auto_reconcile_message(partially_reconciled, reconciled):
@frappe.whitelist()
def reconcile_vouchers(bank_transaction_name: str, vouchers: str):
def reconcile_vouchers(bank_transaction_name, vouchers):
# 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)
@@ -491,13 +487,13 @@ def reconcile_vouchers(bank_transaction_name: str, vouchers: str):
@frappe.whitelist()
def get_linked_payments(
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,
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,
):
# get all matching payments for a bank transaction
transaction = frappe.get_doc("Bank Transaction", bank_transaction_name)

View File

@@ -143,7 +143,7 @@ def preprocess_mt940_content(content: str) -> str:
@frappe.whitelist()
def convert_mt940_to_csv(data_import: str, mt940_file_path: str):
def convert_mt940_to_csv(data_import, mt940_file_path):
doc = frappe.get_doc("Bank Statement Import", data_import)
_file_doc, content = get_file(mt940_file_path)
@@ -208,28 +208,26 @@ def convert_mt940_to_csv(data_import: str, mt940_file_path: str):
@frappe.whitelist()
def get_preview_from_template(
data_import: str, import_file: str | None = None, google_sheets_url: str | None = None
):
def get_preview_from_template(data_import, import_file=None, google_sheets_url=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: str):
def form_start_import(data_import):
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: str):
def download_errored_template(data_import_name):
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: str):
def download_import_log(data_import_name):
return frappe.get_doc("Bank Statement Import", data_import_name).download_import_log()
@@ -365,7 +363,7 @@ def write_xlsx(data, sheet_name, wb=None, column_widths=None, file_path=None):
@frappe.whitelist()
def get_import_status(docname: str):
def get_import_status(docname):
import_status = {}
data_import = frappe.get_doc("Bank Statement Import", docname)

View File

@@ -139,8 +139,6 @@ class BankTransaction(Document):
self.set_status()
def on_cancel(self):
self.ignore_linked_doctypes = ["GL Entry"]
for payment_entry in self.payment_entries:
self.delink_payment_entry(payment_entry)
@@ -375,12 +373,11 @@ 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
)
)

View File

@@ -35,7 +35,7 @@ def upload_bank_statement():
@frappe.whitelist()
def create_bank_entries(columns: str, data: str, bank_account: str):
def create_bank_entries(columns, data, bank_account):
header_map = get_header_mapping(columns, bank_account)
success = 0

View File

@@ -845,7 +845,7 @@ def get_fiscal_year_date_range(from_fiscal_year, to_fiscal_year):
@frappe.whitelist()
def revise_budget(budget_name: str):
def revise_budget(budget_name):
old_budget = frappe.get_doc("Budget", budget_name)
if old_budget.docstatus == 1:

View File

@@ -57,7 +57,7 @@ def validate_columns(data):
@frappe.whitelist()
def validate_company(company: str):
def validate_company(company):
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: str):
@frappe.whitelist()
def import_coa(file_name: str, company: str):
def import_coa(file_name, company):
# delete existing data for accounts
unset_existing_data(company)
@@ -159,9 +159,7 @@ def generate_data_from_excel(file_doc, extension, as_dict=False):
@frappe.whitelist()
def get_coa(
doctype: str, parent: str, is_root: bool = False, file_name: str | None = None, for_validate: int = 0
):
def get_coa(doctype, parent, is_root=False, file_name=None, for_validate=0):
"""called by tree view (to fetch node's children)"""
file_doc, extension = get_file(file_name)
@@ -309,7 +307,7 @@ def build_response_as_excel(writer):
@frappe.whitelist()
def download_template(file_type: str, template_type: str, company: str):
def download_template(file_type, template_type, company):
writer = get_template(template_type, company)
if file_type == "CSV":
@@ -363,7 +361,7 @@ def get_sample_template(writer, company):
@frappe.whitelist()
def validate_accounts(file_doc: Document, extension: str):
def validate_accounts(file_doc, extension):
if extension == "csv":
accounts = generate_data_from_csv(file_doc, as_dict=True)
else:

View File

@@ -47,7 +47,7 @@ class ChequePrintTemplate(Document):
@frappe.whitelist()
def create_or_update_cheque_print_format(template_name: str):
def create_or_update_cheque_print_format(template_name):
if not frappe.db.exists("Print Format", template_name):
cheque_print = frappe.new_doc("Print Format")
cheque_print.update(

View File

@@ -1,7 +1,6 @@
# 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
@@ -615,12 +614,7 @@ def calculate_exchange_rate_using_last_gle(company, account, party_type, party):
@frappe.whitelist()
def get_account_details(
company: str,
posting_date: str | date,
account: str,
party_type: str | None = None,
party: str | None = None,
rounding_loss_allowance: float = 0.0,
company, posting_date, account, party_type=None, party=None, rounding_loss_allowance: float | None = None
):
if not (company and posting_date):
frappe.throw(_("Company and Posting Date is mandatory"))

View File

@@ -15,7 +15,7 @@ from frappe.database.operator_map import OPERATOR_MAP
from frappe.query_builder import Case
from frappe.query_builder.functions import Sum
from frappe.utils import cstr, date_diff, flt, getdate
from pypika.terms import Bracket, LiteralValue
from pypika.terms import LiteralValue
from erpnext import get_company_currency
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
@@ -541,7 +541,7 @@ class FinancialQueryBuilder:
.where(acb_table.period_closing_voucher == closing_voucher)
)
query = self._apply_standard_filters(query, acb_table, "Account Closing Balance")
query = self._apply_standard_filters(query, acb_table)
results = self._execute_with_permissions(query, "Account Closing Balance")
for row in results:
@@ -636,15 +636,12 @@ class FinancialQueryBuilder:
return self._execute_with_permissions(query, "GL Entry")
def _calculate_running_balances(self, balances_data: dict, gl_data: list[dict]) -> dict:
gl_dict = {row["account"]: row for row in gl_data}
accounts = set(balances_data.keys()) | set(gl_dict.keys())
for account in accounts:
for row in gl_data:
account = row["account"]
if account not in balances_data:
balances_data[account] = AccountData(account=account, **self._get_account_meta(account))
account_data: AccountData = balances_data[account]
gl_movement = gl_dict.get(account, {})
if account_data.has_periods():
first_period = account_data.get_period(self.periods[0]["key"])
@@ -654,13 +651,20 @@ class FinancialQueryBuilder:
for period in self.periods:
period_key = period["key"]
movement = gl_movement.get(period_key, 0.0)
movement = row.get(period_key, 0.0)
closing_balance = current_balance + movement
account_data.add_period(PeriodValue(period_key, current_balance, closing_balance, movement))
current_balance = closing_balance
# Accounts with no movements
for account_data in balances_data.values():
for period in self.periods:
period_key = period["key"]
if period_key not in account_data.period_values:
account_data.add_period(PeriodValue(period_key, 0.0, 0.0, 0.0))
def _handle_balance_accumulation(self, balances_data):
for account_data in balances_data.values():
account_data: AccountData
@@ -679,12 +683,12 @@ class FinancialQueryBuilder:
else:
account_data.unaccumulate_values()
def _apply_standard_filters(self, query, table, doctype: str = "GL Entry"):
def _apply_standard_filters(self, query, table):
if self.filters.get("ignore_closing_entries"):
if doctype == "GL Entry":
query = query.where(table.voucher_type != "Period Closing Voucher")
else:
if hasattr(table, "is_period_closing_voucher_entry"):
query = query.where(table.is_period_closing_voucher_entry == 0)
else:
query = query.where(table.voucher_type != "Period Closing Voucher")
if self.filters.get("project"):
projects = self.filters.get("project")
@@ -732,7 +736,7 @@ class FinancialQueryBuilder:
user_conditions = build_match_conditions(doctype)
if user_conditions:
query = query.where(Bracket(LiteralValue(user_conditions)))
query = query.where(LiteralValue(user_conditions))
return query.run(as_dict=True)

View File

@@ -7,14 +7,12 @@ from frappe.utils import flt
from erpnext.accounts.doctype.financial_report_template.financial_report_engine import (
DependencyResolver,
FilterExpressionParser,
FinancialQueryBuilder,
FormulaCalculator,
)
from erpnext.accounts.doctype.financial_report_template.test_financial_report_template import (
FinancialReportTemplateTestCase,
)
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
from erpnext.accounts.utils import get_currency_precision, get_fiscal_year
from erpnext.accounts.utils import get_currency_precision
# On IntegrationTestCase, the doctype test records and all
# link-field test record dependencies are recursively loaded
@@ -1670,360 +1668,3 @@ class TestFilterExpressionParser(FinancialReportTemplateTestCase):
mock_row_invalid = self._create_mock_report_row(invalid_formula)
condition = parser.build_condition(mock_row_invalid, account_table)
self.assertIsNone(condition)
class TestFinancialQueryBuilder(FinancialReportTemplateTestCase):
def test_fetch_balances_with_journal_entries(self):
company = "_Test Company"
cash_account = "_Test Cash - _TC"
bank_account = "_Test Bank - _TC"
# Create journal entries in different periods
# October: Transfer 1000 from Bank to Cash
jv_oct = make_journal_entry(
account1=cash_account,
account2=bank_account,
amount=1000,
posting_date="2024-10-15",
company=company,
submit=True,
)
# November: Transfer 500 from Bank to Cash
jv_nov = make_journal_entry(
account1=cash_account,
account2=bank_account,
amount=500,
posting_date="2024-11-20",
company=company,
submit=True,
)
# December: No transactions (test zero movement period)
try:
# Set up filters and periods for Q4 2024
filters = {
"company": company,
"from_fiscal_year": "2024",
"to_fiscal_year": "2024",
"period_start_date": "2024-10-01",
"period_end_date": "2024-12-31",
"filter_based_on": "Date Range",
"periodicity": "Monthly",
}
periods = [
{"key": "2024_oct", "from_date": "2024-10-01", "to_date": "2024-10-31"},
{"key": "2024_nov", "from_date": "2024-11-01", "to_date": "2024-11-30"},
{"key": "2024_dec", "from_date": "2024-12-01", "to_date": "2024-12-31"},
]
query_builder = FinancialQueryBuilder(filters, periods)
# Create account objects as expected by fetch_account_balances
accounts = [
frappe._dict({"name": cash_account, "account_name": "Cash", "account_number": "1001"}),
frappe._dict({"name": bank_account, "account_name": "Bank", "account_number": "1002"}),
]
# Fetch balances using the full workflow
balances_data = query_builder.fetch_account_balances(accounts)
# Verify Cash account balances
cash_data = balances_data.get(cash_account)
self.assertIsNotNone(cash_data, "Cash account should exist in results")
# October: movement = +1000 (debit)
oct_cash = cash_data.get_period("2024_oct")
self.assertIsNotNone(oct_cash, "October period should exist for cash")
self.assertEqual(oct_cash.movement, 1000.0, "October cash movement should be 1000")
# November: movement = +500
nov_cash = cash_data.get_period("2024_nov")
self.assertIsNotNone(nov_cash, "November period should exist for cash")
self.assertEqual(nov_cash.movement, 500.0, "November cash movement should be 500")
self.assertEqual(
nov_cash.opening, oct_cash.closing, "November opening should equal October closing"
)
# December: movement = 0 (no transactions)
dec_cash = cash_data.get_period("2024_dec")
self.assertIsNotNone(dec_cash, "December period should exist for cash")
self.assertEqual(dec_cash.movement, 0.0, "December cash movement should be 0")
self.assertEqual(
dec_cash.closing,
nov_cash.closing,
"December closing should equal November closing when no movement",
)
# Verify Bank account balances (opposite direction)
bank_data = balances_data.get(bank_account)
self.assertIsNotNone(bank_data, "Bank account should exist in results")
oct_bank = bank_data.get_period("2024_oct")
self.assertEqual(oct_bank.movement, -1000.0, "October bank movement should be -1000")
nov_bank = bank_data.get_period("2024_nov")
self.assertEqual(nov_bank.movement, -500.0, "November bank movement should be -500")
finally:
# Clean up: cancel journal entries
jv_nov.cancel()
jv_oct.cancel()
def test_opening_balance_from_previous_period_closing(self):
company = "_Test Company"
cash_account = "_Test Cash - _TC"
sales_account = "Sales - _TC"
posting_date_2023 = "2023-06-15"
# Create journal entry in prior period (2023)
# Cash Dr 5000, Sales Cr 5000
jv_2023 = make_journal_entry(
account1=cash_account,
account2=sales_account,
amount=5000,
posting_date=posting_date_2023,
company=company,
submit=True,
)
pcv = None
jv_2024 = None
original_pcv_setting = frappe.db.get_single_value(
"Accounts Settings", "use_legacy_controller_for_pcv"
)
try:
# Create Period Closing Voucher for 2023
# This will create Account Closing Balance entries
closing_account = frappe.db.get_value(
"Account",
{
"company": company,
"root_type": "Liability",
"is_group": 0,
"account_type": ["not in", ["Payable", "Receivable"]],
},
"name",
)
fy_2023 = get_fiscal_year(posting_date_2023, company=company)
frappe.db.set_single_value("Accounts Settings", "use_legacy_controller_for_pcv", 1)
pcv = frappe.get_doc(
{
"doctype": "Period Closing Voucher",
"transaction_date": "2023-12-31",
"period_start_date": fy_2023[1],
"period_end_date": fy_2023[2],
"company": company,
"fiscal_year": fy_2023[0],
"cost_center": "_Test Cost Center - _TC",
"closing_account_head": closing_account,
"remarks": "Test Period Closing",
}
)
pcv.insert()
pcv.submit()
pcv.reload()
# Now create a small transaction in 2024 to ensure the account appears
jv_2024 = make_journal_entry(
account1=cash_account,
account2=sales_account,
amount=100,
posting_date="2024-01-15",
company=company,
submit=True,
)
# Set up filters for Q1 2024 (after the period closing)
filters = {
"company": company,
"from_fiscal_year": "2024",
"to_fiscal_year": "2024",
"period_start_date": "2024-01-01",
"period_end_date": "2024-03-31",
"filter_based_on": "Date Range",
"periodicity": "Monthly",
"ignore_closing_entries": True, # Don't include PCV entries in movements
}
periods = [
{"key": "2024_jan", "from_date": "2024-01-01", "to_date": "2024-01-31"},
{"key": "2024_feb", "from_date": "2024-02-01", "to_date": "2024-02-29"},
{"key": "2024_mar", "from_date": "2024-03-01", "to_date": "2024-03-31"},
]
query_builder = FinancialQueryBuilder(filters, periods)
accounts = [
frappe._dict({"name": cash_account, "account_name": "Cash", "account_number": "1001"}),
]
balances_data = query_builder.fetch_account_balances(accounts)
# Verify Cash account has opening balance from 2023 transactions
cash_data = balances_data.get(cash_account)
self.assertIsNotNone(cash_data, "Cash account should exist in results")
jan_cash = cash_data.get_period("2024_jan")
self.assertIsNotNone(jan_cash, "January period should exist")
# Opening balance should be from prior period
# Cash had 5000 debit in 2023, so opening in 2024 should be >= 5000
# (may be higher if there were other test transactions)
self.assertEqual(
jan_cash.opening,
5000.0,
"January opening should equal to balance from 2023 (5000)",
)
# Verify running balance logic
# Movement in January is 100 (from jv_2024)
self.assertEqual(jan_cash.movement, 100.0, "January movement should be 100")
self.assertEqual(
jan_cash.closing, jan_cash.opening + jan_cash.movement, "Closing = Opening + Movement"
)
# February and March should have no movement but carry the balance
feb_cash = cash_data.get_period("2024_feb")
self.assertEqual(feb_cash.opening, jan_cash.closing, "Feb opening = Jan closing")
self.assertEqual(feb_cash.movement, 0.0, "February should have no movement")
self.assertEqual(feb_cash.closing, feb_cash.opening, "Feb closing = opening when no movement")
mar_cash = cash_data.get_period("2024_mar")
self.assertEqual(mar_cash.opening, feb_cash.closing, "Mar opening = Feb closing")
self.assertEqual(mar_cash.movement, 0.0, "March should have no movement")
self.assertEqual(mar_cash.closing, mar_cash.opening, "Mar closing = opening when no movement")
# Set up filters for Q2 2024
filters_q2 = {
"company": company,
"from_fiscal_year": "2024",
"to_fiscal_year": "2024",
"period_start_date": "2024-04-01",
"period_end_date": "2024-06-30",
"filter_based_on": "Date Range",
"periodicity": "Monthly",
"ignore_closing_entries": True,
}
periods_q2 = [
{"key": "2024_apr", "from_date": "2024-04-01", "to_date": "2024-04-30"},
{"key": "2024_may", "from_date": "2024-05-01", "to_date": "2024-05-31"},
{"key": "2024_jun", "from_date": "2024-06-01", "to_date": "2024-06-30"},
]
query_builder_q2 = FinancialQueryBuilder(filters_q2, periods_q2)
balances_data_q2 = query_builder_q2.fetch_account_balances(accounts)
# Verify Cash account in Q2
cash_data_q2 = balances_data_q2.get(cash_account)
self.assertIsNotNone(cash_data_q2, "Cash account should exist in Q2 results")
apr_cash = cash_data_q2.get_period("2024_apr")
self.assertIsNotNone(apr_cash, "April period should exist")
# Opening balance in April should equal closing in March
self.assertEqual(
apr_cash.opening,
mar_cash.closing,
"April opening should equal March closing balance",
)
self.assertEqual(apr_cash.closing, apr_cash.opening, "April closing = opening when no movement")
finally:
# Clean up
frappe.db.set_single_value(
"Accounts Settings", "use_legacy_controller_for_pcv", original_pcv_setting or 0
)
if jv_2024:
jv_2024.cancel()
if pcv:
pcv.reload()
if pcv.docstatus == 1:
pcv.cancel()
jv_2023.cancel()
def test_account_with_gl_entries_but_no_prior_closing_balance(self):
company = "_Test Company"
cash_account = "_Test Cash - _TC"
bank_account = "_Test Bank - _TC"
# Create journal entries WITHOUT any prior Period Closing Voucher
# This ensures the account exists in gl_dict but NOT in balances_data
jv = make_journal_entry(
account1=cash_account,
account2=bank_account,
amount=2500,
posting_date="2024-07-15",
company=company,
submit=True,
)
try:
# Set up filters - use a period with no prior PCV
filters = {
"company": company,
"from_fiscal_year": "2024",
"to_fiscal_year": "2024",
"period_start_date": "2024-07-01",
"period_end_date": "2024-09-30",
"filter_based_on": "Date Range",
"periodicity": "Monthly",
}
periods = [
{"key": "2024_jul", "from_date": "2024-07-01", "to_date": "2024-07-31"},
{"key": "2024_aug", "from_date": "2024-08-01", "to_date": "2024-08-31"},
{"key": "2024_sep", "from_date": "2024-09-01", "to_date": "2024-09-30"},
]
query_builder = FinancialQueryBuilder(filters, periods)
# Use accounts that have GL entries but may not have Account Closing Balance
accounts = [
frappe._dict({"name": cash_account, "account_name": "Cash", "account_number": "1001"}),
frappe._dict({"name": bank_account, "account_name": "Bank", "account_number": "1002"}),
]
balances_data = query_builder.fetch_account_balances(accounts)
# Verify accounts are present in results even without prior closing balance
cash_data = balances_data.get(cash_account)
self.assertIsNotNone(cash_data, "Cash account should exist in results")
bank_data = balances_data.get(bank_account)
self.assertIsNotNone(bank_data, "Bank account should exist in results")
# Verify July has the movement from journal entry
jul_cash = cash_data.get_period("2024_jul")
self.assertIsNotNone(jul_cash, "July period should exist for cash")
self.assertEqual(jul_cash.movement, 2500.0, "July cash movement should be 2500")
jul_bank = bank_data.get_period("2024_jul")
self.assertIsNotNone(jul_bank, "July period should exist for bank")
self.assertEqual(jul_bank.movement, -2500.0, "July bank movement should be -2500")
# Verify subsequent periods exist with zero movement
aug_cash = cash_data.get_period("2024_aug")
self.assertIsNotNone(aug_cash, "August period should exist for cash")
self.assertEqual(aug_cash.movement, 0.0, "August cash movement should be 0")
self.assertEqual(aug_cash.opening, jul_cash.closing, "August opening = July closing")
sep_cash = cash_data.get_period("2024_sep")
self.assertIsNotNone(sep_cash, "September period should exist for cash")
self.assertEqual(sep_cash.movement, 0.0, "September cash movement should be 0")
self.assertEqual(sep_cash.opening, aug_cash.closing, "September opening = August closing")
finally:
jv.cancel()

View File

@@ -4,7 +4,7 @@
import frappe
from dateutil.relativedelta import relativedelta
from frappe import _, cint
from frappe import _
from frappe.model.document import Document
from frappe.utils import add_days, add_years, cstr, getdate
@@ -33,11 +33,23 @@ class FiscalYear(Document):
self.validate_dates()
self.validate_overlap()
def on_update(self):
frappe.cache().delete_key("fiscal_years")
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_trash(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 validate_dates(self):
self.validate_from_to_dates("year_start_date", "year_end_date")
@@ -54,20 +66,28 @@ 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):
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)
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,
)
if existing_fiscal_years:
@@ -90,30 +110,37 @@ class FiscalYear(Document):
frappe.throw(
_(
"Year start date or end date is overlapping with {0}. To avoid please set company"
).format(frappe.get_desk_link("Fiscal Year", existing.name, open_in_new_tab=True)),
).format(existing.name),
frappe.NameError,
)
def auto_create_fiscal_year():
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()
@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)
)
for d in 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)"""
):
try:
current_fy = frappe.get_doc("Fiscal Year", d[0])
new_fy = frappe.new_doc("Fiscal Year")
new_fy.disabled = cint(current_fy.disabled)
new_fy = frappe.copy_doc(current_fy, ignore_no_copy=False)
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)
@@ -121,10 +148,6 @@ 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)

View File

@@ -15,22 +15,20 @@
"ignore_user_permissions": 1,
"in_list_view": 1,
"label": "Company",
"options": "Company",
"reqd": 1
"options": "Company"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2026-02-20 23:02:26.193606",
"modified": "2024-03-27 13:09:44.659251",
"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
}
}

View File

@@ -14,7 +14,7 @@ class FiscalYearCompany(Document):
if TYPE_CHECKING:
from frappe.types import DF
company: DF.Link
company: DF.Link | None
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data

View File

@@ -317,7 +317,7 @@ class InvoiceDiscounting(AccountsController):
@frappe.whitelist()
def get_invoices(filters: str):
def get_invoices(filters):
filters = frappe._dict(json.loads(filters))
cond = []
if filters.customer:

View File

@@ -277,21 +277,7 @@ frappe.ui.form.on("Journal Entry", {
var update_jv_details = function (doc, r) {
$.each(r, function (i, d) {
var row = frappe.model.add_child(doc, "Journal Entry Account", "accounts");
const {
idx,
name,
owner,
parent,
parenttype,
parentfield,
creation,
modified,
modified_by,
doctype,
docstatus,
...fields
} = d;
frappe.model.set_value(row.doctype, row.name, fields);
frappe.model.set_value(row.doctype, row.name, "account", d.account);
});
refresh_field("accounts");
};
@@ -303,6 +289,10 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
}
onload_post_render() {
this.frm.get_field("accounts").grid.set_multiple_add("account");
}
load_defaults() {
//this.frm.show_print_first = true;
if (this.frm.doc.__islocal && this.frm.doc.company) {

View File

@@ -9,16 +9,19 @@
"engine": "InnoDB",
"field_order": [
"entry_type_and_date",
"company",
"is_system_generated",
"title",
"voucher_type",
"column_break1",
"naming_series",
"process_deferred_accounting",
"reversal_of",
"column_break1",
"from_template",
"company",
"posting_date",
"multi_currency",
"finance_book",
"apply_tds",
"tax_withholding_category",
"is_system_generated",
"amended_from",
"section_break_tcvw",
"for_all_stock_asset_accounts",
"column_break_wpau",
@@ -27,60 +30,52 @@
"get_balance_for_periodic_accounting",
"2_add_edit_gl_entries",
"accounts",
"section_break_ouaq",
"total_debit",
"column_break_cixu",
"total_credit",
"difference",
"get_balance",
"section_break99",
"cheque_no",
"cheque_date",
"clearance_date",
"column_break_oizh",
"user_remark",
"subscription_section",
"auto_repeat",
"tax_withholding_tab",
"column_break99",
"total_debit",
"total_credit",
"difference",
"get_balance",
"multi_currency",
"total_amount_currency",
"total_amount",
"total_amount_in_words",
"section_tax_withholding_entry",
"tax_withholding_group",
"ignore_tax_withholding_threshold",
"override_tax_withholding_entries",
"tax_withholding_entries",
"more_info_tab",
"reference",
"clearance_date",
"remark",
"inter_company_journal_entry_reference",
"column_break98",
"bill_no",
"bill_date",
"due_date",
"column_break_isfa",
"inter_company_journal_entry_reference",
"process_deferred_accounting",
"reversal_of",
"payment_order",
"stock_entry",
"printing_settings",
"pay_to_recd_from",
"letter_head",
"select_print_heading",
"column_break_35",
"total_amount_currency",
"total_amount",
"total_amount_in_words",
"write_off",
"write_off_based_on",
"get_outstanding_invoices",
"column_break_30",
"write_off_amount",
"printing_settings",
"pay_to_recd_from",
"column_break_35",
"letter_head",
"select_print_heading",
"addtional_info",
"is_opening",
"finance_book",
"from_template",
"title",
"column_break3",
"remark",
"mode_of_payment",
"party_not_required"
"payment_order",
"party_not_required",
"column_break3",
"is_opening",
"stock_entry",
"subscription_section",
"auto_repeat",
"amended_from"
],
"fields": [
{
@@ -160,7 +155,6 @@
{
"fieldname": "2_add_edit_gl_entries",
"fieldtype": "Section Break",
"hide_border": 1,
"oldfieldtype": "Section Break",
"options": "fa fa-table"
},
@@ -208,6 +202,10 @@
"oldfieldtype": "Small Text",
"print_hide": 1
},
{
"fieldname": "column_break99",
"fieldtype": "Column Break"
},
{
"fieldname": "total_debit",
"fieldtype": "Currency",
@@ -431,7 +429,7 @@
"collapsible": 1,
"fieldname": "addtional_info",
"fieldtype": "Section Break",
"label": "Additional Info",
"label": "More Information",
"oldfieldtype": "Section Break",
"options": "fa fa-file-text"
},
@@ -478,7 +476,7 @@
{
"fieldname": "subscription_section",
"fieldtype": "Section Break",
"label": "Subscription"
"label": "Subscription Section"
},
{
"allow_on_submit": 1,
@@ -595,10 +593,12 @@
"no_copy": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "eval: doc.apply_tds && doc.docstatus == 0",
"depends_on": "eval: doc.apply_tds",
"fieldname": "section_tax_withholding_entry",
"fieldtype": "Section Break"
"fieldtype": "Section Break",
"label": "Tax Withholding Entry"
},
{
"fieldname": "tax_withholding_group",
@@ -624,33 +624,6 @@
"label": "Tax Withholding Entries",
"options": "Tax Withholding Entry",
"read_only_depends_on": "eval: !doc.override_tax_withholding_entries"
},
{
"fieldname": "more_info_tab",
"fieldtype": "Tab Break",
"label": "More Info"
},
{
"fieldname": "section_break_ouaq",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_cixu",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_oizh",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_isfa",
"fieldtype": "Column Break"
},
{
"depends_on": "eval: doc.apply_tds",
"fieldname": "tax_withholding_tab",
"fieldtype": "Tab Break",
"label": "Tax Withholding"
}
],
"icon": "fa fa-file-text",
@@ -665,7 +638,7 @@
"table_fieldname": "payment_entries"
}
],
"modified": "2026-02-16 16:06:10.468482",
"modified": "2025-11-13 17:54:14.542903",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry",

View File

@@ -1,13 +1,12 @@
# 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
@@ -75,8 +74,8 @@ class JournalEntry(AccountsController):
mode_of_payment: DF.Link | None
multi_currency: DF.Check
naming_series: DF.Literal["ACC-JV-.YYYY.-"]
override_tax_withholding_entries: DF.Check
party_not_required: DF.Check
override_tax_withholding_entries: DF.Check
pay_to_recd_from: DF.Data | None
payment_order: DF.Link | None
periodic_entry_difference_account: DF.Link | None
@@ -180,7 +179,7 @@ class JournalEntry(AccountsController):
validate_docs_for_deferred_accounting([self.name], [])
def submit(self):
if len(self.accounts) > 100 and not self.meta.queue_in_background:
if len(self.accounts) > 100:
queue_submission(self, "_submit")
else:
return self._submit()
@@ -1216,7 +1215,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: str | None = None):
def get_balance(self, difference_account=None):
if not self.get("accounts"):
msgprint(_("'Entries' cannot be empty"), raise_exception=True)
else:
@@ -1322,12 +1321,7 @@ class JournalEntry(AccountsController):
@frappe.whitelist()
def get_default_bank_cash_account(
company: str,
account_type: str | None = None,
mode_of_payment: str | None = None,
account: str | None = None,
*,
fetch_balance: bool = True,
company, account_type=None, mode_of_payment=None, account=None, *, fetch_balance=True
):
from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account
@@ -1376,12 +1370,7 @@ def get_default_bank_cash_account(
@frappe.whitelist()
def get_payment_entry_against_order(
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,
dt, dn, amount=None, debit_in_account_currency=None, journal_entry=False, bank_account=None
):
ref_doc = frappe.get_doc(dt, dn)
@@ -1426,12 +1415,7 @@ def get_payment_entry_against_order(
@frappe.whitelist()
def get_payment_entry_against_invoice(
dt: str,
dn: str,
amount: float | None = None,
debit_in_account_currency: str | None = None,
journal_entry: bool = False,
bank_account: str | None = None,
dt, dn, amount=None, debit_in_account_currency=None, journal_entry=False, bank_account=None
):
ref_doc = frappe.get_doc(dt, dn)
if dt == "Sales Invoice":
@@ -1544,7 +1528,7 @@ def get_payment_entry(ref_doc, args):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_against_jv(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict):
def get_against_jv(doctype, txt, searchfield, start, page_len, filters):
if not frappe.db.has_column("Journal Entry", searchfield):
return []
@@ -1575,7 +1559,7 @@ def get_against_jv(doctype: str, txt: str, searchfield: str, start: int, page_le
@frappe.whitelist()
def get_outstanding(args: str | dict):
def get_outstanding(args):
if not frappe.has_permission("Account"):
frappe.msgprint(_("No Permission"), raise_exception=1)
@@ -1635,7 +1619,7 @@ def get_outstanding(args: str | dict):
@frappe.whitelist()
def get_party_account_and_currency(company: str, party_type: str, party: str):
def get_party_account_and_currency(company, party_type, party):
if not frappe.has_permission("Account"):
frappe.msgprint(_("No Permission"), raise_exception=1)
@@ -1648,14 +1632,7 @@ def get_party_account_and_currency(company: str, party_type: str, party: str):
@frappe.whitelist()
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,
):
def get_account_details_and_party_type(account, date, company, debit=None, credit=None, exchange_rate=None):
"""Returns dict of account details and party type to be set in Journal Entry on selection of account."""
if not frappe.has_permission("Account"):
frappe.msgprint(_("No Permission"), raise_exception=1)
@@ -1704,20 +1681,16 @@ def get_account_details_and_party_type(
@frappe.whitelist()
def get_exchange_rate(
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,
posting_date,
account=None,
account_currency=None,
company=None,
reference_type=None,
reference_name=None,
debit=None,
credit=None,
exchange_rate=None,
):
# Ensure exchange_rate is always numeric to avoid calculation errors
if isinstance(exchange_rate, str):
exchange_rate = flt(exchange_rate) or 1
account_details = frappe.get_cached_value(
"Account", account, ["account_type", "root_type", "account_currency", "company"], as_dict=1
)
@@ -1749,7 +1722,7 @@ def get_exchange_rate(
@frappe.whitelist()
def get_average_exchange_rate(account: str):
def get_average_exchange_rate(account):
exchange_rate = 0
bank_balance_in_account_currency = get_balance_on(account)
if bank_balance_in_account_currency:
@@ -1760,7 +1733,7 @@ def get_average_exchange_rate(account: str):
@frappe.whitelist()
def make_inter_company_journal_entry(name: str, voucher_type: str, company: str):
def make_inter_company_journal_entry(name, voucher_type, company):
journal_entry = frappe.new_doc("Journal Entry")
journal_entry.voucher_type = voucher_type
journal_entry.company = company
@@ -1770,7 +1743,7 @@ def make_inter_company_journal_entry(name: str, voucher_type: str, company: str)
@frappe.whitelist()
def make_reverse_journal_entry(source_name: str, target_doc: str | Document | None = None):
def make_reverse_journal_entry(source_name, target_doc=None):
existing_reverse = frappe.db.exists("Journal Entry", {"reversal_of": source_name, "docstatus": 1})
if existing_reverse:
frappe.throw(

View File

@@ -43,7 +43,7 @@
"fields": [
{
"bold": 1,
"columns": 4,
"columns": 2,
"fieldname": "account",
"fieldtype": "Link",
"in_global_search": 1,
@@ -185,19 +185,20 @@
"fieldtype": "Select",
"label": "Reference Type",
"no_copy": 1,
"options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim\nAsset\nLoan\nPayroll Entry\nEmployee Advance\nExchange Rate Revaluation\nInvoice Discounting\nFees\nFull and Final Statement\nPayment Entry\nBank Transaction",
"options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim\nAsset\nLoan\nPayroll Entry\nEmployee Advance\nExchange Rate Revaluation\nInvoice Discounting\nFees\nFull and Final Statement\nPayment Entry",
"search_index": 1
},
{
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
"in_list_view": 1,
"label": "Reference Name",
"no_copy": 1,
"options": "reference_type",
"search_index": 1
},
{
"depends_on": "eval:doc.reference_type&&!in_list(doc.reference_type, ['Expense Claim', 'Asset', 'Employee Loan', 'Employee Advance', 'Bank Transaction'])",
"depends_on": "eval:doc.reference_type&&!in_list(doc.reference_type, ['Expense Claim', 'Asset', 'Employee Loan', 'Employee Advance'])",
"fieldname": "reference_due_date",
"fieldtype": "Date",
"label": "Reference Due Date",
@@ -293,7 +294,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2026-02-19 17:01:22.642454",
"modified": "2025-11-27 12:23:33.157655",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry Account",

View File

@@ -55,7 +55,6 @@ class JournalEntryAccount(Document):
"Fees",
"Full and Final Statement",
"Payment Entry",
"Bank Transaction",
]
user_remark: DF.SmallText | None
# end: auto-generated types

View File

@@ -3,7 +3,6 @@
frappe.ui.form.on("Journal Entry Template", {
onload: function (frm) {
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
if (frm.is_new()) {
frappe.call({
type: "GET",
@@ -38,31 +37,6 @@ frappe.ui.form.on("Journal Entry Template", {
return { filters: filters };
});
frm.set_query("project", "accounts", function (doc, cdt, cdn) {
let row = frappe.get_doc(cdt, cdn);
let filters = {
company: doc.company,
};
if (row.party_type == "Customer") {
filters.customer = row.party;
}
return {
query: "erpnext.controllers.queries.get_project_name",
filters,
};
});
frm.set_query("party_type", "accounts", function (doc, cdt, cdn) {
const row = locals[cdt][cdn];
return {
query: "erpnext.setup.doctype.party_type.party_type.get_party_type",
filters: {
account: row.account,
},
};
});
},
voucher_type: function (frm) {
var add_accounts = function (doc, r) {

View File

@@ -3,7 +3,6 @@
import frappe
from frappe import _
from frappe.model.document import Document
@@ -43,29 +42,7 @@ class JournalEntryTemplate(Document):
]
# end: auto-generated types
def validate(self):
self.validate_party()
def validate_party(self):
"""
Loop over all accounts and see if party and party type is set correctly
"""
for account in self.accounts:
if account.party_type:
account_type = frappe.get_cached_value("Account", account.account, "account_type")
if account_type not in ["Receivable", "Payable"]:
frappe.throw(
_(
"Check row {0} for account {1}: Party Type is only allowed for Receivable or Payable accounts"
).format(account.idx, account.account)
)
if account.party and not account.party_type:
frappe.throw(
_("Check row {0} for account {1}: Party is only allowed if Party Type is set").format(
account.idx, account.account
)
)
pass
@frappe.whitelist()

View File

@@ -5,13 +5,7 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"account",
"party_type",
"party",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"project"
"account"
],
"fields": [
{
@@ -21,55 +15,18 @@
"label": "Account",
"options": "Account",
"reqd": 1
},
{
"fieldname": "party_type",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Party Type",
"options": "DocType"
},
{
"fieldname": "party",
"fieldtype": "Dynamic Link",
"in_list_view": 1,
"label": "Party",
"options": "party_type"
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",
"fieldtype": "Section Break",
"label": "Accounting Dimensions"
},
{
"fieldname": "cost_center",
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
},
{
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"options": "Project"
},
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
}
],
"istable": 1,
"links": [],
"modified": "2026-01-09 13:16:27.615083",
"modified": "2024-03-27 13:09:58.986448",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Journal Entry Template Account",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -16,13 +16,9 @@ class JournalEntryTemplateAccount(Document):
from frappe.types import DF
account: DF.Link
cost_center: DF.Link | None
parent: DF.Data
parentfield: DF.Data
parenttype: DF.Data
party: DF.DynamicLink | None
party_type: DF.Link | None
project: DF.Link | None
# end: auto-generated types
pass

View File

@@ -55,7 +55,7 @@ class LedgerMerge(Document):
@frappe.whitelist()
def form_start_merge(docname: str):
def form_start_merge(docname):
return frappe.get_doc("Ledger Merge", docname).start_merge()

View File

@@ -1,7 +1,6 @@
# 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 _
@@ -89,13 +88,13 @@ def get_loyalty_details(
@frappe.whitelist()
def get_loyalty_program_details_with_points(
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,
customer,
loyalty_program=None,
expiry_date=None,
company=None,
silent=False,
include_expired_entry=False,
current_transaction_amount=0,
):
lp_details = get_loyalty_program_details(customer, loyalty_program, company=company, silent=silent)
loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program)
@@ -120,12 +119,12 @@ def get_loyalty_program_details_with_points(
@frappe.whitelist()
def get_loyalty_program_details(
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,
customer,
loyalty_program=None,
expiry_date=None,
company=None,
silent=False,
include_expired_entry=False,
):
lp_details = frappe._dict()
@@ -147,7 +146,7 @@ def get_loyalty_program_details(
@frappe.whitelist()
def get_redeemption_factor(loyalty_program: str | None = None, customer: str | None = None):
def get_redeemption_factor(loyalty_program=None, customer=None):
customer_loyalty_program = None
if not loyalty_program:
customer_loyalty_program = frappe.db.get_value("Customer", customer, "loyalty_program")

View File

@@ -293,7 +293,7 @@ def publish(index, total, doctype):
@frappe.whitelist()
def get_temporary_opening_account(company: str | None = None):
def get_temporary_opening_account(company=None):
if not company:
return

View File

@@ -67,7 +67,7 @@ class PartyLink(Document):
@frappe.whitelist()
def create_party_link(primary_role: str, primary_party: str, secondary_party: str):
def create_party_link(primary_role, primary_party, secondary_party):
party_link = frappe.new_doc("Party Link")
party_link.primary_role = primary_role
party_link.primary_party = primary_party

View File

@@ -400,16 +400,6 @@ frappe.ui.form.on("Payment Entry", {
);
frm.refresh_fields();
const party_currency =
frm.doc.payment_type === "Receive" ? "paid_from_account_currency" : "paid_to_account_currency";
var reference_grid = frm.fields_dict["references"].grid;
["total_amount", "outstanding_amount", "allocated_amount"].forEach((fieldname) => {
reference_grid.update_docfield_property(fieldname, "options", party_currency);
});
reference_grid.refresh();
},
show_general_ledger: function (frm) {
@@ -512,16 +502,12 @@ 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;
@@ -1118,7 +1104,7 @@ frappe.ui.form.on("Payment Entry", {
allocate_party_amount_against_ref_docs: async function (frm, paid_amount, paid_amount_change) {
await frm.call("allocate_amount_to_references", {
paid_amount: flt(paid_amount),
paid_amount: paid_amount,
paid_amount_change: paid_amount_change,
allocate_payment_amount: frappe.flags.allocate_payment_amount ?? false,
});
@@ -1454,15 +1440,16 @@ frappe.ui.form.on("Payment Entry", {
callback: function (r) {
if (!r.exc && r.message) {
// set taxes table
let taxes = r.message;
taxes.forEach((tax) => {
if (tax.charge_type === "On Net Total") {
tax.charge_type = "On Paid Amount";
if (r.message) {
for (let tax of r.message) {
if (tax.charge_type === "On Net Total") {
tax.charge_type = "On Paid Amount";
}
frm.add_child("taxes", tax);
}
});
frm.set_value("taxes", taxes);
frm.events.apply_taxes(frm);
frm.events.set_unallocated_amount(frm);
frm.events.apply_taxes(frm);
frm.events.set_unallocated_amount(frm);
}
}
},
});

View File

@@ -701,6 +701,7 @@
"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
@@ -792,7 +793,7 @@
"table_fieldname": "payment_entries"
}
],
"modified": "2026-02-03 16:08:49.800381",
"modified": "2025-12-18 13:56:40.206038",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",

View File

@@ -1,13 +1,12 @@
# 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
@@ -1065,12 +1064,8 @@ 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 = 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")
)
self.total_allocated_amount = abs(total_allocated_amount)
self.base_total_allocated_amount = abs(base_total_allocated_amount)
def set_unallocated_amount(self):
self.unallocated_amount = 0
@@ -1086,32 +1081,20 @@ class PaymentEntry(AccountsController):
self.base_paid_amount + deductions_to_consider
):
self.unallocated_amount = (
flt(
(
self.base_paid_amount
+ deductions_to_consider
- self.base_total_allocated_amount
- included_taxes
),
self.precision("unallocated_amount"),
)
/ self.source_exchange_rate
)
self.base_paid_amount
+ deductions_to_consider
- self.base_total_allocated_amount
- included_taxes
) / 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 = (
flt(
(
self.base_received_amount
- deductions_to_consider
- self.base_total_allocated_amount
- included_taxes
),
self.precision("unallocated_amount"),
)
/ self.target_exchange_rate
)
self.base_received_amount
- deductions_to_consider
- self.base_total_allocated_amount
- included_taxes
) / self.target_exchange_rate
def set_exchange_gain_loss(self):
exchange_gain_loss = flt(
@@ -1884,9 +1867,7 @@ class PaymentEntry(AccountsController):
frappe.response["matched_payment_requests"] = matched_payment_requests
@frappe.whitelist()
def allocate_amount_to_references(
self, paid_amount: float, paid_amount_change: bool, allocate_payment_amount: bool
):
def allocate_amount_to_references(self, paid_amount, paid_amount_change, allocate_payment_amount):
"""
Allocate `Allocated Amount` and `Payment Request` against `Reference` based on `Paid Amount` and `Outstanding Amount`.\n
:param paid_amount: Paid Amount / Received Amount.
@@ -2058,7 +2039,7 @@ class PaymentEntry(AccountsController):
)
@frappe.whitelist()
def set_matched_payment_requests(self, matched_payment_requests: str | list | None):
def set_matched_payment_requests(self, matched_payment_requests):
"""
Set `Payment Request` against `Reference` based on `matched_payment_requests`.\n
:param matched_payment_requests: List of tuple of matched Payment Requests.
@@ -2274,7 +2255,7 @@ def validate_inclusive_tax(tax, doc):
@frappe.whitelist()
def get_outstanding_reference_documents(args: str | dict, validate: bool = False):
def get_outstanding_reference_documents(args, validate=False):
if isinstance(args, str):
args = json.loads(args)
@@ -2689,7 +2670,7 @@ def get_negative_outstanding_invoices(
@frappe.whitelist()
def get_party_details(company: str, party_type: str, party: str, date: str, cost_center: str | None = None):
def get_party_details(company, party_type, party, date, cost_center=None):
bank_account = ""
party_bank_account = ""
@@ -2715,7 +2696,7 @@ def get_party_details(company: str, party_type: str, party: str, date: str, cost
@frappe.whitelist()
def get_account_details(account: str, date: str | date, cost_center: str | None = None):
def get_account_details(account, date, cost_center=None):
frappe.has_permission("Payment Entry", throw=True)
# to check if the passed account is accessible under reference doctype Payment Entry
@@ -2735,7 +2716,7 @@ def get_account_details(account: str, date: str | date, cost_center: str | None
@frappe.whitelist()
def get_company_defaults(company: str):
def get_company_defaults(company):
fields = ["write_off_account", "exchange_gain_loss_account", "cost_center"]
return frappe.get_cached_value("Company", company, fields, as_dict=1)
@@ -2774,11 +2755,7 @@ def get_outstanding_on_journal_entry(voucher_no, party_type, party):
@frappe.whitelist()
def get_reference_details(
reference_doctype: str,
reference_name: str,
party_account_currency: str,
party_type: str | None = None,
party: str | None = None,
reference_doctype, reference_name, party_account_currency, party_type=None, party=None
):
total_amount = outstanding_amount = exchange_rate = account = None
@@ -2869,15 +2846,15 @@ def get_reference_details(
@frappe.whitelist()
def get_payment_entry(
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,
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,
):
doc = frappe.get_doc(dt, dn)
over_billing_allowance = frappe.get_single_value("Accounts Settings", "over_billing_allowance")
@@ -3543,7 +3520,7 @@ def get_paid_amount(dt, dn, party_type, party, account, due_date):
@frappe.whitelist()
def make_payment_order(source_name: str, target_doc: str | Document | None = None):
def make_payment_order(source_name, target_doc=None):
from frappe.model.mapper import get_mapped_doc
def set_missing_values(source, target):

View File

@@ -132,12 +132,6 @@
"fieldtype": "Link",
"label": "Cost Center",
"options": "Cost Center"
},
{
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"options": "Project"
},
{
"fieldname": "due_date",

View File

@@ -38,7 +38,6 @@ class PaymentLedgerEntry(Document):
amount_in_account_currency: DF.Currency
company: DF.Link | None
cost_center: DF.Link | None
project: DF.Link | None
delinked: DF.Check
due_date: DF.Date | None
finance_book: DF.Link | None

View File

@@ -59,7 +59,7 @@ class PaymentOrder(Document):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_mop_query(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict):
def get_mop_query(doctype, txt, searchfield, start, page_len, filters):
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: str, txt: str, searchfield: str, start: int, page_len
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_supplier_query(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict):
def get_supplier_query(doctype, txt, searchfield, start, page_len, filters):
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: str, txt: str, searchfield: str, start: int, pag
@frappe.whitelist()
def make_payment_records(name: str, supplier: str, mode_of_payment: str | None = None):
def make_payment_records(name, supplier, mode_of_payment=None):
doc = frappe.get_doc("Payment Order", name)
make_journal_entry(doc, supplier, mode_of_payment)

View File

@@ -433,9 +433,7 @@ class PaymentReconciliation(Document):
return frappe.get_single_value("Accounts Settings", "auto_reconcile_payments")
@frappe.whitelist()
def calculate_difference_on_allocation_change(
self, payment_entry: list, invoice: list, allocated_amount: float
):
def calculate_difference_on_allocation_change(self, payment_entry, invoice, allocated_amount):
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"]:
@@ -447,7 +445,7 @@ class PaymentReconciliation(Document):
return new_difference_amount
@frappe.whitelist()
def allocate_entries(self, args: dict):
def allocate_entries(self, args):
self.validate_entries()
exc_gain_loss_posting_date = frappe.db.get_single_value(
@@ -748,7 +746,7 @@ class PaymentReconciliation(Document):
ple = qb.DocType("Payment Ledger Entry")
for x in self.dimensions:
dimension = x.fieldname
if self.get(dimension) and frappe.db.has_column("Payment Ledger Entry", dimension):
if self.get(dimension):
self.accounting_dimension_filter_conditions.append(ple[dimension] == self.get(dimension))
def build_qb_filter_conditions(self, get_invoices=False, get_return_invoices=False):

View File

@@ -1,88 +0,0 @@
{
"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": []
}

View File

@@ -1,27 +0,0 @@
# 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

View File

@@ -105,29 +105,3 @@ 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();
});

View File

@@ -19,8 +19,6 @@
"column_break_4",
"reference_doctype",
"reference_name",
"payment_reference_section",
"payment_reference",
"transaction_details",
"grand_total",
"currency",
@@ -159,7 +157,6 @@
"label": "Amount",
"non_negative": 1,
"options": "currency",
"read_only_depends_on": "eval:doc.payment_reference.length>0",
"reqd": 1
},
{
@@ -460,17 +457,6 @@
"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,
@@ -478,7 +464,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2026-01-13 12:53:00.963274",
"modified": "2025-08-29 11:52:48.555415",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Request",

View File

@@ -45,7 +45,6 @@ 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,
)
@@ -79,7 +78,6 @@ 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
@@ -111,36 +109,15 @@ 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")),
@@ -558,7 +535,7 @@ class PaymentRequest(Document):
row_number += TO_SKIP_NEW_ROW
@frappe.whitelist()
@frappe.whitelist(allow_guest=True)
def make_payment_request(**args):
"""Make payment request"""
@@ -569,69 +546,12 @@ def make_payment_request(**args):
if args.dn and not isinstance(args.dn, str):
frappe.throw(_("Invalid parameter. 'dn' should be of type str"))
frappe.has_permission("Payment Request", "create", throw=True)
frappe.has_permission(args.dt, "read", args.dn, throw=True)
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()
# 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"))
grand_total = get_amount(ref_doc, gateway_account.get("payment_account"))
if not grand_total:
frappe.throw(_("Payment Entry is already created"))
@@ -641,6 +561,7 @@ 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)
@@ -660,20 +581,19 @@ 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)
elif not selected_payment_schedules:
else:
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"))
@@ -728,10 +648,7 @@ def make_payment_request(**args):
}
)
if selected_payment_schedules:
apply_payment_references(pr, payment_reference)
# Dimensions
# Update dimensions
pr.update(
{
"cost_center": ref_doc.get("cost_center"),
@@ -760,51 +677,6 @@ 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
@@ -937,7 +809,7 @@ def get_payment_gateway_account(filter):
@frappe.whitelist()
def get_print_format_list(ref_doctype: str):
def get_print_format_list(ref_doctype):
print_format_list = ["Standard"]
print_format_list.extend(
@@ -947,13 +819,13 @@ def get_print_format_list(ref_doctype: str):
return {"print_format": print_format_list}
@frappe.whitelist()
def resend_payment_email(docname: str):
@frappe.whitelist(allow_guest=True)
def resend_payment_email(docname):
return frappe.get_doc("Payment Request", docname).send_email()
@frappe.whitelist()
def make_payment_entry(docname: str):
def make_payment_entry(docname):
doc = frappe.get_doc("Payment Request", docname)
return doc.create_payment_entry(submit=False).as_dict()
@@ -1046,7 +918,7 @@ def get_dummy_message(doc):
@frappe.whitelist()
def get_subscription_details(reference_doctype: str, reference_name: str):
def get_subscription_details(reference_doctype, reference_name):
if reference_doctype == "Sales Invoice":
subscriptions = frappe.db.sql(
"""SELECT parent as sub_name FROM `tabSubscription Invoice` WHERE invoice=%s""",
@@ -1062,7 +934,7 @@ def get_subscription_details(reference_doctype: str, reference_name: str):
@frappe.whitelist()
def make_payment_order(source_name: str, target_doc: str | Document | None = None):
def make_payment_order(source_name, target_doc=None):
from frappe.model.mapper import get_mapped_doc
def set_missing_values(source, target):
@@ -1110,9 +982,7 @@ def validate_payment(doc, method=None):
@frappe.whitelist()
def get_open_payment_requests_query(
doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict
):
def get_open_payment_requests_query(doctype, txt, searchfield, start, page_len, filters):
# permission checks in `get_list()`
filters = frappe._dict(filters)
@@ -1151,44 +1021,3 @@ def get_irequests_of_payment_request(doc: str | None = None) -> list:
},
)
return res
@frappe.whitelist()
def get_available_payment_schedules(reference_doctype: str, reference_name: str):
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

View File

@@ -1,13 +1,11 @@
# 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
@@ -852,130 +850,3 @@ 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,
)

View File

@@ -515,7 +515,7 @@ def delete_closing_entries(voucher_no):
@frappe.whitelist()
def get_period_start_end_date(fiscal_year: str, company: str):
def get_period_start_end_date(fiscal_year, company):
fy_start_date, fy_end_date = frappe.db.get_value(
"Fiscal Year", fiscal_year, ["year_start_date", "year_end_date"]
)

View File

@@ -4,6 +4,19 @@
frappe.ui.form.on("POS Closing Entry", {
onload: async function (frm) {
frm.ignore_doctypes_on_cancel_all = ["POS Invoice Merge Log", "Sales Invoice"];
frm.set_query("pos_profile", function (doc) {
return {
filters: { user: doc.user },
};
});
frm.set_query("user", function (doc) {
return {
query: "erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry.get_cashiers",
filters: { parent: doc.pos_profile },
};
});
frm.set_query("pos_opening_entry", function (doc) {
return { filters: { status: "Open", docstatus: 1 } };
});

View File

@@ -2,8 +2,6 @@
# For license information, please see license.txt
from datetime import datetime
import frappe
from frappe import _
from frappe.query_builder import DocType
@@ -254,13 +252,13 @@ class POSClosingEntry(StatusUpdater):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_cashiers(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict):
def get_cashiers(doctype, txt, searchfield, start, page_len, filters):
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: str | datetime, end: str | datetime, pos_profile: str, user: str):
def get_invoices(start, end, pos_profile, user):
invoice_doctype = frappe.db.get_single_value("POS Settings", "invoice_type")
sales_inv_query = build_invoice_query("Sales Invoice", user, pos_profile, start, end)

View File

@@ -159,16 +159,15 @@
"language",
"column_break_84",
"select_print_heading",
"utm_analytics_section",
"utm_source",
"utm_medium",
"column_break_bhao",
"utm_campaign",
"more_information",
"inter_company_invoice_reference",
"customer_group",
"is_discounted",
"col_break23",
"utm_source",
"utm_campaign",
"utm_medium",
"column_break_gpiw",
"status",
"more_info",
"debit_to",
@@ -1542,6 +1541,10 @@
"fieldtype": "Check",
"label": "Update Billed Amount in Delivery Note"
},
{
"fieldname": "column_break_gpiw",
"fieldtype": "Column Break"
},
{
"fieldname": "utm_medium",
"fieldtype": "Link",
@@ -1607,24 +1610,13 @@
"hidden": 1,
"label": "Item Wise Tax Details",
"no_copy": 1,
"options": "Item Wise Tax Detail",
"print_hide": 1
},
{
"collapsible": 1,
"fieldname": "utm_analytics_section",
"fieldtype": "Section Break",
"label": "UTM Analytics"
},
{
"fieldname": "column_break_bhao",
"fieldtype": "Column Break"
"options": "Item Wise Tax Detail"
}
],
"icon": "fa fa-file-text",
"is_submittable": 1,
"links": [],
"modified": "2026-02-10 14:23:07.181782",
"modified": "2025-08-04 22:22:31.471752",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice",

View File

@@ -4,7 +4,6 @@
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
@@ -754,7 +753,7 @@ class POSInvoice(SalesInvoice):
return profile
@frappe.whitelist()
def set_missing_values(self, for_validate: bool = False):
def set_missing_values(self, for_validate=False):
profile = self.set_pos_fields(for_validate)
if not self.debit_to:
@@ -855,7 +854,7 @@ class POSInvoice(SalesInvoice):
return frappe.get_doc("Payment Request", pr)
@frappe.whitelist()
def update_payments(self, payments: list):
def update_payments(self, payments):
if self.status == "Consolidated":
frappe.throw(_("Create Payment Entry for Consolidated POS Invoices."))
@@ -898,7 +897,7 @@ class POSInvoice(SalesInvoice):
@frappe.whitelist()
def get_stock_availability(item_code: str | None, warehouse: str):
def get_stock_availability(item_code, warehouse):
if frappe.db.get_value("Item", item_code, "is_stock_item"):
is_stock_item = True
bin_qty = get_bin_qty(item_code, warehouse)
@@ -1021,14 +1020,14 @@ def get_pos_reserved_qty_from_table(child_table, item_code, warehouse):
@frappe.whitelist()
def make_sales_return(source_name: str, target_doc: Document | None = None):
def make_sales_return(source_name, target_doc=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: str | list):
def make_merge_log(invoices):
import json
if isinstance(invoices, str):
@@ -1078,15 +1077,7 @@ def add_return_modes(doc, pos_profile):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def item_query(
doctype: str,
txt: str,
searchfield: str,
start: int,
page_len: int,
filters: dict,
as_dict: bool = False,
):
def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=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):

View File

@@ -897,53 +897,6 @@ class TestPOSInvoice(IntegrationTestCase):
if batch.batch_no == batch_no and batch.warehouse == "_Test Warehouse - _TC":
self.assertEqual(batch.qty, 5)
def test_pos_batch_reservation_with_return_qty(self):
"""
Test POS Invoice reserved qty for batch without bundle with return invoices.
"""
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
get_auto_batch_nos,
)
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
create_batch_item_with_batch,
)
create_batch_item_with_batch("_Batch Item Reserve Return", "TestBatch-RR 01")
se = make_stock_entry(
target="_Test Warehouse - _TC",
item_code="_Batch Item Reserve Return",
qty=30,
basic_rate=100,
)
se.reload()
batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)
# POS Invoice for the batch without bundle
pos_inv = create_pos_invoice(item="_Batch Item Reserve Return", rate=300, qty=15, do_not_save=1)
pos_inv.append(
"payments",
{"mode_of_payment": "Cash", "amount": 4500},
)
pos_inv.items[0].batch_no = batch_no
pos_inv.save()
pos_inv.submit()
# POS Invoice return
pos_return = make_sales_return(pos_inv.name)
pos_return.insert()
pos_return.submit()
batches = get_auto_batch_nos(
frappe._dict({"item_code": "_Batch Item Reserve Return", "warehouse": "_Test Warehouse - _TC"})
)
for batch in batches:
if batch.batch_no == batch_no and batch.warehouse == "_Test Warehouse - _TC":
self.assertEqual(batch.qty, 30)
def test_pos_batch_item_qty_validation(self):
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
BatchNegativeStockError,

View File

@@ -12,78 +12,56 @@
"disabled",
"column_break_9",
"warehouse",
"utm_source",
"utm_campaign",
"utm_medium",
"company_address",
"accounting_tab",
"section_break_15",
"applicable_for_users",
"section_break_11",
"payments",
"set_grand_total_to_default_mop",
"price_list_and_currency_section",
"currency",
"column_break_bptt",
"selling_price_list",
"write_off_section",
"write_off_account",
"column_break_ukpz",
"write_off_cost_center",
"column_break_pkca",
"write_off_limit",
"income_and_expense_account",
"income_account",
"column_break_byzk",
"expense_account",
"taxes_section",
"taxes_and_charges",
"column_break_cjpp",
"tax_category",
"section_break_19",
"account_for_change_amount",
"disable_rounded_total",
"column_break_23",
"apply_discount_on",
"allow_partial_payment",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"project",
"pos_configurations_tab",
"section_break_14",
"action_on_new_invoice",
"hide_images",
"hide_unavailable_items",
"auto_add_item_to_cart",
"validate_stock_on_save",
"print_receipt_on_order_complete",
"action_on_new_invoice",
"column_break_16",
"update_stock",
"ignore_pricing_rule",
"print_receipt_on_order_complete",
"pos_item_selector_section",
"hide_images",
"column_break_rpny",
"hide_unavailable_items",
"column_break_stcl",
"auto_add_item_to_cart",
"pos_item_details_section",
"allow_rate_change",
"column_break_hwfg",
"allow_discount_change",
"column_break_egpi",
"allow_warehouse_change",
"section_break_15",
"applicable_for_users",
"set_grand_total_to_default_mop",
"allow_partial_payment",
"section_break_23",
"item_groups",
"column_break_25",
"customer_groups",
"more_info_tab",
"section_break_16",
"print_format",
"letter_head",
"column_break0",
"tc_name",
"select_print_heading",
"utm_analytics_section",
"utm_source",
"column_break_tvls",
"utm_campaign",
"column_break_xygw",
"utm_medium"
"section_break_19",
"selling_price_list",
"currency",
"write_off_account",
"write_off_cost_center",
"write_off_limit",
"account_for_change_amount",
"disable_rounded_total",
"column_break_23",
"income_account",
"expense_account",
"taxes_and_charges",
"tax_category",
"apply_discount_on",
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"project"
],
"fields": [
{
@@ -152,7 +130,8 @@
},
{
"fieldname": "section_break_14",
"fieldtype": "Section Break"
"fieldtype": "Section Break",
"label": "Configuration"
},
{
"description": "Only show Items from these Item Groups",
@@ -173,7 +152,6 @@
"options": "POS Customer Group"
},
{
"collapsible": 1,
"fieldname": "section_break_16",
"fieldtype": "Section Break",
"label": "Print Settings"
@@ -213,7 +191,7 @@
{
"fieldname": "section_break_19",
"fieldtype": "Section Break",
"label": "Miscellaneous"
"label": "Accounting"
},
{
"fieldname": "selling_price_list",
@@ -449,111 +427,9 @@
},
{
"default": "0",
"description": "Applicable only on Transactions made using POS",
"fieldname": "allow_partial_payment",
"fieldtype": "Check",
"label": "Allow Partial Payment"
},
{
"fieldname": "column_break_tvls",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_xygw",
"fieldtype": "Column Break"
},
{
"fieldname": "accounting_tab",
"fieldtype": "Tab Break",
"label": "Accounting"
},
{
"fieldname": "more_info_tab",
"fieldtype": "Tab Break",
"label": "More Info"
},
{
"fieldname": "pos_configurations_tab",
"fieldtype": "Tab Break",
"label": "POS Configurations"
},
{
"fieldname": "price_list_and_currency_section",
"fieldtype": "Section Break",
"label": "Price List & Currency"
},
{
"fieldname": "column_break_bptt",
"fieldtype": "Column Break"
},
{
"fieldname": "write_off_section",
"fieldtype": "Section Break",
"label": "Write Off"
},
{
"fieldname": "column_break_ukpz",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_pkca",
"fieldtype": "Column Break"
},
{
"fieldname": "income_and_expense_account",
"fieldtype": "Section Break",
"label": "Income and Expense"
},
{
"fieldname": "column_break_byzk",
"fieldtype": "Column Break"
},
{
"fieldname": "taxes_section",
"fieldtype": "Section Break",
"label": "Taxes"
},
{
"fieldname": "column_break_cjpp",
"fieldtype": "Column Break"
},
{
"fieldname": "pos_item_selector_section",
"fieldtype": "Section Break",
"label": "POS Item Selector"
},
{
"fieldname": "column_break_rpny",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_stcl",
"fieldtype": "Column Break"
},
{
"fieldname": "pos_item_details_section",
"fieldtype": "Section Break",
"label": "POS Item Details"
},
{
"fieldname": "column_break_hwfg",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_egpi",
"fieldtype": "Column Break"
},
{
"default": "0",
"fieldname": "allow_warehouse_change",
"fieldtype": "Check",
"label": "Allow User to Edit Warehouse"
},
{
"collapsible": 1,
"fieldname": "utm_analytics_section",
"fieldtype": "Section Break",
"label": "Campaign"
}
],
"grid_page_length": 50,
@@ -582,7 +458,7 @@
"link_fieldname": "pos_profile"
}
],
"modified": "2026-02-10 14:24:48.597412",
"modified": "2025-06-24 11:19:19.834905",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Profile",

View File

@@ -34,7 +34,6 @@ class POSProfile(Document):
allow_discount_change: DF.Check
allow_partial_payment: DF.Check
allow_rate_change: DF.Check
allow_warehouse_change: DF.Check
applicable_for_users: DF.Table[POSProfileUser]
apply_discount_on: DF.Literal["Grand Total", "Net Total"]
auto_add_item_to_cart: DF.Check
@@ -275,7 +274,7 @@ def get_child_nodes(group_type, root):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def pos_profile_query(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict):
def pos_profile_query(doctype, txt, searchfield, start, page_len, filters):
user = frappe.session["user"]
company = filters.get("company") or frappe.defaults.get_user_default("company")
@@ -319,7 +318,7 @@ def pos_profile_query(doctype: str, txt: str, searchfield: str, start: int, page
@frappe.whitelist()
def set_default_profile(pos_profile: str, company: str):
def set_default_profile(pos_profile, company):
modified = now()
user = frappe.session.user

View File

@@ -98,7 +98,8 @@ def get_customers_list(pos_profile=None):
return (
frappe.db.sql(
f""" select name, customer_name, customer_group, territory from tabCustomer where disabled = 0
f""" select name, customer_name, customer_group,
territory, customer_pos_id from tabCustomer where disabled = 0
and {cond}""",
tuple(customer_groups),
as_dict=1,

View File

@@ -121,7 +121,7 @@
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Apply On",
"options": "Item Code\nItem Group\nBrand\nTransaction",
"options": "\nItem Code\nItem Group\nBrand\nTransaction",
"reqd": 1
},
{
@@ -657,7 +657,7 @@
"icon": "fa fa-gift",
"idx": 1,
"links": [],
"modified": "2026-02-17 12:24:07.553505",
"modified": "2025-08-20 11:40:07.096854",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule",
@@ -714,10 +714,9 @@
"write": 1
}
],
"row_format": "Dynamic",
"show_name_in_global_search": 1,
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"title_field": "title"
}
}

View File

@@ -45,7 +45,7 @@ class PricingRule(Document):
apply_discount_on: DF.Literal["Grand Total", "Net Total"]
apply_discount_on_rate: DF.Check
apply_multiple_pricing_rules: DF.Check
apply_on: DF.Literal["Item Code", "Item Group", "Brand", "Transaction"]
apply_on: DF.Literal["", "Item Code", "Item Group", "Brand", "Transaction"]
apply_recursion_over: DF.Float
apply_rule_on_other: DF.Literal["", "Item Code", "Item Group", "Brand"]
brands: DF.Table[PricingRuleBrand]
@@ -320,7 +320,7 @@ class PricingRule(Document):
@frappe.whitelist()
def apply_pricing_rule(args: str | dict, doc: str | dict | Document | None = None):
def apply_pricing_rule(args, doc=None):
"""
args = {
"items": [{"doctype": "", "name": "", "item_code": "", "brand": "", "item_group": ""}, ...],
@@ -346,7 +346,8 @@ def apply_pricing_rule(args: str | dict, doc: str | dict | Document | None = Non
args = frappe._dict(args)
set_transaction_type(args)
if not args.transaction_type:
set_transaction_type(args)
# list of dictionaries
out = []
@@ -617,12 +618,7 @@ def apply_price_discount_rule(pricing_rule, item_details, args):
@frappe.whitelist()
def remove_pricing_rule_for_item(
pricing_rules: str | None,
item_details: str | frappe._dict,
item_code: str | None = None,
rate: float | None = None,
):
def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None, rate=None):
from erpnext.accounts.doctype.pricing_rule.utils import (
get_applied_pricing_rules,
get_pricing_rule_items,
@@ -670,7 +666,7 @@ def remove_pricing_rule_for_item(
@frappe.whitelist()
def remove_pricing_rules(item_list: str | list):
def remove_pricing_rules(item_list):
if isinstance(item_list, str):
item_list = json.loads(item_list)
@@ -687,28 +683,28 @@ def remove_pricing_rules(item_list: str | list):
return out
def set_transaction_type(pricing_ctx: frappe._dict) -> None:
if pricing_ctx.transaction_type in ["buying", "selling"]:
def set_transaction_type(args):
if args.transaction_type:
return
if pricing_ctx.doctype in ("Opportunity", "Quotation", "Sales Order", "Delivery Note", "Sales Invoice"):
pricing_ctx.transaction_type = "selling"
elif pricing_ctx.doctype in (
if args.doctype in ("Opportunity", "Quotation", "Sales Order", "Delivery Note", "Sales Invoice"):
args.transaction_type = "selling"
elif args.doctype in (
"Material Request",
"Supplier Quotation",
"Purchase Order",
"Purchase Receipt",
"Purchase Invoice",
):
pricing_ctx.transaction_type = "buying"
elif pricing_ctx.customer:
pricing_ctx.transaction_type = "selling"
args.transaction_type = "buying"
elif args.customer:
args.transaction_type = "selling"
else:
pricing_ctx.transaction_type = "buying"
args.transaction_type = "buying"
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_item_uoms(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict):
def get_item_uoms(doctype, txt, searchfield, start, page_len, filters):
items = [filters.get("value")]
if filters.get("apply_on") != "Item Code":
field = frappe.scrub(filters.get("apply_on"))

View File

@@ -10,7 +10,7 @@
],
"fields": [
{
"depends_on": "eval:parent.apply_on == 'Brand'",
"depends_on": "eval:parent.apply_on == 'Item Code'",
"fieldname": "brand",
"fieldtype": "Link",
"in_list_view": 1,
@@ -28,15 +28,14 @@
],
"istable": 1,
"links": [],
"modified": "2026-02-17 12:17:13.073587",
"modified": "2024-03-27 13:10:17.857046",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule Brand",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -10,7 +10,7 @@
],
"fields": [
{
"depends_on": "eval:parent.apply_on == 'Item Group'",
"depends_on": "eval:parent.apply_on == 'Item Code'",
"fieldname": "item_group",
"fieldtype": "Link",
"in_list_view": 1,
@@ -28,15 +28,14 @@
],
"istable": 1,
"links": [],
"modified": "2026-02-17 12:16:57.778471",
"modified": "2024-03-27 13:10:18.221095",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule Item Group",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -415,9 +415,8 @@ def reconcile(doc: None | str = None) -> None:
for x in allocations:
pr.append("allocation", x)
skip_ref_details_update_for_pe = check_multi_currency(pr)
# reconcile
pr.reconcile_allocations(skip_ref_details_update_for_pe=skip_ref_details_update_for_pe)
pr.reconcile_allocations(skip_ref_details_update_for_pe=True)
# If Payment Entry, update details only for newly linked references
# This is for performance
@@ -505,37 +504,6 @@ def reconcile(doc: None | str = None) -> None:
frappe.db.set_value("Process Payment Reconciliation", doc, "status", "Completed")
def check_multi_currency(pr_doc):
GL = frappe.qb.DocType("GL Entry")
Account = frappe.qb.DocType("Account")
def get_account_currency(voucher_type, voucher_no):
currency = (
frappe.qb.from_(GL)
.join(Account)
.on(GL.account == Account.name)
.select(Account.account_currency)
.where(
(GL.voucher_type == voucher_type)
& (GL.voucher_no == voucher_no)
& (Account.account_type.isin(["Payable", "Receivable"]))
)
.limit(1)
).run(as_dict=True)
return currency[0].account_currency if currency else None
for allocation in pr_doc.allocation:
reference_currency = get_account_currency(allocation.reference_type, allocation.reference_name)
invoice_currency = get_account_currency(allocation.invoice_type, allocation.invoice_number)
if reference_currency != invoice_currency:
return True
return False
@frappe.whitelist()
def is_any_doc_running(for_filter: str | dict | None = None) -> str | None:
running_doc = None

View File

@@ -420,7 +420,7 @@ def get_context(customer, doc):
@frappe.whitelist()
def fetch_customers(customer_collection: str, collection_name: str, primary_mandatory: str | int):
def fetch_customers(customer_collection, collection_name, primary_mandatory):
customer_list = []
customers = []
@@ -460,7 +460,7 @@ def fetch_customers(customer_collection: str, collection_name: str, primary_mand
@frappe.whitelist()
def get_customer_emails(customer_name: str, primary_mandatory: str | int, billing_and_primary: bool = True):
def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=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: str, primary_mandatory: str | int, billin
@frappe.whitelist()
def download_statements(document_name: str):
def download_statements(document_name):
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: str):
@frappe.whitelist()
def send_emails(document_name: str, from_scheduler: bool = False, posting_date: str | None = None):
def send_emails(document_name, from_scheduler=False, posting_date=None):
doc = frappe.get_doc("Process Statement Of Accounts", document_name)
report = get_report_pdf(doc, consolidated=False)

View File

@@ -18,19 +18,8 @@ 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()

View File

@@ -134,7 +134,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
this.frm.add_custom_button(
__("Payment Request"),
function () {
me.make_payment_request_with_schedule();
me.make_payment_request();
},
__("Create")
);
@@ -534,7 +534,7 @@ cur_frm.fields_dict["select_print_heading"].get_query = function (doc, cdt, cdn)
cur_frm.set_query("wip_composite_asset", "items", function () {
return {
filters: { asset_type: "Composite Asset", docstatus: 0 },
filters: { is_composite_asset: 1, docstatus: 0 },
};
});

View File

@@ -85,24 +85,20 @@
"taxes_and_charges_added",
"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",
"rounded_total",
"base_totals_section",
"section_break_49",
"base_grand_total",
"base_in_words",
"column_break_hcca",
"base_rounding_adjustment",
"base_rounded_total",
"section_break_ttrv",
"base_in_words",
"column_break8",
"grand_total",
"rounding_adjustment",
"use_company_roundoff_cost_center",
"rounded_total",
"in_words",
"total_advance",
"column_break_peap",
"outstanding_amount",
"disable_rounded_total",
"section_tax_withholding_entry",
"tax_withholding_group",
"ignore_tax_withholding_threshold",
@@ -610,7 +606,6 @@
},
{
"default": "0",
"depends_on": "eval:doc.items.every((item) => !item.pr_detail)",
"fieldname": "update_stock",
"fieldtype": "Check",
"label": "Update Stock",
@@ -887,10 +882,15 @@
"options": "currency",
"print_hide": 1
},
{
"fieldname": "section_break_49",
"fieldtype": "Section Break",
"label": "Totals"
},
{
"fieldname": "base_grand_total",
"fieldtype": "Currency",
"label": "Grand Total",
"label": "Grand Total (Company Currency)",
"oldfieldname": "grand_total",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
@@ -901,7 +901,7 @@
"depends_on": "eval:!doc.disable_rounded_total",
"fieldname": "base_rounding_adjustment",
"fieldtype": "Currency",
"label": "Rounding Adjustment",
"label": "Rounding Adjustment (Company Currency)",
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
@@ -911,7 +911,7 @@
"depends_on": "eval:!doc.disable_rounded_total",
"fieldname": "base_rounded_total",
"fieldtype": "Currency",
"label": "Rounded Total",
"label": "Rounded Total (Company Currency)",
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
@@ -920,7 +920,7 @@
{
"fieldname": "base_in_words",
"fieldtype": "Data",
"label": "In Words",
"label": "In Words (Company Currency)",
"length": 240,
"oldfieldname": "in_words",
"oldfieldtype": "Data",
@@ -1660,28 +1660,6 @@
"fieldname": "override_tax_withholding_entries",
"fieldtype": "Check",
"label": "Edit Tax Withholding Entries"
},
{
"fieldname": "column_break_hcca",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_ttrv",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_peap",
"fieldtype": "Column Break"
},
{
"fieldname": "base_totals_section",
"fieldtype": "Section Break",
"label": "Totals (Company Currency)"
},
{
"fieldname": "totals_section",
"fieldtype": "Section Break",
"label": "Totals"
}
],
"grid_page_length": 50,
@@ -1689,7 +1667,7 @@
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2026-02-23 13:23:57.269770",
"modified": "2025-12-15 06:41:38.237728",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",

View File

@@ -6,7 +6,6 @@ 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
@@ -37,7 +36,7 @@ from erpnext.accounts.utils import get_account_currency, get_fiscal_year, update
from erpnext.assets.doctype.asset.asset import is_cwip_accounting_enabled
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
from erpnext.buying.utils import check_on_hold_or_closed_status
from erpnext.controllers.accounts_controller import merge_taxes, validate_account_head
from erpnext.controllers.accounts_controller import validate_account_head
from erpnext.controllers.buying_controller import BuyingController
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
update_billed_amount_based_on_po,
@@ -1746,6 +1745,10 @@ 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)
@@ -1942,14 +1945,14 @@ def make_regional_gl_entries(gl_entries, doc):
@frappe.whitelist()
def make_debit_note(source_name: str, target_doc: str | Document | None = None):
def make_debit_note(source_name, target_doc=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: str, target_doc: str | Document | None = None):
def make_stock_entry(source_name, target_doc=None):
doc = get_mapped_doc(
"Purchase Invoice",
source_name,
@@ -1967,54 +1970,44 @@ def make_stock_entry(source_name: str, target_doc: str | Document | None = None)
@frappe.whitelist()
def change_release_date(name: str, release_date: str | None = None):
def change_release_date(name, release_date=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: str):
def unblock_invoice(name):
if frappe.db.exists("Purchase Invoice", name):
pi = frappe.get_lazy_doc("Purchase Invoice", name)
pi.unblock_invoice()
@frappe.whitelist()
def block_invoice(name: str, release_date: str, hold_comment: str | None = None):
def block_invoice(name, release_date, hold_comment=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: str, target_doc: Document | None = None):
def make_inter_company_sales_invoice(source_name, target_doc=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: str, target_doc: str | Document | None = None, args: str | dict | None = None
):
def make_purchase_receipt(source_name, target_doc=None, args=None):
if args is None:
args = {}
if isinstance(args, str):
args = json.loads(args)
def post_parent_process(source_parent, target_parent):
remove_items_with_zero_qty(target_parent)
set_missing_values(source_parent, target_parent)
def remove_items_with_zero_qty(target_parent):
target_parent.items = [row for row in target_parent.get("items") if row.get("qty") != 0]
def set_missing_values(source_parent, target_parent):
target_parent.run_method("set_missing_values")
if args and args.get("merge_taxes"):
merge_taxes(source_parent, target_parent)
target_parent.run_method("calculate_taxes_and_totals")
for row in target_parent.get("items"):
if row.get("qty") == 0:
target_parent.remove(row)
def update_item(obj, target, source_parent):
from erpnext.controllers.sales_and_purchase_return import get_returned_qty_map_for_row
@@ -2066,11 +2059,7 @@ def make_purchase_receipt(
"postprocess": update_item,
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty) and select_item(doc),
},
"Purchase Taxes and Charges": {
"doctype": "Purchase Taxes and Charges",
"reset_value": not (args and args.get("merge_taxes")),
"ignore": args.get("merge_taxes") if args else 0,
},
"Purchase Taxes and Charges": {"doctype": "Purchase Taxes and Charges"},
},
target_doc,
post_parent_process,

View File

@@ -52,7 +52,6 @@
"stock_uom_rate",
"is_free_item",
"apply_tds",
"allow_zero_valuation_rate",
"section_break_22",
"net_rate",
"net_amount",
@@ -98,6 +97,7 @@
"service_start_date",
"service_end_date",
"reference",
"allow_zero_valuation_rate",
"item_tax_rate",
"bom",
"include_exploded_items",
@@ -420,7 +420,6 @@
"options": "UOM"
},
{
"depends_on": "eval:parent.update_stock",
"fieldname": "warehouse_section",
"fieldtype": "Section Break",
"label": "Warehouse"
@@ -448,6 +447,7 @@
"print_hide": 1
},
{
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
"fieldname": "batch_no",
"fieldtype": "Link",
"label": "Batch No",
@@ -459,12 +459,14 @@
"fieldtype": "Column Break"
},
{
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
"fieldname": "serial_no",
"fieldtype": "Text",
"label": "Serial No",
"no_copy": 1
},
{
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
"fieldname": "rejected_serial_no",
"fieldtype": "Text",
"label": "Rejected Serial No",
@@ -575,7 +577,6 @@
},
{
"default": "0",
"depends_on": "eval:parent.update_stock",
"fieldname": "allow_zero_valuation_rate",
"fieldtype": "Check",
"label": "Allow Zero Valuation Rate",
@@ -799,7 +800,7 @@
"read_only": 1
},
{
"depends_on": "eval:parent.is_internal_supplier",
"depends_on": "eval:parent.is_internal_supplier && parent.update_stock",
"fieldname": "from_warehouse",
"fieldtype": "Link",
"ignore_user_permissions": 1,
@@ -895,7 +896,7 @@
"label": "Consider for Tax Withholding"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
"depends_on": "eval:parent.update_stock == 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
"fieldname": "serial_and_batch_bundle",
"fieldtype": "Link",
"label": "Serial and Batch Bundle",
@@ -905,7 +906,7 @@
"search_index": 1
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
"depends_on": "eval:parent.update_stock == 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
"fieldname": "rejected_serial_and_batch_bundle",
"fieldtype": "Link",
"label": "Rejected Serial and Batch Bundle",
@@ -921,7 +922,7 @@
"options": "Asset"
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 0 && doc.docstatus === 0",
"depends_on": "eval:parent.update_stock === 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
"fieldname": "add_serial_batch_bundle",
"fieldtype": "Button",
"label": "Add Serial / Batch No"
@@ -991,7 +992,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2026-02-15 21:07:49.455930",
"modified": "2025-12-13 14:10:02.379392",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",

View File

@@ -152,7 +152,7 @@ class RepostAccountingLedger(Document):
@frappe.whitelist()
def start_repost(account_repost_doc: str | None = None) -> None:
def start_repost(account_repost_doc=str) -> None:
from erpnext.accounts.general_ledger import make_reverse_gl_entries
frappe.flags.through_repost_accounting_ledger = True
@@ -286,9 +286,7 @@ def validate_docs_for_voucher_types(doc_voucher_types):
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_repost_allowed_types(
doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict
):
def get_repost_allowed_types(doctype, txt, searchfield, start, page_len, filters):
filters = {"allowed": True}
if txt:

View File

@@ -21,7 +21,7 @@ def repost_ple_for_voucher(voucher_type, voucher_no, gle_map=None):
@frappe.whitelist()
def start_payment_ledger_repost(docname: str | None = None):
def start_payment_ledger_repost(docname=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: str):
def execute_repost_payment_ledger(docname):
"""Repost Payment Ledger Entries by background job."""
job_name = "payment_ledger_repost_" + docname

View File

@@ -44,7 +44,6 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
"Unreconcile Payment Entries",
"Serial and Batch Bundle",
"Bank Transaction",
"Packing Slip",
];
if (!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
@@ -138,7 +137,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
this.frm.add_custom_button(
__("Payment Request"),
function () {
me.make_payment_request_with_schedule();
me.make_payment_request();
},
__("Create")
);

View File

@@ -7,12 +7,12 @@
"engine": "InnoDB",
"field_order": [
"customer_section",
"company",
"company_tax_id",
"naming_series",
"customer",
"customer_name",
"tax_id",
"company",
"company_tax_id",
"column_break1",
"posting_date",
"posting_time",
@@ -77,38 +77,34 @@
"base_total_taxes_and_charges",
"column_break_47",
"total_taxes_and_charges",
"totals_section",
"use_company_roundoff_cost_center",
"grand_total",
"in_words",
"column_break5",
"disable_rounded_total",
"rounding_adjustment",
"rounded_total",
"base_totals_section",
"totals",
"base_grand_total",
"base_in_words",
"column_break_xjag",
"base_rounding_adjustment",
"base_rounded_total",
"section_break_vacb",
"base_in_words",
"column_break5",
"grand_total",
"rounding_adjustment",
"use_company_roundoff_cost_center",
"rounded_total",
"in_words",
"total_advance",
"column_break_rdks",
"outstanding_amount",
"disable_rounded_total",
"section_tax_withholding_entry",
"tax_withholding_group",
"ignore_tax_withholding_threshold",
"override_tax_withholding_entries",
"tax_withholding_entries",
"additional_discount_section",
"section_break_49",
"apply_discount_on",
"base_discount_amount",
"coupon_code",
"is_cash_or_non_trade_discount",
"additional_discount_account",
"column_break_51",
"additional_discount_percentage",
"discount_amount",
"is_cash_or_non_trade_discount",
"additional_discount_account",
"sec_tax_breakup",
"other_charges_calculation",
"item_wise_tax_details",
@@ -198,13 +194,13 @@
"column_break8",
"unrealized_profit_loss_account",
"against_income_account",
"commission_section",
"sales_team_section_break",
"sales_partner",
"amount_eligible_for_commission",
"column_break10",
"commission_rate",
"total_commission",
"sales_team_section",
"section_break2",
"sales_team",
"edit_printing_settings",
"letter_head",
@@ -219,21 +215,20 @@
"column_break_140",
"to_date",
"update_auto_repeat_reference",
"utm_analytics_section",
"utm_source",
"utm_medium",
"column_break_ixxw",
"utm_campaign",
"utm_content",
"more_information",
"status",
"remarks",
"inter_company_invoice_reference",
"represents_company",
"customer_group",
"column_break_imbx",
"utm_source",
"utm_campaign",
"utm_medium",
"utm_content",
"col_break23",
"is_internal_customer",
"represents_company",
"inter_company_invoice_reference",
"is_discounted",
"remarks",
"connections_tab"
],
"fields": [
@@ -271,7 +266,6 @@
"oldfieldtype": "Link",
"options": "Customer",
"print_hide": 1,
"reqd": 1,
"search_index": 1
},
{
@@ -709,7 +703,6 @@
},
{
"default": "0",
"depends_on": "eval:doc.items.every((item) => !item.dn_detail)",
"fieldname": "update_stock",
"fieldtype": "Check",
"hide_days": 1,
@@ -1079,6 +1072,14 @@
"no_copy": 1,
"options": "Cost Center"
},
{
"collapsible": 1,
"fieldname": "section_break_49",
"fieldtype": "Section Break",
"hide_days": 1,
"hide_seconds": 1,
"label": "Additional Discount"
},
{
"default": "Grand Total",
"fieldname": "apply_discount_on",
@@ -1123,12 +1124,22 @@
"options": "currency",
"print_hide": 1
},
{
"fieldname": "totals",
"fieldtype": "Section Break",
"hide_days": 1,
"hide_seconds": 1,
"label": "Totals",
"oldfieldtype": "Section Break",
"options": "fa fa-money",
"print_hide": 1
},
{
"fieldname": "base_grand_total",
"fieldtype": "Currency",
"hide_days": 1,
"hide_seconds": 1,
"label": "Grand Total",
"label": "Grand Total (Company Currency)",
"oldfieldname": "grand_total",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
@@ -1142,8 +1153,9 @@
"fieldtype": "Currency",
"hide_days": 1,
"hide_seconds": 1,
"label": "Rounding Adjustment",
"label": "Rounding Adjustment (Company Currency)",
"no_copy": 1,
"options": "Company:company:default_currency",
"print_hide": 1,
"read_only": 1
},
@@ -1153,9 +1165,10 @@
"fieldtype": "Currency",
"hide_days": 1,
"hide_seconds": 1,
"label": "Rounded Total",
"label": "Rounded Total (Company Currency)",
"oldfieldname": "rounded_total",
"oldfieldtype": "Currency",
"options": "Company:company:default_currency",
"print_hide": 1,
"read_only": 1
},
@@ -1165,7 +1178,7 @@
"fieldtype": "Small Text",
"hide_days": 1,
"hide_seconds": 1,
"label": "In Words",
"label": "In Words (Company Currency)",
"length": 240,
"oldfieldname": "in_words",
"oldfieldtype": "Data",
@@ -1258,6 +1271,7 @@
"read_only": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "advances",
"fieldname": "advances_section",
"fieldtype": "Section Break",
@@ -1633,6 +1647,13 @@
"no_copy": 1,
"read_only": 1
},
{
"fieldname": "col_break23",
"fieldtype": "Column Break",
"hide_days": 1,
"hide_seconds": 1,
"width": "50%"
},
{
"default": "Draft",
"fieldname": "status",
@@ -1684,10 +1705,10 @@
"read_only": 1
},
{
"allow_on_submit": 1,
"default": "No",
"fieldname": "is_opening",
"fieldtype": "Select",
"hidden": 1,
"hide_days": 1,
"hide_seconds": 1,
"label": "Is Opening Entry",
@@ -1716,6 +1737,18 @@
"oldfieldtype": "Text",
"print_hide": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "sales_partner",
"fieldname": "sales_team_section_break",
"fieldtype": "Section Break",
"hide_days": 1,
"hide_seconds": 1,
"label": "Commission",
"oldfieldtype": "Section Break",
"options": "fa fa-group",
"print_hide": 1
},
{
"fieldname": "sales_partner",
"fieldtype": "Link",
@@ -1759,6 +1792,16 @@
"options": "Company:company:default_currency",
"print_hide": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "sales_team",
"fieldname": "section_break2",
"fieldtype": "Section Break",
"hide_days": 1,
"hide_seconds": 1,
"label": "Sales Team",
"print_hide": 1
},
{
"allow_on_submit": 1,
"fieldname": "sales_team",
@@ -2207,8 +2250,7 @@
"hidden": 1,
"label": "Item Wise Tax Details",
"no_copy": 1,
"options": "Item Wise Tax Detail",
"print_hide": 1
"options": "Item Wise Tax Detail"
},
{
"default": "0",
@@ -2249,74 +2291,6 @@
"fieldname": "override_tax_withholding_entries",
"fieldtype": "Check",
"label": "Edit Tax Withholding Entries"
},
{
"fieldname": "totals_section",
"fieldtype": "Section Break",
"hide_days": 1,
"hide_seconds": 1,
"label": "Totals",
"oldfieldtype": "Section Break",
"options": "fa fa-money",
"print_hide": 1
},
{
"fieldname": "base_totals_section",
"fieldtype": "Section Break",
"label": "Totals (Company Currency)",
"options": "Company:company:default_currency"
},
{
"fieldname": "column_break_xjag",
"fieldtype": "Column Break"
},
{
"collapsible": 1,
"fieldname": "additional_discount_section",
"fieldtype": "Section Break",
"hide_days": 1,
"hide_seconds": 1,
"label": "Additional Discount"
},
{
"collapsible": 1,
"collapsible_depends_on": "sales_team",
"fieldname": "sales_team_section",
"fieldtype": "Section Break",
"hide_days": 1,
"hide_seconds": 1,
"label": "Sales Team",
"print_hide": 1
},
{
"collapsible": 1,
"collapsible_depends_on": "sales_partner",
"fieldname": "commission_section",
"fieldtype": "Section Break",
"hide_days": 1,
"hide_seconds": 1,
"label": "Commission",
"oldfieldtype": "Section Break",
"options": "fa fa-group",
"print_hide": 1
},
{
"fieldname": "column_break_ixxw",
"fieldtype": "Column Break"
},
{
"collapsible": 1,
"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,
@@ -2330,7 +2304,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2026-02-28 17:58:56.453076",
"modified": "2025-12-24 18:29:50.242618",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@@ -6,7 +6,6 @@ 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
@@ -120,7 +119,7 @@ class SalesInvoice(SellingController):
cost_center: DF.Link | None
coupon_code: DF.Link | None
currency: DF.Link
customer: DF.Link
customer: DF.Link | None
customer_address: DF.Link | None
customer_group: DF.Link | None
customer_name: DF.SmallText | None
@@ -742,7 +741,7 @@ class SalesInvoice(SellingController):
pos_invoice_doc.cancel()
@frappe.whitelist()
def set_missing_values(self, for_validate: bool = False):
def set_missing_values(self, for_validate=False):
pos = self.set_pos_fields(for_validate)
if not self.debit_to:
@@ -1452,9 +1451,6 @@ class SalesInvoice(SellingController):
return asset_qty_map
def process_asset_depreciation(self):
if self.is_internal_transfer():
return
if (self.is_return and self.docstatus == 2) or (not self.is_return and self.docstatus == 1):
self.depreciate_asset_on_sale()
else:
@@ -2413,7 +2409,7 @@ def get_list_context(context=None):
@frappe.whitelist()
def get_bank_cash_account(mode_of_payment: str, company: str):
def get_bank_cash_account(mode_of_payment, company):
account = frappe.db.get_value(
"Mode of Payment Account", {"parent": mode_of_payment, "company": company}, "default_account"
)
@@ -2428,7 +2424,7 @@ def get_bank_cash_account(mode_of_payment: str, company: str):
@frappe.whitelist()
def make_maintenance_schedule(source_name: str, target_doc: str | Document | None = None):
def make_maintenance_schedule(source_name, target_doc=None):
doclist = get_mapped_doc(
"Sales Invoice",
source_name,
@@ -2445,7 +2441,7 @@ def make_maintenance_schedule(source_name: str, target_doc: str | Document | Non
@frappe.whitelist()
def make_delivery_note(source_name: str, target_doc: Document | None = None):
def make_delivery_note(source_name, target_doc=None):
def set_missing_values(source, target):
target.run_method("set_missing_values")
target.run_method("set_po_nos")
@@ -2494,7 +2490,7 @@ def make_delivery_note(source_name: str, target_doc: Document | None = None):
@frappe.whitelist()
def make_sales_return(source_name: str, target_doc: Document | None = None):
def make_sales_return(source_name, target_doc=None):
from erpnext.controllers.sales_and_purchase_return import make_return_doc
return make_return_doc("Sales Invoice", source_name, target_doc)
@@ -2588,7 +2584,7 @@ def validate_inter_company_transaction(doc, doctype):
@frappe.whitelist()
def make_inter_company_purchase_invoice(source_name: str, target_doc: Document | None = None):
def make_inter_company_purchase_invoice(source_name, target_doc=None):
return make_inter_company_transaction("Sales Invoice", source_name, target_doc)
@@ -2966,7 +2962,7 @@ def update_address(doc, address_field, address_display_field, address_name):
@frappe.whitelist()
def get_loyalty_programs(customer: str):
def get_loyalty_programs(customer):
"""sets applicable loyalty program to the customer or returns a list of applicable programs"""
from erpnext.selling.doctype.customer.customer import get_loyalty_programs
@@ -2984,7 +2980,7 @@ def get_loyalty_programs(customer: str):
@frappe.whitelist()
def create_invoice_discounting(source_name: str, target_doc: str | Document | None = None):
def create_invoice_discounting(source_name, target_doc=None):
invoice = frappe.get_doc("Sales Invoice", source_name)
invoice_discounting = frappe.new_doc("Invoice Discounting")
invoice_discounting.company = invoice.company
@@ -3076,9 +3072,7 @@ def get_mode_of_payment_info(mode_of_payment, company):
@frappe.whitelist()
def create_dunning(
source_name: str, target_doc: str | Document | None = None, ignore_permissions: bool = False
):
def create_dunning(source_name, target_doc=None, ignore_permissions=False):
from frappe.model.mapper import get_mapped_doc
def postprocess_dunning(source, target):

View File

@@ -4739,66 +4739,6 @@ class TestSalesInvoice(ERPNextTestSuite):
doc.db_set("do_not_use_batchwise_valuation", original_value)
@change_settings("Selling Settings", {"set_zero_rate_for_expired_batch": True})
def test_zero_valuation_for_standalone_credit_note_with_expired_batch(self):
item_code = "_Test Item for Expiry Batch Zero Valuation"
make_item_for_si(
item_code,
{
"is_stock_item": 1,
"has_batch_no": 1,
"has_expiry_date": 1,
"shelf_life_in_days": 2,
"create_new_batch": 1,
"batch_number_series": "TBATCH-EBZV.####",
},
)
se = make_stock_entry(
item_code=item_code,
qty=10,
target="_Test Warehouse - _TC",
rate=100,
)
# fetch batch no from bundle
batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)
si = create_sales_invoice(
posting_date=add_days(nowdate(), 3),
item=item_code,
qty=-10,
rate=100,
is_return=1,
update_stock=1,
use_serial_batch_fields=1,
do_not_save=1,
do_not_submit=1,
)
si.items[0].batch_no = batch_no
si.save()
si.submit()
si.reload()
# check zero incoming rate in voucher
self.assertEqual(si.items[0].incoming_rate, 0.0)
# chekc zero incoming rate in stock ledger
stock_ledger_entry = frappe.db.get_value(
"Stock Ledger Entry",
{
"voucher_type": "Sales Invoice",
"voucher_no": si.name,
"item_code": item_code,
"warehouse": "_Test Warehouse - _TC",
},
["incoming_rate", "valuation_rate"],
as_dict=True,
)
self.assertEqual(stock_ledger_entry.incoming_rate, 0.0)
def make_item_for_si(item_code, properties=None):
from erpnext.stock.doctype.item.test_item import make_item

View File

@@ -52,7 +52,6 @@
"is_free_item",
"apply_tds",
"grant_commission",
"allow_zero_valuation_rate",
"section_break_21",
"net_rate",
"net_amount",
@@ -89,6 +88,7 @@
"serial_and_batch_bundle",
"use_serial_batch_fields",
"col_break5",
"allow_zero_valuation_rate",
"incoming_rate",
"item_tax_rate",
"actual_batch_qty",
@@ -580,7 +580,6 @@
{
"collapsible": 1,
"collapsible_depends_on": "eval:doc.serial_no || doc.batch_no",
"depends_on": "eval:parent.update_stock",
"fieldname": "warehouse_and_reference",
"fieldtype": "Section Break",
"label": "Stock Details"
@@ -596,7 +595,7 @@
"print_hide": 1
},
{
"depends_on": "eval: parent.is_internal_customer",
"depends_on": "eval: parent.is_internal_customer && parent.update_stock",
"fieldname": "target_warehouse",
"fieldtype": "Link",
"hidden": 1,
@@ -614,6 +613,7 @@
"options": "Quality Inspection"
},
{
"depends_on": "eval: doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
"fieldname": "batch_no",
"fieldtype": "Link",
"label": "Batch No",
@@ -626,7 +626,6 @@
},
{
"default": "0",
"depends_on": "eval:parent.update_stock",
"fieldname": "allow_zero_valuation_rate",
"fieldtype": "Check",
"label": "Allow Zero Valuation Rate",
@@ -634,6 +633,7 @@
"print_hide": 1
},
{
"depends_on": "eval: doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
"fieldname": "serial_no",
"fieldtype": "Text",
"label": "Serial No",
@@ -843,7 +843,6 @@
"fieldtype": "Currency",
"label": "Incoming Rate (Costing)",
"no_copy": 1,
"non_negative": 1,
"options": "Company:company:default_currency",
"print_hide": 1
},
@@ -907,7 +906,7 @@
"read_only": 1
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
"depends_on": "eval:parent.update_stock == 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
"fieldname": "serial_and_batch_bundle",
"fieldtype": "Link",
"label": "Serial and Batch Bundle",
@@ -917,7 +916,7 @@
"search_index": 1
},
{
"depends_on": "eval:doc.use_serial_batch_fields === 0 && doc.docstatus === 0",
"depends_on": "eval:parent.update_stock === 1",
"fieldname": "pick_serial_and_batch",
"fieldtype": "Button",
"label": "Pick Serial / Batch No"
@@ -1010,7 +1009,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2026-02-23 14:37:14.853941",
"modified": "2025-09-04 11:08:25.583561",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",

View File

@@ -26,7 +26,7 @@
},
{
"default": "0",
"depends_on": "eval: [\"POS Invoice\", \"Sales Invoice\"].includes(parent.doctype)",
"depends_on": "eval:parent.doctype == 'Sales Invoice'",
"fieldname": "amount",
"fieldtype": "Currency",
"in_list_view": 1,
@@ -85,15 +85,14 @@
],
"istable": 1,
"links": [],
"modified": "2026-02-16 20:46:34.592604",
"modified": "2024-03-27 13:10:36.427565",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Payment",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -342,14 +342,14 @@ class ShareTransfer(Document):
@frappe.whitelist()
def make_jv_entry(
company: str,
account: str,
amount: float,
payment_account: str,
credit_applicant_type: str,
credit_applicant: str,
debit_applicant_type: str,
debit_applicant: str,
company,
account,
amount,
payment_account,
credit_applicant_type,
credit_applicant,
debit_applicant_type,
debit_applicant,
):
journal_entry = frappe.new_doc("Journal Entry")
journal_entry.voucher_type = "Journal Entry"

View File

@@ -1,7 +1,6 @@
# 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
@@ -44,13 +43,7 @@ class SubscriptionPlan(Document):
@frappe.whitelist()
def get_plan_rate(
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, quantity=1, customer=None, start_date=None, end_date=None, prorate_factor=1, party=None
):
plan = frappe.get_doc("Subscription Plan", plan)
if plan.price_determination == "Fixed Rate":

View File

@@ -8,8 +8,6 @@ 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
@@ -85,8 +83,6 @@ 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,
@@ -109,38 +105,37 @@ class TaxRule(Document):
"company": self.company,
}
query = (
frappe.qb.from_(TaxRule).select(TaxRule.name, TaxRule.priority).where(TaxRule.name != self.name)
)
for field, value in filters.items():
query = query.where(IfNull(TaxRule[field], "") == cstr(value))
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:
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))
)
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:
query = query.where(TaxRule.to_date > self.from_date)
elif self.from_date and not self.to_date:
conds += f""" and to_date > '{self.from_date}'"""
elif self.to_date:
query = query.where(TaxRule.from_date < self.to_date)
elif self.to_date and not self.from_date:
conds += f""" and from_date < '{self.to_date}'"""
tax_rule = query.run(as_dict=True)
tax_rule = frappe.db.sql(
f"select name, priority \
from `tabTax Rule` where {conds} and name != '{self.name}'",
as_dict=1,
)
if tax_rule and tax_rule[0].priority == self.priority:
frappe.throw(
_("Tax Rule Conflicts with {0}").format(tax_rule[0].name),
ConflictingTaxRule,
)
if tax_rule:
if 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: str | None, party_type: str, args: dict | None = None):
def get_party_details(party, party_type, args=None):
out = {}
billing_address, shipping_address = None, None
if args:

View File

@@ -43,18 +43,16 @@
"read_only": 1
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2025-11-14 16:17:25.584675",
"modified": "2024-03-27 13:10:55.008837",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Transaction Deletion Record Details",
"owner": "Administrator",
"permissions": [],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -194,7 +194,7 @@ def get_linked_advances(company, docname):
@frappe.whitelist()
def create_unreconcile_doc_for_selection(selections: str | None = None):
def create_unreconcile_doc_for_selection(selections=None):
if selections:
selections = json.loads(selections)
# assuming each row is a unique voucher

View File

@@ -1,41 +0,0 @@
{
"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"
}

View File

@@ -1,43 +1,3 @@
<h4>{{ _("New Fiscal Year - {0}").format(doc.name) }}</h4>
<h3>{{ _("Fiscal Year") }}</h3>
<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>
<p>{{ _("New fiscal year created :- ") }} {{ doc.name }}</p>

View File

@@ -1,8 +1,7 @@
{
"attach_print": 0,
"channel": "Email",
"condition": "doc.auto_created == 1",
"condition_type": "Python",
"condition": "doc.auto_created",
"creation": "2018-04-25 14:19:05.440361",
"days_in_advance": 0,
"docstatus": 0,
@@ -12,10 +11,8 @@
"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",
"minutes_offset": 0,
"modified": "2026-02-23 17:37:03.755394",
"modified": "2023-11-17 08:54:51.532104",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Notification for new fiscal year",
@@ -30,5 +27,5 @@
],
"send_system_notification": 0,
"send_to_all_assignees": 0,
"subject": "New Fiscal Year {{ doc.name }} - Review Required"
"subject": "Notification for new fiscal year {{ doc.name }}"
}

View File

@@ -1,20 +0,0 @@
{
"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
}

View File

@@ -1,21 +0,0 @@
{
"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
}

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