mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-18 14:25:10 +00:00
Merge pull request #47430 from frappe/version-14-hotfix
chore: release v14
This commit is contained in:
@@ -73,9 +73,12 @@
|
||||
"reports_tab",
|
||||
"remarks_section",
|
||||
"general_ledger_remarks_length",
|
||||
"ignore_is_opening_check_for_reporting",
|
||||
"column_break_lvjk",
|
||||
"receivable_payable_remarks_length"
|
||||
"receivable_payable_remarks_length",
|
||||
"accounts_receivable_payable_tuning_section",
|
||||
"receivable_payable_fetch_method",
|
||||
"legacy_section",
|
||||
"ignore_is_opening_check_for_reporting"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -479,6 +482,23 @@
|
||||
"fieldname": "ignore_is_opening_check_for_reporting",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore Is Opening check for reporting"
|
||||
},
|
||||
{
|
||||
"default": "Buffered Cursor",
|
||||
"fieldname": "receivable_payable_fetch_method",
|
||||
"fieldtype": "Select",
|
||||
"label": "Data Fetch Method",
|
||||
"options": "Buffered Cursor\nUnBuffered Cursor"
|
||||
},
|
||||
{
|
||||
"fieldname": "accounts_receivable_payable_tuning_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Accounts Receivable / Payable Tuning"
|
||||
},
|
||||
{
|
||||
"fieldname": "legacy_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Legacy Fields"
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
@@ -486,7 +506,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2025-01-23 13:15:44.077853",
|
||||
"modified": "2025-05-05 12:29:38.302027",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
@@ -515,4 +535,4 @@
|
||||
"sort_order": "ASC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,7 +197,7 @@ frappe.ui.form.on("Invoice Discounting", {
|
||||
from_date: frm.doc.posting_date,
|
||||
to_date: moment(frm.doc.modified).format("YYYY-MM-DD"),
|
||||
company: frm.doc.company,
|
||||
group_by: "Group by Voucher (Consolidated)",
|
||||
categorize_by: "Categorize by Voucher (Consolidated)",
|
||||
show_cancelled_entries: frm.doc.docstatus === 2,
|
||||
};
|
||||
frappe.set_route("query-report", "General Ledger");
|
||||
|
||||
@@ -34,7 +34,7 @@ frappe.ui.form.on("Journal Entry", {
|
||||
to_date: moment(frm.doc.modified).format("YYYY-MM-DD"),
|
||||
company: frm.doc.company,
|
||||
finance_book: frm.doc.finance_book,
|
||||
group_by: "",
|
||||
categorize_by: "",
|
||||
show_cancelled_entries: frm.doc.docstatus === 2,
|
||||
};
|
||||
frappe.set_route("query-report", "General Ledger");
|
||||
|
||||
@@ -319,7 +319,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
"from_date": frm.doc.posting_date,
|
||||
"to_date": moment(frm.doc.modified).format('YYYY-MM-DD'),
|
||||
"company": frm.doc.company,
|
||||
"group_by": "",
|
||||
"categorize_by": "",
|
||||
"show_cancelled_entries": frm.doc.docstatus === 2
|
||||
};
|
||||
frappe.set_route("query-report", "General Ledger");
|
||||
|
||||
@@ -1590,7 +1590,7 @@ class PaymentEntry(AccountsController):
|
||||
|
||||
# Re allocate amount to those references which have PR set (Higher priority)
|
||||
for ref in self.references:
|
||||
if not ref.payment_request:
|
||||
if not (ref.reference_doctype and ref.reference_name and ref.payment_request):
|
||||
continue
|
||||
|
||||
# fetch outstanding_amount of `Reference` (Payment Term) and `Payment Request` to allocate new amount
|
||||
@@ -1641,7 +1641,7 @@ class PaymentEntry(AccountsController):
|
||||
)
|
||||
# Re allocate amount to those references which have no PR (Lower priority)
|
||||
for ref in self.references:
|
||||
if ref.payment_request:
|
||||
if ref.payment_request or not (ref.reference_doctype and ref.reference_name):
|
||||
continue
|
||||
|
||||
key = (ref.reference_doctype, ref.reference_name, ref.get("payment_term"))
|
||||
|
||||
@@ -29,7 +29,7 @@ frappe.ui.form.on("Period Closing Voucher", {
|
||||
from_date: frm.doc.posting_date,
|
||||
to_date: moment(frm.doc.modified).format("YYYY-MM-DD"),
|
||||
company: frm.doc.company,
|
||||
group_by: "",
|
||||
categorize_by: "",
|
||||
show_cancelled_entries: frm.doc.docstatus === 2,
|
||||
};
|
||||
frappe.set_route("query-report", "General Ledger");
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"posting_date",
|
||||
"company",
|
||||
"account",
|
||||
"group_by",
|
||||
"categorize_by",
|
||||
"cost_center",
|
||||
"territory",
|
||||
"ignore_exchange_rate_revaluation_journals",
|
||||
@@ -172,14 +172,6 @@
|
||||
"fieldtype": "Date",
|
||||
"label": "Start Date"
|
||||
},
|
||||
{
|
||||
"default": "Group by Voucher (Consolidated)",
|
||||
"depends_on": "eval:(doc.report == 'General Ledger');",
|
||||
"fieldname": "group_by",
|
||||
"fieldtype": "Select",
|
||||
"label": "Group By",
|
||||
"options": "\nGroup by Voucher\nGroup by Voucher (Consolidated)"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: (doc.report == 'General Ledger');",
|
||||
"fieldname": "currency",
|
||||
@@ -395,10 +387,18 @@
|
||||
"fieldname": "show_remarks",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Remarks"
|
||||
},
|
||||
{
|
||||
"default": "Categorize by Voucher (Consolidated)",
|
||||
"depends_on": "eval:(doc.report == 'General Ledger');",
|
||||
"fieldname": "categorize_by",
|
||||
"fieldtype": "Select",
|
||||
"label": "Categorize By",
|
||||
"options": "\nCategorize by Voucher\nCategorize by Voucher (Consolidated)"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2024-10-18 17:51:39.108481",
|
||||
"modified": "2025-04-30 14:43:23.643006",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Process Statement Of Accounts",
|
||||
@@ -433,4 +433,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ def get_gl_filters(doc, entry, tax_id, presentation_currency):
|
||||
"party": [entry.customer],
|
||||
"party_name": [entry.customer_name] if entry.customer_name else None,
|
||||
"presentation_currency": presentation_currency,
|
||||
"group_by": doc.group_by,
|
||||
"categorize_by": doc.categorize_by,
|
||||
"currency": doc.currency,
|
||||
"project": [p.project_name for p in doc.project],
|
||||
"show_opening_entries": 0,
|
||||
|
||||
@@ -597,35 +597,34 @@ def get_due_date_from_template(template_name, posting_date, bill_date):
|
||||
|
||||
|
||||
def validate_due_date(
|
||||
posting_date, due_date, party_type, party, company=None, bill_date=None, template_name=None
|
||||
posting_date, due_date, party_type, party, company=None, bill_date=None, template_name=None, doctype=None
|
||||
):
|
||||
if getdate(due_date) < getdate(posting_date):
|
||||
frappe.throw(_("Due Date cannot be before Posting / Supplier Invoice Date"))
|
||||
else:
|
||||
if not template_name:
|
||||
return
|
||||
validate_due_date_with_template(posting_date, due_date, bill_date, template_name, doctype)
|
||||
|
||||
default_due_date = get_due_date_from_template(template_name, posting_date, bill_date).strftime(
|
||||
"%Y-%m-%d"
|
||||
)
|
||||
|
||||
if not default_due_date:
|
||||
return
|
||||
def validate_due_date_with_template(posting_date, due_date, bill_date, template_name, doctype=None):
|
||||
if not template_name:
|
||||
return
|
||||
|
||||
if default_due_date != posting_date and getdate(due_date) > getdate(default_due_date):
|
||||
is_credit_controller = (
|
||||
frappe.db.get_single_value("Accounts Settings", "credit_controller") in frappe.get_roles()
|
||||
default_due_date = format(get_due_date_from_template(template_name, posting_date, bill_date))
|
||||
|
||||
if not default_due_date:
|
||||
return
|
||||
|
||||
if default_due_date != posting_date and getdate(due_date) > getdate(default_due_date):
|
||||
if frappe.db.get_single_value("Accounts Settings", "credit_controller") in frappe.get_roles():
|
||||
party_type = "supplier" if doctype == "Purchase Invoice" else "customer"
|
||||
|
||||
msgprint(
|
||||
_("Note: Due Date exceeds allowed {0} credit days by {1} day(s)").format(
|
||||
party_type, date_diff(due_date, default_due_date)
|
||||
)
|
||||
)
|
||||
if is_credit_controller:
|
||||
msgprint(
|
||||
_("Note: Due / Reference Date exceeds allowed customer credit days by {0} day(s)").format(
|
||||
date_diff(due_date, default_due_date)
|
||||
)
|
||||
)
|
||||
else:
|
||||
frappe.throw(
|
||||
_("Due / Reference Date cannot be after {0}").format(formatdate(default_due_date))
|
||||
)
|
||||
else:
|
||||
frappe.throw(_("Due Date cannot be after {0}").format(formatdate(default_due_date)))
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@@ -903,12 +902,16 @@ def get_party_shipping_address(doctype: str, name: str) -> str | None:
|
||||
["is_shipping_address", "=", 1],
|
||||
["address_type", "=", "Shipping"],
|
||||
],
|
||||
pluck="name",
|
||||
limit=1,
|
||||
fields=["name", "is_shipping_address"],
|
||||
order_by="is_shipping_address DESC",
|
||||
)
|
||||
|
||||
return shipping_addresses[0] if shipping_addresses else None
|
||||
if shipping_addresses and shipping_addresses[0].is_shipping_address == 1:
|
||||
return shipping_addresses[0].name
|
||||
if len(shipping_addresses) == 1:
|
||||
return shipping_addresses[0].name
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def get_partywise_advanced_payment_amount(
|
||||
|
||||
@@ -49,6 +49,10 @@ class ReceivablePayableReport:
|
||||
self.age_as_on = (
|
||||
getdate(nowdate()) if self.filters.report_date > getdate(nowdate()) else self.filters.report_date
|
||||
)
|
||||
self.ple_fetch_method = (
|
||||
frappe.db.get_single_value("Accounts Settings", "receivable_payable_fetch_method")
|
||||
or "Buffered Cursor"
|
||||
) # Fail Safe
|
||||
|
||||
def run(self, args):
|
||||
self.filters.update(args)
|
||||
@@ -85,10 +89,7 @@ class ReceivablePayableReport:
|
||||
self.skip_total_row = 1
|
||||
|
||||
def get_data(self):
|
||||
self.get_ple_entries()
|
||||
self.get_sales_invoices_or_customers_based_on_sales_person()
|
||||
self.voucher_balance = OrderedDict()
|
||||
self.init_voucher_balance() # invoiced, paid, credit_note, outstanding
|
||||
|
||||
# Build delivery note map against all sales invoices
|
||||
self.build_delivery_note_map()
|
||||
@@ -105,12 +106,40 @@ class ReceivablePayableReport:
|
||||
# Get Exchange Rate Revaluations
|
||||
self.get_exchange_rate_revaluations()
|
||||
|
||||
self.prepare_ple_query()
|
||||
self.data = []
|
||||
self.voucher_balance = OrderedDict()
|
||||
|
||||
if self.ple_fetch_method == "Buffered Cursor":
|
||||
self.fetch_ple_in_buffered_cursor()
|
||||
elif self.ple_fetch_method == "UnBuffered Cursor":
|
||||
self.fetch_ple_in_unbuffered_cursor()
|
||||
|
||||
self.build_data()
|
||||
|
||||
def fetch_ple_in_buffered_cursor(self):
|
||||
self.ple_entries = frappe.db.sql(self.ple_query.get_sql(), as_dict=True)
|
||||
|
||||
for ple in self.ple_entries:
|
||||
self.init_voucher_balance(ple) # invoiced, paid, credit_note, outstanding
|
||||
|
||||
# This is unavoidable. Initialization and allocation cannot happen in same loop
|
||||
for ple in self.ple_entries:
|
||||
self.update_voucher_balance(ple)
|
||||
|
||||
self.build_data()
|
||||
delattr(self, "ple_entries")
|
||||
|
||||
def fetch_ple_in_unbuffered_cursor(self):
|
||||
self.ple_entries = []
|
||||
with frappe.db.unbuffered_cursor():
|
||||
for ple in frappe.db.sql(self.ple_query.get_sql(), as_dict=True, as_iterator=True):
|
||||
self.init_voucher_balance(ple) # invoiced, paid, credit_note, outstanding
|
||||
self.ple_entries.append(ple)
|
||||
|
||||
# This is unavoidable. Initialization and allocation cannot happen in same loop
|
||||
for ple in self.ple_entries:
|
||||
self.update_voucher_balance(ple)
|
||||
delattr(self, "ple_entries")
|
||||
|
||||
def build_voucher_dict(self, ple):
|
||||
return frappe._dict(
|
||||
@@ -131,26 +160,22 @@ class ReceivablePayableReport:
|
||||
outstanding_in_account_currency=0.0,
|
||||
)
|
||||
|
||||
def init_voucher_balance(self):
|
||||
# build all keys, since we want to exclude vouchers beyond the report date
|
||||
for ple in self.ple_entries:
|
||||
# get the balance object for voucher_type
|
||||
def init_voucher_balance(self, ple):
|
||||
if self.filters.get("ignore_accounts"):
|
||||
key = (ple.voucher_type, ple.voucher_no, ple.party)
|
||||
else:
|
||||
key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party)
|
||||
|
||||
if self.filters.get("ignore_accounts"):
|
||||
key = (ple.voucher_type, ple.voucher_no, ple.party)
|
||||
else:
|
||||
key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party)
|
||||
if key not in self.voucher_balance:
|
||||
self.voucher_balance[key] = self.build_voucher_dict(ple)
|
||||
|
||||
if key not in self.voucher_balance:
|
||||
self.voucher_balance[key] = self.build_voucher_dict(ple)
|
||||
if ple.voucher_type == ple.against_voucher_type and ple.voucher_no == ple.against_voucher_no:
|
||||
self.voucher_balance[key].cost_center = ple.cost_center
|
||||
|
||||
if ple.voucher_type == ple.against_voucher_type and ple.voucher_no == ple.against_voucher_no:
|
||||
self.voucher_balance[key].cost_center = ple.cost_center
|
||||
self.get_invoices(ple)
|
||||
|
||||
self.get_invoices(ple)
|
||||
|
||||
if self.filters.get("group_by_party"):
|
||||
self.init_subtotal_row(ple.party)
|
||||
if self.filters.get("group_by_party"):
|
||||
self.init_subtotal_row(ple.party)
|
||||
|
||||
if self.filters.get("group_by_party") and not self.filters.get("in_party_currency"):
|
||||
self.init_subtotal_row("Total")
|
||||
@@ -764,7 +789,7 @@ class ReceivablePayableReport:
|
||||
index = 4
|
||||
row["range" + str(index + 1)] = row.outstanding
|
||||
|
||||
def get_ple_entries(self):
|
||||
def prepare_ple_query(self):
|
||||
# get all the GL entries filtered by the given filters
|
||||
|
||||
self.prepare_conditions()
|
||||
@@ -817,7 +842,7 @@ class ReceivablePayableReport:
|
||||
else:
|
||||
query = query.orderby(self.ple.posting_date, self.ple.party)
|
||||
|
||||
self.ple_entries = query.run(as_dict=True)
|
||||
self.ple_query = query
|
||||
|
||||
def get_sales_invoices_or_customers_based_on_sales_person(self):
|
||||
if self.filters.get("sales_person"):
|
||||
|
||||
@@ -49,7 +49,7 @@ frappe.query_reports["General Ledger"] = {
|
||||
label: __("Voucher No"),
|
||||
fieldtype: "Data",
|
||||
on_change: function () {
|
||||
frappe.query_report.set_filter_value("group_by", "Group by Voucher (Consolidated)");
|
||||
frappe.query_report.set_filter_value("categorize_by", "Categorize by Voucher (Consolidated)");
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -112,29 +112,29 @@ frappe.query_reports["General Ledger"] = {
|
||||
hidden: 1,
|
||||
},
|
||||
{
|
||||
fieldname: "group_by",
|
||||
label: __("Group by"),
|
||||
fieldname: "categorize_by",
|
||||
label: __("Categorize by"),
|
||||
fieldtype: "Select",
|
||||
options: [
|
||||
"",
|
||||
{
|
||||
label: __("Group by Voucher"),
|
||||
value: "Group by Voucher",
|
||||
label: __("Categorize by Voucher"),
|
||||
value: "Categorize by Voucher",
|
||||
},
|
||||
{
|
||||
label: __("Group by Voucher (Consolidated)"),
|
||||
value: "Group by Voucher (Consolidated)",
|
||||
label: __("Categorize by Voucher (Consolidated)"),
|
||||
value: "Categorize by Voucher (Consolidated)",
|
||||
},
|
||||
{
|
||||
label: __("Group by Account"),
|
||||
value: "Group by Account",
|
||||
label: __("Categorize by Account"),
|
||||
value: "Categorize by Account",
|
||||
},
|
||||
{
|
||||
label: __("Group by Party"),
|
||||
value: "Group by Party",
|
||||
label: __("Categorize by Party"),
|
||||
value: "Categorize by Party",
|
||||
},
|
||||
],
|
||||
default: "Group by Voucher (Consolidated)",
|
||||
default: "Categorize by Voucher (Consolidated)",
|
||||
},
|
||||
{
|
||||
fieldname: "tax_id",
|
||||
|
||||
@@ -72,13 +72,17 @@ def validate_filters(filters, account_details):
|
||||
if not account_details.get(account):
|
||||
frappe.throw(_("Account {0} does not exists").format(account))
|
||||
|
||||
if filters.get("account") and filters.get("group_by") == "Group by Account":
|
||||
if not filters.get("categorize_by") and filters.get("group_by"):
|
||||
filters["categorize_by"] = filters["group_by"]
|
||||
filters["categorize_by"] = filters["categorize_by"].replace("Group by", "Categorize by")
|
||||
|
||||
if filters.get("account") and filters.get("categorize_by") == "Categorize by Account":
|
||||
filters.account = frappe.parse_json(filters.get("account"))
|
||||
for account in filters.account:
|
||||
if account_details[account].is_group == 0:
|
||||
frappe.throw(_("Can not filter based on Child Account, if grouped by Account"))
|
||||
|
||||
if filters.get("voucher_no") and filters.get("group_by") in ["Group by Voucher"]:
|
||||
if filters.get("voucher_no") and filters.get("categorize_by") in ["Categorize by Voucher"]:
|
||||
frappe.throw(_("Can not filter based on Voucher No, if grouped by Voucher"))
|
||||
|
||||
if filters.from_date > filters.to_date:
|
||||
@@ -172,9 +176,9 @@ def get_gl_entries(filters, accounting_dimensions):
|
||||
if filters.get("include_dimensions"):
|
||||
order_by_statement = "order by posting_date, creation"
|
||||
|
||||
if filters.get("group_by") == "Group by Voucher":
|
||||
if filters.get("categorize_by") == "Categorize by Voucher":
|
||||
order_by_statement = "order by posting_date, voucher_type, voucher_no"
|
||||
if filters.get("group_by") == "Group by Account":
|
||||
if filters.get("categorize_by") == "Categorize by Account":
|
||||
order_by_statement = "order by account, posting_date, creation"
|
||||
|
||||
if filters.get("include_default_book_entries"):
|
||||
@@ -261,7 +265,7 @@ def get_conditions(filters):
|
||||
if filters.get("voucher_no_not_in"):
|
||||
conditions.append("voucher_no not in %(voucher_no_not_in)s")
|
||||
|
||||
if filters.get("group_by") == "Group by Party" and not filters.get("party_type"):
|
||||
if filters.get("categorize_by") == "Categorize by Party" and not filters.get("party_type"):
|
||||
conditions.append("party_type in ('Customer', 'Supplier')")
|
||||
|
||||
if filters.get("party_type"):
|
||||
@@ -273,7 +277,7 @@ def get_conditions(filters):
|
||||
if not (
|
||||
filters.get("account")
|
||||
or filters.get("party")
|
||||
or filters.get("group_by") in ["Group by Account", "Group by Party"]
|
||||
or filters.get("categorize_by") in ["Categorize by Account", "Categorize by Party"]
|
||||
):
|
||||
if not ignore_is_opening:
|
||||
conditions.append("(posting_date >=%(from_date)s or is_opening = 'Yes')")
|
||||
@@ -368,13 +372,13 @@ def get_data_with_opening_closing(filters, account_details, accounting_dimension
|
||||
# Opening for filtered account
|
||||
data.append(totals.opening)
|
||||
|
||||
if filters.get("group_by") != "Group by Voucher (Consolidated)":
|
||||
if filters.get("categorize_by") != "Categorize by Voucher (Consolidated)":
|
||||
for _acc, acc_dict in gle_map.items():
|
||||
# acc
|
||||
if acc_dict.entries:
|
||||
# opening
|
||||
data.append({})
|
||||
if filters.get("group_by") != "Group by Voucher":
|
||||
if filters.get("categorize_by") != "Categorize by Voucher":
|
||||
data.append(acc_dict.totals.opening)
|
||||
|
||||
data += acc_dict.entries
|
||||
@@ -383,7 +387,7 @@ def get_data_with_opening_closing(filters, account_details, accounting_dimension
|
||||
data.append(acc_dict.totals.total)
|
||||
|
||||
# closing
|
||||
if filters.get("group_by") != "Group by Voucher":
|
||||
if filters.get("categorize_by") != "Categorize by Voucher":
|
||||
data.append(acc_dict.totals.closing)
|
||||
data.append({})
|
||||
else:
|
||||
@@ -416,9 +420,9 @@ def get_totals_dict():
|
||||
|
||||
|
||||
def group_by_field(group_by):
|
||||
if group_by == "Group by Party":
|
||||
if group_by == "Categorize by Party":
|
||||
return "party"
|
||||
elif group_by in ["Group by Voucher (Consolidated)", "Group by Account"]:
|
||||
elif group_by in ["Categorize by Voucher (Consolidated)", "Categorize by Account"]:
|
||||
return "account"
|
||||
else:
|
||||
return "voucher_no"
|
||||
@@ -426,7 +430,7 @@ def group_by_field(group_by):
|
||||
|
||||
def initialize_gle_map(gl_entries, filters):
|
||||
gle_map = OrderedDict()
|
||||
group_by = group_by_field(filters.get("group_by"))
|
||||
group_by = group_by_field(filters.get("categorize_by"))
|
||||
|
||||
for gle in gl_entries:
|
||||
gle_map.setdefault(gle.get(group_by), _dict(totals=get_totals_dict(), entries=[]))
|
||||
@@ -437,8 +441,8 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map):
|
||||
totals = get_totals_dict()
|
||||
entries = []
|
||||
consolidated_gle = OrderedDict()
|
||||
group_by = group_by_field(filters.get("group_by"))
|
||||
group_by_voucher_consolidated = filters.get("group_by") == "Group by Voucher (Consolidated)"
|
||||
group_by = group_by_field(filters.get("categorize_by"))
|
||||
group_by_voucher_consolidated = filters.get("categorize_by") == "Categorize by Voucher (Consolidated)"
|
||||
|
||||
if filters.get("show_net_values_in_party_account"):
|
||||
account_type_map = get_account_type_map(filters.get("company"))
|
||||
|
||||
@@ -155,7 +155,7 @@ class TestGeneralLedger(FrappeTestCase):
|
||||
"from_date": today(),
|
||||
"to_date": today(),
|
||||
"account": [account.name],
|
||||
"group_by": "Group by Voucher (Consolidated)",
|
||||
"categorize_by": "Categorize by Voucher (Consolidated)",
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -246,7 +246,7 @@ class TestGeneralLedger(FrappeTestCase):
|
||||
"from_date": today(),
|
||||
"to_date": today(),
|
||||
"account": [account.name],
|
||||
"group_by": "Group by Voucher (Consolidated)",
|
||||
"categorize_by": "Categorize by Voucher (Consolidated)",
|
||||
"ignore_err": True,
|
||||
}
|
||||
)
|
||||
@@ -261,7 +261,7 @@ class TestGeneralLedger(FrappeTestCase):
|
||||
"from_date": today(),
|
||||
"to_date": today(),
|
||||
"account": [account.name],
|
||||
"group_by": "Group by Voucher (Consolidated)",
|
||||
"categorize_by": "Categorize by Voucher (Consolidated)",
|
||||
"ignore_err": False,
|
||||
}
|
||||
)
|
||||
@@ -308,7 +308,7 @@ class TestGeneralLedger(FrappeTestCase):
|
||||
"from_date": si.posting_date,
|
||||
"to_date": si.posting_date,
|
||||
"account": [si.debit_to],
|
||||
"group_by": "Group by Voucher (Consolidated)",
|
||||
"categorize_by": "Categorize by Voucher (Consolidated)",
|
||||
"ignore_cr_dr_notes": False,
|
||||
}
|
||||
)
|
||||
@@ -325,7 +325,7 @@ class TestGeneralLedger(FrappeTestCase):
|
||||
"from_date": si.posting_date,
|
||||
"to_date": si.posting_date,
|
||||
"account": [si.debit_to],
|
||||
"group_by": "Group by Voucher (Consolidated)",
|
||||
"categorize_by": "Categorize by Voucher (Consolidated)",
|
||||
"ignore_cr_dr_notes": True,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -12,8 +12,8 @@ DEFAULT_FILTERS = {
|
||||
|
||||
|
||||
REPORT_FILTER_TEST_CASES: list[tuple[ReportName, ReportFilters]] = [
|
||||
("General Ledger", {"group_by": "Group by Voucher (Consolidated)"}),
|
||||
("General Ledger", {"group_by": "Group by Voucher (Consolidated)", "include_dimensions": 1}),
|
||||
("General Ledger", {"categorize_by": "Categorize by Voucher (Consolidated)"}),
|
||||
("General Ledger", {"categorize_by": "Categorize by Voucher (Consolidated)", "include_dimensions": 1}),
|
||||
("Accounts Payable", {"range1": 30, "range2": 60, "range3": 90, "range4": 120}),
|
||||
("Accounts Receivable", {"range1": 30, "range2": 60, "range3": 90, "range4": 120}),
|
||||
("Consolidated Financial Statement", {"report": "Balance Sheet"}),
|
||||
|
||||
@@ -76,14 +76,14 @@ frappe.query_reports["Supplier Quotation Comparison"] = {
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldname: "group_by",
|
||||
label: __("Group by"),
|
||||
fieldname: "categorize_by",
|
||||
label: __("Categorize by"),
|
||||
fieldtype: "Select",
|
||||
options: [
|
||||
{ label: __("Group by Supplier"), value: "Group by Supplier" },
|
||||
{ label: __("Group by Item"), value: "Group by Item" },
|
||||
{ label: __("Categorize by Supplier"), value: "Categorize by Supplier" },
|
||||
{ label: __("Categorize by Item"), value: "Categorize by Item" },
|
||||
],
|
||||
default: __("Group by Supplier"),
|
||||
default: __("Categorize by Supplier"),
|
||||
},
|
||||
{
|
||||
fieldtype: "Check",
|
||||
|
||||
@@ -15,6 +15,8 @@ def execute(filters=None):
|
||||
if not filters:
|
||||
return [], []
|
||||
|
||||
validate_filters(filters)
|
||||
|
||||
columns = get_columns(filters)
|
||||
supplier_quotation_data = get_data(filters)
|
||||
|
||||
@@ -24,6 +26,12 @@ def execute(filters=None):
|
||||
return columns, data, message, chart_data
|
||||
|
||||
|
||||
def validate_filters(filters):
|
||||
if not filters.get("categorize_by") and filters.get("group_by"):
|
||||
filters["categorize_by"] = filters["group_by"]
|
||||
filters["categorize_by"] = filters["categorize_by"].replace("Group by", "Categorize by")
|
||||
|
||||
|
||||
def get_data(filters):
|
||||
sq = frappe.qb.DocType("Supplier Quotation")
|
||||
sq_item = frappe.qb.DocType("Supplier Quotation Item")
|
||||
@@ -82,7 +90,9 @@ def prepare_data(supplier_quotation_data, filters):
|
||||
group_wise_map = defaultdict(list)
|
||||
supplier_qty_price_map = {}
|
||||
|
||||
group_by_field = "supplier_name" if filters.get("group_by") == "Group by Supplier" else "item_code"
|
||||
group_by_field = (
|
||||
"supplier_name" if filters.get("categorize_by") == "Categorize by Supplier" else "item_code"
|
||||
)
|
||||
company_currency = frappe.db.get_default("currency")
|
||||
float_precision = cint(frappe.db.get_default("float_precision")) or 2
|
||||
|
||||
@@ -274,7 +284,7 @@ def get_columns(filters):
|
||||
},
|
||||
]
|
||||
|
||||
if filters.get("group_by") == "Group by Item":
|
||||
if filters.get("categorize_by") == "Categorize by Item":
|
||||
group_by_columns.reverse()
|
||||
|
||||
columns[0:0] = group_by_columns # add positioned group by columns to the report
|
||||
|
||||
@@ -684,7 +684,9 @@ class AccountsController(TransactionBase):
|
||||
"Customer",
|
||||
self.customer,
|
||||
self.company,
|
||||
None,
|
||||
self.payment_terms_template,
|
||||
self.doctype,
|
||||
)
|
||||
elif self.doctype == "Purchase Invoice":
|
||||
validate_due_date(
|
||||
@@ -695,6 +697,7 @@ class AccountsController(TransactionBase):
|
||||
self.company,
|
||||
self.bill_date,
|
||||
self.payment_terms_template,
|
||||
self.doctype,
|
||||
)
|
||||
|
||||
def set_price_list_currency(self, buying_or_selling):
|
||||
|
||||
@@ -372,3 +372,6 @@ erpnext.patches.v14_0.disable_add_row_in_gross_profit
|
||||
execute:frappe.db.set_single_value("Accounts Settings", "exchange_gain_loss_posting_date", "Payment")
|
||||
erpnext.patches.v14_0.update_posting_datetime
|
||||
erpnext.stock.doctype.stock_ledger_entry.patches.ensure_sle_indexes
|
||||
erpnext.patches.v14_0.rename_group_by_to_categorize_by
|
||||
execute:frappe.db.set_single_value("Accounts Settings", "receivable_payable_fetch_method", "Buffered Cursor")
|
||||
erpnext.patches.v14_0.set_update_price_list_based_on
|
||||
|
||||
20
erpnext/patches/v14_0/rename_group_by_to_categorize_by.py
Normal file
20
erpnext/patches/v14_0/rename_group_by_to_categorize_by.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import frappe
|
||||
from frappe.model.utils.rename_field import rename_field
|
||||
|
||||
|
||||
def execute():
|
||||
rename_field("Process Statement Of Accounts", "group_by", "categorize_by")
|
||||
|
||||
frappe.db.sql(
|
||||
"""
|
||||
UPDATE
|
||||
`tabProcess Statement Of Accounts`
|
||||
SET
|
||||
categorize_by = CASE
|
||||
WHEN categorize_by = 'Group by Voucher (Consolidated)' THEN 'Categorize by Voucher (Consolidated)'
|
||||
WHEN categorize_by = 'Group by Voucher' THEN 'Categorize by Voucher'
|
||||
END
|
||||
WHERE
|
||||
categorize_by IN ('Group by Voucher (Consolidated)', 'Group by Voucher')
|
||||
"""
|
||||
)
|
||||
14
erpnext/patches/v14_0/set_update_price_list_based_on.py
Normal file
14
erpnext/patches/v14_0/set_update_price_list_based_on.py
Normal file
@@ -0,0 +1,14 @@
|
||||
import frappe
|
||||
from frappe.utils import cint
|
||||
|
||||
|
||||
def execute():
|
||||
frappe.db.set_single_value(
|
||||
"Stock Settings",
|
||||
"update_price_list_based_on",
|
||||
(
|
||||
"Price List Rate"
|
||||
if cint(frappe.db.get_single_value("Selling Settings", "editable_price_list_rate"))
|
||||
else "Rate"
|
||||
),
|
||||
)
|
||||
@@ -87,7 +87,7 @@ erpnext.stock.StockController = class StockController extends frappe.ui.form.Con
|
||||
from_date: me.frm.doc.posting_date,
|
||||
to_date: moment(me.frm.doc.modified).format('YYYY-MM-DD'),
|
||||
company: me.frm.doc.company,
|
||||
group_by: "Group by Voucher (Consolidated)",
|
||||
categorize_by: "Categorize by Voucher (Consolidated)",
|
||||
show_cancelled_entries: me.frm.doc.docstatus === 2,
|
||||
ignore_prepared_report: true
|
||||
};
|
||||
|
||||
@@ -853,7 +853,13 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase):
|
||||
def test_auto_insert_price(self):
|
||||
make_item("_Test Item for Auto Price List", {"is_stock_item": 0})
|
||||
make_item("_Test Item for Auto Price List with Discount Percentage", {"is_stock_item": 0})
|
||||
frappe.db.set_value("Stock Settings", None, "auto_insert_price_list_rate_if_missing", 1)
|
||||
frappe.db.set_single_value(
|
||||
"Stock Settings",
|
||||
{
|
||||
"auto_insert_price_list_rate_if_missing": 1,
|
||||
"update_price_list_based_on": "Price List Rate",
|
||||
},
|
||||
)
|
||||
|
||||
item_price = frappe.db.get_value(
|
||||
"Item Price", {"price_list": "_Test Price List", "item_code": "_Test Item for Auto Price List"}
|
||||
@@ -865,6 +871,7 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase):
|
||||
item_code="_Test Item for Auto Price List", selling_price_list="_Test Price List", rate=100
|
||||
)
|
||||
|
||||
# ensure price gets inserted based on rate if price list rate is not defined by user
|
||||
self.assertEqual(
|
||||
frappe.db.get_value(
|
||||
"Item Price",
|
||||
@@ -874,6 +881,8 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase):
|
||||
100,
|
||||
)
|
||||
|
||||
# ensure price gets insterted based on user-defined *Price List Rate*
|
||||
# if update_price_list_based_on is set to Price List Rate
|
||||
make_sales_order(
|
||||
item_code="_Test Item for Auto Price List with Discount Percentage",
|
||||
selling_price_list="_Test Price List",
|
||||
@@ -881,18 +890,43 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase):
|
||||
discount_percentage=20,
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
frappe.db.get_value(
|
||||
"Item Price",
|
||||
{
|
||||
"price_list": "_Test Price List",
|
||||
"item_code": "_Test Item for Auto Price List with Discount Percentage",
|
||||
},
|
||||
"price_list_rate",
|
||||
),
|
||||
200,
|
||||
item_price = frappe.db.get_value(
|
||||
"Item Price",
|
||||
{
|
||||
"price_list": "_Test Price List",
|
||||
"item_code": "_Test Item for Auto Price List with Discount Percentage",
|
||||
},
|
||||
("name", "price_list_rate"),
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
self.assertEqual(item_price.price_list_rate, 200)
|
||||
frappe.delete_doc("Item Price", item_price.name)
|
||||
|
||||
frappe.db.set_single_value("Stock Settings", "update_price_list_based_on", "Rate")
|
||||
|
||||
# ensure price gets insterted based on user-defined *Rate*
|
||||
# if update_price_list_based_on is set to Rate
|
||||
make_sales_order(
|
||||
item_code="_Test Item for Auto Price List with Discount Percentage",
|
||||
selling_price_list="_Test Price List",
|
||||
price_list_rate=200,
|
||||
discount_percentage=20,
|
||||
)
|
||||
|
||||
item_price = frappe.db.get_value(
|
||||
"Item Price",
|
||||
{
|
||||
"price_list": "_Test Price List",
|
||||
"item_code": "_Test Item for Auto Price List with Discount Percentage",
|
||||
},
|
||||
("name", "price_list_rate"),
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
self.assertEqual(item_price.price_list_rate, 160)
|
||||
frappe.delete_doc("Item Price", item_price.name)
|
||||
|
||||
# do not update price list
|
||||
frappe.db.set_value("Stock Settings", None, "auto_insert_price_list_rate_if_missing", 0)
|
||||
|
||||
@@ -917,6 +951,63 @@ class TestSalesOrder(AccountsTestMixin, FrappeTestCase):
|
||||
|
||||
frappe.db.set_value("Stock Settings", None, "auto_insert_price_list_rate_if_missing", 1)
|
||||
|
||||
def test_update_existing_item_price(self):
|
||||
item_code = "_Test Item for Price List Updation"
|
||||
price_list = "_Test Price List"
|
||||
|
||||
make_item(item_code, {"is_stock_item": 0})
|
||||
|
||||
frappe.db.set_single_value(
|
||||
"Stock Settings",
|
||||
{
|
||||
"auto_insert_price_list_rate_if_missing": 1,
|
||||
"update_existing_price_list_rate": 1,
|
||||
"update_price_list_based_on": "Rate",
|
||||
},
|
||||
)
|
||||
|
||||
# setup: price creation
|
||||
make_sales_order(item_code=item_code, selling_price_list=price_list, rate=100)
|
||||
|
||||
# test price updation based on Rate
|
||||
make_sales_order(item_code=item_code, selling_price_list=price_list, rate=90)
|
||||
|
||||
self.assertEqual(
|
||||
frappe.db.get_value(
|
||||
"Item Price",
|
||||
{"price_list": price_list, "item_code": item_code},
|
||||
"price_list_rate",
|
||||
),
|
||||
90,
|
||||
)
|
||||
|
||||
frappe.db.set_single_value(
|
||||
"Stock Settings",
|
||||
{
|
||||
"update_price_list_based_on": "Price List Rate",
|
||||
},
|
||||
)
|
||||
|
||||
# test price updation based on Price List Rate
|
||||
make_sales_order(
|
||||
item_code=item_code,
|
||||
selling_price_list=price_list,
|
||||
price_list_rate=200,
|
||||
discount_percentage=20,
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
frappe.db.get_value(
|
||||
"Item Price",
|
||||
{"price_list": price_list, "item_code": item_code},
|
||||
"price_list_rate",
|
||||
),
|
||||
200,
|
||||
)
|
||||
|
||||
# reset `update_existing_price_list_rate` to 0
|
||||
frappe.db.set_single_value("Stock Settings", "update_existing_price_list_rate", 0)
|
||||
|
||||
def test_drop_shipping(self):
|
||||
from erpnext.buying.doctype.purchase_order.purchase_order import update_status
|
||||
from erpnext.selling.doctype.sales_order.sales_order import (
|
||||
|
||||
@@ -2,5 +2,7 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("Selling Settings", {
|
||||
refresh: function (frm) {},
|
||||
after_save(frm) {
|
||||
frappe.boot.user.defaults.editable_price_list_rate = frm.doc.editable_price_list_rate;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -34,6 +34,7 @@ def set_default_settings(args):
|
||||
stock_settings.stock_uom = _("Nos")
|
||||
stock_settings.auto_indent = 1
|
||||
stock_settings.auto_insert_price_list_rate_if_missing = 1
|
||||
stock_settings.update_price_list_based_on = "Rate"
|
||||
stock_settings.automatically_set_serial_nos_based_on_fifo = 1
|
||||
stock_settings.set_qty_in_transactions_based_on_serial_no_input = 1
|
||||
stock_settings.save()
|
||||
|
||||
@@ -478,6 +478,7 @@ def update_stock_settings():
|
||||
stock_settings.stock_uom = _("Nos")
|
||||
stock_settings.auto_indent = 1
|
||||
stock_settings.auto_insert_price_list_rate_if_missing = 1
|
||||
stock_settings.update_price_list_based_on = "Rate"
|
||||
stock_settings.automatically_set_serial_nos_based_on_fifo = 1
|
||||
stock_settings.set_qty_in_transactions_based_on_serial_no_input = 1
|
||||
stock_settings.save()
|
||||
|
||||
@@ -7,6 +7,35 @@ const SALES_DOCTYPES = ["Quotation", "Sales Order", "Delivery Note", "Sales Invo
|
||||
const PURCHASE_DOCTYPES = ["Purchase Order", "Purchase Receipt", "Purchase Invoice"];
|
||||
|
||||
frappe.ui.form.on("Item", {
|
||||
valuation_method(frm) {
|
||||
if (!frm.is_new() && frm.doc.valuation_method === "Moving Average") {
|
||||
let stock_exists = frm.doc.__onload && frm.doc.__onload.stock_exists ? 1 : 0;
|
||||
let current_valuation_method = frm.doc.__onload.current_valuation_method;
|
||||
|
||||
if (stock_exists && current_valuation_method !== frm.doc.valuation_method) {
|
||||
let msg = __(
|
||||
"Changing the valuation method to Moving Average will affect new transactions. If backdated entries are added, earlier FIFO-based entries will be reposted, which may change closing balances."
|
||||
);
|
||||
msg += "<br>";
|
||||
msg += __(
|
||||
"Also you can't switch back to FIFO after setting the valuation method to Moving Average for this item."
|
||||
);
|
||||
msg += "<br>";
|
||||
msg += __("Do you want to change valuation method?");
|
||||
|
||||
frappe.confirm(
|
||||
msg,
|
||||
() => {
|
||||
frm.set_value("valuation_method", "Moving Average");
|
||||
},
|
||||
() => {
|
||||
frm.set_value("valuation_method", current_valuation_method);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
setup: function (frm) {
|
||||
frm.add_fetch("attribute", "numeric_values", "numeric_values");
|
||||
frm.add_fetch("attribute", "from_range", "from_range");
|
||||
|
||||
@@ -31,6 +31,7 @@ from erpnext.controllers.item_variant import (
|
||||
)
|
||||
from erpnext.setup.doctype.item_group.item_group import invalidate_cache_for
|
||||
from erpnext.stock.doctype.item_default.item_default import ItemDefault
|
||||
from erpnext.stock.utils import get_valuation_method
|
||||
|
||||
|
||||
class DuplicateReorderRows(frappe.ValidationError):
|
||||
@@ -53,6 +54,7 @@ class Item(Document):
|
||||
def onload(self):
|
||||
self.set_onload("stock_exists", self.stock_ledger_created())
|
||||
self.set_onload("asset_naming_series", get_asset_naming_series())
|
||||
self.set_onload("current_valuation_method", get_valuation_method(self.name))
|
||||
|
||||
def autoname(self):
|
||||
if frappe.db.get_default("item_naming_by") == "Naming Series":
|
||||
|
||||
@@ -364,6 +364,7 @@ frappe.ui.form.on('Stock Entry', {
|
||||
docstatus: 1,
|
||||
purpose: "Material Transfer",
|
||||
add_to_transit: 1,
|
||||
per_transferred: ["<", 100],
|
||||
}
|
||||
})
|
||||
}, __("Get Items From"));
|
||||
|
||||
@@ -9,7 +9,17 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import cint, comma_or, cstr, flt, format_time, formatdate, getdate, nowdate
|
||||
from frappe.utils import (
|
||||
cint,
|
||||
comma_or,
|
||||
cstr,
|
||||
flt,
|
||||
format_time,
|
||||
formatdate,
|
||||
get_link_to_form,
|
||||
getdate,
|
||||
nowdate,
|
||||
)
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.general_ledger import process_gl_map
|
||||
@@ -430,17 +440,29 @@ class StockEntry(StockController):
|
||||
).format(frappe.bold(self.company))
|
||||
)
|
||||
|
||||
elif (
|
||||
self.is_opening == "Yes"
|
||||
and frappe.db.get_value("Account", d.expense_account, "report_type") == "Profit and Loss"
|
||||
):
|
||||
acc_details = frappe.get_cached_value(
|
||||
"Account",
|
||||
d.expense_account,
|
||||
["account_type", "report_type"],
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
if self.is_opening == "Yes" and acc_details.report_type == "Profit and Loss":
|
||||
frappe.throw(
|
||||
_(
|
||||
"Difference Account must be a Asset/Liability type account, since this Stock Entry is an Opening Entry"
|
||||
"Difference Account must be a Asset/Liability type account (Temporary Opening), since this Stock Entry is an Opening Entry"
|
||||
),
|
||||
OpeningEntryAccountError,
|
||||
)
|
||||
|
||||
if acc_details.account_type == "Stock":
|
||||
frappe.throw(
|
||||
_(
|
||||
"At row {0}: the Difference Account must not be a Stock type account, please change the Account Type for the account {1} or select a different account"
|
||||
).format(d.idx, get_link_to_form("Account", d.expense_account)),
|
||||
OpeningEntryAccountError,
|
||||
)
|
||||
|
||||
def validate_warehouse(self):
|
||||
"""perform various (sometimes conditional) validations on warehouse"""
|
||||
|
||||
|
||||
@@ -35,4 +35,30 @@ frappe.ui.form.on("Stock Settings", {
|
||||
}
|
||||
);
|
||||
},
|
||||
auto_insert_price_list_rate_if_missing(frm) {
|
||||
if (!frm.doc.auto_insert_price_list_rate_if_missing) return;
|
||||
|
||||
frm.set_value(
|
||||
"update_price_list_based_on",
|
||||
cint(frappe.defaults.get_default("editable_price_list_rate")) ? "Price List Rate" : "Rate"
|
||||
);
|
||||
},
|
||||
update_price_list_based_on(frm) {
|
||||
if (
|
||||
frm.doc.update_price_list_based_on === "Price List Rate" &&
|
||||
!cint(frappe.defaults.get_default("editable_price_list_rate"))
|
||||
) {
|
||||
const dialog = frappe.warn(
|
||||
__("Incompatible Setting Detected"),
|
||||
__(
|
||||
"<p>Price List Rate has not been set as editable in Selling Settings. In this scenario, setting <strong>Update Price List Based On</strong> to <strong>Price List Rate</strong> will prevent auto-updation of Item Price.</p>Are you sure you want to continue?"
|
||||
)
|
||||
);
|
||||
dialog.set_secondary_action(() => {
|
||||
frm.set_value("update_price_list_based_on", "Rate");
|
||||
dialog.hide();
|
||||
});
|
||||
return;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"stock_uom",
|
||||
"price_list_defaults_section",
|
||||
"auto_insert_price_list_rate_if_missing",
|
||||
"update_price_list_based_on",
|
||||
"column_break_12",
|
||||
"update_existing_price_list_rate",
|
||||
"stock_validations_tab",
|
||||
@@ -347,6 +348,15 @@
|
||||
"fieldname": "allow_existing_serial_no",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow existing Serial No to be Manufactured/Received again"
|
||||
},
|
||||
{
|
||||
"default": "Rate",
|
||||
"depends_on": "eval: doc.auto_insert_price_list_rate_if_missing",
|
||||
"fieldname": "update_price_list_based_on",
|
||||
"fieldtype": "Select",
|
||||
"label": "Update Price List Based On",
|
||||
"mandatory_depends_on": "eval: doc.auto_insert_price_list_rate_if_missing",
|
||||
"options": "Rate\nPrice List Rate"
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
@@ -354,7 +364,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-05-31 14:15:14.145048",
|
||||
"modified": "2025-05-06 02:39:24.284587",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Settings",
|
||||
|
||||
@@ -855,8 +855,8 @@ def get_price_list_rate(args, item_doc, out=None):
|
||||
price_list_rate = get_price_list_rate_for(args, item_doc.variant_of)
|
||||
|
||||
# insert in database
|
||||
if price_list_rate is None or frappe.db.get_single_value(
|
||||
"Stock Settings", "update_existing_price_list_rate"
|
||||
if price_list_rate is None or frappe.get_cached_value(
|
||||
"Stock Settings", "Stock Settings", "update_existing_price_list_rate"
|
||||
):
|
||||
insert_item_price(args)
|
||||
|
||||
@@ -890,49 +890,71 @@ def insert_item_price(args):
|
||||
):
|
||||
return
|
||||
|
||||
if frappe.db.get_value("Price List", args.price_list, "currency", cache=True) == args.currency and cint(
|
||||
frappe.db.get_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing")
|
||||
):
|
||||
if frappe.has_permission("Item Price", "write"):
|
||||
price_list_rate = (
|
||||
(flt(args.rate) + flt(args.discount_amount)) / args.get("conversion_factor")
|
||||
if args.get("conversion_factor")
|
||||
else (flt(args.rate) + flt(args.discount_amount))
|
||||
)
|
||||
stock_settings = frappe.get_cached_doc("Stock Settings")
|
||||
|
||||
item_price = frappe.db.get_value(
|
||||
"Item Price",
|
||||
{"item_code": args.item_code, "price_list": args.price_list, "currency": args.currency},
|
||||
["name", "price_list_rate"],
|
||||
as_dict=1,
|
||||
)
|
||||
if item_price and item_price.name:
|
||||
if item_price.price_list_rate != price_list_rate and frappe.db.get_single_value(
|
||||
"Stock Settings", "update_existing_price_list_rate"
|
||||
):
|
||||
frappe.db.set_value("Item Price", item_price.name, "price_list_rate", price_list_rate)
|
||||
frappe.msgprint(
|
||||
_("Item Price updated for {0} in Price List {1}").format(
|
||||
args.item_code, args.price_list
|
||||
),
|
||||
alert=True,
|
||||
)
|
||||
else:
|
||||
item_price = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Item Price",
|
||||
"price_list": args.price_list,
|
||||
"item_code": args.item_code,
|
||||
"currency": args.currency,
|
||||
"price_list_rate": price_list_rate,
|
||||
"uom": args.stock_uom,
|
||||
}
|
||||
)
|
||||
item_price.insert()
|
||||
frappe.msgprint(
|
||||
_("Item Price added for {0} in Price List {1}").format(args.item_code, args.price_list),
|
||||
alert=True,
|
||||
)
|
||||
if (
|
||||
not frappe.db.get_value("Price List", args.price_list, "currency", cache=True) == args.currency
|
||||
or not stock_settings.auto_insert_price_list_rate_if_missing
|
||||
or not frappe.has_permission("Item Price", "write")
|
||||
):
|
||||
return
|
||||
|
||||
item_price = frappe.db.get_value(
|
||||
"Item Price",
|
||||
{
|
||||
"item_code": args.item_code,
|
||||
"price_list": args.price_list,
|
||||
"currency": args.currency,
|
||||
"uom": args.stock_uom,
|
||||
},
|
||||
["name", "price_list_rate"],
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
update_based_on_price_list_rate = stock_settings.update_price_list_based_on == "Price List Rate"
|
||||
|
||||
if item_price and item_price.name:
|
||||
if not stock_settings.update_existing_price_list_rate:
|
||||
return
|
||||
|
||||
rate_to_consider = flt(args.price_list_rate) if update_based_on_price_list_rate else flt(args.rate)
|
||||
price_list_rate = _get_stock_uom_rate(rate_to_consider, args)
|
||||
|
||||
if not price_list_rate or item_price.price_list_rate == price_list_rate:
|
||||
return
|
||||
|
||||
frappe.db.set_value("Item Price", item_price.name, "price_list_rate", price_list_rate)
|
||||
frappe.msgprint(
|
||||
_("Item Price updated for {0} in Price List {1}").format(args.item_code, args.price_list),
|
||||
alert=True,
|
||||
)
|
||||
else:
|
||||
rate_to_consider = (
|
||||
(flt(args.price_list_rate) or flt(args.rate))
|
||||
if update_based_on_price_list_rate
|
||||
else flt(args.rate)
|
||||
)
|
||||
price_list_rate = _get_stock_uom_rate(rate_to_consider, args)
|
||||
|
||||
item_price = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Item Price",
|
||||
"price_list": args.price_list,
|
||||
"item_code": args.item_code,
|
||||
"currency": args.currency,
|
||||
"price_list_rate": price_list_rate,
|
||||
"uom": args.stock_uom,
|
||||
}
|
||||
)
|
||||
item_price.insert()
|
||||
frappe.msgprint(
|
||||
_("Item Price added for {0} in Price List {1}").format(args.item_code, args.price_list),
|
||||
alert=True,
|
||||
)
|
||||
|
||||
|
||||
def _get_stock_uom_rate(rate, args):
|
||||
return rate / args.conversion_factor if args.conversion_factor else rate
|
||||
|
||||
|
||||
def get_item_price(args, item_code, ignore_party=False):
|
||||
|
||||
Reference in New Issue
Block a user