mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-12 03:15:07 +00:00
Merge branch 'develop' into selling-coupon-code
This commit is contained in:
@@ -261,14 +261,16 @@ def get_accounting_dimensions(as_list=True, filters=None):
|
||||
|
||||
|
||||
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):
|
||||
|
||||
@@ -78,6 +78,8 @@ class TestAccountingDimension(unittest.TestCase):
|
||||
|
||||
def tearDown(self):
|
||||
disable_dimension()
|
||||
frappe.flags.accounting_dimensions_details = None
|
||||
frappe.flags.dimension_filter_map = None
|
||||
|
||||
|
||||
def create_dimension():
|
||||
|
||||
@@ -66,37 +66,39 @@ class AccountingDimensionFilter(Document):
|
||||
|
||||
|
||||
def get_dimension_filter_map():
|
||||
filters = frappe.db.sql(
|
||||
"""
|
||||
SELECT
|
||||
a.applicable_on_account, d.dimension_value, p.accounting_dimension,
|
||||
p.allow_or_restrict, 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):
|
||||
|
||||
@@ -47,6 +47,8 @@ class TestAccountingDimensionFilter(unittest.TestCase):
|
||||
def tearDown(self):
|
||||
disable_dimension_filter()
|
||||
disable_dimension()
|
||||
frappe.flags.accounting_dimensions_details = None
|
||||
frappe.flags.dimension_filter_map = None
|
||||
|
||||
for si in self.invoice_list:
|
||||
si.load_from_db()
|
||||
|
||||
@@ -139,6 +139,8 @@ class Budget(Document):
|
||||
|
||||
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 @@ def validate_expense_against_budget(args, expense_amount=0):
|
||||
"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 @@ def validate_expense_against_budget(args, expense_amount=0):
|
||||
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,
|
||||
|
||||
@@ -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": []
|
||||
}
|
||||
@@ -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");
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -31,6 +31,7 @@ class CurrencyExchangeSettings(Document):
|
||||
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 @@ class CurrencyExchangeSettings(Document):
|
||||
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 @@ class CurrencyExchangeSettings(Document):
|
||||
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 @@ class CurrencyExchangeSettings(Document):
|
||||
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
|
||||
|
||||
@@ -57,13 +57,13 @@ frappe.ui.form.on("Exchange Rate Revaluation", {
|
||||
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");
|
||||
|
||||
@@ -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": []
|
||||
}
|
||||
}
|
||||
@@ -32,8 +32,6 @@ class GLEntry(Document):
|
||||
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 @@ def update_outstanding_amt(
|
||||
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 update_outstanding_amt(
|
||||
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:
|
||||
|
||||
@@ -189,7 +189,7 @@ frappe.ui.form.on("Invoice Discounting", {
|
||||
|
||||
show_general_ledger: (frm) => {
|
||||
if (frm.doc.docstatus > 0) {
|
||||
cur_frm.add_custom_button(
|
||||
frm.add_custom_button(
|
||||
__("Accounting Ledger"),
|
||||
function () {
|
||||
frappe.route_options = {
|
||||
|
||||
@@ -196,7 +196,7 @@ frappe.ui.form.on("Journal Entry", {
|
||||
!(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 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
|
||||
}
|
||||
|
||||
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 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
|
||||
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 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
|
||||
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 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
|
||||
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 @@ frappe.ui.form.on("Journal Entry Account", {
|
||||
},
|
||||
|
||||
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", {
|
||||
});
|
||||
|
||||
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 @@ $.extend(erpnext.journal_entry, {
|
||||
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 @@ $.extend(erpnext.journal_entry, {
|
||||
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,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -32,7 +32,7 @@ frappe.ui.form.on("Payment Entry", {
|
||||
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 @@ frappe.ui.form.on("Payment Entry", {
|
||||
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 @@ frappe.ui.form.on("Payment Entry", {
|
||||
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 @@ frappe.ui.form.on("Payment Entry", {
|
||||
"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
|
||||
@@ -623,7 +623,7 @@ frappe.ui.form.on("Payment Entry", {
|
||||
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",
|
||||
@@ -1042,7 +1042,7 @@ frappe.ui.form.on("Payment Entry", {
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -1213,7 +1213,7 @@ frappe.ui.form.on("Payment Entry", {
|
||||
|
||||
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(
|
||||
@@ -1227,7 +1227,7 @@ frappe.ui.form.on("Payment Entry", {
|
||||
|
||||
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(
|
||||
@@ -1323,7 +1323,7 @@ frappe.ui.form.on("Payment Entry", {
|
||||
|
||||
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: {
|
||||
|
||||
@@ -489,7 +489,9 @@ class PaymentEntry(AccountsController):
|
||||
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():
|
||||
|
||||
@@ -161,11 +161,12 @@ class PaymentLedgerEntry(Document):
|
||||
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 (
|
||||
|
||||
@@ -1130,6 +1130,17 @@ class TestPaymentReconciliation(FrappeTestCase):
|
||||
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)
|
||||
|
||||
@@ -32,7 +32,7 @@ frappe.ui.form.on("Payment Request", "refresh", function (frm) {
|
||||
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
|
||||
) {
|
||||
|
||||
@@ -141,7 +141,8 @@ class PeriodClosingVoucher(AccountsController):
|
||||
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
|
||||
|
||||
|
||||
@@ -193,7 +193,7 @@ erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnex
|
||||
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 @@ frappe.ui.form.on("POS Invoice", {
|
||||
});
|
||||
} 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({
|
||||
|
||||
@@ -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-20 16:00:34.268756",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice",
|
||||
|
||||
@@ -109,7 +109,7 @@ class POSInvoice(SalesInvoice):
|
||||
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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -131,17 +131,17 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
||||
|
||||
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();
|
||||
@@ -462,7 +462,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
||||
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,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -760,7 +760,7 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "other_charges_calculation",
|
||||
"fieldtype": "Long Text",
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "Taxes and Charges Calculation",
|
||||
"no_copy": 1,
|
||||
"oldfieldtype": "HTML",
|
||||
@@ -1638,7 +1638,7 @@
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-11 14:46:30.298184",
|
||||
"modified": "2024-03-20 15:57:00.736868",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
||||
@@ -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
|
||||
@@ -147,7 +147,7 @@ class PurchaseInvoice(BuyingController):
|
||||
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
|
||||
@@ -742,13 +742,12 @@ class PurchaseInvoice(BuyingController):
|
||||
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 @@ class PurchaseInvoice(BuyingController):
|
||||
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 @@ class PurchaseInvoice(BuyingController):
|
||||
"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 @@ class PurchaseInvoice(BuyingController):
|
||||
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 @@ class PurchaseInvoice(BuyingController):
|
||||
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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -61,9 +61,9 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
||||
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);
|
||||
@@ -113,33 +113,33 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
||||
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();
|
||||
@@ -147,10 +147,10 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
||||
__("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")
|
||||
);
|
||||
@@ -171,10 +171,10 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
||||
}
|
||||
|
||||
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")
|
||||
);
|
||||
@@ -182,7 +182,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
||||
}
|
||||
|
||||
// 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();
|
||||
@@ -213,7 +213,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -232,28 +232,27 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -465,7 +464,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -947,7 +947,7 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "other_charges_calculation",
|
||||
"fieldtype": "Long Text",
|
||||
"fieldtype": "Text Editor",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Taxes and Charges Calculation",
|
||||
@@ -2187,7 +2187,8 @@
|
||||
"fieldname": "update_outstanding_for_self",
|
||||
"fieldtype": "Check",
|
||||
"label": "Update Outstanding for Self",
|
||||
"no_copy": 1
|
||||
"no_copy": 1,
|
||||
"print_hide": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
@@ -2200,7 +2201,7 @@
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2024-03-13 16:42:18.153555",
|
||||
"modified": "2024-03-22 17:50:34.395602",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
@@ -2255,4 +2256,4 @@
|
||||
"title_field": "title",
|
||||
"track_changes": 1,
|
||||
"track_seen": 1
|
||||
}
|
||||
}
|
||||
@@ -151,7 +151,7 @@ class SalesInvoice(SellingController):
|
||||
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
|
||||
|
||||
@@ -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": []
|
||||
}
|
||||
@@ -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
|
||||
@@ -7,7 +7,7 @@ import copy
|
||||
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 get_cost_center_allocation_data(company, posting_date):
|
||||
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 @@ def merge_similar_entries(gl_map, precision=None):
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -669,20 +669,20 @@ class GrossProfitGenerator(object):
|
||||
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)
|
||||
|
||||
@@ -460,3 +460,95 @@ class TestGrossProfit(FrappeTestCase):
|
||||
}
|
||||
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])
|
||||
|
||||
@@ -48,7 +48,7 @@ frappe.ui.form.on("Asset", {
|
||||
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 @@ frappe.ui.form.on("Asset", {
|
||||
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 @@ frappe.ui.form.on("Asset", {
|
||||
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 @@ frappe.ui.form.on("Asset", {
|
||||
});
|
||||
}
|
||||
|
||||
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 @@ erpnext.asset.scrap_asset = function (frm) {
|
||||
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 @@ erpnext.asset.restore_asset = function (frm) {
|
||||
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) {
|
||||
|
||||
@@ -86,7 +86,7 @@ frappe.ui.form.on("Purchase Order", {
|
||||
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) {
|
||||
@@ -270,8 +270,8 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
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 {
|
||||
@@ -291,7 +291,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
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 &&
|
||||
@@ -336,7 +336,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
|
||||
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"),
|
||||
@@ -348,7 +348,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
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")
|
||||
@@ -356,7 +356,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
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();
|
||||
@@ -365,7 +365,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
);
|
||||
}
|
||||
} else {
|
||||
cur_frm.add_custom_button(
|
||||
this.frm.add_custom_button(
|
||||
__("Subcontracting Order"),
|
||||
this.make_subcontracting_order,
|
||||
__("Create")
|
||||
@@ -374,7 +374,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
}
|
||||
}
|
||||
if (flt(doc.per_billed, 2) < 100)
|
||||
cur_frm.add_custom_button(
|
||||
this.frm.add_custom_button(
|
||||
__("Purchase Invoice"),
|
||||
this.make_purchase_invoice,
|
||||
__("Create")
|
||||
@@ -418,10 +418,10 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,8 +458,8 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
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);
|
||||
@@ -478,7 +478,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
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 ..."),
|
||||
});
|
||||
}
|
||||
@@ -486,14 +486,14 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
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 ..."),
|
||||
});
|
||||
}
|
||||
@@ -652,7 +652,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
}
|
||||
|
||||
unhold_purchase_order() {
|
||||
cur_frm.cscript.update_status("Resume", "Draft");
|
||||
this.frm.cscript.update_status("Resume", "Draft");
|
||||
}
|
||||
|
||||
hold_purchase_order() {
|
||||
@@ -692,15 +692,15 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
}
|
||||
|
||||
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() {
|
||||
|
||||
@@ -642,7 +642,7 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "other_charges_calculation",
|
||||
"fieldtype": "Long Text",
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "Taxes and Charges Calculation",
|
||||
"no_copy": 1,
|
||||
"oldfieldtype": "HTML",
|
||||
@@ -1288,7 +1288,7 @@
|
||||
"idx": 105,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-10-10 13:37:40.158761",
|
||||
"modified": "2024-03-20 16:03:31.611808",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order",
|
||||
@@ -1343,4 +1343,4 @@
|
||||
"timeline_field": "supplier",
|
||||
"title_field": "supplier_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -57,6 +57,7 @@ class PurchaseOrder(BuyingController):
|
||||
additional_discount_percentage: DF.Float
|
||||
address_display: DF.SmallText | 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
|
||||
@@ -109,7 +110,7 @@ class PurchaseOrder(BuyingController):
|
||||
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
|
||||
|
||||
@@ -518,16 +518,15 @@ erpnext.buying.RequestforQuotationController = class RequestforQuotationControll
|
||||
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 });
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -22,9 +22,9 @@ erpnext.buying.SupplierQuotationController = class SupplierQuotationController e
|
||||
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 @@ erpnext.buying.SupplierQuotationController = class SupplierQuotationController e
|
||||
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,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -462,7 +462,7 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "other_charges_calculation",
|
||||
"fieldtype": "Long Text",
|
||||
"fieldtype": "Markdown Editor",
|
||||
"label": "Taxes and Charges Calculation",
|
||||
"no_copy": 1,
|
||||
"oldfieldtype": "HTML",
|
||||
@@ -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-20 16:03:59.069145",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier Quotation",
|
||||
|
||||
@@ -71,7 +71,7 @@ class SupplierQuotation(BuyingController):
|
||||
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]
|
||||
|
||||
@@ -77,7 +77,10 @@ frappe.query_reports["Supplier Quotation Comparison"] = {
|
||||
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"),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1739,8 +1739,8 @@ class AccountsController(TransactionBase):
|
||||
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()
|
||||
|
||||
|
||||
@@ -577,6 +577,7 @@ class StatusUpdater(Document):
|
||||
ref_doc.set_status(update=True)
|
||||
|
||||
|
||||
@frappe.request_cache
|
||||
def get_allowance_for(
|
||||
item_code,
|
||||
item_allowance=None,
|
||||
@@ -606,20 +607,20 @@ def get_allowance_for(
|
||||
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
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ frappe.ui.form.on("Campaign", {
|
||||
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 };
|
||||
|
||||
@@ -89,32 +89,33 @@ erpnext.LeadController = class LeadController extends frappe.ui.form.Controller
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -318,14 +318,14 @@ erpnext.crm.Opportunity = class Opportunity extends frappe.ui.form.Controller {
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -306,7 +306,10 @@ period_closing_doctypes = [
|
||||
|
||||
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",
|
||||
|
||||
@@ -400,7 +400,7 @@ frappe.ui.form.on("BOM", {
|
||||
},
|
||||
|
||||
rm_cost_as_per(frm) {
|
||||
if (in_list(["Valuation Rate", "Last Purchase Rate"], frm.doc.rm_cost_as_per)) {
|
||||
if (["Valuation Rate", "Last Purchase Rate"].includes(frm.doc.rm_cost_as_per)) {
|
||||
frm.set_value("plc_conversion_rate", 1.0);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -129,7 +129,7 @@ frappe.ui.form.on("Production Plan", {
|
||||
if (
|
||||
frm.doc.mr_items &&
|
||||
frm.doc.mr_items.length &&
|
||||
!in_list(["Material Requested", "Closed"], frm.doc.status)
|
||||
!["Material Requested", "Closed"].includes(frm.doc.status)
|
||||
) {
|
||||
frm.add_custom_button(
|
||||
__("Material Request"),
|
||||
|
||||
@@ -9,6 +9,8 @@ frappe.ui.form.on("Work Order", {
|
||||
"Job Card": "Create Job Card",
|
||||
};
|
||||
|
||||
frm.ignore_doctypes_on_cancel_all = ["Serial and Batch Bundle"];
|
||||
|
||||
// Set query for warehouses
|
||||
frm.set_query("wip_warehouse", function () {
|
||||
return {
|
||||
@@ -194,7 +196,7 @@ frappe.ui.form.on("Work Order", {
|
||||
},
|
||||
|
||||
add_custom_button_to_return_components: function (frm) {
|
||||
if (frm.doc.docstatus === 1 && in_list(["Closed", "Completed"], frm.doc.status)) {
|
||||
if (frm.doc.docstatus === 1 && ["Closed", "Completed"].includes(frm.doc.status)) {
|
||||
let non_consumed_items = frm.doc.required_items.filter((d) => {
|
||||
return flt(d.consumed_qty) < flt(d.transferred_qty - d.returned_qty);
|
||||
});
|
||||
@@ -594,7 +596,7 @@ erpnext.work_order = {
|
||||
);
|
||||
}
|
||||
|
||||
if (doc.docstatus === 1 && !in_list(["Closed", "Completed"], doc.status)) {
|
||||
if (doc.docstatus === 1 && !["Closed", "Completed"].includes(doc.status)) {
|
||||
if (doc.status != "Stopped" && doc.status != "Completed") {
|
||||
frm.add_custom_button(
|
||||
__("Stop"),
|
||||
|
||||
@@ -356,7 +356,7 @@ erpnext.patches.v14_0.update_total_asset_cost_field
|
||||
erpnext.patches.v15_0.create_advance_payment_status
|
||||
erpnext.patches.v15_0.allow_on_submit_dimensions_for_repostable_doctypes
|
||||
erpnext.patches.v14_0.create_accounting_dimensions_in_reconciliation_tool
|
||||
erpnext.patches.v14_0.update_flag_for_return_invoices
|
||||
erpnext.patches.v14_0.update_flag_for_return_invoices #2024-03-22
|
||||
# below migration patch should always run last
|
||||
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
|
||||
erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20
|
||||
|
||||
@@ -12,6 +12,10 @@ def execute():
|
||||
creation_date = "2024-01-25"
|
||||
|
||||
si = qb.DocType("Sales Invoice")
|
||||
|
||||
# unset flag, as migration would have set it for all records, as the field was introduced with default '1'
|
||||
qb.update(si).set(si.update_outstanding_for_self, False).run()
|
||||
|
||||
if cr_notes := (
|
||||
qb.from_(si)
|
||||
.select(si.name)
|
||||
@@ -37,6 +41,10 @@ def execute():
|
||||
).run()
|
||||
|
||||
pi = qb.DocType("Purchase Invoice")
|
||||
|
||||
# unset flag, as migration would have set it for all records, as the field was introduced with default '1'
|
||||
qb.update(pi).set(pi.update_outstanding_for_self, False).run()
|
||||
|
||||
if dr_notes := (
|
||||
qb.from_(pi)
|
||||
.select(pi.name)
|
||||
|
||||
@@ -107,7 +107,7 @@ class BOMConfigurator {
|
||||
this.frm?.doc.docstatus === 0
|
||||
? [
|
||||
{
|
||||
label: __(frappe.utils.icon("edit", "sm") + " Qty"),
|
||||
label: `${frappe.utils.icon("edit", "sm")} ${__("Qty")}`,
|
||||
click: function (node) {
|
||||
let view = frappe.views.trees["BOM Configurator"];
|
||||
view.events.edit_qty(node, view);
|
||||
@@ -115,7 +115,7 @@ class BOMConfigurator {
|
||||
btnClass: "hidden-xs",
|
||||
},
|
||||
{
|
||||
label: __(frappe.utils.icon("add", "sm") + " Raw Material"),
|
||||
label: `${frappe.utils.icon("add", "sm")} ${__("Raw Material")}`,
|
||||
click: function (node) {
|
||||
let view = frappe.views.trees["BOM Configurator"];
|
||||
view.events.add_item(node, view);
|
||||
@@ -126,7 +126,7 @@ class BOMConfigurator {
|
||||
btnClass: "hidden-xs",
|
||||
},
|
||||
{
|
||||
label: __(frappe.utils.icon("add", "sm") + " Sub Assembly"),
|
||||
label: `${frappe.utils.icon("add", "sm")} ${__("Sub Assembly")}`,
|
||||
click: function (node) {
|
||||
let view = frappe.views.trees["BOM Configurator"];
|
||||
view.events.add_sub_assembly(node, view);
|
||||
@@ -156,7 +156,7 @@ class BOMConfigurator {
|
||||
btnClass: "hidden-xs expand-all-btn",
|
||||
},
|
||||
{
|
||||
label: __(frappe.utils.icon("move", "sm") + " Sub Assembly"),
|
||||
label: `${frappe.utils.icon("move", "sm")} ${__("Sub Assembly")}`,
|
||||
click: function (node) {
|
||||
let view = frappe.views.trees["BOM Configurator"];
|
||||
view.events.convert_to_sub_assembly(node, view);
|
||||
@@ -167,7 +167,7 @@ class BOMConfigurator {
|
||||
btnClass: "hidden-xs",
|
||||
},
|
||||
{
|
||||
label: __(frappe.utils.icon("delete", "sm") + __(" Item")),
|
||||
label: `${frappe.utils.icon("delete", "sm")} ${__("Item")}`,
|
||||
click: function (node) {
|
||||
let view = frappe.views.trees["BOM Configurator"];
|
||||
view.events.delete_node(node, view);
|
||||
|
||||
@@ -20,7 +20,7 @@ frappe.ui.form.on("Communication", {
|
||||
);
|
||||
}
|
||||
|
||||
if (!in_list(["Lead", "Opportunity"], frm.doc.reference_doctype)) {
|
||||
if (!["Lead", "Opportunity"].includes(frm.doc.reference_doctype)) {
|
||||
frm.add_custom_button(
|
||||
__("Lead"),
|
||||
() => {
|
||||
|
||||
@@ -11,7 +11,7 @@ erpnext.accounts.taxes = {
|
||||
setup: function(frm) {
|
||||
// set conditional display for rate column in taxes
|
||||
$(frm.wrapper).on('grid-row-render', function(e, grid_row) {
|
||||
if(in_list(['Sales Taxes and Charges', 'Purchase Taxes and Charges'], grid_row.doc.doctype)) {
|
||||
if(['Sales Taxes and Charges', 'Purchase Taxes and Charges'].includes(grid_row.doc.doctype)) {
|
||||
me.set_conditional_mandatory_rate_or_amount(grid_row);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -129,7 +129,7 @@ erpnext.buying = {
|
||||
}
|
||||
|
||||
toggle_subcontracting_fields() {
|
||||
if (in_list(['Purchase Receipt', 'Purchase Invoice'], this.frm.doc.doctype)) {
|
||||
if (['Purchase Receipt', 'Purchase Invoice'].includes(this.frm.doc.doctype)) {
|
||||
this.frm.fields_dict.supplied_items.grid.update_docfield_property('consumed_qty',
|
||||
'read_only', this.frm.doc.__onload && this.frm.doc.__onload.backflush_based_on === 'BOM');
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
apply_pricing_rule_on_item(item) {
|
||||
let effective_item_rate = item.price_list_rate;
|
||||
let item_rate = item.rate;
|
||||
if (in_list(["Sales Order", "Quotation"], item.parenttype) && item.blanket_order_rate) {
|
||||
if (["Sales Order", "Quotation"].includes(item.parenttype) && item.blanket_order_rate) {
|
||||
effective_item_rate = item.blanket_order_rate;
|
||||
}
|
||||
if (item.margin_type == "Percentage") {
|
||||
@@ -26,7 +26,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
item.discount_amount = flt(item.rate_with_margin) * flt(item.discount_percentage) / 100;
|
||||
}
|
||||
|
||||
if (item.discount_amount) {
|
||||
if (item.discount_amount > 0) {
|
||||
item_rate = flt((item.rate_with_margin) - (item.discount_amount), precision('rate', item));
|
||||
item.discount_percentage = 100 * flt(item.discount_amount) / flt(item.rate_with_margin);
|
||||
}
|
||||
@@ -52,7 +52,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
|
||||
// Advance calculation applicable to Sales/Purchase Invoice
|
||||
if (
|
||||
in_list(["Sales Invoice", "POS Invoice", "Purchase Invoice"], this.frm.doc.doctype)
|
||||
["Sales Invoice", "POS Invoice", "Purchase Invoice"].includes(this.frm.doc.doctype)
|
||||
&& this.frm.doc.docstatus < 2
|
||||
&& !this.frm.doc.is_return
|
||||
) {
|
||||
@@ -60,7 +60,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
}
|
||||
|
||||
if (
|
||||
in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype)
|
||||
["Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype)
|
||||
&& this.frm.doc.is_pos
|
||||
&& this.frm.doc.is_return
|
||||
) {
|
||||
@@ -69,7 +69,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
}
|
||||
|
||||
// Sales person's commission
|
||||
if (in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"], this.frm.doc.doctype)) {
|
||||
if (["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"].includes(this.frm.doc.doctype)) {
|
||||
this.calculate_commission();
|
||||
this.calculate_contribution();
|
||||
}
|
||||
@@ -575,7 +575,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
? this.frm.doc["taxes"][tax_count - 1].total + flt(this.frm.doc.grand_total_diff)
|
||||
: this.frm.doc.net_total);
|
||||
|
||||
if(in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"], this.frm.doc.doctype)) {
|
||||
if(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype)) {
|
||||
this.frm.doc.base_grand_total = (this.frm.doc.total_taxes_and_charges) ?
|
||||
flt(this.frm.doc.grand_total * this.frm.doc.conversion_rate) : this.frm.doc.base_net_total;
|
||||
} else {
|
||||
@@ -583,7 +583,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
this.frm.doc.taxes_and_charges_added = this.frm.doc.taxes_and_charges_deducted = 0.0;
|
||||
if(tax_count) {
|
||||
$.each(this.frm.doc["taxes"] || [], function(i, tax) {
|
||||
if (in_list(["Valuation and Total", "Total"], tax.category)) {
|
||||
if (["Valuation and Total", "Total"].includes(tax.category)) {
|
||||
if(tax.add_deduct_tax == "Add") {
|
||||
me.frm.doc.taxes_and_charges_added += flt(tax.tax_amount_after_discount_amount);
|
||||
} else {
|
||||
@@ -729,7 +729,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
var actual_taxes_dict = {};
|
||||
|
||||
$.each(this.frm.doc["taxes"] || [], function(i, tax) {
|
||||
if (in_list(["Actual", "On Item Quantity"], tax.charge_type)) {
|
||||
if (["Actual", "On Item Quantity"].includes(tax.charge_type)) {
|
||||
var tax_amount = (tax.category == "Valuation") ? 0.0 : tax.tax_amount;
|
||||
tax_amount *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0;
|
||||
actual_taxes_dict[tax.idx] = tax_amount;
|
||||
@@ -774,7 +774,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
// NOTE:
|
||||
// paid_amount and write_off_amount is only for POS/Loyalty Point Redemption Invoice
|
||||
// total_advance is only for non POS Invoice
|
||||
if(in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype) && this.frm.doc.is_return){
|
||||
if(["Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype) && this.frm.doc.is_return){
|
||||
this.calculate_paid_amount();
|
||||
}
|
||||
|
||||
@@ -782,7 +782,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
|
||||
frappe.model.round_floats_in(this.frm.doc, ["grand_total", "total_advance", "write_off_amount"]);
|
||||
|
||||
if(in_list(["Sales Invoice", "POS Invoice", "Purchase Invoice"], this.frm.doc.doctype)) {
|
||||
if(["Sales Invoice", "POS Invoice", "Purchase Invoice"].includes(this.frm.doc.doctype)) {
|
||||
let grand_total = this.frm.doc.rounded_total || this.frm.doc.grand_total;
|
||||
let base_grand_total = this.frm.doc.base_rounded_total || this.frm.doc.base_grand_total;
|
||||
|
||||
@@ -805,7 +805,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
this.frm.refresh_field("base_paid_amount");
|
||||
}
|
||||
|
||||
if(in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype)) {
|
||||
if(["Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype)) {
|
||||
let total_amount_for_payment = (this.frm.doc.redeem_loyalty_points && this.frm.doc.loyalty_amount)
|
||||
? flt(total_amount_to_pay - this.frm.doc.loyalty_amount, precision("base_grand_total"))
|
||||
: total_amount_to_pay;
|
||||
@@ -909,7 +909,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
calculate_change_amount(){
|
||||
this.frm.doc.change_amount = 0.0;
|
||||
this.frm.doc.base_change_amount = 0.0;
|
||||
if(in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype)
|
||||
if(["Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype)
|
||||
&& this.frm.doc.paid_amount > this.frm.doc.grand_total && !this.frm.doc.is_return) {
|
||||
|
||||
var payment_types = $.map(this.frm.doc.payments, function(d) { return d.type; });
|
||||
|
||||
@@ -315,7 +315,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
}
|
||||
|
||||
setup_quality_inspection() {
|
||||
if(!in_list(["Delivery Note", "Sales Invoice", "Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"], this.frm.doc.doctype)) {
|
||||
if(!["Delivery Note", "Sales Invoice", "Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"].includes(this.frm.doc.doctype)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -327,7 +327,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
this.frm.page.set_inner_btn_group_as_primary(__('Create'));
|
||||
}
|
||||
|
||||
const inspection_type = in_list(["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"], this.frm.doc.doctype)
|
||||
const inspection_type = ["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"].includes(this.frm.doc.doctype)
|
||||
? "Incoming" : "Outgoing";
|
||||
|
||||
let quality_inspection_field = this.frm.get_docfield("items", "quality_inspection");
|
||||
@@ -359,7 +359,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
|
||||
make_payment_request() {
|
||||
let me = this;
|
||||
const payment_request_type = (in_list(['Sales Order', 'Sales Invoice'], this.frm.doc.doctype))
|
||||
const payment_request_type = (['Sales Order', 'Sales Invoice'].includes(this.frm.doc.doctype))
|
||||
? "Inward" : "Outward";
|
||||
|
||||
frappe.call({
|
||||
@@ -474,7 +474,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
setup_sms() {
|
||||
var me = this;
|
||||
let blacklist = ['Purchase Invoice', 'BOM'];
|
||||
if(this.frm.doc.docstatus===1 && !in_list(["Lost", "Stopped", "Closed"], this.frm.doc.status)
|
||||
if(this.frm.doc.docstatus===1 && !["Lost", "Stopped", "Closed"].includes(this.frm.doc.status)
|
||||
&& !blacklist.includes(this.frm.doctype)) {
|
||||
this.frm.page.add_menu_item(__('Send SMS'), function() { me.send_sms(); });
|
||||
}
|
||||
@@ -760,7 +760,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
}
|
||||
|
||||
on_submit() {
|
||||
if (in_list(["Purchase Invoice", "Sales Invoice"], this.frm.doc.doctype)
|
||||
if (["Purchase Invoice", "Sales Invoice"].includes(this.frm.doc.doctype)
|
||||
&& !this.frm.doc.update_stock) {
|
||||
return;
|
||||
}
|
||||
@@ -864,7 +864,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
}
|
||||
|
||||
var set_party_account = function(set_pricing) {
|
||||
if (in_list(["Sales Invoice", "Purchase Invoice"], me.frm.doc.doctype)) {
|
||||
if (["Sales Invoice", "Purchase Invoice"].includes(me.frm.doc.doctype)) {
|
||||
if(me.frm.doc.doctype=="Sales Invoice") {
|
||||
var party_type = "Customer";
|
||||
var party_account_field = 'debit_to';
|
||||
@@ -899,7 +899,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
}
|
||||
|
||||
if (frappe.meta.get_docfield(this.frm.doctype, "shipping_address") &&
|
||||
in_list(['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'], this.frm.doctype)) {
|
||||
['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'].includes(this.frm.doctype)) {
|
||||
erpnext.utils.get_shipping_address(this.frm, function() {
|
||||
set_party_account(set_pricing);
|
||||
});
|
||||
@@ -1276,8 +1276,11 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
|
||||
calculate_stock_uom_rate(doc, cdt, cdn) {
|
||||
let item = frappe.get_doc(cdt, cdn);
|
||||
item.stock_uom_rate = flt(item.rate)/flt(item.conversion_factor);
|
||||
refresh_field("stock_uom_rate", item.name, item.parentfield);
|
||||
|
||||
if (item?.rate) {
|
||||
item.stock_uom_rate = flt(item.rate) / flt(item.conversion_factor);
|
||||
refresh_field("stock_uom_rate", item.name, item.parentfield);
|
||||
}
|
||||
}
|
||||
service_stop_date(frm, cdt, cdn) {
|
||||
var child = locals[cdt][cdn];
|
||||
@@ -1620,7 +1623,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
"doctype": me.frm.doc.doctype,
|
||||
"name": me.frm.doc.name,
|
||||
"is_return": cint(me.frm.doc.is_return),
|
||||
"update_stock": in_list(['Sales Invoice', 'Purchase Invoice'], me.frm.doc.doctype) ? cint(me.frm.doc.update_stock) : 0,
|
||||
"update_stock": ['Sales Invoice', 'Purchase Invoice'].includes(me.frm.doc.doctype) ? cint(me.frm.doc.update_stock) : 0,
|
||||
"conversion_factor": me.frm.doc.conversion_factor,
|
||||
"pos_profile": me.frm.doc.doctype == 'Sales Invoice' ? me.frm.doc.pos_profile : '',
|
||||
"coupon_code": me.frm.doc.coupon_code
|
||||
@@ -2264,9 +2267,9 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
}
|
||||
|
||||
get_method_for_payment() {
|
||||
var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry";
|
||||
if(cur_frm.doc.__onload && cur_frm.doc.__onload.make_payment_via_journal_entry){
|
||||
if(in_list(['Sales Invoice', 'Purchase Invoice'], cur_frm.doc.doctype)){
|
||||
let method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry";
|
||||
if(this.frm.doc.__onload && this.frm.doc.__onload.make_payment_via_journal_entry){
|
||||
if(['Sales Invoice', 'Purchase Invoice'].includes( this.frm.doc.doctype)){
|
||||
method = "erpnext.accounts.doctype.journal_entry.journal_entry.get_payment_entry_against_invoice";
|
||||
}else {
|
||||
method= "erpnext.accounts.doctype.journal_entry.journal_entry.get_payment_entry_against_order";
|
||||
@@ -2506,7 +2509,7 @@ erpnext.show_serial_batch_selector = function (frm, item_row, callback, on_close
|
||||
}
|
||||
|
||||
frappe.require("assets/erpnext/js/utils/serial_no_batch_selector.js", function() {
|
||||
if (in_list(["Sales Invoice", "Delivery Note"], frm.doc.doctype)) {
|
||||
if (["Sales Invoice", "Delivery Note"].includes(frm.doc.doctype)) {
|
||||
item_row.type_of_transaction = frm.doc.is_return ? "Inward" : "Outward";
|
||||
} else {
|
||||
item_row.type_of_transaction = frm.doc.is_return ? "Outward" : "Inward";
|
||||
|
||||
@@ -218,7 +218,7 @@ erpnext.payments = class payments extends erpnext.stock.StockController {
|
||||
|
||||
update_paid_amount(update_write_off) {
|
||||
var me = this;
|
||||
if (in_list(["change_amount", "write_off_amount"], this.idx)) {
|
||||
if (["change_amount", "write_off_amount"].includes(this.idx)) {
|
||||
var value = me.selected_mode.val();
|
||||
if (me.idx == "change_amount") {
|
||||
me.change_amount(value);
|
||||
|
||||
@@ -110,7 +110,7 @@ erpnext.timesheet.control_timer = function (frm, dialog, row, timestamp = 0) {
|
||||
|
||||
// Stop the timer and update the time logged by the timer on click of 'Complete' button
|
||||
$btn_complete.click(function () {
|
||||
var grid_row = cur_frm.fields_dict["time_logs"].grid.get_row(row.idx - 1);
|
||||
var grid_row = frm.fields_dict["time_logs"].grid.get_row(row.idx - 1);
|
||||
var args = dialog.get_values();
|
||||
grid_row.doc.completed = 1;
|
||||
grid_row.doc.activity_type = args.activity_type;
|
||||
|
||||
@@ -28,11 +28,11 @@ erpnext.SMSManager = function SMSManager(doc) {
|
||||
"Purchase Receipt": "Items has been received against purchase receipt: " + doc.name,
|
||||
};
|
||||
|
||||
if (in_list(["Sales Order", "Delivery Note", "Sales Invoice"], doc.doctype))
|
||||
if (["Sales Order", "Delivery Note", "Sales Invoice"].includes(doc.doctype))
|
||||
this.show(doc.contact_person, "Customer", doc.customer, "", default_msg[doc.doctype]);
|
||||
else if (doc.doctype === "Quotation")
|
||||
this.show(doc.contact_person, "Customer", doc.party_name, "", default_msg[doc.doctype]);
|
||||
else if (in_list(["Purchase Order", "Purchase Receipt"], doc.doctype))
|
||||
else if (["Purchase Order", "Purchase Receipt"].includes(doc.doctype))
|
||||
this.show(doc.contact_person, "Supplier", doc.supplier, "", default_msg[doc.doctype]);
|
||||
else if (doc.doctype == "Lead") this.show("", "", "", doc.mobile_no, default_msg[doc.doctype]);
|
||||
else if (doc.doctype == "Opportunity")
|
||||
|
||||
@@ -14,10 +14,10 @@ erpnext.utils.get_party_details = function (frm, method, args, callback) {
|
||||
if (!args) {
|
||||
if (
|
||||
(frm.doctype != "Purchase Order" && frm.doc.customer) ||
|
||||
(frm.doc.party_name && in_list(["Quotation", "Opportunity"], frm.doc.doctype))
|
||||
(frm.doc.party_name && ["Quotation", "Opportunity"].includes(frm.doc.doctype))
|
||||
) {
|
||||
let party_type = "Customer";
|
||||
if (frm.doc.quotation_to && in_list(["Lead", "Prospect"], frm.doc.quotation_to)) {
|
||||
if (frm.doc.quotation_to && ["Lead", "Prospect"].includes(frm.doc.quotation_to)) {
|
||||
party_type = frm.doc.quotation_to;
|
||||
}
|
||||
|
||||
|
||||
@@ -303,7 +303,7 @@ erpnext.sales_common = {
|
||||
if ((doc.packed_items || []).length) {
|
||||
$(this.frm.fields_dict.packing_list.row.wrapper).toggle(true);
|
||||
|
||||
if (in_list(["Delivery Note", "Sales Invoice"], doc.doctype)) {
|
||||
if (["Delivery Note", "Sales Invoice"].includes(doc.doctype)) {
|
||||
var help_msg =
|
||||
"<div class='alert alert-warning'>" +
|
||||
__(
|
||||
@@ -315,7 +315,7 @@ erpnext.sales_common = {
|
||||
}
|
||||
} else {
|
||||
$(this.frm.fields_dict.packing_list.row.wrapper).toggle(false);
|
||||
if (in_list(["Delivery Note", "Sales Invoice"], doc.doctype)) {
|
||||
if (["Delivery Note", "Sales Invoice"].includes(doc.doctype)) {
|
||||
frappe.meta.get_docfield(doc.doctype, "product_bundle_help", doc.name).options = "";
|
||||
}
|
||||
}
|
||||
@@ -416,7 +416,7 @@ erpnext.sales_common = {
|
||||
|
||||
project() {
|
||||
let me = this;
|
||||
if (in_list(["Delivery Note", "Sales Invoice", "Sales Order"], this.frm.doc.doctype)) {
|
||||
if (["Delivery Note", "Sales Invoice", "Sales Order"].includes(this.frm.doc.doctype)) {
|
||||
if (this.frm.doc.project) {
|
||||
frappe.call({
|
||||
method: "erpnext.projects.doctype.project.project.get_cost_center_name",
|
||||
|
||||
@@ -548,3 +548,7 @@ body[data-route="pos"] {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.frappe-control[data-fieldname="other_charges_calculation"] .ql-editor {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ frappe.ui.form.on("Import Supplier Invoice", {
|
||||
},
|
||||
|
||||
toggle_read_only_fields: function (frm) {
|
||||
if (in_list(["File Import Completed", "Processing File Data"], frm.doc.status)) {
|
||||
if (["File Import Completed", "Processing File Data"].includes(frm.doc.status)) {
|
||||
cur_frm.set_read_only();
|
||||
cur_frm.refresh_fields();
|
||||
frm.set_df_property("import_invoices", "hidden", 1);
|
||||
|
||||
@@ -557,7 +557,7 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "other_charges_calculation",
|
||||
"fieldtype": "Long Text",
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "Taxes and Charges Calculation",
|
||||
"no_copy": 1,
|
||||
"oldfieldtype": "HTML",
|
||||
@@ -1073,7 +1073,7 @@
|
||||
"idx": 82,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-06-03 16:21:04.980033",
|
||||
"modified": "2024-03-20 16:04:21.567847",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Quotation",
|
||||
|
||||
@@ -78,7 +78,7 @@ class Quotation(SellingController):
|
||||
opportunity: DF.Link | None
|
||||
order_lost_reason: DF.SmallText | None
|
||||
order_type: DF.Literal["", "Sales", "Maintenance", "Shopping Cart"]
|
||||
other_charges_calculation: DF.LongText | None
|
||||
other_charges_calculation: DF.TextEditor | None
|
||||
packed_items: DF.Table[PackedItem]
|
||||
party_name: DF.DynamicLink | None
|
||||
payment_schedule: DF.Table[PaymentSchedule]
|
||||
|
||||
@@ -777,7 +777,7 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "other_charges_calculation",
|
||||
"fieldtype": "Long Text",
|
||||
"fieldtype": "Text Editor",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Taxes and Charges Calculation",
|
||||
@@ -1657,7 +1657,7 @@
|
||||
"idx": 105,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-10-18 12:41:54.813462",
|
||||
"modified": "2024-03-20 16:04:43.627183",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Sales Order",
|
||||
@@ -1735,4 +1735,4 @@
|
||||
"title_field": "customer_name",
|
||||
"track_changes": 1,
|
||||
"track_seen": 1
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,7 @@ class SalesOrder(SellingController):
|
||||
additional_discount_percentage: DF.Float
|
||||
address_display: DF.SmallText | None
|
||||
advance_paid: DF.Currency
|
||||
advance_payment_status: DF.Literal["Not Requested", "Requested", "Partially Paid", "Fully Paid"]
|
||||
amended_from: DF.Link | None
|
||||
amount_eligible_for_commission: DF.Currency
|
||||
apply_discount_on: DF.Literal["", "Grand Total", "Net Total"]
|
||||
@@ -122,7 +123,7 @@ class SalesOrder(SellingController):
|
||||
naming_series: DF.Literal["SAL-ORD-.YYYY.-"]
|
||||
net_total: DF.Currency
|
||||
order_type: DF.Literal["", "Sales", "Maintenance", "Shopping Cart"]
|
||||
other_charges_calculation: DF.LongText | None
|
||||
other_charges_calculation: DF.TextEditor | None
|
||||
packed_items: DF.Table[PackedItem]
|
||||
party_account_currency: DF.Link | None
|
||||
payment_schedule: DF.Table[PaymentSchedule]
|
||||
@@ -155,6 +156,7 @@ class SalesOrder(SellingController):
|
||||
"",
|
||||
"Draft",
|
||||
"On Hold",
|
||||
"To Pay",
|
||||
"To Deliver and Bill",
|
||||
"To Bill",
|
||||
"To Deliver",
|
||||
@@ -753,6 +755,13 @@ def get_list_context(context=None):
|
||||
return list_context
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def is_enable_cutoff_date_on_bulk_delivery_note_creation():
|
||||
return frappe.db.get_single_value(
|
||||
"Selling Settings", "enable_cutoff_date_on_bulk_delivery_note_creation"
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def close_or_unclose_sales_orders(names, status):
|
||||
if not frappe.has_permission("Sales Order", "write"):
|
||||
|
||||
@@ -68,10 +68,10 @@ frappe.listview_settings["Sales Order"] = {
|
||||
});
|
||||
|
||||
listview.page.add_action_item(__("Delivery Note"), () => {
|
||||
frappe.db
|
||||
.get_single_value("Selling Settings", "enable_cutoff_date_on_bulk_delivery_note_creation")
|
||||
.then((value) => {
|
||||
if (value) {
|
||||
frappe.call({
|
||||
method: "erpnext.selling.doctype.sales_order.sales_order.is_enable_cutoff_date_on_bulk_delivery_note_creation",
|
||||
callback: (r) => {
|
||||
if (r.message) {
|
||||
var dialog = new frappe.ui.Dialog({
|
||||
title: __("Select Items up to Delivery Date"),
|
||||
fields: [
|
||||
@@ -98,7 +98,8 @@ frappe.listview_settings["Sales Order"] = {
|
||||
} else {
|
||||
erpnext.bulk_transaction_processing.create(listview, "Sales Order", "Delivery Note");
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
listview.page.add_action_item(__("Advance Payment"), () => {
|
||||
|
||||
@@ -2141,6 +2141,40 @@ class TestSalesOrder(FrappeTestCase):
|
||||
dn.submit()
|
||||
dn.reload()
|
||||
|
||||
def test_auto_update_price_list(self):
|
||||
item = make_item(
|
||||
"_Test Auto Update Price List Item",
|
||||
)
|
||||
|
||||
frappe.db.set_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing", 1)
|
||||
so = make_sales_order(
|
||||
item_code=item.name, currency="USD", qty=1, rate=100, price_list_rate=100, do_not_submit=True
|
||||
)
|
||||
so.save()
|
||||
|
||||
item_price = frappe.db.get_value("Item Price", {"item_code": item.name}, "price_list_rate")
|
||||
self.assertEqual(item_price, 100)
|
||||
|
||||
so = make_sales_order(
|
||||
item_code=item.name, currency="USD", qty=1, rate=200, price_list_rate=100, do_not_submit=True
|
||||
)
|
||||
so.save()
|
||||
|
||||
item_price = frappe.db.get_value("Item Price", {"item_code": item.name}, "price_list_rate")
|
||||
self.assertEqual(item_price, 100)
|
||||
|
||||
frappe.db.set_single_value("Stock Settings", "update_existing_price_list_rate", 1)
|
||||
so = make_sales_order(
|
||||
item_code=item.name, currency="USD", qty=1, rate=200, price_list_rate=200, do_not_submit=True
|
||||
)
|
||||
so.save()
|
||||
|
||||
item_price = frappe.db.get_value("Item Price", {"item_code": item.name}, "price_list_rate")
|
||||
self.assertEqual(item_price, 200)
|
||||
|
||||
frappe.db.set_single_value("Stock Settings", "update_existing_price_list_rate", 0)
|
||||
frappe.db.set_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing", 0)
|
||||
|
||||
|
||||
def automatically_fetch_payment_terms(enable=1):
|
||||
accounts_settings = frappe.get_doc("Accounts Settings")
|
||||
|
||||
@@ -834,7 +834,8 @@
|
||||
"label": "Purchase Order",
|
||||
"options": "Purchase Order",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_89",
|
||||
@@ -909,7 +910,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-01-25 14:24:00.330219",
|
||||
"modified": "2024-03-21 18:15:56.625005",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Sales Order Item",
|
||||
|
||||
@@ -73,7 +73,7 @@ erpnext.PointOfSale.PastOrderSummary = class {
|
||||
const { status } = doc;
|
||||
let indicator_color = "";
|
||||
|
||||
in_list(["Paid", "Consolidated"], status) && (indicator_color = "green");
|
||||
["Paid", "Consolidated"].includes(status) && (indicator_color = "green");
|
||||
status === "Draft" && (indicator_color = "red");
|
||||
status === "Return" && (indicator_color = "grey");
|
||||
|
||||
|
||||
@@ -181,8 +181,10 @@ def get_random_date(start_date, start_range, end_range):
|
||||
def create_transaction_deletion_record(company):
|
||||
transaction_deletion_record = frappe.new_doc("Transaction Deletion Record")
|
||||
transaction_deletion_record.company = company
|
||||
transaction_deletion_record.process_in_single_transaction = True
|
||||
transaction_deletion_record.save(ignore_permissions=True)
|
||||
transaction_deletion_record.submit()
|
||||
transaction_deletion_record.start_deletion_tasks()
|
||||
|
||||
|
||||
def clear_masters():
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"item_group": "Demo Item Group",
|
||||
"item_code": "SKU001",
|
||||
"item_name": "T-shirt",
|
||||
"valuation_rate": 400.0,
|
||||
"gst_hsn_code": "999512",
|
||||
"image": "https://images.pexels.com/photos/1484808/pexels-photo-1484808.jpeg"
|
||||
},
|
||||
@@ -11,6 +12,7 @@
|
||||
"doctype": "Item",
|
||||
"item_group": "Demo Item Group",
|
||||
"item_code": "SKU002",
|
||||
"valuation_rate": 300.0,
|
||||
"item_name": "Laptop",
|
||||
"gst_hsn_code": "999512",
|
||||
"image": "https://images.pexels.com/photos/3999538/pexels-photo-3999538.jpeg"
|
||||
@@ -19,6 +21,7 @@
|
||||
"doctype": "Item",
|
||||
"item_group": "Demo Item Group",
|
||||
"item_code": "SKU003",
|
||||
"valuation_rate": 523.0,
|
||||
"item_name": "Book",
|
||||
"gst_hsn_code": "999512",
|
||||
"image": "https://images.pexels.com/photos/2422178/pexels-photo-2422178.jpeg"
|
||||
@@ -27,6 +30,7 @@
|
||||
"doctype": "Item",
|
||||
"item_group": "Demo Item Group",
|
||||
"item_code": "SKU004",
|
||||
"valuation_rate": 725.0,
|
||||
"item_name": "Smartphone",
|
||||
"gst_hsn_code": "999512",
|
||||
"image": "https://images.pexels.com/photos/1647976/pexels-photo-1647976.jpeg"
|
||||
@@ -35,6 +39,7 @@
|
||||
"doctype": "Item",
|
||||
"item_group": "Demo Item Group",
|
||||
"item_code": "SKU005",
|
||||
"valuation_rate": 222.0,
|
||||
"item_name": "Sneakers",
|
||||
"gst_hsn_code": "999512",
|
||||
"image": "https://images.pexels.com/photos/1598505/pexels-photo-1598505.jpeg"
|
||||
@@ -43,6 +48,7 @@
|
||||
"doctype": "Item",
|
||||
"item_group": "Demo Item Group",
|
||||
"item_code": "SKU006",
|
||||
"valuation_rate": 420.0,
|
||||
"item_name": "Coffee Mug",
|
||||
"gst_hsn_code": "999512",
|
||||
"image": "https://images.pexels.com/photos/585753/pexels-photo-585753.jpeg"
|
||||
@@ -51,6 +57,7 @@
|
||||
"doctype": "Item",
|
||||
"item_group": "Demo Item Group",
|
||||
"item_code": "SKU007",
|
||||
"valuation_rate": 375.0,
|
||||
"item_name": "Television",
|
||||
"gst_hsn_code": "999512",
|
||||
"image": "https://images.pexels.com/photos/8059376/pexels-photo-8059376.jpeg"
|
||||
@@ -59,6 +66,7 @@
|
||||
"doctype": "Item",
|
||||
"item_group": "Demo Item Group",
|
||||
"item_code": "SKU008",
|
||||
"valuation_rate": 333.0,
|
||||
"item_name": "Backpack",
|
||||
"gst_hsn_code": "999512",
|
||||
"image": "https://images.pexels.com/photos/3731256/pexels-photo-3731256.jpeg"
|
||||
@@ -67,6 +75,7 @@
|
||||
"doctype": "Item",
|
||||
"item_group": "Demo Item Group",
|
||||
"item_code": "SKU009",
|
||||
"valuation_rate": 700.0,
|
||||
"item_name": "Headphones",
|
||||
"gst_hsn_code": "999512",
|
||||
"image": "https://images.pexels.com/photos/3587478/pexels-photo-3587478.jpeg"
|
||||
@@ -75,6 +84,7 @@
|
||||
"doctype": "Item",
|
||||
"item_group": "Demo Item Group",
|
||||
"item_code": "SKU010",
|
||||
"valuation_rate": 500.0,
|
||||
"item_name": "Camera",
|
||||
"gst_hsn_code": "999512",
|
||||
"image": "https://images.pexels.com/photos/51383/photo-camera-subject-photographer-51383.jpeg"
|
||||
|
||||
@@ -168,7 +168,7 @@ frappe.ui.form.on("Company", {
|
||||
|
||||
delete_company_transactions: function (frm) {
|
||||
frappe.call({
|
||||
method: "erpnext.setup.doctype.company.company.is_deletion_job_running",
|
||||
method: "erpnext.setup.doctype.transaction_deletion_record.transaction_deletion_record.is_deletion_doc_running",
|
||||
args: {
|
||||
company: frm.doc.name,
|
||||
},
|
||||
|
||||
@@ -12,7 +12,6 @@ from frappe.contacts.address_and_contact import load_address_and_contact
|
||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||
from frappe.desk.page.setup_wizard.setup_wizard import make_records
|
||||
from frappe.utils import cint, formatdate, get_link_to_form, get_timestamp, today
|
||||
from frappe.utils.background_jobs import get_job, is_job_enqueued
|
||||
from frappe.utils.nestedset import NestedSet, rebuild_tree
|
||||
|
||||
from erpnext.accounts.doctype.account.account import get_account_currency
|
||||
@@ -901,37 +900,21 @@ def get_default_company_address(name, sort_key="is_primary_address", existing_ad
|
||||
return None
|
||||
|
||||
|
||||
def generate_id_for_deletion_job(company):
|
||||
return "delete_company_transactions_" + company
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def is_deletion_job_running(company):
|
||||
job_id = generate_id_for_deletion_job(company)
|
||||
if is_job_enqueued(job_id):
|
||||
job_name = get_job(job_id).get_id() # job name will have site prefix
|
||||
frappe.throw(
|
||||
_("A Transaction Deletion Job: {0} is already running for {1}").format(
|
||||
frappe.bold(get_link_to_form("RQ Job", job_name)), frappe.bold(company)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_transaction_deletion_request(company):
|
||||
is_deletion_job_running(company)
|
||||
job_id = generate_id_for_deletion_job(company)
|
||||
from erpnext.setup.doctype.transaction_deletion_record.transaction_deletion_record import (
|
||||
is_deletion_doc_running,
|
||||
)
|
||||
|
||||
is_deletion_doc_running(company)
|
||||
|
||||
tdr = frappe.get_doc({"doctype": "Transaction Deletion Record", "company": company})
|
||||
tdr.insert()
|
||||
tdr.submit()
|
||||
tdr.start_deletion_tasks()
|
||||
|
||||
frappe.enqueue(
|
||||
"frappe.utils.background_jobs.run_doc_method",
|
||||
doctype=tdr.doctype,
|
||||
name=tdr.name,
|
||||
doc_method="submit",
|
||||
job_id=job_id,
|
||||
queue="long",
|
||||
enqueue_after_commit=True,
|
||||
frappe.msgprint(
|
||||
_("A Transaction Deletion Document: {0} is triggered for {0}").format(
|
||||
get_link_to_form("Transaction Deletion Record", tdr.name)
|
||||
),
|
||||
frappe.bold(company),
|
||||
)
|
||||
frappe.msgprint(_("A Transaction Deletion Job is triggered for {0}").format(frappe.bold(company)))
|
||||
|
||||
@@ -29,6 +29,7 @@ class TestTransactionDeletionRecord(FrappeTestCase):
|
||||
for i in range(5):
|
||||
create_task("Dunder Mifflin Paper Co")
|
||||
tdr = create_transaction_deletion_doc("Dunder Mifflin Paper Co")
|
||||
tdr.reload()
|
||||
for doctype in tdr.doctypes:
|
||||
if doctype.doctype_name == "Task":
|
||||
self.assertEqual(doctype.no_of_docs, 5)
|
||||
@@ -60,7 +61,9 @@ def create_company(company_name):
|
||||
def create_transaction_deletion_doc(company):
|
||||
tdr = frappe.get_doc({"doctype": "Transaction Deletion Record", "company": company})
|
||||
tdr.insert()
|
||||
tdr.process_in_single_transaction = True
|
||||
tdr.submit()
|
||||
tdr.start_deletion_tasks()
|
||||
return tdr
|
||||
|
||||
|
||||
|
||||
@@ -10,20 +10,24 @@ frappe.ui.form.on("Transaction Deletion Record", {
|
||||
callback: function (r) {
|
||||
doctypes_to_be_ignored_array = r.message;
|
||||
populate_doctypes_to_be_ignored(doctypes_to_be_ignored_array, frm);
|
||||
frm.fields_dict["doctypes_to_be_ignored"].grid.set_column_disp("no_of_docs", false);
|
||||
frm.refresh_field("doctypes_to_be_ignored");
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
frm.get_field("doctypes_to_be_ignored").grid.cannot_add_rows = true;
|
||||
frm.fields_dict["doctypes_to_be_ignored"].grid.set_column_disp("no_of_docs", false);
|
||||
frm.refresh_field("doctypes_to_be_ignored");
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
frm.fields_dict["doctypes_to_be_ignored"].grid.set_column_disp("no_of_docs", false);
|
||||
frm.refresh_field("doctypes_to_be_ignored");
|
||||
if (frm.doc.docstatus == 1 && ["Queued", "Failed"].find((x) => x == frm.doc.status)) {
|
||||
let execute_btn = frm.doc.status == "Queued" ? __("Start Deletion") : __("Retry");
|
||||
|
||||
frm.add_custom_button(execute_btn, () => {
|
||||
// Entry point for chain of events
|
||||
frm.call({
|
||||
method: "start_deletion_tasks",
|
||||
doc: frm.doc,
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -7,10 +7,21 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"company",
|
||||
"section_break_qpwb",
|
||||
"status",
|
||||
"error_log",
|
||||
"tasks_section",
|
||||
"delete_bin_data",
|
||||
"delete_leads_and_addresses",
|
||||
"reset_company_default_values",
|
||||
"clear_notifications",
|
||||
"initialize_doctypes_table",
|
||||
"delete_transactions",
|
||||
"section_break_tbej",
|
||||
"doctypes",
|
||||
"doctypes_to_be_ignored",
|
||||
"amended_from",
|
||||
"status"
|
||||
"process_in_single_transaction"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -25,14 +36,16 @@
|
||||
"fieldname": "doctypes",
|
||||
"fieldtype": "Table",
|
||||
"label": "Summary",
|
||||
"options": "Transaction Deletion Record Item",
|
||||
"no_copy": 1,
|
||||
"options": "Transaction Deletion Record Details",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "doctypes_to_be_ignored",
|
||||
"fieldtype": "Table",
|
||||
"label": "Excluded DocTypes",
|
||||
"options": "Transaction Deletion Record Item"
|
||||
"options": "Transaction Deletion Record Item",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
@@ -46,18 +59,96 @@
|
||||
{
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 1,
|
||||
"label": "Status",
|
||||
"options": "Draft\nCompleted"
|
||||
"no_copy": 1,
|
||||
"options": "Queued\nRunning\nFailed\nCompleted\nCancelled",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_tbej",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "tasks_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Tasks"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "delete_bin_data",
|
||||
"fieldtype": "Check",
|
||||
"label": "Delete Bins",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "delete_leads_and_addresses",
|
||||
"fieldtype": "Check",
|
||||
"label": "Delete Leads and Addresses",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "clear_notifications",
|
||||
"fieldtype": "Check",
|
||||
"label": "Clear Notifications",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "reset_company_default_values",
|
||||
"fieldtype": "Check",
|
||||
"label": "Reset Company Default Values",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "delete_transactions",
|
||||
"fieldtype": "Check",
|
||||
"label": "Delete Transactions",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "initialize_doctypes_table",
|
||||
"fieldtype": "Check",
|
||||
"label": "Initialize Summary Table",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.error_log",
|
||||
"fieldname": "error_log",
|
||||
"fieldtype": "Long Text",
|
||||
"label": "Error Log"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_qpwb",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "process_in_single_transaction",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Process in Single Transaction",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-08-04 20:15:59.071493",
|
||||
"modified": "2024-03-21 10:29:19.456413",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Transaction Deletion Record",
|
||||
"naming_rule": "Expression (old style)",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@@ -76,5 +167,6 @@
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
import frappe
|
||||
from frappe import _, qb
|
||||
from frappe.desk.notifications import clear_notifications
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cint, create_batch
|
||||
from frappe.utils import cint, comma_and, create_batch, get_link_to_form
|
||||
from frappe.utils.background_jobs import get_job, is_job_enqueued
|
||||
|
||||
|
||||
class TransactionDeletionRecord(Document):
|
||||
@@ -18,20 +20,42 @@ class TransactionDeletionRecord(Document):
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
from erpnext.accounts.doctype.transaction_deletion_record_details.transaction_deletion_record_details import (
|
||||
TransactionDeletionRecordDetails,
|
||||
)
|
||||
from erpnext.setup.doctype.transaction_deletion_record_item.transaction_deletion_record_item import (
|
||||
TransactionDeletionRecordItem,
|
||||
)
|
||||
|
||||
amended_from: DF.Link | None
|
||||
clear_notifications: DF.Check
|
||||
company: DF.Link
|
||||
doctypes: DF.Table[TransactionDeletionRecordItem]
|
||||
delete_bin_data: DF.Check
|
||||
delete_leads_and_addresses: DF.Check
|
||||
delete_transactions: DF.Check
|
||||
doctypes: DF.Table[TransactionDeletionRecordDetails]
|
||||
doctypes_to_be_ignored: DF.Table[TransactionDeletionRecordItem]
|
||||
status: DF.Literal["Draft", "Completed"]
|
||||
error_log: DF.LongText | None
|
||||
initialize_doctypes_table: DF.Check
|
||||
process_in_single_transaction: DF.Check
|
||||
reset_company_default_values: DF.Check
|
||||
status: DF.Literal["Queued", "Running", "Failed", "Completed", "Cancelled"]
|
||||
# end: auto-generated types
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(TransactionDeletionRecord, self).__init__(*args, **kwargs)
|
||||
self.batch_size = 5000
|
||||
# Tasks are listed by their execution order
|
||||
self.task_to_internal_method_map = OrderedDict(
|
||||
{
|
||||
"Delete Bins": "delete_bins",
|
||||
"Delete Leads and Addresses": "delete_lead_addresses",
|
||||
"Reset Company Values": "reset_company_values",
|
||||
"Clear Notifications": "delete_notifications",
|
||||
"Initialize Summary Table": "initialize_doctypes_to_be_deleted_table",
|
||||
"Delete Transactions": "delete_company_transactions",
|
||||
}
|
||||
)
|
||||
|
||||
def validate(self):
|
||||
frappe.only_for("System Manager")
|
||||
@@ -48,104 +72,266 @@ class TransactionDeletionRecord(Document):
|
||||
title=_("Not Allowed"),
|
||||
)
|
||||
|
||||
def generate_job_name_for_task(self, task=None):
|
||||
method = self.task_to_internal_method_map[task]
|
||||
return f"{self.name}_{method}"
|
||||
|
||||
def generate_job_name_for_next_tasks(self, task=None):
|
||||
job_names = []
|
||||
current_task_idx = list(self.task_to_internal_method_map).index(task)
|
||||
for idx, task in enumerate(self.task_to_internal_method_map.keys(), 0):
|
||||
# generate job_name for next tasks
|
||||
if idx > current_task_idx:
|
||||
job_names.append(self.generate_job_name_for_task(task))
|
||||
return job_names
|
||||
|
||||
def generate_job_name_for_all_tasks(self):
|
||||
job_names = []
|
||||
for task in self.task_to_internal_method_map.keys():
|
||||
job_names.append(self.generate_job_name_for_task(task))
|
||||
return job_names
|
||||
|
||||
def before_submit(self):
|
||||
if queued_docs := frappe.db.get_all(
|
||||
"Transaction Deletion Record",
|
||||
filters={"company": self.company, "status": ("in", ["Running", "Queued"]), "docstatus": 1},
|
||||
pluck="name",
|
||||
):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Cannot enqueue multi docs for one company. {0} is already queued/running for company: {1}"
|
||||
).format(
|
||||
comma_and([get_link_to_form("Transaction Deletion Record", x) for x in queued_docs]),
|
||||
frappe.bold(self.company),
|
||||
)
|
||||
)
|
||||
|
||||
if not self.doctypes_to_be_ignored:
|
||||
self.populate_doctypes_to_be_ignored_table()
|
||||
|
||||
self.delete_bins()
|
||||
self.delete_lead_addresses()
|
||||
self.reset_company_values()
|
||||
clear_notifications()
|
||||
self.delete_company_transactions()
|
||||
def reset_task_flags(self):
|
||||
self.clear_notifications = 0
|
||||
self.delete_bin_data = 0
|
||||
self.delete_leads_and_addresses = 0
|
||||
self.delete_transactions = 0
|
||||
self.initialize_doctypes_table = 0
|
||||
self.reset_company_default_values = 0
|
||||
|
||||
def before_save(self):
|
||||
self.status = ""
|
||||
self.doctypes.clear()
|
||||
self.reset_task_flags()
|
||||
|
||||
def on_submit(self):
|
||||
self.db_set("status", "Queued")
|
||||
|
||||
def on_cancel(self):
|
||||
self.db_set("status", "Cancelled")
|
||||
|
||||
def enqueue_task(self, task: str | None = None):
|
||||
if task and task in self.task_to_internal_method_map:
|
||||
# make sure that none of next tasks are already running
|
||||
job_names = self.generate_job_name_for_next_tasks(task=task)
|
||||
self.validate_running_task_for_doc(job_names=job_names)
|
||||
|
||||
# Generate Job Id to uniquely identify each task for this document
|
||||
job_id = self.generate_job_name_for_task(task)
|
||||
|
||||
if self.process_in_single_transaction:
|
||||
self.execute_task(task_to_execute=task)
|
||||
else:
|
||||
frappe.enqueue(
|
||||
"frappe.utils.background_jobs.run_doc_method",
|
||||
doctype=self.doctype,
|
||||
name=self.name,
|
||||
doc_method="execute_task",
|
||||
job_id=job_id,
|
||||
queue="long",
|
||||
enqueue_after_commit=True,
|
||||
task_to_execute=task,
|
||||
)
|
||||
|
||||
def execute_task(self, task_to_execute: str | None = None):
|
||||
if task_to_execute:
|
||||
method = self.task_to_internal_method_map[task_to_execute]
|
||||
if task := getattr(self, method, None):
|
||||
try:
|
||||
task()
|
||||
except Exception as err:
|
||||
frappe.db.rollback()
|
||||
traceback = frappe.get_traceback(with_context=True)
|
||||
if traceback:
|
||||
message = "Traceback: <br>" + traceback
|
||||
frappe.db.set_value(self.doctype, self.name, "error_log", message)
|
||||
frappe.db.set_value(self.doctype, self.name, "status", "Failed")
|
||||
|
||||
def delete_notifications(self):
|
||||
self.validate_doc_status()
|
||||
if not self.clear_notifications:
|
||||
clear_notifications()
|
||||
self.db_set("clear_notifications", 1)
|
||||
self.enqueue_task(task="Initialize Summary Table")
|
||||
|
||||
def populate_doctypes_to_be_ignored_table(self):
|
||||
doctypes_to_be_ignored_list = get_doctypes_to_be_ignored()
|
||||
for doctype in doctypes_to_be_ignored_list:
|
||||
self.append("doctypes_to_be_ignored", {"doctype_name": doctype})
|
||||
|
||||
def delete_bins(self):
|
||||
frappe.db.sql(
|
||||
"""delete from `tabBin` where warehouse in
|
||||
(select name from tabWarehouse where company=%s)""",
|
||||
self.company,
|
||||
)
|
||||
def validate_running_task_for_doc(self, job_names: list = None):
|
||||
# at most only one task should be runnning
|
||||
running_tasks = []
|
||||
for x in job_names:
|
||||
if is_job_enqueued(x):
|
||||
running_tasks.append(get_job(x).get_id())
|
||||
|
||||
def delete_lead_addresses(self):
|
||||
"""Delete addresses to which leads are linked"""
|
||||
leads = frappe.get_all("Lead", filters={"company": self.company})
|
||||
leads = ["'%s'" % row.get("name") for row in leads]
|
||||
addresses = []
|
||||
if leads:
|
||||
addresses = frappe.db.sql_list(
|
||||
"""select parent from `tabDynamic Link` where link_name
|
||||
in ({leads})""".format(
|
||||
leads=",".join(leads)
|
||||
if running_tasks:
|
||||
frappe.throw(
|
||||
_("{0} is already running for {1}").format(
|
||||
comma_and([get_link_to_form("RQ Job", x) for x in running_tasks]), self.name
|
||||
)
|
||||
)
|
||||
|
||||
if addresses:
|
||||
addresses = ["%s" % frappe.db.escape(addr) for addr in addresses]
|
||||
|
||||
frappe.db.sql(
|
||||
"""delete from `tabAddress` where name in ({addresses}) and
|
||||
name not in (select distinct dl1.parent from `tabDynamic Link` dl1
|
||||
inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent
|
||||
and dl1.link_doctype<>dl2.link_doctype)""".format(
|
||||
addresses=",".join(addresses)
|
||||
)
|
||||
def validate_doc_status(self):
|
||||
if self.status != "Running":
|
||||
frappe.throw(
|
||||
_("{0} is not running. Cannot trigger events for this Document").format(
|
||||
get_link_to_form("Transaction Deletion Record", self.name)
|
||||
)
|
||||
)
|
||||
|
||||
frappe.db.sql(
|
||||
"""delete from `tabDynamic Link` where link_doctype='Lead'
|
||||
and parenttype='Address' and link_name in ({leads})""".format(
|
||||
@frappe.whitelist()
|
||||
def start_deletion_tasks(self):
|
||||
# This method is the entry point for the chain of events that follow
|
||||
self.db_set("status", "Running")
|
||||
self.enqueue_task(task="Delete Bins")
|
||||
|
||||
def delete_bins(self):
|
||||
self.validate_doc_status()
|
||||
if not self.delete_bin_data:
|
||||
frappe.db.sql(
|
||||
"""delete from `tabBin` where warehouse in
|
||||
(select name from tabWarehouse where company=%s)""",
|
||||
self.company,
|
||||
)
|
||||
self.db_set("delete_bin_data", 1)
|
||||
self.enqueue_task(task="Delete Leads and Addresses")
|
||||
|
||||
def delete_lead_addresses(self):
|
||||
"""Delete addresses to which leads are linked"""
|
||||
self.validate_doc_status()
|
||||
if not self.delete_leads_and_addresses:
|
||||
leads = frappe.get_all("Lead", filters={"company": self.company})
|
||||
leads = ["'%s'" % row.get("name") for row in leads]
|
||||
addresses = []
|
||||
if leads:
|
||||
addresses = frappe.db.sql_list(
|
||||
"""select parent from `tabDynamic Link` where link_name
|
||||
in ({leads})""".format(
|
||||
leads=",".join(leads)
|
||||
)
|
||||
)
|
||||
|
||||
frappe.db.sql(
|
||||
"""update `tabCustomer` set lead_name=NULL where lead_name in ({leads})""".format(
|
||||
leads=",".join(leads)
|
||||
if addresses:
|
||||
addresses = ["%s" % frappe.db.escape(addr) for addr in addresses]
|
||||
|
||||
frappe.db.sql(
|
||||
"""delete from `tabAddress` where name in ({addresses}) and
|
||||
name not in (select distinct dl1.parent from `tabDynamic Link` dl1
|
||||
inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent
|
||||
and dl1.link_doctype<>dl2.link_doctype)""".format(
|
||||
addresses=",".join(addresses)
|
||||
)
|
||||
)
|
||||
|
||||
frappe.db.sql(
|
||||
"""delete from `tabDynamic Link` where link_doctype='Lead'
|
||||
and parenttype='Address' and link_name in ({leads})""".format(
|
||||
leads=",".join(leads)
|
||||
)
|
||||
)
|
||||
|
||||
frappe.db.sql(
|
||||
"""update `tabCustomer` set lead_name=NULL where lead_name in ({leads})""".format(
|
||||
leads=",".join(leads)
|
||||
)
|
||||
)
|
||||
)
|
||||
self.db_set("delete_leads_and_addresses", 1)
|
||||
self.enqueue_task(task="Reset Company Values")
|
||||
|
||||
def reset_company_values(self):
|
||||
company_obj = frappe.get_doc("Company", self.company)
|
||||
company_obj.total_monthly_sales = 0
|
||||
company_obj.sales_monthly_history = None
|
||||
company_obj.save()
|
||||
self.validate_doc_status()
|
||||
if not self.reset_company_default_values:
|
||||
company_obj = frappe.get_doc("Company", self.company)
|
||||
company_obj.total_monthly_sales = 0
|
||||
company_obj.sales_monthly_history = None
|
||||
company_obj.save()
|
||||
self.db_set("reset_company_default_values", 1)
|
||||
self.enqueue_task(task="Clear Notifications")
|
||||
|
||||
def initialize_doctypes_to_be_deleted_table(self):
|
||||
self.validate_doc_status()
|
||||
if not self.initialize_doctypes_table:
|
||||
doctypes_to_be_ignored_list = self.get_doctypes_to_be_ignored_list()
|
||||
docfields = self.get_doctypes_with_company_field(doctypes_to_be_ignored_list)
|
||||
tables = self.get_all_child_doctypes()
|
||||
for docfield in docfields:
|
||||
if docfield["parent"] != self.doctype:
|
||||
no_of_docs = self.get_number_of_docs_linked_with_specified_company(
|
||||
docfield["parent"], docfield["fieldname"]
|
||||
)
|
||||
if no_of_docs > 0:
|
||||
# Initialize
|
||||
self.populate_doctypes_table(tables, docfield["parent"], docfield["fieldname"], 0)
|
||||
self.db_set("initialize_doctypes_table", 1)
|
||||
self.enqueue_task(task="Delete Transactions")
|
||||
|
||||
def delete_company_transactions(self):
|
||||
doctypes_to_be_ignored_list = self.get_doctypes_to_be_ignored_list()
|
||||
docfields = self.get_doctypes_with_company_field(doctypes_to_be_ignored_list)
|
||||
self.validate_doc_status()
|
||||
if not self.delete_transactions:
|
||||
doctypes_to_be_ignored_list = self.get_doctypes_to_be_ignored_list()
|
||||
docfields = self.get_doctypes_with_company_field(doctypes_to_be_ignored_list)
|
||||
|
||||
tables = self.get_all_child_doctypes()
|
||||
for docfield in docfields:
|
||||
if docfield["parent"] != self.doctype:
|
||||
no_of_docs = self.get_number_of_docs_linked_with_specified_company(
|
||||
docfield["parent"], docfield["fieldname"]
|
||||
)
|
||||
|
||||
if no_of_docs > 0:
|
||||
self.delete_version_log(docfield["parent"], docfield["fieldname"])
|
||||
|
||||
reference_docs = frappe.get_all(
|
||||
docfield["parent"], filters={docfield["fieldname"]: self.company}
|
||||
tables = self.get_all_child_doctypes()
|
||||
for docfield in self.doctypes:
|
||||
if docfield.doctype_name != self.doctype and not docfield.done:
|
||||
no_of_docs = self.get_number_of_docs_linked_with_specified_company(
|
||||
docfield.doctype_name, docfield.docfield_name
|
||||
)
|
||||
reference_doc_names = [r.name for r in reference_docs]
|
||||
if no_of_docs > 0:
|
||||
reference_docs = frappe.get_all(
|
||||
docfield.doctype_name, filters={docfield.docfield_name: self.company}, limit=self.batch_size
|
||||
)
|
||||
reference_doc_names = [r.name for r in reference_docs]
|
||||
|
||||
self.delete_communications(docfield["parent"], reference_doc_names)
|
||||
self.delete_comments(docfield["parent"], reference_doc_names)
|
||||
self.unlink_attachments(docfield["parent"], reference_doc_names)
|
||||
self.delete_version_log(docfield.doctype_name, reference_doc_names)
|
||||
self.delete_communications(docfield.doctype_name, reference_doc_names)
|
||||
self.delete_comments(docfield.doctype_name, reference_doc_names)
|
||||
self.unlink_attachments(docfield.doctype_name, reference_doc_names)
|
||||
self.delete_child_tables(docfield.doctype_name, reference_doc_names)
|
||||
self.delete_docs_linked_with_specified_company(docfield.doctype_name, reference_doc_names)
|
||||
processed = int(docfield.no_of_docs) + len(reference_doc_names)
|
||||
frappe.db.set_value(docfield.doctype, docfield.name, "no_of_docs", processed)
|
||||
else:
|
||||
# reset naming series
|
||||
naming_series = frappe.db.get_value("DocType", docfield.doctype_name, "autoname")
|
||||
if naming_series:
|
||||
if "#" in naming_series:
|
||||
self.update_naming_series(naming_series, docfield.doctype_name)
|
||||
frappe.db.set_value(docfield.doctype, docfield.name, "done", 1)
|
||||
|
||||
self.populate_doctypes_table(tables, docfield["parent"], no_of_docs)
|
||||
|
||||
self.delete_child_tables(docfield["parent"], docfield["fieldname"])
|
||||
self.delete_docs_linked_with_specified_company(docfield["parent"], docfield["fieldname"])
|
||||
|
||||
naming_series = frappe.db.get_value("DocType", docfield["parent"], "autoname")
|
||||
if naming_series:
|
||||
if "#" in naming_series:
|
||||
self.update_naming_series(naming_series, docfield["parent"])
|
||||
pending_doctypes = frappe.db.get_all(
|
||||
"Transaction Deletion Record Details",
|
||||
filters={"parent": self.name, "done": 0},
|
||||
pluck="doctype_name",
|
||||
)
|
||||
if pending_doctypes:
|
||||
# as method is enqueued after commit, calling itself will not make validate_doc_status to throw
|
||||
# recursively call this task to delete all transactions
|
||||
self.enqueue_task(task="Delete Transactions")
|
||||
else:
|
||||
self.db_set("status", "Completed")
|
||||
self.db_set("delete_transactions", 1)
|
||||
self.db_set("error_log", None)
|
||||
|
||||
def get_doctypes_to_be_ignored_list(self):
|
||||
singles = frappe.get_all("DocType", filters={"issingle": 1}, pluck="name")
|
||||
@@ -174,25 +360,24 @@ class TransactionDeletionRecord(Document):
|
||||
def get_number_of_docs_linked_with_specified_company(self, doctype, company_fieldname):
|
||||
return frappe.db.count(doctype, {company_fieldname: self.company})
|
||||
|
||||
def populate_doctypes_table(self, tables, doctype, no_of_docs):
|
||||
def populate_doctypes_table(self, tables, doctype, fieldname, no_of_docs):
|
||||
self.flags.ignore_validate_update_after_submit = True
|
||||
if doctype not in tables:
|
||||
self.append("doctypes", {"doctype_name": doctype, "no_of_docs": no_of_docs})
|
||||
|
||||
def delete_child_tables(self, doctype, company_fieldname):
|
||||
parent_docs_to_be_deleted = frappe.get_all(
|
||||
doctype, {company_fieldname: self.company}, pluck="name"
|
||||
)
|
||||
self.append(
|
||||
"doctypes", {"doctype_name": doctype, "docfield_name": fieldname, "no_of_docs": no_of_docs}
|
||||
)
|
||||
self.save(ignore_permissions=True)
|
||||
|
||||
def delete_child_tables(self, doctype, reference_doc_names):
|
||||
child_tables = frappe.get_all(
|
||||
"DocField", filters={"fieldtype": "Table", "parent": doctype}, pluck="options"
|
||||
)
|
||||
|
||||
for batch in create_batch(parent_docs_to_be_deleted, self.batch_size):
|
||||
for table in child_tables:
|
||||
frappe.db.delete(table, {"parent": ["in", batch]})
|
||||
for table in child_tables:
|
||||
frappe.db.delete(table, {"parent": ["in", reference_doc_names]})
|
||||
|
||||
def delete_docs_linked_with_specified_company(self, doctype, company_fieldname):
|
||||
frappe.db.delete(doctype, {company_fieldname: self.company})
|
||||
def delete_docs_linked_with_specified_company(self, doctype, reference_doc_names):
|
||||
frappe.db.delete(doctype, {"name": ("in", reference_doc_names)})
|
||||
|
||||
def update_naming_series(self, naming_series, doctype_name):
|
||||
if "." in naming_series:
|
||||
@@ -213,17 +398,11 @@ class TransactionDeletionRecord(Document):
|
||||
|
||||
frappe.db.sql("""update `tabSeries` set current = %s where name=%s""", (last, prefix))
|
||||
|
||||
def delete_version_log(self, doctype, company_fieldname):
|
||||
dt = qb.DocType(doctype)
|
||||
names = qb.from_(dt).select(dt.name).where(dt[company_fieldname] == self.company).run(as_list=1)
|
||||
names = [x[0] for x in names]
|
||||
|
||||
if names:
|
||||
versions = qb.DocType("Version")
|
||||
for batch in create_batch(names, self.batch_size):
|
||||
qb.from_(versions).delete().where(
|
||||
(versions.ref_doctype == doctype) & (versions.docname.isin(batch))
|
||||
).run()
|
||||
def delete_version_log(self, doctype, docnames):
|
||||
versions = qb.DocType("Version")
|
||||
qb.from_(versions).delete().where(
|
||||
(versions.ref_doctype == doctype) & (versions.docname.isin(docnames))
|
||||
).run()
|
||||
|
||||
def delete_communications(self, doctype, reference_doc_names):
|
||||
communications = frappe.get_all(
|
||||
@@ -295,3 +474,34 @@ def get_doctypes_to_be_ignored():
|
||||
doctypes_to_be_ignored.extend(frappe.get_hooks("company_data_to_be_ignored") or [])
|
||||
|
||||
return doctypes_to_be_ignored
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def is_deletion_doc_running(company: str | None = None, err_msg: str | None = None):
|
||||
if company:
|
||||
if running_deletion_jobs := frappe.db.get_all(
|
||||
"Transaction Deletion Record",
|
||||
filters={"docstatus": 1, "company": company, "status": "Running"},
|
||||
):
|
||||
if not err_msg:
|
||||
err_msg = ""
|
||||
frappe.throw(
|
||||
title=_("Deletion in Progress!"),
|
||||
msg=_("Transaction Deletion Document: {0} is running for this Company. {1}").format(
|
||||
get_link_to_form("Transaction Deletion Record", running_deletion_jobs[0].name), err_msg
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def check_for_running_deletion_job(doc, method=None):
|
||||
# Check if DocType has 'company' field
|
||||
df = qb.DocType("DocField")
|
||||
if (
|
||||
not_allowed := qb.from_(df)
|
||||
.select(df.parent)
|
||||
.where((df.fieldname == "company") & (df.parent == doc.doctype))
|
||||
.run()
|
||||
):
|
||||
is_deletion_doc_running(
|
||||
doc.company, _("Cannot make any transactions until the deletion job is completed")
|
||||
)
|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.listview_settings["Transaction Deletion Record"] = {
|
||||
add_fields: ["status"],
|
||||
get_indicator: function (doc) {
|
||||
if (doc.docstatus == 0) {
|
||||
return [__("Draft"), "red"];
|
||||
} else {
|
||||
return [__("Completed"), "green"];
|
||||
}
|
||||
let colors = {
|
||||
Queued: "orange",
|
||||
Completed: "green",
|
||||
Running: "blue",
|
||||
Failed: "red",
|
||||
};
|
||||
let status = doc.status;
|
||||
return [__(status), colors[status], "status,=," + status];
|
||||
},
|
||||
};
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"doctype_name",
|
||||
"no_of_docs"
|
||||
"doctype_name"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -16,18 +15,12 @@
|
||||
"label": "DocType",
|
||||
"options": "DocType",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "no_of_docs",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Number of Docs"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-05-08 23:10:46.166744",
|
||||
"modified": "2024-02-04 10:56:27.413691",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Transaction Deletion Record Item",
|
||||
@@ -35,5 +28,6 @@
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -16,7 +16,6 @@ class TransactionDeletionRecordItem(Document):
|
||||
from frappe.types import DF
|
||||
|
||||
doctype_name: DF.Link
|
||||
no_of_docs: DF.Data | None
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
|
||||
@@ -8,7 +8,7 @@ frappe.ui.form.on("Closing Stock Balance", {
|
||||
},
|
||||
|
||||
generate_closing_balance(frm) {
|
||||
if (in_list(["Queued", "Failed"], frm.doc.status)) {
|
||||
if (["Queued", "Failed"].includes(frm.doc.status)) {
|
||||
frm.add_custom_button(__("Generate Closing Stock Balance"), () => {
|
||||
frm.call({
|
||||
method: "enqueue_job",
|
||||
|
||||
@@ -680,7 +680,7 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "other_charges_calculation",
|
||||
"fieldtype": "Long Text",
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "Taxes and Charges Calculation",
|
||||
"no_copy": 1,
|
||||
"oldfieldtype": "HTML",
|
||||
@@ -1397,7 +1397,7 @@
|
||||
"idx": 146,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-05 11:58:47.784349",
|
||||
"modified": "2024-03-20 16:05:02.854990",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Delivery Note",
|
||||
|
||||
@@ -76,7 +76,7 @@ class DeliveryNote(SellingController):
|
||||
ignore_pricing_rule: DF.Check
|
||||
in_words: DF.Data | None
|
||||
incoterm: DF.Link | None
|
||||
installation_status: DF.Literal
|
||||
installation_status: DF.Literal[None]
|
||||
instructions: DF.Text | None
|
||||
inter_company_reference: DF.Link | None
|
||||
is_internal_customer: DF.Check
|
||||
@@ -90,7 +90,7 @@ class DeliveryNote(SellingController):
|
||||
named_place: DF.Data | None
|
||||
naming_series: DF.Literal["MAT-DN-.YYYY.-", "MAT-DN-RET-.YYYY.-"]
|
||||
net_total: DF.Currency
|
||||
other_charges_calculation: DF.LongText | None
|
||||
other_charges_calculation: DF.TextEditor | None
|
||||
packed_items: DF.Table[PackedItem]
|
||||
per_billed: DF.Percent
|
||||
per_installed: DF.Percent
|
||||
|
||||
@@ -1108,9 +1108,30 @@ class TestDeliveryNote(FrappeTestCase):
|
||||
dn.load_from_db()
|
||||
|
||||
batch_no = get_batch_from_bundle(dn.packed_items[0].serial_and_batch_bundle)
|
||||
packed_name = dn.packed_items[0].name
|
||||
self.assertTrue(batch_no)
|
||||
|
||||
dn.cancel()
|
||||
|
||||
# Cancel the reposting entry
|
||||
reposting_entries = frappe.get_all("Repost Item Valuation", filters={"docstatus": 1})
|
||||
for entry in reposting_entries:
|
||||
doc = frappe.get_doc("Repost Item Valuation", entry.name)
|
||||
doc.cancel()
|
||||
doc.delete()
|
||||
|
||||
frappe.db.set_single_value("Accounts Settings", "delete_linked_ledger_entries", 1)
|
||||
|
||||
dn.reload()
|
||||
dn.delete()
|
||||
|
||||
bundle = frappe.db.get_value(
|
||||
"Serial and Batch Bundle", {"voucher_detail_no": packed_name}, "name"
|
||||
)
|
||||
self.assertFalse(bundle)
|
||||
|
||||
frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)
|
||||
frappe.db.set_single_value("Accounts Settings", "delete_linked_ledger_entries", 0)
|
||||
|
||||
def test_payment_terms_are_fetched_when_creating_sales_invoice(self):
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import (
|
||||
|
||||
@@ -796,7 +796,8 @@
|
||||
"label": "Purchase Order",
|
||||
"options": "Purchase Order",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_82",
|
||||
@@ -912,7 +913,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-02-04 14:10:31.750340",
|
||||
"modified": "2024-03-21 18:15:07.603672",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Delivery Note Item",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
frappe.listview_settings["Delivery Trip"] = {
|
||||
add_fields: ["status"],
|
||||
get_indicator: function (doc) {
|
||||
if (in_list(["Cancelled", "Draft"], doc.status)) {
|
||||
if (["Cancelled", "Draft"].includes(doc.status)) {
|
||||
return [__(doc.status), "red", "status,=," + doc.status];
|
||||
} else if (in_list(["In Transit", "Scheduled"], doc.status)) {
|
||||
} else if (["In Transit", "Scheduled"].includes(doc.status)) {
|
||||
return [__(doc.status), "orange", "status,=," + doc.status];
|
||||
} else if (doc.status === "Completed") {
|
||||
return [__(doc.status), "green", "status,=," + doc.status];
|
||||
|
||||
@@ -104,7 +104,8 @@
|
||||
"in_standard_filter": 1,
|
||||
"label": "Price List",
|
||||
"options": "Price List",
|
||||
"reqd": 1
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"bold": 1,
|
||||
@@ -220,7 +221,7 @@
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2024-01-30 14:02:19.304854",
|
||||
"modified": "2024-03-13 12:23:39.630290",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Item Price",
|
||||
|
||||
@@ -7,7 +7,6 @@ from frappe import _, bold
|
||||
from frappe.model.document import Document
|
||||
from frappe.query_builder import Criterion
|
||||
from frappe.query_builder.functions import Cast_
|
||||
from frappe.utils import getdate
|
||||
|
||||
|
||||
class ItemPriceDuplicateItem(frappe.ValidationError):
|
||||
@@ -46,7 +45,7 @@ class ItemPrice(Document):
|
||||
|
||||
def validate(self):
|
||||
self.validate_item()
|
||||
self.validate_dates()
|
||||
self.validate_from_to_dates("valid_from", "valid_upto")
|
||||
self.update_price_list_details()
|
||||
self.update_item_details()
|
||||
self.check_duplicates()
|
||||
@@ -56,11 +55,6 @@ class ItemPrice(Document):
|
||||
if not frappe.db.exists("Item", self.item_code):
|
||||
frappe.throw(_("Item {0} not found.").format(self.item_code))
|
||||
|
||||
def validate_dates(self):
|
||||
if self.valid_from and self.valid_upto:
|
||||
if getdate(self.valid_from) > getdate(self.valid_upto):
|
||||
frappe.throw(_("Valid From Date must be lesser than Valid Up To Date."))
|
||||
|
||||
def update_price_list_details(self):
|
||||
if self.price_list:
|
||||
price_list_details = frappe.db.get_value(
|
||||
|
||||
@@ -184,7 +184,11 @@ class PickList(Document):
|
||||
|
||||
def delink_serial_and_batch_bundle(self):
|
||||
for row in self.locations:
|
||||
if row.serial_and_batch_bundle:
|
||||
if (
|
||||
row.serial_and_batch_bundle
|
||||
and frappe.db.get_value("Serial and Batch Bundle", row.serial_and_batch_bundle, "docstatus")
|
||||
== 1
|
||||
):
|
||||
frappe.db.set_value(
|
||||
"Serial and Batch Bundle",
|
||||
row.serial_and_batch_bundle,
|
||||
@@ -774,7 +778,7 @@ def get_available_item_locations(
|
||||
|
||||
if picked_item_details:
|
||||
for location in list(locations):
|
||||
if location["qty"] < 1:
|
||||
if location["qty"] < 0:
|
||||
locations.remove(location)
|
||||
|
||||
total_qty_available = sum(location.get("qty") for location in locations)
|
||||
|
||||
@@ -651,7 +651,7 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "other_charges_calculation",
|
||||
"fieldtype": "Long Text",
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "Taxes and Charges Calculation",
|
||||
"no_copy": 1,
|
||||
"oldfieldtype": "HTML",
|
||||
@@ -1252,7 +1252,7 @@
|
||||
"idx": 261,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-12-18 17:26:41.279663",
|
||||
"modified": "2024-03-20 16:05:31.713453",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Purchase Receipt",
|
||||
|
||||
@@ -87,7 +87,7 @@ class PurchaseReceipt(BuyingController):
|
||||
named_place: DF.Data | None
|
||||
naming_series: DF.Literal["MAT-PRE-.YYYY.-", "MAT-PR-RET-.YYYY.-"]
|
||||
net_total: DF.Currency
|
||||
other_charges_calculation: DF.LongText | None
|
||||
other_charges_calculation: DF.TextEditor | None
|
||||
per_billed: DF.Percent
|
||||
per_returned: DF.Percent
|
||||
plc_conversion_rate: DF.Float
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user