Merge pull request #40431 from FHenry/dev_fix_opportunity_type_translatable
fix: Opportunity Type Doctype must be translatable
diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
index 5258214..41af06f 100644
--- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
+++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
@@ -261,14 +261,16 @@
def get_checks_for_pl_and_bs_accounts():
- dimensions = frappe.db.sql(
- """SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
- FROM `tabAccounting Dimension`p ,`tabAccounting Dimension Detail` c
- WHERE p.name = c.parent""",
- as_dict=1,
- )
+ if frappe.flags.accounting_dimensions_details is None:
+ # nosemgrep
+ frappe.flags.accounting_dimensions_details = frappe.db.sql(
+ """SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
+ FROM `tabAccounting Dimension`p ,`tabAccounting Dimension Detail` c
+ WHERE p.name = c.parent""",
+ as_dict=1,
+ )
- return dimensions
+ return frappe.flags.accounting_dimensions_details
def get_dimension_with_children(doctype, dimensions):
diff --git a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py
index cb7f5f5..10dbe3b 100644
--- a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py
+++ b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py
@@ -78,6 +78,8 @@
def tearDown(self):
disable_dimension()
+ frappe.flags.accounting_dimensions_details = None
+ frappe.flags.dimension_filter_map = None
def create_dimension():
diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py
index 01f6e60..2179a4d 100644
--- a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py
+++ b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py
@@ -66,37 +66,39 @@
def get_dimension_filter_map():
- filters = frappe.db.sql(
- """
- SELECT
- a.applicable_on_account, d.dimension_value, p.accounting_dimension,
- p.allow_or_restrict, a.is_mandatory
- FROM
- `tabApplicable On Account` a,
- `tabAccounting Dimension Filter` p
- LEFT JOIN `tabAllowed Dimension` d ON d.parent = p.name
- WHERE
- p.name = a.parent
- AND p.disabled = 0
- """,
- as_dict=1,
- )
-
- dimension_filter_map = {}
-
- for f in filters:
- f.fieldname = scrub(f.accounting_dimension)
-
- build_map(
- dimension_filter_map,
- f.fieldname,
- f.applicable_on_account,
- f.dimension_value,
- f.allow_or_restrict,
- f.is_mandatory,
+ 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, 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:
+ f.fieldname = scrub(f.accounting_dimension)
+
+ build_map(
+ dimension_filter_map,
+ f.fieldname,
+ f.applicable_on_account,
+ f.dimension_value,
+ f.allow_or_restrict,
+ f.is_mandatory,
+ )
+ frappe.flags.dimension_filter_map = dimension_filter_map
+
+ return frappe.flags.dimension_filter_map
def build_map(map_object, dimension, account, filter_value, allow_or_restrict, is_mandatory):
diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py b/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py
index 6aba2ab..3a7bf80 100644
--- a/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py
+++ b/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py
@@ -47,6 +47,8 @@
def tearDown(self):
disable_dimension_filter()
disable_dimension()
+ frappe.flags.accounting_dimensions_details = None
+ frappe.flags.dimension_filter_map = None
for si in self.invoice_list:
si.load_from_db()
diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py
index 30e564c..6728fea 100644
--- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py
+++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py
@@ -149,6 +149,9 @@
import_file = ImportFile("Bank Transaction", file=file, import_type="Insert New Records")
data = parse_data_from_template(import_file.raw_data)
+ # Importer expects 'Data Import' class, which has 'payload_count' attribute
+ if not data_import.get("payload_count"):
+ data_import.payload_count = len(data) - 1
if import_file_path:
add_bank_account(data, bank_account)
diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
index 5e17881..4246ba5 100644
--- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
+++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
@@ -56,17 +56,17 @@
Bank Transaction should be on the same currency as the Bank Account.
"""
if self.currency and self.bank_account:
- account = frappe.get_cached_value("Bank Account", self.bank_account, "account")
- account_currency = frappe.get_cached_value("Account", account, "account_currency")
+ if account := frappe.get_cached_value("Bank Account", self.bank_account, "account"):
+ account_currency = frappe.get_cached_value("Account", account, "account_currency")
- if self.currency != account_currency:
- frappe.throw(
- _(
- "Transaction currency: {0} cannot be different from Bank Account({1}) currency: {2}"
- ).format(
- frappe.bold(self.currency), frappe.bold(self.bank_account), frappe.bold(account_currency)
+ if self.currency != account_currency:
+ frappe.throw(
+ _(
+ "Transaction currency: {0} cannot be different from Bank Account({1}) currency: {2}"
+ ).format(
+ frappe.bold(self.currency), frappe.bold(self.bank_account), frappe.bold(account_currency)
+ )
)
- )
def set_status(self):
if self.docstatus == 2:
diff --git a/erpnext/accounts/doctype/budget/budget.py b/erpnext/accounts/doctype/budget/budget.py
index 2cf9d97..aa77af6 100644
--- a/erpnext/accounts/doctype/budget/budget.py
+++ b/erpnext/accounts/doctype/budget/budget.py
@@ -139,6 +139,8 @@
def validate_expense_against_budget(args, expense_amount=0):
args = frappe._dict(args)
+ if not frappe.get_all("Budget", limit=1):
+ return
if args.get("company") and not args.fiscal_year:
args.fiscal_year = get_fiscal_year(args.get("posting_date"), company=args.get("company"))[0]
@@ -146,6 +148,11 @@
"Company", args.get("company"), "exception_budget_approver_role"
)
+ if not frappe.get_cached_value(
+ "Budget", {"fiscal_year": args.fiscal_year, "company": args.company}
+ ): # nosec
+ return
+
if not args.account:
args.account = args.get("expense_account")
@@ -172,13 +179,13 @@
if (
args.get(budget_against)
and args.account
- and frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"})
+ and (frappe.get_cached_value("Account", args.account, "root_type") == "Expense")
):
doctype = dimension.get("document_type")
if frappe.get_cached_value("DocType", doctype, "is_tree"):
- lft, rgt = frappe.db.get_value(doctype, args.get(budget_against), ["lft", "rgt"])
+ lft, rgt = frappe.get_cached_value(doctype, args.get(budget_against), ["lft", "rgt"])
condition = """and exists(select name from `tab%s`
where lft<=%s and rgt>=%s and name=b.%s)""" % (
doctype,
diff --git a/erpnext/accounts/doctype/budget_account/budget_account.json b/erpnext/accounts/doctype/budget_account/budget_account.json
index ead0761..c7d8726 100644
--- a/erpnext/accounts/doctype/budget_account/budget_account.json
+++ b/erpnext/accounts/doctype/budget_account/budget_account.json
@@ -1,94 +1,42 @@
{
- "allow_copy": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2016-05-16 11:54:09.286135",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "creation": "2016-05-16 11:54:09.286135",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "account",
+ "budget_amount"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "account",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "Account",
- "length": 0,
- "no_copy": 0,
- "options": "Account",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "account",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Account",
+ "options": "Account",
+ "reqd": 1,
+ "search_index": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "budget_amount",
- "fieldtype": "Currency",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "Budget Amount",
- "length": 0,
- "no_copy": 0,
- "options": "Company:company:default_currency",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "fieldname": "budget_amount",
+ "fieldtype": "Currency",
+ "in_list_view": 1,
+ "label": "Budget Amount",
+ "options": "Company:company:default_currency",
+ "reqd": 1
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
-
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2017-01-02 17:02:53.339420",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Budget Account",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_seen": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2024-03-04 15:43:27.016947",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Budget Account",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.js b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.js
index d931f62..ad68352 100644
--- a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.js
+++ b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.js
@@ -3,22 +3,36 @@
frappe.ui.form.on("Currency Exchange Settings", {
service_provider: function (frm) {
- if (frm.doc.service_provider == "exchangerate.host") {
- let result = ["result"];
- let params = {
- date: "{transaction_date}",
- from: "{from_currency}",
- to: "{to_currency}",
- };
- add_param(frm, "https://api.exchangerate.host/convert", params, result);
- } else if (frm.doc.service_provider == "frankfurter.app") {
- let result = ["rates", "{to_currency}"];
- let params = {
- base: "{from_currency}",
- symbols: "{to_currency}",
- };
- add_param(frm, "https://frankfurter.app/{transaction_date}", params, result);
- }
+ frm.call({
+ method: "erpnext.accounts.doctype.currency_exchange_settings.currency_exchange_settings.get_api_endpoint",
+ args: {
+ service_provider: frm.doc.service_provider,
+ use_http: frm.doc.use_http,
+ },
+ callback: function (r) {
+ if (r && r.message) {
+ if (frm.doc.service_provider == "exchangerate.host") {
+ let result = ["result"];
+ let params = {
+ date: "{transaction_date}",
+ from: "{from_currency}",
+ to: "{to_currency}",
+ };
+ add_param(frm, r.message, params, result);
+ } else if (frm.doc.service_provider == "frankfurter.app") {
+ let result = ["rates", "{to_currency}"];
+ let params = {
+ base: "{from_currency}",
+ symbols: "{to_currency}",
+ };
+ add_param(frm, r.message, params, result);
+ }
+ }
+ },
+ });
+ },
+ use_http: function (frm) {
+ frm.trigger("service_provider");
},
});
diff --git a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json
index df232a5..bd90b8a 100644
--- a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json
+++ b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json
@@ -9,6 +9,7 @@
"disabled",
"service_provider",
"api_endpoint",
+ "use_http",
"access_key",
"url",
"column_break_3",
@@ -91,12 +92,19 @@
"fieldname": "access_key",
"fieldtype": "Data",
"label": "Access Key"
+ },
+ {
+ "default": "0",
+ "depends_on": "eval: doc.service_provider != \"Custom\"",
+ "fieldname": "use_http",
+ "fieldtype": "Check",
+ "label": "Use HTTP Protocol"
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
- "modified": "2023-10-04 15:30:25.333860",
+ "modified": "2024-03-18 08:32:26.895076",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Currency Exchange Settings",
diff --git a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py
index 3393d41..b8817c6 100644
--- a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py
+++ b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py
@@ -31,6 +31,7 @@
result_key: DF.Table[CurrencyExchangeSettingsResult]
service_provider: DF.Literal["frankfurter.app", "exchangerate.host", "Custom"]
url: DF.Data | None
+ use_http: DF.Check
# end: auto-generated types
def validate(self):
@@ -53,7 +54,7 @@
self.set("result_key", [])
self.set("req_params", [])
- self.api_endpoint = "https://api.exchangerate.host/convert"
+ self.api_endpoint = get_api_endpoint(self.service_provider, self.use_http)
self.append("result_key", {"key": "result"})
self.append("req_params", {"key": "access_key", "value": self.access_key})
self.append("req_params", {"key": "amount", "value": "1"})
@@ -64,7 +65,7 @@
self.set("result_key", [])
self.set("req_params", [])
- self.api_endpoint = "https://frankfurter.app/{transaction_date}"
+ self.api_endpoint = get_api_endpoint(self.service_provider, self.use_http)
self.append("result_key", {"key": "rates"})
self.append("result_key", {"key": "{to_currency}"})
self.append("req_params", {"key": "base", "value": "{from_currency}"})
@@ -103,3 +104,19 @@
frappe.throw(_("Returned exchange rate is neither integer not float."))
self.url = response.url
+
+
+@frappe.whitelist()
+def get_api_endpoint(service_provider: str = None, use_http: bool = False):
+ if service_provider and service_provider in ["exchangerate.host", "frankfurter.app"]:
+ if service_provider == "exchangerate.host":
+ api = "api.exchangerate.host/convert"
+ elif service_provider == "frankfurter.app":
+ api = "frankfurter.app/{transaction_date}"
+
+ protocol = "https://"
+ if use_http:
+ protocol = "http://"
+
+ return protocol + api
+ return None
diff --git a/erpnext/accounts/doctype/dunning/dunning.json b/erpnext/accounts/doctype/dunning/dunning.json
index b7e8aea..3273462 100644
--- a/erpnext/accounts/doctype/dunning/dunning.json
+++ b/erpnext/accounts/doctype/dunning/dunning.json
@@ -185,7 +185,7 @@
},
{
"fieldname": "address_display",
- "fieldtype": "Small Text",
+ "fieldtype": "Text Editor",
"label": "Address",
"read_only": 1
},
@@ -204,7 +204,7 @@
},
{
"fieldname": "company_address_display",
- "fieldtype": "Small Text",
+ "fieldtype": "Text Editor",
"label": "Company Address Display",
"read_only": 1
},
@@ -381,7 +381,7 @@
],
"is_submittable": 1,
"links": [],
- "modified": "2023-06-15 15:46:53.865712",
+ "modified": "2024-03-22 16:01:13.231067",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Dunning",
diff --git a/erpnext/accounts/doctype/dunning/dunning.py b/erpnext/accounts/doctype/dunning/dunning.py
index e3897bf..f7c4d90 100644
--- a/erpnext/accounts/doctype/dunning/dunning.py
+++ b/erpnext/accounts/doctype/dunning/dunning.py
@@ -32,14 +32,14 @@
from erpnext.accounts.doctype.overdue_payment.overdue_payment import OverduePayment
- address_display: DF.SmallText | None
+ address_display: DF.TextEditor | None
amended_from: DF.Link | None
base_dunning_amount: DF.Currency
body_text: DF.TextEditor | None
closing_text: DF.TextEditor | None
company: DF.Link
company_address: DF.Link | None
- company_address_display: DF.SmallText | None
+ company_address_display: DF.TextEditor | None
contact_display: DF.SmallText | None
contact_email: DF.Data | None
contact_mobile: DF.SmallText | None
diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js
index 741039a..7d467cb 100644
--- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js
+++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.js
@@ -57,13 +57,13 @@
get_entries: function (frm, account) {
frappe.call({
method: "get_accounts_data",
- doc: cur_frm.doc,
+ doc: frm.doc,
account: account,
callback: function (r) {
frappe.model.clear_table(frm.doc, "accounts");
if (r.message) {
r.message.forEach((d) => {
- cur_frm.add_child("accounts", d);
+ frm.add_child("accounts", d);
});
frm.events.get_total_gain_loss(frm);
refresh_field("accounts");
diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
index 8be09db..29732ef 100644
--- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
+++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
@@ -628,21 +628,21 @@
if account_balance and (
account_balance[0].balance or account_balance[0].balance_in_account_currency
):
- account_with_new_balance = ExchangeRateRevaluation.calculate_new_account_balance(
+ if account_with_new_balance := ExchangeRateRevaluation.calculate_new_account_balance(
company, posting_date, account_balance
- )
- row = account_with_new_balance[0]
- account_details.update(
- {
- "balance_in_base_currency": row["balance_in_base_currency"],
- "balance_in_account_currency": row["balance_in_account_currency"],
- "current_exchange_rate": row["current_exchange_rate"],
- "new_exchange_rate": row["new_exchange_rate"],
- "new_balance_in_base_currency": row["new_balance_in_base_currency"],
- "new_balance_in_account_currency": row["new_balance_in_account_currency"],
- "zero_balance": row["zero_balance"],
- "gain_loss": row["gain_loss"],
- }
- )
+ ):
+ row = account_with_new_balance[0]
+ account_details.update(
+ {
+ "balance_in_base_currency": row["balance_in_base_currency"],
+ "balance_in_account_currency": row["balance_in_account_currency"],
+ "current_exchange_rate": row["current_exchange_rate"],
+ "new_exchange_rate": row["new_exchange_rate"],
+ "new_balance_in_base_currency": row["new_balance_in_base_currency"],
+ "new_balance_in_account_currency": row["new_balance_in_account_currency"],
+ "zero_balance": row["zero_balance"],
+ "gain_loss": row["gain_loss"],
+ }
+ )
return account_details
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.json b/erpnext/accounts/doctype/gl_entry/gl_entry.json
index c071193..991a08b 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.json
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.json
@@ -179,7 +179,8 @@
"fieldname": "voucher_detail_no",
"fieldtype": "Data",
"label": "Voucher Detail No",
- "read_only": 1
+ "read_only": 1,
+ "search_index": 1
},
{
"fieldname": "project",
@@ -290,7 +291,7 @@
"idx": 1,
"in_create": 1,
"links": [],
- "modified": "2023-09-26 12:03:23.031733",
+ "modified": "2024-03-19 18:43:42.235373",
"modified_by": "Administrator",
"module": "Accounts",
"name": "GL Entry",
@@ -325,4 +326,4 @@
"sort_field": "modified",
"sort_order": "DESC",
"states": []
-}
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py
index def2838..a6f6d4e 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.py
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py
@@ -32,8 +32,6 @@
account: DF.Link | None
account_currency: DF.Link | None
against: DF.Text | None
- against_link: DF.DynamicLink | None
- against_type: DF.Link | None
against_voucher: DF.DynamicLink | None
against_voucher_type: DF.Link | None
company: DF.Link | None
@@ -323,7 +321,7 @@
party_condition = ""
if against_voucher_type == "Sales Invoice":
- party_account = frappe.db.get_value(against_voucher_type, against_voucher, "debit_to")
+ party_account = frappe.get_cached_value(against_voucher_type, against_voucher, "debit_to")
account_condition = "and account in ({0}, {1})".format(
frappe.db.escape(account), frappe.db.escape(party_account)
)
@@ -391,8 +389,8 @@
def validate_frozen_account(account, adv_adj=None):
frozen_account = frappe.get_cached_value("Account", account, "freeze_account")
if frozen_account == "Yes" and not adv_adj:
- frozen_accounts_modifier = frappe.db.get_single_value(
- "Accounts Settings", "frozen_accounts_modifier"
+ frozen_accounts_modifier = frappe.get_cached_value(
+ "Accounts Settings", None, "frozen_accounts_modifier"
)
if not frozen_accounts_modifier:
diff --git a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.js b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.js
index 6d90c26..f65f0c2 100644
--- a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.js
+++ b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.js
@@ -189,7 +189,7 @@
show_general_ledger: (frm) => {
if (frm.doc.docstatus > 0) {
- cur_frm.add_custom_button(
+ frm.add_custom_button(
__("Accounting Ledger"),
function () {
frappe.route_options = {
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js
index f6d35fe..ed134ba 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.js
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js
@@ -196,7 +196,7 @@
!(frm.doc.accounts || []).length ||
((frm.doc.accounts || []).length === 1 && !frm.doc.accounts[0].account)
) {
- if (in_list(["Bank Entry", "Cash Entry"], frm.doc.voucher_type)) {
+ if (["Bank Entry", "Cash Entry"].includes(frm.doc.voucher_type)) {
return frappe.call({
type: "GET",
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_default_bank_cash_account",
@@ -255,7 +255,7 @@
}
onload_post_render() {
- cur_frm.get_field("accounts").grid.set_multiple_add("account");
+ this.frm.get_field("accounts").grid.set_multiple_add("account");
}
load_defaults() {
@@ -308,7 +308,7 @@
filters: [[jvd.reference_type, "docstatus", "=", 1]],
};
- if (in_list(["Sales Invoice", "Purchase Invoice"], jvd.reference_type)) {
+ if (["Sales Invoice", "Purchase Invoice"].includes(jvd.reference_type)) {
out.filters.push([jvd.reference_type, "outstanding_amount", "!=", 0]);
// Filter by cost center
if (jvd.cost_center) {
@@ -320,7 +320,7 @@
out.filters.push([jvd.reference_type, party_account_field, "=", jvd.account]);
}
- if (in_list(["Sales Order", "Purchase Order"], jvd.reference_type)) {
+ if (["Sales Order", "Purchase Order"].includes(jvd.reference_type)) {
// party_type and party mandatory
frappe.model.validate_missing(jvd, "party_type");
frappe.model.validate_missing(jvd, "party");
@@ -402,7 +402,7 @@
row.debit = -doc.difference;
}
}
- cur_frm.cscript.update_totals(doc);
+ this.frm.cscript.update_totals(doc);
erpnext.accounts.dimensions.copy_dimension_from_first_row(this.frm, cdt, cdn, "accounts");
}
@@ -469,11 +469,11 @@
},
debit: function (frm, dt, dn) {
- cur_frm.cscript.update_totals(frm.doc);
+ frm.cscript.update_totals(frm.doc);
},
credit: function (frm, dt, dn) {
- cur_frm.cscript.update_totals(frm.doc);
+ frm.cscript.update_totals(frm.doc);
},
exchange_rate: function (frm, cdt, cdn) {
@@ -489,7 +489,7 @@
});
frappe.ui.form.on("Journal Entry Account", "accounts_remove", function (frm) {
- cur_frm.cscript.update_totals(frm.doc);
+ frm.cscript.update_totals(frm.doc);
});
$.extend(erpnext.journal_entry, {
@@ -531,7 +531,7 @@
flt(flt(row.credit_in_account_currency) * row.exchange_rate, precision("credit", row))
);
- cur_frm.cscript.update_totals(frm.doc);
+ frm.cscript.update_totals(frm.doc);
},
set_exchange_rate: function (frm, cdt, cdn) {
@@ -673,10 +673,10 @@
return { filters: filters };
},
- reverse_journal_entry: function () {
+ reverse_journal_entry: function (frm) {
frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.journal_entry.journal_entry.make_reverse_journal_entry",
- frm: cur_frm,
+ frm: frm,
});
},
});
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index ab50c38..781d9f5 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -32,7 +32,7 @@
frm.set_query("paid_from", function () {
frm.events.validate_company(frm);
- var account_types = in_list(["Pay", "Internal Transfer"], frm.doc.payment_type)
+ var account_types = ["Pay", "Internal Transfer"].includes(frm.doc.payment_type)
? ["Bank", "Cash"]
: [frappe.boot.party_account_types[frm.doc.party_type]];
return {
@@ -87,7 +87,7 @@
frm.set_query("paid_to", function () {
frm.events.validate_company(frm);
- var account_types = in_list(["Receive", "Internal Transfer"], frm.doc.payment_type)
+ var account_types = ["Receive", "Internal Transfer"].includes(frm.doc.payment_type)
? ["Bank", "Cash"]
: [frappe.boot.party_account_types[frm.doc.party_type]];
return {
@@ -134,7 +134,7 @@
frm.set_query("payment_term", "references", function (frm, cdt, cdn) {
const child = locals[cdt][cdn];
if (
- in_list(["Purchase Invoice", "Sales Invoice"], child.reference_doctype) &&
+ ["Purchase Invoice", "Sales Invoice"].includes(child.reference_doctype) &&
child.reference_name
) {
return {
@@ -322,13 +322,13 @@
"references"
);
- cur_frm.set_df_property(
+ frm.set_df_property(
"source_exchange_rate",
"description",
"1 " + frm.doc.paid_from_account_currency + " = [?] " + company_currency
);
- cur_frm.set_df_property(
+ frm.set_df_property(
"target_exchange_rate",
"description",
"1 " + frm.doc.paid_to_account_currency + " = [?] " + company_currency
@@ -395,10 +395,6 @@
return {
query: "erpnext.controllers.queries.employee_query",
};
- } else if (frm.doc.party_type == "Customer") {
- return {
- query: "erpnext.controllers.queries.customer_query",
- };
}
});
@@ -627,7 +623,7 @@
if (frm.doc.paid_from_account_currency == company_currency) {
frm.set_value("source_exchange_rate", 1);
} else if (frm.doc.paid_from) {
- if (in_list(["Internal Transfer", "Pay"], frm.doc.payment_type)) {
+ if (["Internal Transfer", "Pay"].includes(frm.doc.payment_type)) {
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
frappe.call({
method: "erpnext.setup.utils.get_exchange_rate",
@@ -1046,7 +1042,7 @@
}
var allocated_positive_outstanding = paid_amount + allocated_negative_outstanding;
- } else if (in_list(["Customer", "Supplier"], frm.doc.party_type)) {
+ } else if (["Customer", "Supplier"].includes(frm.doc.party_type)) {
total_negative_outstanding = flt(total_negative_outstanding, precision("outstanding_amount"));
if (paid_amount > total_negative_outstanding) {
if (total_negative_outstanding == 0) {
@@ -1217,7 +1213,7 @@
if (
frm.doc.party_type == "Customer" &&
- !in_list(["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"], row.reference_doctype)
+ !["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"].includes(row.reference_doctype)
) {
frappe.model.set_value(row.doctype, row.name, "reference_doctype", null);
frappe.msgprint(
@@ -1231,7 +1227,7 @@
if (
frm.doc.party_type == "Supplier" &&
- !in_list(["Purchase Order", "Purchase Invoice", "Journal Entry"], row.reference_doctype)
+ !["Purchase Order", "Purchase Invoice", "Journal Entry"].includes(row.reference_doctype)
) {
frappe.model.set_value(row.doctype, row.name, "against_voucher_type", null);
frappe.msgprint(
@@ -1327,7 +1323,7 @@
bank_account: function (frm) {
const field = frm.doc.payment_type == "Pay" ? "paid_from" : "paid_to";
- if (frm.doc.bank_account && in_list(["Pay", "Receive"], frm.doc.payment_type)) {
+ if (frm.doc.bank_account && ["Pay", "Receive"].includes(frm.doc.payment_type)) {
frappe.call({
method: "erpnext.accounts.doctype.bank_account.bank_account.get_bank_account_details",
args: {
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 95bb188..4684aab 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -30,7 +30,7 @@
make_reverse_gl_entries,
process_gl_map,
)
-from erpnext.accounts.party import get_party_account, set_contact_details
+from erpnext.accounts.party import complete_contact_details, get_party_account, set_contact_details
from erpnext.accounts.utils import (
cancel_exchange_gain_loss_journal,
get_account_currency,
@@ -446,6 +446,8 @@
if self.party:
if not self.contact_person:
set_contact_details(self, party=frappe._dict({"name": self.party}), party_type=self.party_type)
+ else:
+ complete_contact_details(self)
if not self.party_balance:
self.party_balance = get_balance_on(
party_type=self.party_type, party=self.party, date=self.posting_date, company=self.company
@@ -489,7 +491,9 @@
ref_details = get_reference_details(
d.reference_doctype, d.reference_name, self.party_account_currency
)
- if ref_exchange_rate:
+
+ # Only update exchange rate when the reference is Journal Entry
+ if ref_exchange_rate and d.reference_doctype == "Journal Entry":
ref_details.update({"exchange_rate": ref_exchange_rate})
for field, value in ref_details.items():
@@ -611,9 +615,9 @@
def get_valid_reference_doctypes(self):
if self.party_type == "Customer":
- return ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning")
+ return ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning", "Payment Entry")
elif self.party_type == "Supplier":
- return ("Purchase Order", "Purchase Invoice", "Journal Entry")
+ return ("Purchase Order", "Purchase Invoice", "Journal Entry", "Payment Entry")
elif self.party_type == "Shareholder":
return ("Journal Entry",)
elif self.party_type == "Employee":
@@ -1279,6 +1283,7 @@
"Journal Entry",
"Sales Order",
"Purchase Order",
+ "Payment Entry",
):
self.add_advance_gl_for_reference(gl_entries, ref)
@@ -1301,7 +1306,9 @@
if getdate(posting_date) < getdate(self.posting_date):
posting_date = self.posting_date
- dr_or_cr = "credit" if invoice.reference_doctype in ["Sales Invoice", "Sales Order"] else "debit"
+ dr_or_cr = (
+ "credit" if invoice.reference_doctype in ["Sales Invoice", "Payment Entry"] else "debit"
+ )
args_dict["account"] = invoice.account
args_dict[dr_or_cr] = invoice.allocated_amount
args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount
@@ -1751,7 +1758,7 @@
outstanding_invoices = get_outstanding_invoices(
args.get("party_type"),
args.get("party"),
- party_account,
+ [party_account],
common_filter=common_filter,
posting_date=posting_and_due_date,
min_outstanding=args.get("outstanding_amt_greater_than"),
@@ -2290,7 +2297,7 @@
pe.party_type = party_type
pe.party = doc.get(scrub(party_type))
pe.contact_person = doc.get("contact_person")
- pe.contact_email = doc.get("contact_email")
+ complete_contact_details(pe)
pe.ensure_supplier_is_not_blocked()
pe.paid_from = party_account if payment_type == "Receive" else bank.account
diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
index 5a014b8..6323e4c 100644
--- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
@@ -1514,6 +1514,168 @@
for field in ["account", "debit", "credit"]:
self.assertEqual(self.expected_gle[row][field], gl_entries[row][field])
+ def test_reverse_payment_reconciliation(self):
+ customer = create_customer(frappe.generate_hash(length=10), "INR")
+ pe = create_payment_entry(
+ party_type="Customer",
+ party=customer,
+ payment_type="Receive",
+ paid_from="Debtors - _TC",
+ paid_to="_Test Cash - _TC",
+ )
+ pe.submit()
+
+ reverse_pe = create_payment_entry(
+ party_type="Customer",
+ party=customer,
+ payment_type="Pay",
+ paid_from="_Test Cash - _TC",
+ paid_to="Debtors - _TC",
+ )
+ reverse_pe.submit()
+
+ pr = frappe.get_doc("Payment Reconciliation")
+ pr.company = "_Test Company"
+ pr.party_type = "Customer"
+ pr.party = customer
+ pr.receivable_payable_account = "Debtors - _TC"
+ pr.get_unreconciled_entries()
+ self.assertEqual(len(pr.invoices), 1)
+ self.assertEqual(len(pr.payments), 1)
+
+ self.assertEqual(reverse_pe.name, pr.invoices[0].invoice_number)
+ self.assertEqual(pe.name, pr.payments[0].reference_name)
+
+ invoices = [x.as_dict() for x in pr.invoices]
+ payments = [pr.payments[0].as_dict()]
+ pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
+ pr.reconcile()
+ self.assertEqual(len(pr.invoices), 0)
+ self.assertEqual(len(pr.payments), 0)
+
+ def test_advance_reverse_payment_reconciliation(self):
+ from erpnext.accounts.doctype.account.test_account import create_account
+
+ company = "_Test Company"
+ customer = create_customer(frappe.generate_hash(length=10), "INR")
+ advance_account = create_account(
+ parent_account="Current Assets - _TC",
+ account_name="Advances Received",
+ company=company,
+ account_type="Receivable",
+ )
+
+ frappe.db.set_value(
+ "Company",
+ company,
+ {
+ "book_advance_payments_in_separate_party_account": 1,
+ "default_advance_received_account": advance_account,
+ },
+ )
+ # Reverse Payment(essentially an Invoice)
+ reverse_pe = create_payment_entry(
+ party_type="Customer",
+ party=customer,
+ payment_type="Pay",
+ paid_from="_Test Cash - _TC",
+ paid_to=advance_account,
+ )
+ reverse_pe.save() # use save() to trigger set_liability_account()
+ reverse_pe.submit()
+
+ # Advance Payment
+ pe = create_payment_entry(
+ party_type="Customer",
+ party=customer,
+ payment_type="Receive",
+ paid_from=advance_account,
+ paid_to="_Test Cash - _TC",
+ )
+ pe.save() # use save() to trigger set_liability_account()
+ pe.submit()
+
+ # Partially reconcile advance against invoice
+ pr = frappe.get_doc("Payment Reconciliation")
+ pr.company = company
+ pr.party_type = "Customer"
+ pr.party = customer
+ pr.receivable_payable_account = "Debtors - _TC"
+ pr.default_advance_account = advance_account
+ pr.get_unreconciled_entries()
+
+ self.assertEqual(len(pr.invoices), 1)
+ self.assertEqual(len(pr.payments), 1)
+
+ invoices = [x.as_dict() for x in pr.get("invoices")]
+ payments = [x.as_dict() for x in pr.get("payments")]
+ pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
+ pr.allocation[0].allocated_amount = 400
+ pr.reconcile()
+
+ # assert General and Payment Ledger entries post partial reconciliation
+ self.expected_gle = [
+ {"account": "Debtors - _TC", "debit": 0.0, "credit": 400.0},
+ {"account": advance_account, "debit": 400.0, "credit": 0.0},
+ {"account": advance_account, "debit": 0.0, "credit": 1000.0},
+ {"account": "_Test Cash - _TC", "debit": 1000.0, "credit": 0.0},
+ ]
+ self.expected_ple = [
+ {
+ "account": advance_account,
+ "voucher_no": pe.name,
+ "against_voucher_no": pe.name,
+ "amount": -1000.0,
+ },
+ {
+ "account": "Debtors - _TC",
+ "voucher_no": pe.name,
+ "against_voucher_no": reverse_pe.name,
+ "amount": -400.0,
+ },
+ {
+ "account": advance_account,
+ "voucher_no": pe.name,
+ "against_voucher_no": pe.name,
+ "amount": 400.0,
+ },
+ ]
+ self.voucher_no = pe.name
+ self.check_gl_entries()
+ self.check_pl_entries()
+
+ # Unreconcile
+ unrecon = (
+ frappe.get_doc(
+ {
+ "doctype": "Unreconcile Payment",
+ "company": company,
+ "voucher_type": pe.doctype,
+ "voucher_no": pe.name,
+ "allocations": [{"reference_doctype": reverse_pe.doctype, "reference_name": reverse_pe.name}],
+ }
+ )
+ .save()
+ .submit()
+ )
+
+ # assert General and Payment Ledger entries post unreconciliation
+ self.expected_gle = [
+ {"account": advance_account, "debit": 0.0, "credit": 1000.0},
+ {"account": "_Test Cash - _TC", "debit": 1000.0, "credit": 0.0},
+ ]
+ self.expected_ple = [
+ {
+ "account": advance_account,
+ "voucher_no": pe.name,
+ "against_voucher_no": pe.name,
+ "amount": -1000.0,
+ },
+ ]
+ self.voucher_no = pe.name
+ self.check_gl_entries()
+ self.check_pl_entries()
+
def create_payment_entry(**args):
payment_entry = frappe.new_doc("Payment Entry")
diff --git a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py
index e8dfda2..3fea325 100644
--- a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py
+++ b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py
@@ -161,11 +161,12 @@
def on_update(self):
adv_adj = self.flags.adv_adj
if not self.flags.from_repost:
- self.validate_account_details()
- self.validate_dimensions_for_pl_and_bs()
- self.validate_allowed_dimensions()
- validate_balance_type(self.account, adv_adj)
validate_frozen_account(self.account, adv_adj)
+ if not self.delinked:
+ self.validate_account_details()
+ self.validate_dimensions_for_pl_and_bs()
+ self.validate_allowed_dimensions()
+ validate_balance_type(self.account, adv_adj)
# update outstanding amount
if (
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index 972ce26..dcb1a16 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -340,10 +340,15 @@
self.build_qb_filter_conditions(get_invoices=True)
+ accounts = [self.receivable_payable_account]
+
+ if self.default_advance_account:
+ accounts.append(self.default_advance_account)
+
non_reconciled_invoices = get_outstanding_invoices(
self.party_type,
self.party,
- self.receivable_payable_account,
+ accounts,
common_filter=self.common_filter_conditions,
posting_date=self.ple_posting_date_filter,
min_outstanding=self.minimum_invoice_amount if self.minimum_invoice_amount else None,
diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
index 301e6ef..1d20a5b 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
@@ -1130,6 +1130,17 @@
self.assertEqual(pr.allocation[0].allocated_amount, 85)
self.assertEqual(pr.allocation[0].difference_amount, 0)
+ pr.reconcile()
+ si.reload()
+ self.assertEqual(si.outstanding_amount, 0)
+ # No Exchange Gain/Loss journal should be generated
+ exc_gain_loss_journals = frappe.db.get_all(
+ "Journal Entry Account",
+ filters={"reference_type": si.doctype, "reference_name": si.name, "docstatus": 1},
+ fields=["parent"],
+ )
+ self.assertEqual(exc_gain_loss_journals, [])
+
def test_reconciliation_purchase_invoice_against_return(self):
self.supplier = "_Test Supplier USD"
pi = self.create_purchase_invoice(qty=5, rate=50, do_not_submit=True)
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.js b/erpnext/accounts/doctype/payment_request/payment_request.js
index d07f824..f12facf 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.js
+++ b/erpnext/accounts/doctype/payment_request/payment_request.js
@@ -32,7 +32,7 @@
if (
frm.doc.payment_request_type == "Inward" &&
frm.doc.payment_channel !== "Phone" &&
- !in_list(["Initiated", "Paid"], frm.doc.status) &&
+ !["Initiated", "Paid"].includes(frm.doc.status) &&
!frm.doc.__islocal &&
frm.doc.docstatus == 1
) {
diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
index 2a84d97..76c0a09 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
+++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
@@ -141,7 +141,8 @@
previous_fiscal_year = get_fiscal_year(last_year_closing, company=self.company, boolean=True)
if previous_fiscal_year and not frappe.db.exists(
- "GL Entry", {"posting_date": ("<=", last_year_closing), "company": self.company}
+ "GL Entry",
+ {"posting_date": ("<=", last_year_closing), "company": self.company, "is_cancelled": 0},
):
return
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
index a6e8bfa..52c7706 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
@@ -193,7 +193,7 @@
make_sales_return() {
frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.pos_invoice.pos_invoice.make_sales_return",
- frm: cur_frm,
+ frm: this.frm,
});
}
};
@@ -293,7 +293,7 @@
});
} else if (frappe.dom.freeze_count != 0) {
frappe.dom.unfreeze();
- cur_frm.reload_doc();
+ frm.reload_doc();
cur_pos.payment.events.submit_invoice();
frappe.show_alert({
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
index 955b66a..467ea54 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
@@ -420,7 +420,7 @@
},
{
"fieldname": "address_display",
- "fieldtype": "Small Text",
+ "fieldtype": "Text Editor",
"label": "Address",
"read_only": 1
},
@@ -475,7 +475,7 @@
},
{
"fieldname": "shipping_address",
- "fieldtype": "Small Text",
+ "fieldtype": "Text Editor",
"label": "Shipping Address",
"print_hide": 1,
"read_only": 1
@@ -489,7 +489,7 @@
},
{
"fieldname": "company_address_display",
- "fieldtype": "Small Text",
+ "fieldtype": "Text Editor",
"hidden": 1,
"label": "Company Address",
"print_hide": 1,
@@ -775,7 +775,7 @@
},
{
"fieldname": "other_charges_calculation",
- "fieldtype": "Long Text",
+ "fieldtype": "Text Editor",
"label": "Taxes and Charges Calculation",
"no_copy": 1,
"oldfieldtype": "HTML",
@@ -1563,7 +1563,7 @@
"icon": "fa fa-file-text",
"is_submittable": 1,
"links": [],
- "modified": "2023-11-20 12:27:12.848149",
+ "modified": "2024-03-22 16:15:08.561034",
"modified_by": "Administrator",
"module": "Accounts",
"name": "POS Invoice",
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index 1a7eef6..8052c4c 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -47,7 +47,7 @@
account_for_change_amount: DF.Link | None
additional_discount_percentage: DF.Float
- address_display: DF.SmallText | None
+ address_display: DF.TextEditor | None
advances: DF.Table[SalesInvoiceAdvance]
against_income_account: DF.SmallText | None
allocate_advances_automatically: DF.Check
@@ -72,7 +72,7 @@
commission_rate: DF.Float
company: DF.Link
company_address: DF.Link | None
- company_address_display: DF.SmallText | None
+ company_address_display: DF.TextEditor | None
consolidated_invoice: DF.Link | None
contact_display: DF.SmallText | None
contact_email: DF.Data | None
@@ -109,7 +109,7 @@
loyalty_redemption_cost_center: DF.Link | None
naming_series: DF.Literal["ACC-PSINV-.YYYY.-"]
net_total: DF.Currency
- other_charges_calculation: DF.LongText | None
+ other_charges_calculation: DF.TextEditor | None
outstanding_amount: DF.Currency
packed_items: DF.Table[PackedItem]
paid_amount: DF.Currency
@@ -138,7 +138,7 @@
selling_price_list: DF.Link
set_posting_time: DF.Check
set_warehouse: DF.Link | None
- shipping_address: DF.SmallText | None
+ shipping_address: DF.TextEditor | None
shipping_address_name: DF.Link | None
shipping_rule: DF.Link | None
source: DF.Link | None
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index 17293ad..73c5d59 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -736,7 +736,6 @@
def validate_coupon_code(coupon_name):
coupon = frappe.get_doc("Coupon Code", coupon_name)
-
if coupon.valid_from:
if coupon.valid_from > getdate(today()):
frappe.throw(_("Sorry, this coupon code's validity has not started"))
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
index 5307ccb..81ebf97 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
@@ -89,10 +89,11 @@
<table class="table table-bordered">
<thead>
<tr>
- <th style="width: 25%">30 Days</th>
- <th style="width: 25%">60 Days</th>
- <th style="width: 25%">90 Days</th>
- <th style="width: 25%">120 Days</th>
+ <th style="width: 20%">0 - 30 Days</th>
+ <th style="width: 20%">30 - 60 Days</th>
+ <th style="width: 20%">60 - 90 Days</th>
+ <th style="width: 20%">90 - 120 Days</th>
+ <th style="width: 20%">Above 120 Days</th>
</tr>
</thead>
<tbody>
@@ -101,6 +102,7 @@
<td>{{ frappe.utils.fmt_money(ageing.range2, currency=filters.presentation_currency) }}</td>
<td>{{ frappe.utils.fmt_money(ageing.range3, currency=filters.presentation_currency) }}</td>
<td>{{ frappe.utils.fmt_money(ageing.range4, currency=filters.presentation_currency) }}</td>
+ <td>{{ frappe.utils.fmt_money(ageing.range5, currency=filters.presentation_currency) }}</td>
</tr>
</tbody>
</table>
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index d6455b2..2bd39a5 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -3,6 +3,8 @@
frappe.provide("erpnext.accounts");
+cur_frm.cscript.tax_table = "Purchase Taxes and Charges";
+
erpnext.accounts.payment_triggers.setup("Purchase Invoice");
erpnext.accounts.taxes.setup_tax_filters("Purchase Taxes and Charges");
erpnext.accounts.taxes.setup_tax_validations("Purchase Invoice");
@@ -129,17 +131,17 @@
if (doc.docstatus == 1 && doc.outstanding_amount != 0 && !doc.on_hold) {
this.frm.add_custom_button(__("Payment"), () => this.make_payment_entry(), __("Create"));
- cur_frm.page.set_inner_btn_group_as_primary(__("Create"));
+ this.frm.page.set_inner_btn_group_as_primary(__("Create"));
}
if (!doc.is_return && doc.docstatus == 1) {
if (doc.outstanding_amount >= 0 || Math.abs(flt(doc.outstanding_amount)) < flt(doc.grand_total)) {
- cur_frm.add_custom_button(__("Return / Debit Note"), this.make_debit_note, __("Create"));
+ this.frm.add_custom_button(__("Return / Debit Note"), this.make_debit_note, __("Create"));
}
}
if (doc.outstanding_amount > 0 && !cint(doc.is_return) && !doc.on_hold) {
- cur_frm.add_custom_button(
+ this.frm.add_custom_button(
__("Payment Request"),
function () {
me.make_payment_request();
@@ -460,7 +462,7 @@
make_debit_note() {
frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_debit_note",
- frm: cur_frm,
+ frm: this.frm,
});
}
};
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index 22f2d13..01a3746 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -443,7 +443,7 @@
},
{
"fieldname": "address_display",
- "fieldtype": "Small Text",
+ "fieldtype": "Text Editor",
"label": "Address",
"read_only": 1
},
@@ -489,7 +489,7 @@
},
{
"fieldname": "shipping_address_display",
- "fieldtype": "Small Text",
+ "fieldtype": "Text Editor",
"label": "Shipping Address",
"print_hide": 1,
"read_only": 1
@@ -760,7 +760,7 @@
},
{
"fieldname": "other_charges_calculation",
- "fieldtype": "Long Text",
+ "fieldtype": "Text Editor",
"label": "Taxes and Charges Calculation",
"no_copy": 1,
"oldfieldtype": "HTML",
@@ -1363,7 +1363,7 @@
},
{
"fieldname": "billing_address_display",
- "fieldtype": "Small Text",
+ "fieldtype": "Text Editor",
"label": "Billing Address",
"read_only": 1
},
@@ -1638,7 +1638,7 @@
"idx": 204,
"is_submittable": 1,
"links": [],
- "modified": "2024-03-11 14:46:30.298184",
+ "modified": "2024-03-22 16:15:09.099187",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 28d4a5e..4b5b456 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -3,7 +3,7 @@
import frappe
-from frappe import _, throw
+from frappe import _, qb, throw
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
@@ -82,7 +82,7 @@
)
additional_discount_percentage: DF.Float
- address_display: DF.SmallText | None
+ address_display: DF.TextEditor | None
advance_tax: DF.Table[AdvanceTax]
advances: DF.Table[PurchaseInvoiceAdvance]
against_expense_account: DF.SmallText | None
@@ -107,7 +107,7 @@
bill_date: DF.Date | None
bill_no: DF.Data | None
billing_address: DF.Link | None
- billing_address_display: DF.SmallText | None
+ billing_address_display: DF.TextEditor | None
buying_price_list: DF.Link | None
cash_bank_account: DF.Link | None
clearance_date: DF.Date | None
@@ -147,7 +147,7 @@
net_total: DF.Currency
on_hold: DF.Check
only_include_allocated_payments: DF.Check
- other_charges_calculation: DF.LongText | None
+ other_charges_calculation: DF.TextEditor | None
outstanding_amount: DF.Currency
paid_amount: DF.Currency
party_account_currency: DF.Link | None
@@ -174,7 +174,7 @@
set_posting_time: DF.Check
set_warehouse: DF.Link | None
shipping_address: DF.Link | None
- shipping_address_display: DF.SmallText | None
+ shipping_address_display: DF.TextEditor | None
shipping_rule: DF.Link | None
status: DF.Literal[
"",
@@ -742,13 +742,12 @@
self.db_set("repost_required", self.needs_repost)
def make_gl_entries(self, gl_entries=None, from_repost=False):
- if not gl_entries:
- gl_entries = self.get_gl_entries()
+ update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes"
+ if self.docstatus == 1:
+ if not gl_entries:
+ gl_entries = self.get_gl_entries()
- if gl_entries:
- update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes"
-
- if self.docstatus == 1:
+ if gl_entries:
make_gl_entries(
gl_entries,
update_outstanding=update_outstanding,
@@ -756,29 +755,43 @@
from_repost=from_repost,
)
self.make_exchange_gain_loss_journal()
- elif self.docstatus == 2:
- provisional_entries = [a for a in gl_entries if a.voucher_type == "Purchase Receipt"]
- make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
- if provisional_entries:
- for entry in provisional_entries:
- frappe.db.set_value(
- "GL Entry",
- {"voucher_type": "Purchase Receipt", "voucher_detail_no": entry.voucher_detail_no},
- "is_cancelled",
- 1,
- )
-
- if update_outstanding == "No":
- update_outstanding_amt(
- self.credit_to,
- "Supplier",
- self.supplier,
- self.doctype,
- self.return_against if cint(self.is_return) and self.return_against else self.name,
- )
-
- elif self.docstatus == 2 and cint(self.update_stock) and self.auto_accounting_for_stock:
+ elif self.docstatus == 2:
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
+ self.cancel_provisional_entries()
+
+ self.update_supplier_outstanding(update_outstanding)
+
+ def cancel_provisional_entries(self):
+ rows = set()
+ purchase_receipts = set()
+ for d in self.items:
+ if d.purchase_receipt:
+ purchase_receipts.add(d.purchase_receipt)
+ rows.add(d.name)
+
+ if rows:
+ # cancel gl entries
+ gle = qb.DocType("GL Entry")
+ gle_update_query = (
+ qb.update(gle)
+ .set(gle.is_cancelled, 1)
+ .where(
+ (gle.voucher_type == "Purchase Receipt")
+ & (gle.voucher_no.isin(purchase_receipts))
+ & (gle.voucher_detail_no.isin(rows))
+ )
+ )
+ gle_update_query.run()
+
+ def update_supplier_outstanding(self, update_outstanding):
+ if update_outstanding == "No":
+ update_outstanding_amt(
+ self.credit_to,
+ "Supplier",
+ self.supplier,
+ self.doctype,
+ self.return_against if cint(self.is_return) and self.return_against else self.name,
+ )
def get_gl_entries(self, warehouse_account=None):
self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
@@ -891,8 +904,8 @@
"Company", self.company, "enable_provisional_accounting_for_non_stock_items"
)
)
-
- purchase_receipt_doc_map = {}
+ if provisional_accounting_for_non_stock_items:
+ self.get_provisional_accounts()
for item in self.get("items"):
if flt(item.base_net_amount):
@@ -1029,44 +1042,7 @@
dummy, amount = self.get_amount_and_base_amount(item, None)
if provisional_accounting_for_non_stock_items:
- if item.purchase_receipt:
- provisional_account, pr_qty, pr_base_rate = frappe.get_cached_value(
- "Purchase Receipt Item",
- item.pr_detail,
- ["provisional_expense_account", "qty", "base_rate"],
- )
- provisional_account = provisional_account or self.get_company_default(
- "default_provisional_account"
- )
- purchase_receipt_doc = purchase_receipt_doc_map.get(item.purchase_receipt)
-
- if not purchase_receipt_doc:
- purchase_receipt_doc = frappe.get_doc("Purchase Receipt", item.purchase_receipt)
- purchase_receipt_doc_map[item.purchase_receipt] = purchase_receipt_doc
-
- # Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt
- expense_booked_in_pr = frappe.db.get_value(
- "GL Entry",
- {
- "is_cancelled": 0,
- "voucher_type": "Purchase Receipt",
- "voucher_no": item.purchase_receipt,
- "voucher_detail_no": item.pr_detail,
- "account": provisional_account,
- },
- "name",
- )
-
- if expense_booked_in_pr:
- # Intentionally passing purchase invoice item to handle partial billing
- purchase_receipt_doc.add_provisional_gl_entry(
- item,
- gl_entries,
- self.posting_date,
- provisional_account,
- reverse=1,
- item_amount=(min(item.qty, pr_qty) * pr_base_rate),
- )
+ self.make_provisional_gl_entry(gl_entries, item)
if not self.is_internal_transfer():
gl_entries.append(
@@ -1163,6 +1139,57 @@
if item.is_fixed_asset and item.landed_cost_voucher_amount:
self.update_gross_purchase_amount_for_linked_assets(item)
+ def get_provisional_accounts(self):
+ self.provisional_accounts = frappe._dict()
+ linked_purchase_receipts = set([d.purchase_receipt for d in self.items if d.purchase_receipt])
+ pr_items = frappe.get_all(
+ "Purchase Receipt Item",
+ filters={"parent": ("in", linked_purchase_receipts)},
+ fields=["name", "provisional_expense_account", "qty", "base_rate"],
+ )
+ default_provisional_account = self.get_company_default("default_provisional_account")
+ provisional_accounts = set(
+ [
+ d.provisional_expense_account if d.provisional_expense_account else default_provisional_account
+ for d in pr_items
+ ]
+ )
+
+ provisional_gl_entries = frappe.get_all(
+ "GL Entry",
+ filters={
+ "voucher_type": "Purchase Receipt",
+ "voucher_no": ("in", linked_purchase_receipts),
+ "account": ("in", provisional_accounts),
+ "is_cancelled": 0,
+ },
+ fields=["voucher_detail_no"],
+ )
+ rows_with_provisional_entries = [d.voucher_detail_no for d in provisional_gl_entries]
+ for item in pr_items:
+ self.provisional_accounts[item.name] = {
+ "provisional_account": item.provisional_expense_account or default_provisional_account,
+ "qty": item.qty,
+ "base_rate": item.base_rate,
+ "has_provisional_entry": item.name in rows_with_provisional_entries,
+ }
+
+ def make_provisional_gl_entry(self, gl_entries, item):
+ if item.purchase_receipt:
+ pr_item = self.provisional_accounts.get(item.pr_detail, {})
+ if pr_item.get("has_provisional_entry"):
+ purchase_receipt_doc = frappe.get_cached_doc("Purchase Receipt", item.purchase_receipt)
+
+ # Intentionally passing purchase invoice item to handle partial billing
+ purchase_receipt_doc.add_provisional_gl_entry(
+ item,
+ gl_entries,
+ self.posting_date,
+ pr_item.get("provisional_account"),
+ reverse=1,
+ item_amount=(min(item.qty, pr_item.get("qty")) * pr_item.get("base_rate")),
+ )
+
def update_gross_purchase_amount_for_linked_assets(self, item):
assets = frappe.db.get_all(
"Asset",
diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
index 3ee4214..66df76a 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -745,6 +745,7 @@
"fieldtype": "Currency",
"label": "Landed Cost Voucher Amount",
"no_copy": 1,
+ "options": "Company:company:default_currency",
"print_hide": 1,
"read_only": 1
},
@@ -938,7 +939,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2024-02-04 14:11:52.742228",
+ "modified": "2024-03-19 19:09:47.210965",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",
diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.js b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.js
index 66a9cbe..4c94503 100644
--- a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.js
+++ b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.js
@@ -1,6 +1,7 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
+cur_frm.cscript.tax_table = "Purchase Taxes and Charges";
erpnext.accounts.taxes.setup_tax_validations("Purchase Taxes and Charges Template");
erpnext.accounts.taxes.setup_tax_filters("Purchase Taxes and Charges");
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 17101cd..cf01bdd 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -3,6 +3,8 @@
frappe.provide("erpnext.accounts");
+cur_frm.cscript.tax_table = "Sales Taxes and Charges";
+
erpnext.accounts.taxes.setup_tax_validations("Sales Invoice");
erpnext.accounts.payment_triggers.setup("Sales Invoice");
erpnext.accounts.pos.setup("Sales Invoice");
@@ -59,9 +61,9 @@
refresh(doc, dt, dn) {
const me = this;
super.refresh();
- if (cur_frm.msgbox && cur_frm.msgbox.$wrapper.is(":visible")) {
+ if (this.frm.msgbox && this.frm.msgbox.$wrapper.is(":visible")) {
// hide new msgbox
- cur_frm.msgbox.hide();
+ this.frm.msgbox.hide();
}
this.frm.toggle_reqd("due_date", !this.frm.doc.is_return);
@@ -111,33 +113,33 @@
if (doc.docstatus == 1 && !doc.is_return) {
var is_delivered_by_supplier = false;
- is_delivered_by_supplier = cur_frm.doc.items.some(function (item) {
+ is_delivered_by_supplier = this.frm.doc.items.some(function (item) {
return item.is_delivered_by_supplier ? true : false;
});
if (doc.outstanding_amount >= 0 || Math.abs(flt(doc.outstanding_amount)) < flt(doc.grand_total)) {
- cur_frm.add_custom_button(__("Return / Credit Note"), this.make_sales_return, __("Create"));
- cur_frm.page.set_inner_btn_group_as_primary(__("Create"));
+ this.frm.add_custom_button(__("Return / Credit Note"), this.make_sales_return, __("Create"));
+ this.frm.page.set_inner_btn_group_as_primary(__("Create"));
}
if (cint(doc.update_stock) != 1) {
// show Make Delivery Note button only if Sales Invoice is not created from Delivery Note
var from_delivery_note = false;
- from_delivery_note = cur_frm.doc.items.some(function (item) {
+ from_delivery_note = this.frm.doc.items.some(function (item) {
return item.delivery_note ? true : false;
});
if (!from_delivery_note && !is_delivered_by_supplier) {
- cur_frm.add_custom_button(
+ this.frm.add_custom_button(
__("Delivery"),
- cur_frm.cscript["Make Delivery Note"],
+ this.frm.cscript["Make Delivery Note"],
__("Create")
);
}
}
if (doc.outstanding_amount > 0) {
- cur_frm.add_custom_button(
+ this.frm.add_custom_button(
__("Payment Request"),
function () {
me.make_payment_request();
@@ -145,10 +147,10 @@
__("Create")
);
- cur_frm.add_custom_button(
+ this.frm.add_custom_button(
__("Invoice Discounting"),
function () {
- cur_frm.events.create_invoice_discounting(cur_frm);
+ this.frm.events.create_invoice_discounting(this.frm);
},
__("Create")
);
@@ -169,10 +171,10 @@
}
if (doc.docstatus === 1) {
- cur_frm.add_custom_button(
+ this.frm.add_custom_button(
__("Maintenance Schedule"),
function () {
- cur_frm.cscript.make_maintenance_schedule();
+ this.frm.cscript.make_maintenance_schedule();
},
__("Create")
);
@@ -180,7 +182,7 @@
}
// Show buttons only when pos view is active
- if (cint(doc.docstatus == 0) && cur_frm.page.current_view_name !== "pos" && !doc.is_return) {
+ if (cint(doc.docstatus == 0) && this.frm.page.current_view_name !== "pos" && !doc.is_return) {
this.frm.cscript.sales_order_btn();
this.frm.cscript.delivery_note_btn();
this.frm.cscript.quotation_btn();
@@ -211,7 +213,7 @@
make_maintenance_schedule() {
frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_maintenance_schedule",
- frm: cur_frm,
+ frm: this.frm,
});
}
@@ -230,28 +232,27 @@
set_default_print_format() {
// set default print format to POS type or Credit Note
- if (cur_frm.doc.is_pos) {
- if (cur_frm.pos_print_format) {
- cur_frm.meta._default_print_format = cur_frm.meta.default_print_format;
- cur_frm.meta.default_print_format = cur_frm.pos_print_format;
+ if (this.frm.doc.is_pos) {
+ if (this.frm.pos_print_format) {
+ this.frm.meta._default_print_format = this.frm.meta.default_print_format;
+ this.frm.meta.default_print_format = this.frm.pos_print_format;
}
- } else if (cur_frm.doc.is_return && !cur_frm.meta.default_print_format) {
- if (cur_frm.return_print_format) {
- cur_frm.meta._default_print_format = cur_frm.meta.default_print_format;
- cur_frm.meta.default_print_format = cur_frm.return_print_format;
+ } else if (this.frm.doc.is_return && !this.frm.meta.default_print_format) {
+ if (this.frm.return_print_format) {
+ this.frm.meta._default_print_format = this.frm.meta.default_print_format;
+ this.frm.meta.default_print_format = this.frm.return_print_format;
}
} else {
- if (cur_frm.meta._default_print_format) {
- cur_frm.meta.default_print_format = cur_frm.meta._default_print_format;
- cur_frm.meta._default_print_format = null;
+ if (this.frm.meta._default_print_format) {
+ this.frm.meta.default_print_format = this.frm.meta._default_print_format;
+ this.frm.meta._default_print_format = null;
} else if (
- in_list(
- [cur_frm.pos_print_format, cur_frm.return_print_format],
- cur_frm.meta.default_print_format
+ [this.frm.pos_print_format, this.frm.return_print_format].includes(
+ this.frm.meta.default_print_format
)
) {
- cur_frm.meta.default_print_format = null;
- cur_frm.meta._default_print_format = null;
+ this.frm.meta.default_print_format = null;
+ this.frm.meta._default_print_format = null;
}
}
}
@@ -463,7 +464,7 @@
make_sales_return() {
frappe.model.open_mapped_doc({
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_sales_return",
- frm: cur_frm,
+ frm: this.frm,
});
}
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index ac14d98..436f510 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -90,6 +90,7 @@
"section_break_49",
"apply_discount_on",
"base_discount_amount",
+ "coupon_code",
"is_cash_or_non_trade_discount",
"additional_discount_account",
"column_break_51",
@@ -494,7 +495,7 @@
},
{
"fieldname": "address_display",
- "fieldtype": "Small Text",
+ "fieldtype": "Text Editor",
"hide_days": 1,
"hide_seconds": 1,
"label": "Address",
@@ -565,7 +566,7 @@
},
{
"fieldname": "shipping_address",
- "fieldtype": "Small Text",
+ "fieldtype": "Text Editor",
"hide_days": 1,
"hide_seconds": 1,
"label": "Shipping Address",
@@ -583,7 +584,7 @@
},
{
"fieldname": "company_address_display",
- "fieldtype": "Small Text",
+ "fieldtype": "Text Editor",
"hide_days": 1,
"hide_seconds": 1,
"label": "Company Address",
@@ -946,7 +947,7 @@
},
{
"fieldname": "other_charges_calculation",
- "fieldtype": "Long Text",
+ "fieldtype": "Text Editor",
"hide_days": 1,
"hide_seconds": 1,
"label": "Taxes and Charges Calculation",
@@ -1998,7 +1999,7 @@
{
"allow_on_submit": 1,
"fieldname": "dispatch_address",
- "fieldtype": "Small Text",
+ "fieldtype": "Text Editor",
"label": "Dispatch Address",
"read_only": 1
},
@@ -2174,12 +2175,20 @@
"no_copy": 1
},
{
+ "fieldname": "coupon_code",
+ "fieldtype": "Link",
+ "label": "Coupon Code",
+ "options": "Coupon Code"
+ },
+ {
"default": "1",
"depends_on": "eval: doc.is_return && doc.return_against",
"description": "Credit Note will update it's own outstanding amount, even if \"Return Against\" is specified.",
"fieldname": "update_outstanding_for_self",
"fieldtype": "Check",
- "label": "Update Outstanding for Self"
+ "label": "Update Outstanding for Self",
+ "no_copy": 1,
+ "print_hide": 1
}
],
"icon": "fa fa-file-text",
@@ -2192,7 +2201,7 @@
"link_fieldname": "consolidated_invoice"
}
],
- "modified": "2024-03-11 14:20:34.874192",
+ "modified": "2024-03-22 17:50:34.395602",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index bf50e77..a35c6b6 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -16,6 +16,10 @@
get_loyalty_program_details_with_points,
validate_loyalty_points,
)
+from erpnext.accounts.doctype.pricing_rule.utils import (
+ update_coupon_code_count,
+ validate_coupon_code,
+)
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
validate_docs_for_deferred_accounting,
validate_docs_for_voucher_types,
@@ -73,7 +77,7 @@
account_for_change_amount: DF.Link | None
additional_discount_account: DF.Link | None
additional_discount_percentage: DF.Float
- address_display: DF.SmallText | None
+ address_display: DF.TextEditor | None
advances: DF.Table[SalesInvoiceAdvance]
against_income_account: DF.SmallText | None
allocate_advances_automatically: DF.Check
@@ -98,7 +102,7 @@
commission_rate: DF.Float
company: DF.Link
company_address: DF.Link | None
- company_address_display: DF.SmallText | None
+ company_address_display: DF.TextEditor | None
company_tax_id: DF.Data | None
contact_display: DF.SmallText | None
contact_email: DF.Data | None
@@ -106,6 +110,7 @@
contact_person: DF.Link | None
conversion_rate: DF.Float
cost_center: DF.Link | None
+ coupon_code: DF.Link | None
currency: DF.Link
customer: DF.Link | None
customer_address: DF.Link | None
@@ -114,7 +119,7 @@
debit_to: DF.Link
disable_rounded_total: DF.Check
discount_amount: DF.Currency
- dispatch_address: DF.SmallText | None
+ dispatch_address: DF.TextEditor | None
dispatch_address_name: DF.Link | None
dont_create_loyalty_points: DF.Check
due_date: DF.Date | None
@@ -146,7 +151,7 @@
naming_series: DF.Literal["ACC-SINV-.YYYY.-", "ACC-SINV-RET-.YYYY.-"]
net_total: DF.Currency
only_include_allocated_payments: DF.Check
- other_charges_calculation: DF.LongText | None
+ other_charges_calculation: DF.TextEditor | None
outstanding_amount: DF.Currency
packed_items: DF.Table[PackedItem]
paid_amount: DF.Currency
@@ -178,7 +183,7 @@
set_posting_time: DF.Check
set_target_warehouse: DF.Link | None
set_warehouse: DF.Link | None
- shipping_address: DF.SmallText | None
+ shipping_address: DF.TextEditor | None
shipping_address_name: DF.Link | None
shipping_rule: DF.Link | None
source: DF.Link | None
@@ -294,6 +299,10 @@
self.doctype, self.customer, self.company, self.inter_company_invoice_reference
)
+ # Validating coupon code
+ if self.coupon_code:
+ validate_coupon_code(self.coupon_code)
+
if cint(self.is_pos):
self.validate_pos()
@@ -473,6 +482,9 @@
self.update_project()
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
+ if self.coupon_code:
+ update_coupon_code_count(self.coupon_code, "used")
+
# create the loyalty point ledger entry if the customer is enrolled in any loyalty program
if (
not self.is_return
@@ -563,6 +575,9 @@
self.db_set("status", "Cancelled")
self.db_set("repost_required", 0)
+ if self.coupon_code:
+ update_coupon_code_count(self.coupon_code, "cancelled")
+
if (
frappe.db.get_single_value("Selling Settings", "sales_update_frequency") == "Each Transaction"
):
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index c8a35eb..7e3eec5 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -1588,6 +1588,12 @@
self.assertEqual(frappe.db.get_value("Sales Invoice", si1.name, "outstanding_amount"), -1000)
self.assertEqual(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"), 2500)
+ def test_zero_qty_return_invoice_with_stock_effect(self):
+ cr_note = create_sales_invoice(qty=-1, rate=300, is_return=1, do_not_submit=True)
+ cr_note.update_stock = True
+ cr_note.items[0].qty = 0
+ self.assertRaises(frappe.ValidationError, cr_note.save)
+
def test_return_invoice_with_account_mismatch(self):
debtors2 = create_account(
parent_account="Accounts Receivable - _TC",
@@ -3945,7 +3951,6 @@
)
supplier.append("companies", {"company": allowed_to_interact_with})
-
supplier.insert()
supplier_name = supplier.name
else:
diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js
index 91d4d04..c42623a 100644
--- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js
+++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js
@@ -1,5 +1,6 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
+cur_frm.cscript.tax_table = "Sales Taxes and Charges";
erpnext.accounts.taxes.setup_tax_validations("Sales Taxes and Charges Template");
erpnext.accounts.taxes.setup_tax_filters("Sales Taxes and Charges");
diff --git a/erpnext/accounts/doctype/transaction_deletion_record_details/__init__.py b/erpnext/accounts/doctype/transaction_deletion_record_details/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/doctype/transaction_deletion_record_details/__init__.py
diff --git a/erpnext/accounts/doctype/transaction_deletion_record_details/transaction_deletion_record_details.json b/erpnext/accounts/doctype/transaction_deletion_record_details/transaction_deletion_record_details.json
new file mode 100644
index 0000000..fe4b085
--- /dev/null
+++ b/erpnext/accounts/doctype/transaction_deletion_record_details/transaction_deletion_record_details.json
@@ -0,0 +1,58 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2024-02-04 10:53:32.307930",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "doctype_name",
+ "docfield_name",
+ "no_of_docs",
+ "done"
+ ],
+ "fields": [
+ {
+ "fieldname": "doctype_name",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "DocType",
+ "options": "DocType",
+ "read_only": 1,
+ "reqd": 1
+ },
+ {
+ "fieldname": "docfield_name",
+ "fieldtype": "Data",
+ "label": "DocField",
+ "read_only": 1
+ },
+ {
+ "fieldname": "no_of_docs",
+ "fieldtype": "Int",
+ "in_list_view": 1,
+ "label": "No of Docs",
+ "read_only": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "done",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Done",
+ "read_only": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2024-02-05 17:35:09.556054",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Transaction Deletion Record Details",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/transaction_deletion_record_details/transaction_deletion_record_details.py b/erpnext/accounts/doctype/transaction_deletion_record_details/transaction_deletion_record_details.py
new file mode 100644
index 0000000..bc5b5c4
--- /dev/null
+++ b/erpnext/accounts/doctype/transaction_deletion_record_details/transaction_deletion_record_details.py
@@ -0,0 +1,26 @@
+# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class TransactionDeletionRecordDetails(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
+
+ docfield_name: DF.Data | None
+ doctype_name: DF.Link
+ done: DF.Check
+ no_of_docs: DF.Int
+ parent: DF.Data
+ parentfield: DF.Data
+ parenttype: DF.Data
+ # end: auto-generated types
+
+ pass
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index 2e82886..825a01e 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -7,7 +7,7 @@
import frappe
from frappe import _
from frappe.model.meta import get_field_precision
-from frappe.utils import cint, cstr, flt, formatdate, getdate, now
+from frappe.utils import cint, flt, formatdate, getdate, now
import erpnext
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
@@ -234,11 +234,13 @@
def merge_similar_entries(gl_map, precision=None):
merged_gl_map = []
accounting_dimensions = get_accounting_dimensions()
+ merge_properties = get_merge_properties(accounting_dimensions)
for entry in gl_map:
+ entry.merge_key = get_merge_key(entry, merge_properties)
# if there is already an entry in this account then just add it
# to that entry
- same_head = check_if_in_list(entry, merged_gl_map, accounting_dimensions)
+ same_head = check_if_in_list(entry, merged_gl_map)
if same_head:
same_head.debit = flt(same_head.debit) + flt(entry.debit)
same_head.debit_in_account_currency = flt(same_head.debit_in_account_currency) + flt(
@@ -273,34 +275,35 @@
return merged_gl_map
-def check_if_in_list(gle, gl_map, dimensions=None):
- account_head_fieldnames = [
- "voucher_detail_no",
- "party",
- "against_voucher",
+def get_merge_properties(dimensions=None):
+ merge_properties = [
+ "account",
"cost_center",
- "against_voucher_type",
+ "party",
"party_type",
+ "voucher_detail_no",
+ "against_voucher",
+ "against_voucher_type",
"project",
"finance_book",
"voucher_no",
]
-
if dimensions:
- account_head_fieldnames = account_head_fieldnames + dimensions
+ merge_properties.extend(dimensions)
+ return merge_properties
+
+def get_merge_key(entry, merge_properties):
+ merge_key = []
+ for fieldname in merge_properties:
+ merge_key.append(entry.get(fieldname, ""))
+
+ return tuple(merge_key)
+
+
+def check_if_in_list(gle, gl_map):
for e in gl_map:
- same_head = True
- if e.account != gle.account:
- same_head = False
- continue
-
- for fieldname in account_head_fieldnames:
- if cstr(e.get(fieldname)) != cstr(gle.get(fieldname)):
- same_head = False
- break
-
- if same_head:
+ if e.merge_key == gle.merge_key:
return e
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index d8ae2a4..4b3f0c8 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -282,9 +282,7 @@
pass
-def set_contact_details(party_details, party, party_type):
- party_details.contact_person = get_default_contact(party_type, party.name)
-
+def complete_contact_details(party_details):
if not party_details.contact_person:
party_details.update(
{
@@ -315,6 +313,11 @@
party_details.update(contact_details)
+def set_contact_details(party_details, party, party_type):
+ party_details.contact_person = get_default_contact(party_type, party.name)
+ complete_contact_details(party_details)
+
+
def set_other_values(party_details, party, party_type):
# copy
if party_type == "Customer":
diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
index a0f8af5..de49139 100644
--- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
@@ -182,8 +182,10 @@
}
# check invoice grand total and invoiced column's value for 3 payment terms
- si = self.create_sales_invoice(no_payment_schedule=True)
- name = si.name
+ si = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
+ si.set_posting_time = True
+ si.posting_date = add_days(today(), -1)
+ si.save().submit()
report = execute(filters)
@@ -207,30 +209,42 @@
# check invoice grand total, invoiced, paid and outstanding column's value after credit note
cr_note = self.create_credit_note(si.name, do_not_submit=True)
- cr_note.posting_date = add_days(today(), 1)
cr_note.update_outstanding_for_self = True
cr_note.save().submit()
report = execute(filters)
expected_data_after_credit_note = [
- [100.0, 100.0, 40.0, 0.0, 60.0, self.debit_to],
- [0, 0, 100.0, 0.0, -100.0, self.debit_to],
+ [100.0, 100.0, 40.0, 0.0, 60.0, si.name],
+ [0, 0, 100.0, 0.0, -100.0, cr_note.name],
]
self.assertEqual(len(report[1]), 2)
- for i in range(2):
- row = report[1][i - 1]
- # row = report[1][0]
- self.assertEqual(
- expected_data_after_credit_note[i - 1],
- [
- row.invoice_grand_total,
- row.invoiced,
- row.paid,
- row.credit_note,
- row.outstanding,
- row.party_account,
- ],
- )
+ si_row = [
+ [
+ row.invoice_grand_total,
+ row.invoiced,
+ row.paid,
+ row.credit_note,
+ row.outstanding,
+ row.voucher_no,
+ ]
+ for row in report[1]
+ if row.voucher_no == si.name
+ ][0]
+
+ cr_note_row = [
+ [
+ row.invoice_grand_total,
+ row.invoiced,
+ row.paid,
+ row.credit_note,
+ row.outstanding,
+ row.voucher_no,
+ ]
+ for row in report[1]
+ if row.voucher_no == cr_note.name
+ ][0]
+ self.assertEqual(expected_data_after_credit_note[0], si_row)
+ self.assertEqual(expected_data_after_credit_note[1], cr_note_row)
def test_payment_againt_po_in_receivable_report(self):
"""
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py
index 7162aef..25958692 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.py
+++ b/erpnext/accounts/report/gross_profit/gross_profit.py
@@ -669,20 +669,20 @@
elif row.sales_order and row.so_detail:
incoming_amount = self.get_buying_amount_from_so_dn(row.sales_order, row.so_detail, item_code)
if incoming_amount:
- return incoming_amount
+ return flt(row.qty) * incoming_amount
else:
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
def get_buying_amount_from_so_dn(self, sales_order, so_detail, item_code):
- from frappe.query_builder.functions import Sum
+ from frappe.query_builder.functions import Avg
delivery_note_item = frappe.qb.DocType("Delivery Note Item")
query = (
frappe.qb.from_(delivery_note_item)
- .select(Sum(delivery_note_item.incoming_rate * delivery_note_item.stock_qty))
+ .select(Avg(delivery_note_item.incoming_rate))
.where(delivery_note_item.docstatus == 1)
.where(delivery_note_item.item_code == item_code)
.where(delivery_note_item.against_sales_order == sales_order)
diff --git a/erpnext/accounts/report/gross_profit/test_gross_profit.py b/erpnext/accounts/report/gross_profit/test_gross_profit.py
index 82fe1a0..aa820aa 100644
--- a/erpnext/accounts/report/gross_profit/test_gross_profit.py
+++ b/erpnext/accounts/report/gross_profit/test_gross_profit.py
@@ -460,3 +460,95 @@
}
gp_entry = [x for x in data if x.parent_invoice == sinv.name]
self.assertDictContainsSubset(expected_entry, gp_entry[0])
+
+ def test_different_rates_in_si_and_dn(self):
+ from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
+
+ """
+ Test gp calculation when invoice and delivery note differ in qty and aren't connected
+ SO -- INV
+ |
+ DN
+ """
+ se = make_stock_entry(
+ company=self.company,
+ item_code=self.item,
+ target=self.warehouse,
+ qty=3,
+ basic_rate=700,
+ do_not_submit=True,
+ )
+ item = se.items[0]
+ se.append(
+ "items",
+ {
+ "item_code": item.item_code,
+ "s_warehouse": item.s_warehouse,
+ "t_warehouse": item.t_warehouse,
+ "qty": 10,
+ "basic_rate": 700,
+ "conversion_factor": item.conversion_factor or 1.0,
+ "transfer_qty": flt(item.qty) * (flt(item.conversion_factor) or 1.0),
+ "serial_no": item.serial_no,
+ "batch_no": item.batch_no,
+ "cost_center": item.cost_center,
+ "expense_account": item.expense_account,
+ },
+ )
+ se = se.save().submit()
+
+ so = make_sales_order(
+ customer=self.customer,
+ company=self.company,
+ warehouse=self.warehouse,
+ item=self.item,
+ rate=800,
+ qty=10,
+ do_not_save=False,
+ do_not_submit=False,
+ )
+
+ from erpnext.selling.doctype.sales_order.sales_order import (
+ make_delivery_note,
+ make_sales_invoice,
+ )
+
+ dn1 = make_delivery_note(so.name)
+ dn1.items[0].qty = 4
+ dn1.items[0].rate = 800
+ dn1.save().submit()
+
+ dn2 = make_delivery_note(so.name)
+ dn2.items[0].qty = 6
+ dn2.items[0].rate = 800
+ dn2.save().submit()
+
+ sinv = make_sales_invoice(so.name)
+ sinv.items[0].qty = 4
+ sinv.items[0].rate = 800
+ sinv.save().submit()
+
+ filters = frappe._dict(
+ company=self.company, from_date=nowdate(), to_date=nowdate(), group_by="Invoice"
+ )
+
+ columns, data = execute(filters=filters)
+ expected_entry = {
+ "parent_invoice": sinv.name,
+ "currency": "INR",
+ "sales_invoice": self.item,
+ "customer": self.customer,
+ "posting_date": frappe.utils.datetime.date.fromisoformat(nowdate()),
+ "item_code": self.item,
+ "item_name": self.item,
+ "warehouse": "Stores - _GP",
+ "qty": 4.0,
+ "avg._selling_rate": 800.0,
+ "valuation_rate": 700.0,
+ "selling_amount": 3200.0,
+ "buying_amount": 2800.0,
+ "gross_profit": 400.0,
+ "gross_profit_%": 12.5,
+ }
+ gp_entry = [x for x in data if x.parent_invoice == sinv.name]
+ self.assertDictContainsSubset(expected_entry, gp_entry[0])
diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js
index 2ffd3b3..98c6656 100644
--- a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js
+++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.js
@@ -24,3 +24,10 @@
fieldtype: "Check",
default: 1,
});
+
+frappe.query_reports["Profit and Loss Statement"]["filters"].push({
+ fieldname: "include_default_book_entries",
+ label: __("Include Default FB Entries"),
+ fieldtype: "Check",
+ default: 1,
+});
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 0755f2e..02012ad 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -1027,7 +1027,7 @@
if account:
root_type, account_type = frappe.get_cached_value(
- "Account", account, ["root_type", "account_type"]
+ "Account", account[0], ["root_type", "account_type"]
)
party_account_type = "Receivable" if root_type == "Asset" else "Payable"
party_account_type = account_type or party_account_type
@@ -1038,7 +1038,7 @@
common_filter = common_filter or []
common_filter.append(ple.account_type == party_account_type)
- common_filter.append(ple.account == account)
+ common_filter.append(ple.account.isin(account))
common_filter.append(ple.party_type == party_type)
common_filter.append(ple.party == party)
diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js
index 6dbb53a..b4fd739 100644
--- a/erpnext/assets/doctype/asset/asset.js
+++ b/erpnext/assets/doctype/asset/asset.js
@@ -48,7 +48,7 @@
method: "erpnext.assets.doctype.asset.asset.make_asset_movement",
freeze: true,
args: {
- assets: [{ name: cur_frm.doc.name }],
+ assets: [{ name: frm.doc.name }],
},
callback: function (r) {
if (r.message) {
@@ -79,7 +79,7 @@
frm.toggle_display("next_depreciation_date", frm.doc.docstatus < 1);
if (frm.doc.docstatus == 1) {
- if (in_list(["Submitted", "Partially Depreciated", "Fully Depreciated"], frm.doc.status)) {
+ if (["Submitted", "Partially Depreciated", "Fully Depreciated"].includes(frm.doc.status)) {
frm.add_custom_button(
__("Transfer Asset"),
function () {
@@ -365,7 +365,7 @@
if (v.journal_entry) {
asset_values.push(asset_value);
} else {
- if (in_list(["Scrapped", "Sold"], frm.doc.status)) {
+ if (["Scrapped", "Sold"].includes(frm.doc.status)) {
asset_values.push(null);
} else {
asset_values.push(asset_value);
@@ -400,7 +400,7 @@
});
}
- if (in_list(["Scrapped", "Sold"], frm.doc.status)) {
+ if (["Scrapped", "Sold"].includes(frm.doc.status)) {
x_intervals.push(frappe.format(frm.doc.disposal_date, { fieldtype: "Date" }));
asset_values.push(0);
}
@@ -791,9 +791,7 @@
asset_name: frm.doc.name,
},
method: "erpnext.assets.doctype.asset.depreciation.scrap_asset",
- callback: function (r) {
- cur_frm.reload_doc();
- },
+ callback: (r) => frm.reload_doc(),
});
});
};
@@ -805,19 +803,17 @@
asset_name: frm.doc.name,
},
method: "erpnext.assets.doctype.asset.depreciation.restore_asset",
- callback: function (r) {
- cur_frm.reload_doc();
- },
+ callback: (r) => frm.reload_doc(),
});
});
};
-erpnext.asset.transfer_asset = function () {
+erpnext.asset.transfer_asset = function (frm) {
frappe.call({
method: "erpnext.assets.doctype.asset.asset.make_asset_movement",
freeze: true,
args: {
- assets: [{ name: cur_frm.doc.name }],
+ assets: [{ name: frm.doc.name }],
purpose: "Transfer",
},
callback: function (r) {
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index 7875646..c3a155a 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -4,6 +4,8 @@
frappe.provide("erpnext.buying");
frappe.provide("erpnext.accounts.dimensions");
+cur_frm.cscript.tax_table = "Purchase Taxes and Charges";
+
erpnext.accounts.taxes.setup_tax_filters("Purchase Taxes and Charges");
erpnext.accounts.taxes.setup_tax_validations("Purchase Order");
erpnext.buying.setup_buying_controller();
@@ -84,7 +86,7 @@
args: {
subcontract_order: frm.doc.name,
rm_details: po_details,
- order_doctype: cur_frm.doc.doctype,
+ order_doctype: frm.doc.doctype,
},
callback: function (r) {
if (r && r.message) {
@@ -268,8 +270,8 @@
var allow_receipt = false;
var is_drop_ship = false;
- for (var i in cur_frm.doc.items) {
- var item = cur_frm.doc.items[i];
+ for (var i in this.frm.doc.items) {
+ var item = this.frm.doc.items[i];
if (item.delivered_by_supplier !== 1) {
allow_receipt = true;
} else {
@@ -289,7 +291,7 @@
this.frm.fields_dict.items_section.wrapper.removeClass("hide-border");
}
- if (!in_list(["Closed", "Delivered"], doc.status)) {
+ if (!["Closed", "Delivered"].includes(doc.status)) {
if (
this.frm.doc.status !== "Closed" &&
flt(this.frm.doc.per_received, 2) < 100 &&
@@ -334,7 +336,7 @@
this.frm.page.set_inner_btn_group_as_primary(__("Status"));
}
- } else if (in_list(["Closed", "Delivered"], doc.status)) {
+ } else if (["Closed", "Delivered"].includes(doc.status)) {
if (this.frm.has_perm("submit")) {
this.frm.add_custom_button(
__("Re-open"),
@@ -346,7 +348,7 @@
if (doc.status != "Closed") {
if (doc.status != "On Hold") {
if (flt(doc.per_received, 2) < 100 && allow_receipt) {
- cur_frm.add_custom_button(
+ this.frm.add_custom_button(
__("Purchase Receipt"),
this.make_purchase_receipt,
__("Create")
@@ -354,7 +356,7 @@
if (doc.is_subcontracted) {
if (doc.is_old_subcontracting_flow) {
if (me.has_unsupplied_items()) {
- cur_frm.add_custom_button(
+ this.frm.add_custom_button(
__("Material to Supplier"),
function () {
me.make_stock_entry();
@@ -363,7 +365,7 @@
);
}
} else {
- cur_frm.add_custom_button(
+ this.frm.add_custom_button(
__("Subcontracting Order"),
this.make_subcontracting_order,
__("Create")
@@ -372,7 +374,7 @@
}
}
if (flt(doc.per_billed, 2) < 100)
- cur_frm.add_custom_button(
+ this.frm.add_custom_button(
__("Purchase Invoice"),
this.make_purchase_invoice,
__("Create")
@@ -416,10 +418,10 @@
}
}
- cur_frm.page.set_inner_btn_group_as_primary(__("Create"));
+ this.frm.page.set_inner_btn_group_as_primary(__("Create"));
}
} else if (doc.docstatus === 0) {
- cur_frm.cscript.add_from_mappers();
+ this.frm.cscript.add_from_mappers();
}
}
@@ -456,8 +458,8 @@
frappe.call({
method: "erpnext.controllers.subcontracting_controller.make_rm_stock_entry",
args: {
- subcontract_order: cur_frm.doc.name,
- order_doctype: cur_frm.doc.doctype,
+ subcontract_order: this.frm.doc.name,
+ order_doctype: this.frm.doc.doctype,
},
callback: function (r) {
var doclist = frappe.model.sync(r.message);
@@ -476,7 +478,7 @@
make_purchase_receipt() {
frappe.model.open_mapped_doc({
method: "erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_receipt",
- frm: cur_frm,
+ frm: this.frm,
freeze_message: __("Creating Purchase Receipt ..."),
});
}
@@ -484,14 +486,14 @@
make_purchase_invoice() {
frappe.model.open_mapped_doc({
method: "erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_invoice",
- frm: cur_frm,
+ frm: this.frm,
});
}
make_subcontracting_order() {
frappe.model.open_mapped_doc({
method: "erpnext.buying.doctype.purchase_order.purchase_order.make_subcontracting_order",
- frm: cur_frm,
+ frm: this.frm,
freeze_message: __("Creating Subcontracting Order ..."),
});
}
@@ -507,7 +509,6 @@
target: me.frm,
setters: {
schedule_date: undefined,
- status: undefined,
},
get_query_filters: {
material_request_type: "Purchase",
@@ -651,7 +652,7 @@
}
unhold_purchase_order() {
- cur_frm.cscript.update_status("Resume", "Draft");
+ this.frm.cscript.update_status("Resume", "Draft");
}
hold_purchase_order() {
@@ -691,15 +692,15 @@
}
unclose_purchase_order() {
- cur_frm.cscript.update_status("Re-open", "Submitted");
+ this.frm.cscript.update_status("Re-open", "Submitted");
}
close_purchase_order() {
- cur_frm.cscript.update_status("Close", "Closed");
+ this.frm.cscript.update_status("Close", "Closed");
}
delivered_by_supplier() {
- cur_frm.cscript.update_status("Deliver", "Delivered");
+ this.frm.cscript.update_status("Deliver", "Delivered");
}
items_on_form_rendered() {
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json
index 9da49a7..1ee9794 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.json
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.json
@@ -355,7 +355,7 @@
},
{
"fieldname": "address_display",
- "fieldtype": "Small Text",
+ "fieldtype": "Text Editor",
"label": "Supplier Address Details",
"read_only": 1
},
@@ -394,7 +394,7 @@
},
{
"fieldname": "shipping_address_display",
- "fieldtype": "Small Text",
+ "fieldtype": "Text Editor",
"label": "Shipping Address Details",
"print_hide": 1,
"read_only": 1
@@ -642,7 +642,7 @@
},
{
"fieldname": "other_charges_calculation",
- "fieldtype": "Long Text",
+ "fieldtype": "Text Editor",
"label": "Taxes and Charges Calculation",
"no_copy": 1,
"oldfieldtype": "HTML",
@@ -1098,7 +1098,7 @@
},
{
"fieldname": "billing_address_display",
- "fieldtype": "Small Text",
+ "fieldtype": "Text Editor",
"label": "Billing Address Details",
"read_only": 1
},
@@ -1288,7 +1288,7 @@
"idx": 105,
"is_submittable": 1,
"links": [],
- "modified": "2023-10-10 13:37:40.158761",
+ "modified": "2024-03-22 16:15:09.674963",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order",
@@ -1343,4 +1343,4 @@
"timeline_field": "supplier",
"title_field": "supplier_name",
"track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index 4d94868..e462820 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -55,8 +55,9 @@
)
additional_discount_percentage: DF.Float
- address_display: DF.SmallText | None
+ address_display: DF.TextEditor | None
advance_paid: DF.Currency
+ advance_payment_status: DF.Literal["Not Initiated", "Initiated", "Partially Paid", "Fully Paid"]
amended_from: DF.Link | None
apply_discount_on: DF.Literal["", "Grand Total", "Net Total"]
apply_tds: DF.Check
@@ -73,7 +74,7 @@
base_total: DF.Currency
base_total_taxes_and_charges: DF.Currency
billing_address: DF.Link | None
- billing_address_display: DF.SmallText | None
+ billing_address_display: DF.TextEditor | None
buying_price_list: DF.Link | None
company: DF.Link
contact_display: DF.SmallText | None
@@ -109,7 +110,7 @@
net_total: DF.Currency
order_confirmation_date: DF.Date | None
order_confirmation_no: DF.Data | None
- other_charges_calculation: DF.LongText | None
+ other_charges_calculation: DF.TextEditor | None
party_account_currency: DF.Link | None
payment_schedule: DF.Table[PaymentSchedule]
payment_terms_template: DF.Link | None
@@ -130,7 +131,7 @@
set_reserve_warehouse: DF.Link | None
set_warehouse: DF.Link | None
shipping_address: DF.Link | None
- shipping_address_display: DF.SmallText | None
+ shipping_address_display: DF.TextEditor | None
shipping_rule: DF.Link | None
status: DF.Literal[
"",
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
index 272d077..6b10df8 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
@@ -518,16 +518,15 @@
callback: load_suppliers,
});
} else if (args.supplier_group) {
- return frappe.call({
- method: "frappe.client.get_list",
- args: {
- doctype: "Supplier",
+ frappe.db
+ .get_list("Supplier", {
+ filters: { supplier_group: args.supplier_group },
+ limit: 100,
order_by: "name",
- fields: ["name"],
- filters: [["Supplier", "supplier_group", "=", args.supplier_group]],
- },
- callback: load_suppliers,
- });
+ })
+ .then((r) => {
+ load_suppliers({ message: r });
+ });
}
},
});
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
index fd73f77..f386b64 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
@@ -303,7 +303,7 @@
},
{
"fieldname": "billing_address_display",
- "fieldtype": "Small Text",
+ "fieldtype": "Text Editor",
"label": "Billing Address Details",
"read_only": 1
}
@@ -312,7 +312,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2023-11-06 12:45:28.898706",
+ "modified": "2024-03-22 16:01:19.097788",
"modified_by": "Administrator",
"module": "Buying",
"name": "Request for Quotation",
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
index f261773..fb4dc6a 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
@@ -40,7 +40,7 @@
amended_from: DF.Link | None
billing_address: DF.Link | None
- billing_address_display: DF.SmallText | None
+ billing_address_display: DF.TextEditor | None
company: DF.Link
email_template: DF.Link | None
incoterm: DF.Link | None
diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json
index 60dd54c..3dae044 100644
--- a/erpnext/buying/doctype/supplier/supplier.json
+++ b/erpnext/buying/doctype/supplier/supplier.json
@@ -485,7 +485,7 @@
"link_fieldname": "party"
}
],
- "modified": "2023-10-19 16:55:15.148325",
+ "modified": "2024-03-13 11:14:06.516519",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier",
@@ -544,7 +544,7 @@
}
],
"quick_entry": 1,
- "search_fields": "supplier_name, supplier_group",
+ "search_fields": "supplier_group",
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "ASC",
diff --git a/erpnext/buying/doctype/supplier/test_supplier.py b/erpnext/buying/doctype/supplier/test_supplier.py
index 350a25f..55974ea 100644
--- a/erpnext/buying/doctype/supplier/test_supplier.py
+++ b/erpnext/buying/doctype/supplier/test_supplier.py
@@ -154,44 +154,6 @@
# Rollback
address.delete()
- def test_serach_fields_for_supplier(self):
- from erpnext.controllers.queries import supplier_query
-
- frappe.db.set_single_value("Buying Settings", "supp_master_name", "Naming Series")
-
- supplier_name = create_supplier(supplier_name="Test Supplier 1").name
-
- make_property_setter(
- "Supplier", None, "search_fields", "supplier_group", "Data", for_doctype="Doctype"
- )
-
- data = supplier_query(
- "Supplier", supplier_name, "name", 0, 20, filters={"name": supplier_name}, as_dict=True
- )
-
- self.assertEqual(data[0].name, supplier_name)
- self.assertEqual(data[0].supplier_group, "Services")
- self.assertTrue("supplier_type" not in data[0])
-
- make_property_setter(
- "Supplier",
- None,
- "search_fields",
- "supplier_group, supplier_type",
- "Data",
- for_doctype="Doctype",
- )
- data = supplier_query(
- "Supplier", supplier_name, "name", 0, 20, filters={"name": supplier_name}, as_dict=True
- )
-
- self.assertEqual(data[0].name, supplier_name)
- self.assertEqual(data[0].supplier_group, "Services")
- self.assertEqual(data[0].supplier_type, "Company")
- self.assertTrue("supplier_type" in data[0])
-
- frappe.db.set_single_value("Buying Settings", "supp_master_name", "Supplier Name")
-
def create_supplier(**args):
args = frappe._dict(args)
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js
index c169871..abb5702 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.js
@@ -22,9 +22,9 @@
this.frm.set_value("valid_till", frappe.datetime.add_months(this.frm.doc.transaction_date, 1));
}
if (this.frm.doc.docstatus === 1) {
- cur_frm.add_custom_button(__("Purchase Order"), this.make_purchase_order, __("Create"));
- cur_frm.page.set_inner_btn_group_as_primary(__("Create"));
- cur_frm.add_custom_button(__("Quotation"), this.make_quotation, __("Create"));
+ this.frm.add_custom_button(__("Purchase Order"), this.make_purchase_order, __("Create"));
+ this.frm.page.set_inner_btn_group_as_primary(__("Create"));
+ this.frm.add_custom_button(__("Quotation"), this.make_quotation, __("Create"));
} else if (this.frm.doc.docstatus === 0) {
this.frm.add_custom_button(
__("Material Request"),
@@ -87,13 +87,13 @@
make_purchase_order() {
frappe.model.open_mapped_doc({
method: "erpnext.buying.doctype.supplier_quotation.supplier_quotation.make_purchase_order",
- frm: cur_frm,
+ frm: this.frm,
});
}
make_quotation() {
frappe.model.open_mapped_doc({
method: "erpnext.buying.doctype.supplier_quotation.supplier_quotation.make_quotation",
- frm: cur_frm,
+ frm: this.frm,
});
}
};
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
index 1891261..993cde0 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
@@ -228,7 +228,7 @@
},
{
"fieldname": "address_display",
- "fieldtype": "Small Text",
+ "fieldtype": "Text Editor",
"label": "Address",
"read_only": 1
},
@@ -462,7 +462,7 @@
},
{
"fieldname": "other_charges_calculation",
- "fieldtype": "Long Text",
+ "fieldtype": "Markdown Editor",
"label": "Taxes and Charges Calculation",
"no_copy": 1,
"oldfieldtype": "HTML",
@@ -865,7 +865,7 @@
},
{
"fieldname": "shipping_address_display",
- "fieldtype": "Small Text",
+ "fieldtype": "Text Editor",
"label": "Shipping Address Details",
"print_hide": 1,
"read_only": 1
@@ -897,7 +897,7 @@
},
{
"fieldname": "billing_address_display",
- "fieldtype": "Small Text",
+ "fieldtype": "Text Editor",
"label": "Billing Address Details",
"read_only": 1
},
@@ -928,7 +928,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2023-11-17 12:34:30.083077",
+ "modified": "2024-03-22 16:15:10.122197",
"modified_by": "Administrator",
"module": "Buying",
"name": "Supplier Quotation",
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
index e2b737b..52bd83b 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
@@ -31,7 +31,7 @@
)
additional_discount_percentage: DF.Float
- address_display: DF.SmallText | None
+ address_display: DF.TextEditor | None
amended_from: DF.Link | None
apply_discount_on: DF.Literal["", "Grand Total", "Net Total"]
auto_repeat: DF.Link | None
@@ -46,7 +46,7 @@
base_total: DF.Currency
base_total_taxes_and_charges: DF.Currency
billing_address: DF.Link | None
- billing_address_display: DF.SmallText | None
+ billing_address_display: DF.TextEditor | None
buying_price_list: DF.Link | None
company: DF.Link
contact_display: DF.SmallText | None
@@ -71,7 +71,7 @@
naming_series: DF.Literal["PUR-SQTN-.YYYY.-"]
net_total: DF.Currency
opportunity: DF.Link | None
- other_charges_calculation: DF.LongText | None
+ other_charges_calculation: DF.MarkdownEditor | None
plc_conversion_rate: DF.Float
price_list_currency: DF.Link | None
pricing_rules: DF.Table[PricingRuleDetail]
@@ -81,7 +81,7 @@
rounding_adjustment: DF.Currency
select_print_heading: DF.Link | None
shipping_address: DF.Link | None
- shipping_address_display: DF.SmallText | None
+ shipping_address_display: DF.TextEditor | None
shipping_rule: DF.Link | None
status: DF.Literal["", "Draft", "Submitted", "Stopped", "Cancelled", "Expired"]
supplier: DF.Link
diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js
index c109abd..f7d0d94 100644
--- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js
+++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js
@@ -77,7 +77,10 @@
fieldname: "group_by",
label: __("Group by"),
fieldtype: "Select",
- options: [__("Group by Supplier"), __("Group by Item")],
+ options: [
+ { label: __("Group by Supplier"), value: "Group by Supplier" },
+ { label: __("Group by Item"), value: "Group by Item" },
+ ],
default: __("Group by Supplier"),
},
{
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 250f21b..3b83c7c 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -89,6 +89,7 @@
"weight_per_unit",
"weight_uom",
"total_weight",
+ "valuation_rate",
)
@@ -168,6 +169,13 @@
if not self.get("is_return") and not self.get("is_debit_note"):
self.validate_qty_is_not_zero()
+ if (
+ self.doctype in ["Sales Invoice", "Purchase Invoice"]
+ and self.get("is_return")
+ and self.get("update_stock")
+ ):
+ self.validate_zero_qty_for_return_invoices_with_stock()
+
if self.get("_action") and self._action != "update_after_submit":
self.set_missing_values(for_validate=True)
@@ -372,6 +380,12 @@
for bundle in bundles:
frappe.delete_doc("Serial and Batch Bundle", bundle.name)
+ batches = frappe.get_all(
+ "Batch", filters={"reference_doctype": self.doctype, "reference_name": self.name}
+ )
+ for row in batches:
+ frappe.delete_doc("Batch", row.name)
+
def validate_return_against_account(self):
if (
self.doctype in ["Sales Invoice", "Purchase Invoice"] and self.is_return and self.return_against
@@ -602,23 +616,31 @@
)
def validate_due_date(self):
- if self.get("is_pos"):
+ if self.get("is_pos") or self.doctype not in ["Sales Invoice", "Purchase Invoice"]:
return
from erpnext.accounts.party import validate_due_date
- if self.doctype == "Sales Invoice":
+ posting_date = (
+ self.posting_date if self.doctype == "Sales Invoice" else (self.bill_date or self.posting_date)
+ )
+
+ # skip due date validation for records via Data Import
+ if frappe.flags.in_import and getdate(self.due_date) < getdate(posting_date):
+ self.due_date = posting_date
+
+ elif self.doctype == "Sales Invoice":
if not self.due_date:
frappe.throw(_("Due Date is mandatory"))
validate_due_date(
- self.posting_date,
+ posting_date,
self.due_date,
self.payment_terms_template,
)
elif self.doctype == "Purchase Invoice":
validate_due_date(
- self.bill_date or self.posting_date,
+ posting_date,
self.due_date,
self.bill_date,
self.payment_terms_template,
@@ -1044,6 +1066,18 @@
else:
return flt(args.get(field, 0) / self.get("conversion_rate", 1))
+ def validate_zero_qty_for_return_invoices_with_stock(self):
+ rows = []
+ for item in self.items:
+ if not flt(item.qty):
+ rows.append(item)
+ if rows:
+ frappe.throw(
+ _(
+ "For Return Invoices with Stock effect, '0' qty Items are not allowed. Following rows are affected: {0}"
+ ).format(frappe.bold(comma_and(["#" + str(x.idx) for x in rows])))
+ )
+
def validate_qty_is_not_zero(self):
for item in self.items:
if self.doctype == "Purchase Receipt" and item.rejected_qty:
@@ -1615,52 +1649,30 @@
return amount, base_amount
def make_discount_gl_entries(self, gl_entries):
- if self.doctype == "Purchase Invoice":
- enable_discount_accounting = cint(
- frappe.db.get_single_value("Buying Settings", "enable_discount_accounting")
- )
- elif self.doctype == "Sales Invoice":
- enable_discount_accounting = cint(
- frappe.db.get_single_value("Selling Settings", "enable_discount_accounting")
- )
-
- if self.doctype == "Purchase Invoice":
- dr_or_cr = "credit"
- rev_dr_cr = "debit"
- supplier_or_customer = self.supplier
-
- else:
- dr_or_cr = "debit"
- rev_dr_cr = "credit"
- supplier_or_customer = self.customer
+ enable_discount_accounting = cint(
+ frappe.db.get_single_value("Selling Settings", "enable_discount_accounting")
+ )
if enable_discount_accounting:
for item in self.get("items"):
if item.get("discount_amount") and item.get("discount_account"):
discount_amount = item.discount_amount * item.qty
- if self.doctype == "Purchase Invoice":
- income_or_expense_account = (
- item.expense_account
- if (not item.enable_deferred_expense or self.is_return)
- else item.deferred_expense_account
- )
- else:
- income_or_expense_account = (
- item.income_account
- if (not item.enable_deferred_revenue or self.is_return)
- else item.deferred_revenue_account
- )
+ income_account = (
+ item.income_account
+ if (not item.enable_deferred_revenue or self.is_return)
+ else item.deferred_revenue_account
+ )
account_currency = get_account_currency(item.discount_account)
gl_entries.append(
self.get_gl_dict(
{
"account": item.discount_account,
- "against": supplier_or_customer,
- dr_or_cr: flt(
+ "against": self.customer,
+ "debit": flt(
discount_amount * self.get("conversion_rate"), item.precision("discount_amount")
),
- dr_or_cr + "_in_account_currency": flt(discount_amount, item.precision("discount_amount")),
+ "debit_in_account_currency": flt(discount_amount, item.precision("discount_amount")),
"cost_center": item.cost_center,
"project": item.project,
},
@@ -1669,17 +1681,16 @@
)
)
- account_currency = get_account_currency(income_or_expense_account)
+ account_currency = get_account_currency(income_account)
gl_entries.append(
self.get_gl_dict(
{
- "account": income_or_expense_account,
- "against": supplier_or_customer,
- rev_dr_cr: flt(
+ "account": income_account,
+ "against": self.customer,
+ "credit": flt(
discount_amount * self.get("conversion_rate"), item.precision("discount_amount")
),
- rev_dr_cr
- + "_in_account_currency": flt(discount_amount, item.precision("discount_amount")),
+ "credit_in_account_currency": flt(discount_amount, item.precision("discount_amount")),
"cost_center": item.cost_center,
"project": item.project or self.project,
},
@@ -1697,8 +1708,8 @@
self.get_gl_dict(
{
"account": self.additional_discount_account,
- "against": supplier_or_customer,
- dr_or_cr: self.base_discount_amount,
+ "against": self.customer,
+ "debit": self.base_discount_amount,
"cost_center": self.cost_center or erpnext.get_default_cost_center(self.company),
},
item=self,
@@ -1711,8 +1722,8 @@
item_allowance = {}
global_qty_allowance, global_amount_allowance = None, None
- role_allowed_to_over_bill = frappe.db.get_single_value(
- "Accounts Settings", "role_allowed_to_over_bill"
+ role_allowed_to_over_bill = frappe.get_cached_value(
+ "Accounts Settings", None, "role_allowed_to_over_bill"
)
user_roles = frappe.get_roles()
@@ -2708,14 +2719,20 @@
else:
q = q.where(journal_acc.debit_in_account_currency > 0)
+ reference_or_condition = []
+
if include_unallocated:
- q = q.where((journal_acc.reference_name.isnull()) | (journal_acc.reference_name == ""))
+ reference_or_condition.append(journal_acc.reference_name.isnull())
+ reference_or_condition.append(journal_acc.reference_name == "")
if order_list:
- q = q.where(
+ reference_or_condition.append(
(journal_acc.reference_type == order_doctype) & ((journal_acc.reference_name).isin(order_list))
)
+ if reference_or_condition:
+ q = q.where(Criterion.any(reference_or_condition))
+
q = q.orderby(journal_entry.posting_date)
journal_entries = q.run(as_dict=True)
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 8211857..c530727 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -513,6 +513,14 @@
(not cint(self.is_return) and self.docstatus == 1)
or (cint(self.is_return) and self.docstatus == 2)
):
+ serial_and_batch_bundle = d.get("serial_and_batch_bundle")
+ if self.is_internal_transfer() and self.is_return and self.docstatus == 2:
+ serial_and_batch_bundle = frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"voucher_detail_no": d.name, "warehouse": d.from_warehouse},
+ "serial_and_batch_bundle",
+ )
+
from_warehouse_sle = self.get_sl_entries(
d,
{
@@ -521,19 +529,24 @@
"outgoing_rate": d.rate,
"recalculate_rate": 1,
"dependant_sle_voucher_detail_no": d.name,
+ "serial_and_batch_bundle": serial_and_batch_bundle,
},
)
sl_entries.append(from_warehouse_sle)
+ type_of_transaction = "Inward"
+ if self.docstatus == 2:
+ type_of_transaction = "Outward"
+
sle = self.get_sl_entries(
d,
{
"actual_qty": flt(pr_qty),
"serial_and_batch_bundle": (
d.serial_and_batch_bundle
- if not self.is_internal_transfer()
- else self.get_package_for_target_warehouse(d)
+ if not self.is_internal_transfer() or self.is_return
+ else self.get_package_for_target_warehouse(d, type_of_transaction=type_of_transaction)
),
},
)
@@ -570,7 +583,17 @@
or (cint(self.is_return) and self.docstatus == 1)
):
from_warehouse_sle = self.get_sl_entries(
- d, {"actual_qty": -1 * pr_qty, "warehouse": d.from_warehouse, "recalculate_rate": 1}
+ d,
+ {
+ "actual_qty": -1 * pr_qty,
+ "warehouse": d.from_warehouse,
+ "recalculate_rate": 1,
+ "serial_and_batch_bundle": (
+ self.get_package_for_target_warehouse(d, d.from_warehouse, "Inward")
+ if self.is_internal_transfer() and self.is_return
+ else None
+ ),
+ },
)
sl_entries.append(from_warehouse_sle)
@@ -597,13 +620,15 @@
via_landed_cost_voucher=via_landed_cost_voucher,
)
- def get_package_for_target_warehouse(self, item) -> str:
+ def get_package_for_target_warehouse(self, item, warehouse=None, type_of_transaction=None) -> str:
if not item.serial_and_batch_bundle:
return ""
+ if not warehouse:
+ warehouse = item.warehouse
+
return self.make_package_for_transfer(
- item.serial_and_batch_bundle,
- item.warehouse,
+ item.serial_and_batch_bundle, warehouse, type_of_transaction=type_of_transaction
)
def update_ordered_and_reserved_qty(self):
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index bb1ed35..0de75d4 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -85,79 +85,6 @@
{"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len},
)
- # searches for customer
-
-
-@frappe.whitelist()
-@frappe.validate_and_sanitize_search_inputs
-def customer_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False):
- doctype = "Customer"
- conditions = []
- cust_master_name = frappe.defaults.get_user_default("cust_master_name")
-
- fields = ["name"]
- if cust_master_name != "Customer Name":
- fields.append("customer_name")
-
- fields = get_fields(doctype, fields)
- searchfields = frappe.get_meta(doctype).get_search_fields()
- searchfields = " or ".join(field + " like %(txt)s" for field in searchfields)
-
- return frappe.db.sql(
- """select {fields} from `tabCustomer`
- where docstatus < 2
- and ({scond}) and disabled=0
- {fcond} {mcond}
- order by
- (case when locate(%(_txt)s, name) > 0 then locate(%(_txt)s, name) else 99999 end),
- (case when locate(%(_txt)s, customer_name) > 0 then locate(%(_txt)s, customer_name) else 99999 end),
- idx desc,
- name, customer_name
- limit %(page_len)s offset %(start)s""".format(
- **{
- "fields": ", ".join(fields),
- "scond": searchfields,
- "mcond": get_match_cond(doctype),
- "fcond": get_filters_cond(doctype, filters, conditions).replace("%", "%%"),
- }
- ),
- {"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len},
- as_dict=as_dict,
- )
-
-
-# searches for supplier
-@frappe.whitelist()
-@frappe.validate_and_sanitize_search_inputs
-def supplier_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False):
- doctype = "Supplier"
- supp_master_name = frappe.defaults.get_user_default("supp_master_name")
-
- fields = ["name"]
- if supp_master_name != "Supplier Name":
- fields.append("supplier_name")
-
- fields = get_fields(doctype, fields)
-
- return frappe.db.sql(
- """select {field} from `tabSupplier`
- where docstatus < 2
- and ({key} like %(txt)s
- or supplier_name like %(txt)s) and disabled=0
- and (on_hold = 0 or (on_hold = 1 and CURRENT_DATE > release_date))
- {mcond}
- order by
- (case when locate(%(_txt)s, name) > 0 then locate(%(_txt)s, name) else 99999 end),
- (case when locate(%(_txt)s, supplier_name) > 0 then locate(%(_txt)s, supplier_name) else 99999 end),
- idx desc,
- name, supplier_name
- limit %(page_len)s offset %(start)s""".format(
- **{"field": ", ".join(fields), "key": searchfield, "mcond": get_match_cond(doctype)}
- ),
- {"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len},
- as_dict=as_dict,
- )
-
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 1ddcaa7..5594816 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -423,6 +423,15 @@
]:
type_of_transaction = "Outward"
+ warehouse = source_doc.warehouse if qty_field == "stock_qty" else source_doc.rejected_warehouse
+ if source_parent.doctype in [
+ "Sales Invoice",
+ "POS Invoice",
+ "Delivery Note",
+ ] and source_parent.get("is_internal_customer"):
+ type_of_transaction = "Outward"
+ warehouse = source_doc.target_warehouse
+
cls_obj = SerialBatchCreation(
{
"type_of_transaction": type_of_transaction,
@@ -432,7 +441,7 @@
"returned_serial_nos": returned_serial_nos,
"voucher_type": source_parent.doctype,
"do_not_submit": True,
- "warehouse": source_doc.warehouse,
+ "warehouse": warehouse,
"has_serial_no": item_details.has_serial_no,
"has_batch_no": item_details.has_batch_no,
}
@@ -575,11 +584,14 @@
if not item_details.has_batch_no and not item_details.has_serial_no:
return
- for qty_field in ["stock_qty", "rejected_qty"]:
- if target_doc.get(qty_field) and not target_doc.get("use_serial_batch_fields"):
+ if not target_doc.get("use_serial_batch_fields"):
+ for qty_field in ["stock_qty", "rejected_qty"]:
+ if not target_doc.get(qty_field):
+ continue
+
update_serial_batch_no(source_doc, target_doc, source_parent, item_details, qty_field)
- elif target_doc.get(qty_field) and target_doc.get("use_serial_batch_fields"):
- update_non_bundled_serial_nos(source_doc, target_doc, source_parent)
+ elif target_doc.get("use_serial_batch_fields"):
+ update_non_bundled_serial_nos(source_doc, target_doc, source_parent)
def update_non_bundled_serial_nos(source_doc, target_doc, source_parent):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index 359d721..9d86cb2 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -439,8 +439,10 @@
# Get incoming rate based on original item cost based on valuation method
qty = flt(d.get("stock_qty") or d.get("actual_qty"))
- if not d.incoming_rate or (
- get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return")
+ if (
+ not d.incoming_rate
+ or self.is_internal_transfer()
+ or (get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return"))
):
d.incoming_rate = get_incoming_rate(
{
@@ -455,6 +457,8 @@
"voucher_no": self.name,
"voucher_detail_no": d.name,
"allow_zero_valuation": d.get("allow_zero_valuation"),
+ "batch_no": d.batch_no,
+ "serial_no": d.serial_no,
},
raise_error_if_no_rate=False,
)
@@ -527,13 +531,26 @@
self.make_sl_entries(sl_entries)
def get_sle_for_source_warehouse(self, item_row):
+ serial_and_batch_bundle = item_row.serial_and_batch_bundle
+ if serial_and_batch_bundle and self.is_internal_transfer() and self.is_return:
+ if self.docstatus == 1:
+ serial_and_batch_bundle = self.make_package_for_transfer(
+ serial_and_batch_bundle, item_row.warehouse, type_of_transaction="Inward"
+ )
+ else:
+ serial_and_batch_bundle = frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"voucher_detail_no": item_row.name, "warehouse": item_row.warehouse},
+ "serial_and_batch_bundle",
+ )
+
sle = self.get_sl_entries(
item_row,
{
"actual_qty": -1 * flt(item_row.qty),
"incoming_rate": item_row.incoming_rate,
"recalculate_rate": cint(self.is_return),
- "serial_and_batch_bundle": item_row.serial_and_batch_bundle,
+ "serial_and_batch_bundle": serial_and_batch_bundle,
},
)
if item_row.target_warehouse and not cint(self.is_return):
@@ -554,9 +571,15 @@
if item_row.warehouse:
sle.dependant_sle_voucher_detail_no = item_row.name
- if item_row.serial_and_batch_bundle:
+ if item_row.serial_and_batch_bundle and not cint(self.is_return):
+ type_of_transaction = "Inward"
+ if cint(self.is_return):
+ type_of_transaction = "Outward"
+
sle["serial_and_batch_bundle"] = self.make_package_for_transfer(
- item_row.serial_and_batch_bundle, item_row.target_warehouse
+ item_row.serial_and_batch_bundle,
+ item_row.target_warehouse,
+ type_of_transaction=type_of_transaction,
)
return sle
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index e5f341f..fcbec22 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -577,6 +577,7 @@
ref_doc.set_status(update=True)
+@frappe.request_cache
def get_allowance_for(
item_code,
item_allowance=None,
@@ -606,20 +607,20 @@
global_amount_allowance,
)
- qty_allowance, over_billing_allowance = frappe.db.get_value(
+ qty_allowance, over_billing_allowance = frappe.get_cached_value(
"Item", item_code, ["over_delivery_receipt_allowance", "over_billing_allowance"]
)
if qty_or_amount == "qty" and not qty_allowance:
if global_qty_allowance == None:
global_qty_allowance = flt(
- frappe.db.get_single_value("Stock Settings", "over_delivery_receipt_allowance")
+ frappe.get_cached_value("Stock Settings", None, "over_delivery_receipt_allowance")
)
qty_allowance = global_qty_allowance
elif qty_or_amount == "amount" and not over_billing_allowance:
if global_amount_allowance == None:
global_amount_allowance = flt(
- frappe.db.get_single_value("Accounts Settings", "over_billing_allowance")
+ frappe.get_cached_value("Accounts Settings", None, "over_billing_allowance")
)
over_billing_allowance = global_amount_allowance
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index a3fbdda..a1946e8 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -48,7 +48,9 @@
super(StockController, self).validate()
if self.docstatus == 0:
- self.validate_duplicate_serial_and_batch_bundle()
+ for table_name in ["items", "packed_items", "supplied_items"]:
+ self.validate_duplicate_serial_and_batch_bundle(table_name)
+
if not self.get("is_return"):
self.validate_inspection()
self.validate_serialized_batch()
@@ -58,12 +60,19 @@
self.validate_internal_transfer()
self.validate_putaway_capacity()
- def validate_duplicate_serial_and_batch_bundle(self):
- if sbb_list := [
- item.get("serial_and_batch_bundle")
- for item in self.items
- if item.get("serial_and_batch_bundle")
- ]:
+ def validate_duplicate_serial_and_batch_bundle(self, table_name):
+ if not self.get(table_name):
+ return
+
+ sbb_list = []
+ for item in self.get(table_name):
+ if item.get("serial_and_batch_bundle"):
+ sbb_list.append(item.get("serial_and_batch_bundle"))
+
+ if item.get("rejected_serial_and_batch_bundle"):
+ sbb_list.append(item.get("rejected_serial_and_batch_bundle"))
+
+ if sbb_list:
SLE = frappe.qb.DocType("Stock Ledger Entry")
data = (
frappe.qb.from_(SLE)
@@ -188,7 +197,7 @@
not row.serial_and_batch_bundle and not row.get("rejected_serial_and_batch_bundle")
):
bundle_details = {
- "item_code": row.item_code,
+ "item_code": row.get("rm_item_code") or row.item_code,
"posting_date": self.posting_date,
"posting_time": self.posting_time,
"voucher_type": self.doctype,
@@ -200,7 +209,7 @@
"do_not_submit": True,
}
- if row.qty:
+ if row.get("qty") or row.get("consumed_qty"):
self.update_bundle_details(bundle_details, table_name, row)
self.create_serial_batch_bundle(bundle_details, row)
@@ -219,6 +228,12 @@
type_of_transaction = "Inward"
if not self.is_return:
type_of_transaction = "Outward"
+ elif table_name == "supplied_items":
+ qty = row.consumed_qty
+ warehouse = self.supplier_warehouse
+ type_of_transaction = "Outward"
+ if self.is_return:
+ type_of_transaction = "Inward"
else:
type_of_transaction = get_type_of_transaction(self, row)
@@ -236,6 +251,14 @@
qty = row.get("rejected_qty")
warehouse = row.get("rejected_warehouse")
+ if (
+ self.is_internal_transfer()
+ and self.doctype in ["Sales Invoice", "Delivery Note"]
+ and self.is_return
+ ):
+ warehouse = row.get("target_warehouse") or row.get("warehouse")
+ type_of_transaction = "Outward"
+
bundle_details.update(
{
"qty": qty,
@@ -542,13 +565,30 @@
)
def delete_auto_created_batches(self):
- for row in self.items:
- if row.serial_and_batch_bundle:
- frappe.db.set_value(
- "Serial and Batch Bundle", row.serial_and_batch_bundle, {"is_cancelled": 1}
- )
+ for table_name in ["items", "packed_items", "supplied_items"]:
+ if not self.get(table_name):
+ continue
- row.db_set("serial_and_batch_bundle", None)
+ for row in self.get(table_name):
+ update_values = {}
+ if row.get("batch_no"):
+ update_values["batch_no"] = None
+
+ if row.serial_and_batch_bundle:
+ update_values["serial_and_batch_bundle"] = None
+ frappe.db.set_value(
+ "Serial and Batch Bundle", row.serial_and_batch_bundle, {"is_cancelled": 1}
+ )
+
+ if update_values:
+ row.db_set(update_values)
+
+ if table_name == "items" and row.get("rejected_serial_and_batch_bundle"):
+ frappe.db.set_value(
+ "Serial and Batch Bundle", row.rejected_serial_and_batch_bundle, {"is_cancelled": 1}
+ )
+
+ row.db_set("rejected_serial_and_batch_bundle", None)
def set_serial_and_batch_bundle(self, table_name=None, ignore_validate=False):
if not table_name:
@@ -579,7 +619,7 @@
bundle_doc.warehouse = warehouse
bundle_doc.type_of_transaction = type_of_transaction
bundle_doc.voucher_type = self.doctype
- bundle_doc.voucher_no = self.name
+ bundle_doc.voucher_no = "" if self.is_new() or self.docstatus == 2 else self.name
bundle_doc.is_cancelled = 0
for row in bundle_doc.entries:
@@ -595,6 +635,7 @@
bundle_doc.calculate_qty_and_amount()
bundle_doc.flags.ignore_permissions = True
+ bundle_doc.flags.ignore_validate = True
bundle_doc.save(ignore_permissions=True)
return bundle_doc.name
diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py
index e66fe8b..ffc7f91 100644
--- a/erpnext/controllers/subcontracting_controller.py
+++ b/erpnext/controllers/subcontracting_controller.py
@@ -379,10 +379,10 @@
if row.serial_no:
details.serial_no.extend(get_serial_nos(row.serial_no))
- if row.batch_no:
+ elif row.batch_no:
details.batch_no[row.batch_no] += row.qty
- if voucher_bundle_data:
+ elif voucher_bundle_data:
bundle_key = (row.rm_item_code, row.main_item_code, row.t_warehouse, row.voucher_no)
bundle_data = voucher_bundle_data.get(bundle_key, frappe._dict())
@@ -392,6 +392,9 @@
if bundle_data.batch_nos:
for batch_no, qty in bundle_data.batch_nos.items():
+ if qty < 0:
+ qty = abs(qty)
+
if qty > 0:
details.batch_no[batch_no] += qty
bundle_data.batch_nos[batch_no] -= qty
@@ -545,17 +548,24 @@
rm_obj.reference_name = item_row.name
+ use_serial_batch_fields = frappe.db.get_single_value("Stock Settings", "use_serial_batch_fields")
+
if self.doctype == self.subcontract_data.order_doctype:
rm_obj.required_qty = qty
rm_obj.amount = rm_obj.required_qty * rm_obj.rate
else:
rm_obj.consumed_qty = qty
rm_obj.required_qty = bom_item.required_qty or qty
+ rm_obj.serial_and_batch_bundle = None
setattr(
rm_obj, self.subcontract_data.order_field, item_row.get(self.subcontract_data.order_field)
)
- if self.doctype == "Subcontracting Receipt":
+ if use_serial_batch_fields:
+ rm_obj.use_serial_batch_fields = 1
+ self.__set_batch_nos(bom_item, item_row, rm_obj, qty)
+
+ if self.doctype == "Subcontracting Receipt" and not use_serial_batch_fields:
args = frappe._dict(
{
"item_code": rm_obj.rm_item_code,
@@ -581,6 +591,68 @@
rm_obj.rate = get_incoming_rate(args)
+ def __set_batch_nos(self, bom_item, item_row, rm_obj, qty):
+ key = (rm_obj.rm_item_code, item_row.item_code, item_row.get(self.subcontract_data.order_field))
+
+ if self.available_materials.get(key) and self.available_materials[key]["batch_no"]:
+ new_rm_obj = None
+ for batch_no, batch_qty in self.available_materials[key]["batch_no"].items():
+ if batch_qty >= qty or (
+ rm_obj.consumed_qty == 0
+ and self.backflush_based_on == "BOM"
+ and len(self.available_materials[key]["batch_no"]) == 1
+ ):
+ if rm_obj.consumed_qty == 0:
+ self.__set_consumed_qty(rm_obj, qty)
+
+ self.__set_batch_no_as_per_qty(item_row, rm_obj, batch_no, qty)
+ self.available_materials[key]["batch_no"][batch_no] -= qty
+ return
+
+ elif qty > 0 and batch_qty > 0:
+ qty -= batch_qty
+ new_rm_obj = self.append(self.raw_material_table, bom_item)
+ new_rm_obj.serial_and_batch_bundle = None
+ new_rm_obj.use_serial_batch_fields = 1
+ new_rm_obj.reference_name = item_row.name
+ self.__set_batch_no_as_per_qty(item_row, new_rm_obj, batch_no, batch_qty)
+ self.available_materials[key]["batch_no"][batch_no] = 0
+
+ if new_rm_obj:
+ self.remove(rm_obj)
+ elif abs(qty) > 0:
+ self.__set_consumed_qty(rm_obj, qty)
+
+ else:
+ self.__set_consumed_qty(rm_obj, qty, bom_item.required_qty or qty)
+ self.__set_serial_nos(item_row, rm_obj)
+
+ def __set_consumed_qty(self, rm_obj, consumed_qty, required_qty=0):
+ rm_obj.required_qty = required_qty
+ rm_obj.consumed_qty = consumed_qty
+
+ def __set_serial_nos(self, item_row, rm_obj):
+ key = (rm_obj.rm_item_code, item_row.item_code, item_row.get(self.subcontract_data.order_field))
+ if self.available_materials.get(key) and self.available_materials[key]["serial_no"]:
+ used_serial_nos = self.available_materials[key]["serial_no"][0 : cint(rm_obj.consumed_qty)]
+ rm_obj.serial_no = "\n".join(used_serial_nos)
+
+ # Removed the used serial nos from the list
+ for sn in used_serial_nos:
+ self.available_materials[key]["serial_no"].remove(sn)
+
+ def __set_batch_no_as_per_qty(self, item_row, rm_obj, batch_no, qty):
+ rm_obj.update(
+ {
+ "consumed_qty": qty,
+ "batch_no": batch_no,
+ "required_qty": qty,
+ self.subcontract_data.order_field: item_row.get(self.subcontract_data.order_field),
+ }
+ )
+
+ self.__set_serial_nos(item_row, rm_obj)
+
def __get_qty_based_on_material_transfer(self, item_row, transfer_item):
key = (item_row.item_code, item_row.get(self.subcontract_data.order_field))
@@ -1076,6 +1148,9 @@
"serial_and_batch_bundle": rm_item.get("serial_and_batch_bundle"),
"main_item_code": fg_item_code,
"allow_alternative_item": item_wh.get(rm_item_code, {}).get("allow_alternative_item"),
+ "use_serial_batch_fields": rm_item.get("use_serial_batch_fields"),
+ "serial_no": rm_item.get("serial_no") if rm_item.get("use_serial_batch_fields") else None,
+ "batch_no": rm_item.get("batch_no") if rm_item.get("use_serial_batch_fields") else None,
}
}
diff --git a/erpnext/controllers/tests/test_queries.py b/erpnext/controllers/tests/test_queries.py
index 3a3bc1c..c536d1c 100644
--- a/erpnext/controllers/tests/test_queries.py
+++ b/erpnext/controllers/tests/test_queries.py
@@ -31,18 +31,6 @@
self.assertGreaterEqual(len(query(txt="_Test Lead")), 4)
self.assertEqual(len(query(txt="_Test Lead 4")), 1)
- def test_customer_query(self):
- query = add_default_params(queries.customer_query, "Customer")
-
- self.assertGreaterEqual(len(query(txt="_Test Customer")), 7)
- self.assertGreaterEqual(len(query(txt="_Test Customer USD")), 1)
-
- def test_supplier_query(self):
- query = add_default_params(queries.supplier_query, "Supplier")
-
- self.assertGreaterEqual(len(query(txt="_Test Supplier")), 7)
- self.assertGreaterEqual(len(query(txt="_Test Supplier USD")), 1)
-
def test_item_query(self):
query = add_default_params(queries.item_query, "Item")
diff --git a/erpnext/controllers/tests/test_subcontracting_controller.py b/erpnext/controllers/tests/test_subcontracting_controller.py
index 95a7bcb..7374e1e 100644
--- a/erpnext/controllers/tests/test_subcontracting_controller.py
+++ b/erpnext/controllers/tests/test_subcontracting_controller.py
@@ -140,6 +140,7 @@
- Create partial SCR against the SCO and check serial nos and batch no.
"""
+ frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)
set_backflush_based_on("Material Transferred for Subcontract")
service_items = [
{
@@ -202,6 +203,8 @@
if value.get(field):
self.assertEqual(value.get(field), transferred_detais.get(field))
+ frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)
+
def test_subcontracting_with_same_components_different_fg(self):
"""
- Set backflush based on Material Transfer.
@@ -211,6 +214,7 @@
- Create partial SCR against the SCO and check serial nos.
"""
+ frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)
set_backflush_based_on("Material Transferred for Subcontract")
service_items = [
{
@@ -278,6 +282,8 @@
self.assertEqual(value.qty, 6)
self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[6:12]))
+ frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)
+
def test_return_non_consumed_materials(self):
"""
- Set backflush based on Material Transfer.
@@ -288,6 +294,7 @@
- After that return the non consumed material back to the store from supplier's warehouse.
"""
+ frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)
set_backflush_based_on("Material Transferred for Subcontract")
service_items = [
{
@@ -333,6 +340,7 @@
get_serial_nos(doc.items[0].serial_no),
itemwise_details.get(doc.items[0].item_code)["serial_no"][5:6],
)
+ frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)
def test_item_with_batch_based_on_bom(self):
"""
@@ -578,6 +586,7 @@
- Create SCR for remaining qty against the SCO and change the qty manually.
"""
+ frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)
set_backflush_based_on("Material Transferred for Subcontract")
service_items = [
{
@@ -643,6 +652,8 @@
self.assertEqual(value.qty, details.qty)
self.assertEqual(sorted(value.serial_no), sorted(details.serial_no))
+ frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)
+
def test_incorrect_serial_no_components_based_on_material_transfer(self):
"""
- Set backflush based on Material Transferred for Subcontract.
@@ -652,6 +663,7 @@
- System should throw the error and not allowed to save the SCR.
"""
+ frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)
serial_no = "ABC"
if not frappe.db.exists("Serial No", serial_no):
frappe.get_doc(
@@ -712,6 +724,7 @@
scr1.save()
self.delete_bundle_from_scr(scr1)
scr1.delete()
+ frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)
@staticmethod
def delete_bundle_from_scr(scr):
@@ -844,6 +857,223 @@
for item in sco.get("supplied_items"):
self.assertEqual(item.supplied_qty, 0.0)
+ def test_sco_with_material_transfer_with_use_serial_batch_fields(self):
+ """
+ - Set backflush based on Material Transfer.
+ - Create SCO for the item Subcontracted Item SA1 and Subcontracted Item SA5.
+ - Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
+ - Transfer extra item Subcontracted SRM Item 4 for the subcontract item Subcontracted Item SA5.
+ - Create partial SCR against the SCO and check serial nos and batch no.
+ """
+
+ set_backflush_based_on("Material Transferred for Subcontract")
+ service_items = [
+ {
+ "warehouse": "_Test Warehouse - _TC",
+ "item_code": "Subcontracted Service Item 1",
+ "qty": 5,
+ "rate": 100,
+ "fg_item": "Subcontracted Item SA1",
+ "fg_item_qty": 5,
+ },
+ {
+ "warehouse": "_Test Warehouse - _TC",
+ "item_code": "Subcontracted Service Item 5",
+ "qty": 6,
+ "rate": 100,
+ "fg_item": "Subcontracted Item SA5",
+ "fg_item_qty": 6,
+ },
+ ]
+ sco = get_subcontracting_order(service_items=service_items)
+ rm_items = get_rm_items(sco.supplied_items)
+ rm_items.append(
+ {
+ "main_item_code": "Subcontracted Item SA5",
+ "item_code": "Subcontracted SRM Item 4",
+ "qty": 6,
+ }
+ )
+ itemwise_details = make_stock_in_entry(rm_items=rm_items)
+
+ for item in rm_items:
+ item["sco_rm_detail"] = sco.items[0].name if item.get("qty") == 5 else sco.items[1].name
+
+ make_stock_transfer_entry(
+ sco_no=sco.name,
+ rm_items=rm_items,
+ itemwise_details=copy.deepcopy(itemwise_details),
+ )
+
+ scr1 = make_subcontracting_receipt(sco.name)
+ scr1.remove(scr1.items[1])
+ scr1.save()
+ scr1.submit()
+
+ for key, value in get_supplied_items(scr1).items():
+ transferred_detais = itemwise_details.get(key)
+
+ for field in ["qty", "serial_no", "batch_no"]:
+ if value.get(field):
+ data = value.get(field)
+ if field == "serial_no":
+ data = sorted(data)
+
+ self.assertEqual(data, transferred_detais.get(field))
+
+ scr2 = make_subcontracting_receipt(sco.name)
+ scr2.save()
+ scr2.submit()
+
+ for key, value in get_supplied_items(scr2).items():
+ transferred_detais = itemwise_details.get(key)
+
+ for field in ["qty", "serial_no", "batch_no"]:
+ if value.get(field):
+ data = value.get(field)
+ if field == "serial_no":
+ data = sorted(data)
+
+ self.assertEqual(data, transferred_detais.get(field))
+
+ def test_subcontracting_with_same_components_different_fg_with_serial_batch_fields(self):
+ """
+ - Set backflush based on Material Transfer.
+ - Create SCO for the item Subcontracted Item SA2 and Subcontracted Item SA3.
+ - Transfer the components from Stores to Supplier warehouse with serial nos.
+ - Transfer extra qty of components for the item Subcontracted Item SA2.
+ - Create partial SCR against the SCO and check serial nos.
+ """
+
+ set_backflush_based_on("Material Transferred for Subcontract")
+ service_items = [
+ {
+ "warehouse": "_Test Warehouse - _TC",
+ "item_code": "Subcontracted Service Item 2",
+ "qty": 5,
+ "rate": 100,
+ "fg_item": "Subcontracted Item SA2",
+ "fg_item_qty": 5,
+ },
+ {
+ "warehouse": "_Test Warehouse - _TC",
+ "item_code": "Subcontracted Service Item 3",
+ "qty": 6,
+ "rate": 100,
+ "fg_item": "Subcontracted Item SA3",
+ "fg_item_qty": 6,
+ },
+ ]
+ sco = get_subcontracting_order(service_items=service_items)
+ rm_items = get_rm_items(sco.supplied_items)
+ rm_items[0]["qty"] += 1
+ itemwise_details = make_stock_in_entry(rm_items=rm_items)
+
+ for item in rm_items:
+ item["sco_rm_detail"] = sco.items[0].name if item.get("qty") == 5 else sco.items[1].name
+ item["use_serial_batch_fields"] = 1
+
+ make_stock_transfer_entry(
+ sco_no=sco.name,
+ rm_items=rm_items,
+ itemwise_details=copy.deepcopy(itemwise_details),
+ )
+
+ scr1 = make_subcontracting_receipt(sco.name)
+ scr1.items[0].qty = 3
+ scr1.remove(scr1.items[1])
+ scr1.save()
+ scr1.submit()
+
+ for key, value in get_supplied_items(scr1).items():
+ transferred_detais = itemwise_details.get(key)
+
+ self.assertEqual(value.qty, 4)
+ self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[0:4]))
+
+ scr2 = make_subcontracting_receipt(sco.name)
+ scr2.items[0].qty = 2
+ scr2.remove(scr2.items[1])
+ scr2.save()
+ scr2.submit()
+
+ for key, value in get_supplied_items(scr2).items():
+ transferred_detais = itemwise_details.get(key)
+
+ self.assertEqual(value.qty, 2)
+ self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[4:6]))
+
+ scr3 = make_subcontracting_receipt(sco.name)
+ scr3.save()
+ scr3.submit()
+
+ for key, value in get_supplied_items(scr3).items():
+ transferred_detais = itemwise_details.get(key)
+
+ self.assertEqual(value.qty, 6)
+ self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[6:12]))
+
+ def test_return_non_consumed_materials_with_serial_batch_fields(self):
+ """
+ - Set backflush based on Material Transfer.
+ - Create SCO for item Subcontracted Item SA2.
+ - Transfer the components from Stores to Supplier warehouse with serial nos.
+ - Transfer extra qty of component for the subcontracted item Subcontracted Item SA2.
+ - Create SCR for full qty against the SCO and change the qty of raw material.
+ - After that return the non consumed material back to the store from supplier's warehouse.
+ """
+
+ set_backflush_based_on("Material Transferred for Subcontract")
+ service_items = [
+ {
+ "warehouse": "_Test Warehouse - _TC",
+ "item_code": "Subcontracted Service Item 2",
+ "qty": 5,
+ "rate": 100,
+ "fg_item": "Subcontracted Item SA2",
+ "fg_item_qty": 5,
+ },
+ ]
+ sco = get_subcontracting_order(service_items=service_items)
+ rm_items = get_rm_items(sco.supplied_items)
+ rm_items[0]["qty"] += 1
+ itemwise_details = make_stock_in_entry(rm_items=rm_items)
+
+ for item in rm_items:
+ item["use_serial_batch_fields"] = 1
+ item["sco_rm_detail"] = sco.items[0].name
+
+ make_stock_transfer_entry(
+ sco_no=sco.name,
+ rm_items=rm_items,
+ itemwise_details=copy.deepcopy(itemwise_details),
+ )
+
+ scr1 = make_subcontracting_receipt(sco.name)
+ scr1.save()
+ scr1.supplied_items[0].consumed_qty = 5
+ scr1.supplied_items[0].serial_no = "\n".join(
+ sorted(itemwise_details.get("Subcontracted SRM Item 2").get("serial_no")[0:5])
+ )
+ scr1.submit()
+
+ for key, value in get_supplied_items(scr1).items():
+ transferred_detais = itemwise_details.get(key)
+ self.assertTrue(value.use_serial_batch_fields)
+ self.assertEqual(value.qty, 5)
+ self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[0:5]))
+
+ sco.load_from_db()
+ self.assertEqual(sco.supplied_items[0].consumed_qty, 5)
+ doc = get_materials_from_supplier(sco.name, [d.name for d in sco.supplied_items])
+ self.assertEqual(doc.items[0].qty, 1)
+ self.assertEqual(doc.items[0].s_warehouse, "_Test Warehouse 1 - _TC")
+ self.assertEqual(doc.items[0].t_warehouse, "_Test Warehouse - _TC")
+ self.assertEqual(
+ get_serial_nos(doc.items[0].serial_no),
+ itemwise_details.get(doc.items[0].item_code)["serial_no"][5:6],
+ )
+
def add_second_row_in_scr(scr):
item_dict = {}
@@ -914,6 +1144,7 @@
else child_row.get("consumed_qty")
)
+ details.use_serial_batch_fields = child_row.get("use_serial_batch_fields")
if child_row.serial_and_batch_bundle:
doc = frappe.get_doc("Serial and Batch Bundle", child_row.serial_and_batch_bundle)
for row in doc.get("entries"):
@@ -945,6 +1176,7 @@
"rate": row.rate or 100,
"stock_uom": row.stock_uom or "Nos",
"warehouse": row.warehouse or "_Test Warehouse - _TC",
+ "use_serial_batch_fields": row.get("use_serial_batch_fields"),
}
item_details = args.itemwise_details.get(row.item_code)
@@ -960,9 +1192,12 @@
if batch_qty >= row.qty:
batches[batch_no] = row.qty
item_details.batch_no[batch_no] -= row.qty
+ if row.get("use_serial_batch_fields"):
+ item["batch_no"] = batch_no
+
break
- if serial_nos or batches:
+ if not row.get("use_serial_batch_fields") and (serial_nos or batches):
item["serial_and_batch_bundle"] = make_serial_batch_bundle(
frappe._dict(
{
@@ -978,6 +1213,9 @@
)
).name
+ if serial_nos and row.get("use_serial_batch_fields"):
+ item["serial_no"] = "\n".join(serial_nos)
+
items.append(item)
ste_dict = make_rm_stock_entry(args.sco_no, items)
@@ -1132,6 +1370,7 @@
"rate": item.rate,
"stock_uom": item.stock_uom,
"warehouse": item.reserve_warehouse,
+ "use_serial_batch_fields": 0,
}
)
diff --git a/erpnext/crm/doctype/campaign/campaign.js b/erpnext/crm/doctype/campaign/campaign.js
index 9e4a0a9..219e8cf 100644
--- a/erpnext/crm/doctype/campaign/campaign.js
+++ b/erpnext/crm/doctype/campaign/campaign.js
@@ -11,7 +11,7 @@
frappe.boot.sysdefaults.campaign_naming_by == "Naming Series"
);
} else {
- cur_frm.add_custom_button(
+ frm.add_custom_button(
__("View Leads"),
function () {
frappe.route_options = { source: "Campaign", campaign_name: frm.doc.name };
diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js
index 0b6cdf2..848d697 100644
--- a/erpnext/crm/doctype/lead/lead.js
+++ b/erpnext/crm/doctype/lead/lead.js
@@ -17,10 +17,6 @@
}
onload() {
- this.frm.set_query("customer", function (doc, cdt, cdn) {
- return { query: "erpnext.controllers.queries.customer_query" };
- });
-
this.frm.set_query("lead_owner", function (doc, cdt, cdn) {
return { query: "frappe.core.doctype.user.user.user_query" };
});
@@ -93,32 +89,33 @@
make_customer() {
frappe.model.open_mapped_doc({
method: "erpnext.crm.doctype.lead.lead.make_customer",
- frm: cur_frm,
+ frm: this.frm,
});
}
make_quotation() {
frappe.model.open_mapped_doc({
method: "erpnext.crm.doctype.lead.lead.make_quotation",
- frm: cur_frm,
+ frm: this.frm,
});
}
make_prospect() {
+ const me = this;
frappe.model.with_doctype("Prospect", function () {
let prospect = frappe.model.get_new_doc("Prospect");
- prospect.company_name = cur_frm.doc.company_name;
- prospect.no_of_employees = cur_frm.doc.no_of_employees;
- prospect.industry = cur_frm.doc.industry;
- prospect.market_segment = cur_frm.doc.market_segment;
- prospect.territory = cur_frm.doc.territory;
- prospect.fax = cur_frm.doc.fax;
- prospect.website = cur_frm.doc.website;
- prospect.prospect_owner = cur_frm.doc.lead_owner;
- prospect.notes = cur_frm.doc.notes;
+ prospect.company_name = me.frm.doc.company_name;
+ prospect.no_of_employees = me.frm.doc.no_of_employees;
+ prospect.industry = me.frm.doc.industry;
+ prospect.market_segment = me.frm.doc.market_segment;
+ prospect.territory = me.frm.doc.territory;
+ prospect.fax = me.frm.doc.fax;
+ prospect.website = me.frm.doc.website;
+ prospect.prospect_owner = me.frm.doc.lead_owner;
+ prospect.notes = me.frm.doc.notes;
let leads_row = frappe.model.add_child(prospect, "leads");
- leads_row.lead = cur_frm.doc.name;
+ leads_row.lead = me.frm.doc.name;
frappe.set_route("Form", "Prospect", prospect.name);
});
diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js
index 1c8a80a..c706357 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.js
+++ b/erpnext/crm/doctype/opportunity/opportunity.js
@@ -318,14 +318,14 @@
create_quotation() {
frappe.model.open_mapped_doc({
method: "erpnext.crm.doctype.opportunity.opportunity.make_quotation",
- frm: cur_frm,
+ frm: this.frm,
});
}
make_customer() {
frappe.model.open_mapped_doc({
method: "erpnext.crm.doctype.opportunity.opportunity.make_customer",
- frm: cur_frm,
+ frm: this.frm,
});
}
diff --git a/erpnext/crm/doctype/opportunity/opportunity.json b/erpnext/crm/doctype/opportunity/opportunity.json
index 07641d2..e6f7bfc 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.json
+++ b/erpnext/crm/doctype/opportunity/opportunity.json
@@ -250,7 +250,7 @@
},
{
"fieldname": "address_display",
- "fieldtype": "Small Text",
+ "fieldtype": "Text Editor",
"hidden": 1,
"label": "Address",
"oldfieldname": "address",
@@ -622,7 +622,7 @@
"icon": "fa fa-info-sign",
"idx": 195,
"links": [],
- "modified": "2022-10-13 12:42:21.545636",
+ "modified": "2024-03-22 16:01:10.721453",
"modified_by": "Administrator",
"module": "CRM",
"name": "Opportunity",
diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py
index 72e26de..7abbb63 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.py
+++ b/erpnext/crm/doctype/opportunity/opportunity.py
@@ -40,7 +40,7 @@
OpportunityLostReasonDetail,
)
- address_display: DF.SmallText | None
+ address_display: DF.TextEditor | None
amended_from: DF.Link | None
annual_revenue: DF.Currency
base_opportunity_amount: DF.Currency
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 308e6ca..0d70476 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -282,9 +282,6 @@
before_tests = "erpnext.setup.utils.before_tests"
-standard_queries = {
- "Customer": "erpnext.controllers.queries.customer_query",
-}
period_closing_doctypes = [
"Sales Invoice",
@@ -309,7 +306,10 @@
doc_events = {
"*": {
- "validate": "erpnext.support.doctype.service_level_agreement.service_level_agreement.apply",
+ "validate": [
+ "erpnext.support.doctype.service_level_agreement.service_level_agreement.apply",
+ "erpnext.setup.doctype.transaction_deletion_record.transaction_deletion_record.check_for_running_deletion_job",
+ ],
},
tuple(period_closing_doctypes): {
"validate": "erpnext.accounts.doctype.accounting_period.accounting_period.validate_accounting_period_on_doc_save",