diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.js b/erpnext/accounts/doctype/accounts_settings/accounts_settings.js index 931e05a716b..1e4d0bbca7e 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.js +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.js @@ -30,16 +30,6 @@ frappe.ui.form.on("Accounts Settings", { add_taxes_from_item_tax_template(frm) { toggle_tax_settings(frm, "add_taxes_from_item_tax_template"); }, - - drop_ar_procedures: function (frm) { - frm.call({ - doc: frm.doc, - method: "drop_ar_sql_procedures", - callback: function (r) { - frappe.show_alert(__("Procedures dropped"), 5); - }, - }); - }, }); function toggle_tax_settings(frm, field_name) { diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 25e62e20f3e..18a0a86fd52 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -95,7 +95,6 @@ "receivable_payable_fetch_method", "default_ageing_range", "column_break_ntmi", - "drop_ar_procedures", "legacy_section", "ignore_is_opening_check_for_reporting", "payment_request_settings", @@ -561,7 +560,7 @@ "fieldname": "receivable_payable_fetch_method", "fieldtype": "Select", "label": "Data Fetch Method", - "options": "Buffered Cursor\nUnBuffered Cursor\nRaw SQL" + "options": "Buffered Cursor\nUnBuffered Cursor" }, { "fieldname": "accounts_receivable_payable_tuning_section", @@ -623,13 +622,6 @@ "fieldname": "column_break_ntmi", "fieldtype": "Column Break" }, - { - "depends_on": "eval:doc.receivable_payable_fetch_method == \"Raw SQL\"", - "description": "Drops existing SQL Procedures and Function setup by Accounts Receivable report", - "fieldname": "drop_ar_procedures", - "fieldtype": "Button", - "label": "Drop Procedures" - }, { "default": "0", "fieldname": "fetch_valuation_rate_for_internal_transaction", @@ -671,7 +663,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2026-03-06 14:49:11.467716", + "modified": "2026-05-18 12:16:33.679345", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", @@ -701,4 +693,4 @@ "sort_order": "ASC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index 793f44bd5c8..5cd4955cdd7 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -60,7 +60,7 @@ class AccountsSettings(Document): merge_similar_account_heads: DF.Check over_billing_allowance: DF.Currency post_change_gl_entries: DF.Check - receivable_payable_fetch_method: DF.Literal["Buffered Cursor", "UnBuffered Cursor", "Raw SQL"] + receivable_payable_fetch_method: DF.Literal["Buffered Cursor", "UnBuffered Cursor"] receivable_payable_remarks_length: DF.Int reconciliation_queue_size: DF.Int role_allowed_to_over_bill: DF.Link | None @@ -170,11 +170,3 @@ class AccountsSettings(Document): ), title=_("Auto Tax Settings Error"), ) - - @frappe.whitelist() - def drop_ar_sql_procedures(self): - from erpnext.accounts.report.accounts_receivable.accounts_receivable import InitSQLProceduresForAR - - frappe.db.sql(f"drop function if exists {InitSQLProceduresForAR.genkey_function_name}") - frappe.db.sql(f"drop procedure if exists {InitSQLProceduresForAR.init_procedure_name}") - frappe.db.sql(f"drop procedure if exists {InitSQLProceduresForAR.allocate_procedure_name}") diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 60c8e47f8f0..80a8388eea4 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -725,31 +725,12 @@ frappe.ui.form.on("Payment Entry", { if (!frm.doc.paid_from_account_currency || !frm.doc.company) return; let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency; - if (frm.doc.paid_from_account_currency == company_currency) { - frm.set_value("source_exchange_rate", 1); - } else if (frm.doc.paid_from) { - 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", - args: { - from_currency: frm.doc.paid_from_account_currency, - to_currency: company_currency, - transaction_date: frm.doc.posting_date, - }, - callback: function (r, rt) { - frm.set_value("source_exchange_rate", r.message); - }, - }); - } else { - frm.events.set_current_exchange_rate( - frm, - "source_exchange_rate", - frm.doc.paid_from_account_currency, - company_currency - ); - } - } + frm.events.set_current_exchange_rate( + frm, + "source_exchange_rate", + frm.doc.paid_from_account_currency, + company_currency + ); }, paid_to_account_currency: function (frm) { @@ -781,49 +762,24 @@ frappe.ui.form.on("Payment Entry", { posting_date: function (frm) { frm.events.paid_from_account_currency(frm); + frm.events.paid_to_account_currency(frm); }, source_exchange_rate: function (frm) { - let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency; - if (frm.doc.paid_amount) { - frm.set_value("base_paid_amount", flt(frm.doc.paid_amount) * flt(frm.doc.source_exchange_rate)); - // target exchange rate should always be same as source if both account currencies is same - if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) { - frm.set_value("target_exchange_rate", frm.doc.source_exchange_rate); - frm.set_value("base_received_amount", frm.doc.base_paid_amount); - } else if (company_currency == frm.doc.paid_to_account_currency) { - frm.set_value("received_amount", frm.doc.base_paid_amount); - frm.set_value("base_received_amount", frm.doc.base_paid_amount); - } - - // set_unallocated_amount is called by below method, - // no need trigger separately - frm.events.set_total_allocated_amount(frm); - } - - // Make read only if Accounts Settings doesn't allow stale rates - frm.set_df_property("source_exchange_rate", "read_only", erpnext.stale_rate_allowed() ? 0 : 1); - }, - - target_exchange_rate: function (frm) { frm.set_paid_amount_based_on_received_amount = true; let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency; - if (frm.doc.received_amount) { - frm.set_value( - "base_received_amount", - flt(frm.doc.received_amount) * flt(frm.doc.target_exchange_rate) - ); + if (frm.doc.base_received_amount && frm.doc.source_exchange_rate) { + frm.set_value("base_paid_amount", frm.doc.base_received_amount); - if ( - !frm.doc.source_exchange_rate && - frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency - ) { - frm.set_value("source_exchange_rate", frm.doc.target_exchange_rate); - frm.set_value("base_paid_amount", frm.doc.base_received_amount); - } else if (company_currency == frm.doc.paid_from_account_currency) { - frm.set_value("paid_amount", frm.doc.base_received_amount); - frm.set_value("base_paid_amount", frm.doc.base_received_amount); + // target exchange rate should always be same as source if both account currencies is same + if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) { + frm.set_value("target_exchange_rate", frm.doc.source_exchange_rate); + } else { + frm.set_value( + "paid_amount", + flt(frm.doc.base_paid_amount) / flt(frm.doc.source_exchange_rate) + ); } // set_unallocated_amount is called by below method, @@ -832,6 +788,32 @@ frappe.ui.form.on("Payment Entry", { } frm.set_paid_amount_based_on_received_amount = false; + // Make read only if Accounts Settings doesn't allow stale rates + frm.set_df_property("source_exchange_rate", "read_only", erpnext.stale_rate_allowed() ? 0 : 1); + }, + + target_exchange_rate: function (frm) { + let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency; + + if (frm.doc.base_paid_amount && frm.doc.target_exchange_rate) { + frm.set_value("base_received_amount", frm.doc.base_paid_amount); + if ( + !frm.doc.source_exchange_rate && + frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency + ) { + frm.set_value("source_exchange_rate", frm.doc.target_exchange_rate); + } else { + frm.set_value( + "received_amount", + flt(frm.doc.base_received_amount) / flt(frm.doc.target_exchange_rate) + ); + } + + // set_unallocated_amount is called by below method, + // no need trigger separately + frm.events.set_total_allocated_amount(frm); + } + // Make read only if Accounts Settings doesn't allow stale rates frm.set_df_property("target_exchange_rate", "read_only", erpnext.stale_rate_allowed() ? 0 : 1); }, diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index 93a591ed44f..4f5eae52a88 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -350,7 +350,7 @@ "reqd": 1 }, { - "depends_on": "doc.received_amount", + "depends_on": "eval:doc.received_amount;", "fieldname": "base_received_amount", "fieldtype": "Currency", "label": "Received Amount (Company Currency)", @@ -800,7 +800,7 @@ "table_fieldname": "payment_entries" } ], - "modified": "2025-05-15 18:01:04.013025", + "modified": "2026-05-15 13:31:01.166010", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index c4622f0b06b..a27d3aab125 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -669,7 +669,7 @@ def validate_due_date_with_template(posting_date, due_date, bill_date, template_ if not default_due_date: return - if default_due_date != posting_date and getdate(due_date) > getdate(default_due_date): + if getdate(default_due_date) != getdate(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" diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 96992895f93..d28f886a4fd 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -129,8 +129,6 @@ class ReceivablePayableReport: self.fetch_ple_in_buffered_cursor() elif self.ple_fetch_method == "UnBuffered Cursor": self.fetch_ple_in_unbuffered_cursor() - elif self.ple_fetch_method == "Raw SQL": - self.fetch_ple_in_sql_procedures() # Build delivery note map against all sales invoices self.build_delivery_note_map() @@ -321,81 +319,6 @@ class ReceivablePayableReport: row.paid -= amount row.paid_in_account_currency -= amount_in_account_currency - def fetch_ple_in_sql_procedures(self): - self.proc = InitSQLProceduresForAR() - - build_balance = f""" - begin not atomic - declare done boolean default false; - declare rec1 row type of `{self.proc._row_def_table_name}`; - declare ple cursor for {self.ple_query.get_sql()}; - declare continue handler for not found set done = true; - - open ple; - fetch ple into rec1; - while not done do - call {self.proc.init_procedure_name}(rec1); - fetch ple into rec1; - end while; - close ple; - - set done = false; - open ple; - fetch ple into rec1; - while not done do - call {self.proc.allocate_procedure_name}(rec1); - fetch ple into rec1; - end while; - close ple; - end; - """ - frappe.db.sql(build_balance) - - balances = frappe.db.sql( - f"""select - name, - voucher_type, - voucher_no, - party, - party_account `account`, - posting_date, - account_currency, - cost_center, - project, - sum(invoiced) `invoiced`, - sum(paid) `paid`, - sum(credit_note) `credit_note`, - sum(invoiced) - sum(paid) - sum(credit_note) `outstanding`, - sum(invoiced_in_account_currency) `invoiced_in_account_currency`, - sum(paid_in_account_currency) `paid_in_account_currency`, - sum(credit_note_in_account_currency) `credit_note_in_account_currency`, - sum(invoiced_in_account_currency) - sum(paid_in_account_currency) - sum(credit_note_in_account_currency) `outstanding_in_account_currency` - from `{self.proc._voucher_balance_name}` group by name order by posting_date;""", - as_dict=True, - ) - for x in balances: - if self.filters.get("ignore_accounts"): - key = (x.voucher_type, x.voucher_no, x.party) - else: - key = (x.account, x.voucher_type, x.voucher_no, x.party) - - _d = self.build_voucher_dict(x) - for field in [ - "invoiced", - "paid", - "credit_note", - "outstanding", - "invoiced_in_account_currency", - "paid_in_account_currency", - "credit_note_in_account_currency", - "outstanding_in_account_currency", - "cost_center", - "project", - ]: - _d[field] = x.get(field) - - self.voucher_balance[key] = _d - def update_sub_total_row(self, row, party): total_row = self.total_row_map.get(party) @@ -1390,136 +1313,3 @@ def get_party_group_with_children(party, party_groups): frappe.throw(_("{0}: {1} does not exist").format(group_dtype, d)) return list(set(all_party_groups)) - - -class InitSQLProceduresForAR: - """ - Initialize SQL Procedures, Functions and Temporary tables to build Receivable / Payable report - """ - - _varchar_type = get_definition("Data") - _currency_type = get_definition("Currency") - # Temporary Tables - _voucher_balance_name = "_ar_voucher_balance" - _voucher_balance_definition = f""" - create temporary table `{_voucher_balance_name}`( - name {_varchar_type}, - voucher_type {_varchar_type}, - voucher_no {_varchar_type}, - party {_varchar_type}, - party_account {_varchar_type}, - posting_date date, - account_currency {_varchar_type}, - cost_center {_varchar_type}, - project {_varchar_type}, - invoiced {_currency_type}, - paid {_currency_type}, - credit_note {_currency_type}, - invoiced_in_account_currency {_currency_type}, - paid_in_account_currency {_currency_type}, - credit_note_in_account_currency {_currency_type}) engine=memory; - """ - - _row_def_table_name = "_ar_ple_row" - _row_def_table_definition = f""" - create temporary table `{_row_def_table_name}`( - name {_varchar_type}, - account {_varchar_type}, - voucher_type {_varchar_type}, - voucher_no {_varchar_type}, - against_voucher_type {_varchar_type}, - against_voucher_no {_varchar_type}, - party_type {_varchar_type}, - cost_center {_varchar_type}, - project {_varchar_type}, - party {_varchar_type}, - posting_date date, - due_date date, - account_currency {_varchar_type}, - amount {_currency_type}, - amount_in_account_currency {_currency_type}) engine=memory; - """ - - # Function - genkey_function_name = "ar_genkey" - genkey_function_sql = f""" - create function `{genkey_function_name}`(rec row type of `{_row_def_table_name}`, allocate bool) returns char(40) - begin - if allocate then - return sha1(concat_ws(',', rec.account, rec.against_voucher_type, rec.against_voucher_no, rec.party)); - else - return sha1(concat_ws(',', rec.account, rec.voucher_type, rec.voucher_no, rec.party)); - end if; - end - """ - - # Procedures - init_procedure_name = "ar_init_tmp_table" - init_procedure_sql = f""" - create procedure ar_init_tmp_table(in ple row type of `{_row_def_table_name}`) - begin - if not exists (select name from `{_voucher_balance_name}` where name = `{genkey_function_name}`(ple, false)) - then - insert into `{_voucher_balance_name}` values (`{genkey_function_name}`(ple, false), ple.voucher_type, ple.voucher_no, ple.party, ple.account, ple.posting_date, ple.account_currency, ple.cost_center, ple.project, 0, 0, 0, 0, 0, 0); - end if; - end; - """ - - allocate_procedure_name = "ar_allocate_to_tmp_table" - allocate_procedure_sql = f""" - create procedure ar_allocate_to_tmp_table(in ple row type of `{_row_def_table_name}`) - begin - declare invoiced {_currency_type} default 0; - declare invoiced_in_account_currency {_currency_type} default 0; - declare paid {_currency_type} default 0; - declare paid_in_account_currency {_currency_type} default 0; - declare credit_note {_currency_type} default 0; - declare credit_note_in_account_currency {_currency_type} default 0; - - - if ple.amount > 0 then - if (ple.voucher_type in ("Journal Entry", "Payment Entry") and (ple.voucher_no != ple.against_voucher_no)) then - set paid = -1 * ple.amount; - set paid_in_account_currency = -1 * ple.amount_in_account_currency; - else - set invoiced = ple.amount; - set invoiced_in_account_currency = ple.amount_in_account_currency; - end if; - else - - if ple.voucher_type in ("Sales Invoice", "Purchase Invoice") then - if (ple.voucher_no = ple.against_voucher_no) then - set paid = -1 * ple.amount; - set paid_in_account_currency = -1 * ple.amount_in_account_currency; - else - set credit_note = -1 * ple.amount; - set credit_note_in_account_currency = -1 * ple.amount_in_account_currency; - end if; - else - set paid = -1 * ple.amount; - set paid_in_account_currency = -1 * ple.amount_in_account_currency; - end if; - - end if; - - insert into `{_voucher_balance_name}` values (`{genkey_function_name}`(ple, true), ple.against_voucher_type, ple.against_voucher_no, ple.party, ple.account, ple.posting_date, ple.account_currency,'', '', invoiced, paid, 0, invoiced_in_account_currency, paid_in_account_currency, 0); - end; - """ - - def __init__(self): - existing_procedures = frappe.db.get_routines() - - if self.genkey_function_name not in existing_procedures: - frappe.db.sql(self.genkey_function_sql) - - if self.init_procedure_name not in existing_procedures: - frappe.db.sql(self.init_procedure_sql) - - if self.allocate_procedure_name not in existing_procedures: - frappe.db.sql(self.allocate_procedure_sql) - - frappe.db.sql(f"drop table if exists `{self._voucher_balance_name}`") - frappe.db.sql(self._voucher_balance_definition) - - frappe.db.sql(f"drop table if exists `{self._row_def_table_name}`") - frappe.db.sql(self._row_def_table_definition) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index a53c2134e3f..21f999197c2 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -786,19 +786,11 @@ class GrossProfitGenerator: return self.calculate_buying_amount_from_sle( row, my_sle, parenttype, parent, row.item_row, item_code ) - elif self.delivery_notes.get((row.parent, row.item_code), None): - # check if Invoice has delivery notes - dn = self.delivery_notes.get((row.parent, row.item_code)) - parenttype, parent, item_row, dn_warehouse = ( - "Delivery Note", - dn["delivery_note"], - dn["item_row"], - dn["warehouse"], - ) - my_sle = self.get_stock_ledger_entries(item_code, dn_warehouse) - return self.calculate_buying_amount_from_sle( - row, my_sle, parenttype, parent, item_row, item_code - ) + elif row.item_row and self.delivery_notes.get(row.item_row): + dn = self.delivery_notes[row.item_row] + if flt(dn.total_qty): + return flt(row.qty) * flt(dn.total_incoming_value) / flt(dn.total_qty) + return flt(row.qty) * self.get_average_buying_rate(row, item_code) 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: @@ -1049,25 +1041,29 @@ class GrossProfitGenerator: def get_delivery_notes(self): self.delivery_notes = frappe._dict({}) if self.si_list: + from frappe.query_builder.functions import Sum + invoices = [x.parent for x in self.si_list] dni = qb.DocType("Delivery Note Item") delivery_notes = ( qb.from_(dni) .select( - dni.against_sales_invoice.as_("sales_invoice"), - dni.item_code, - dni.warehouse, - dni.parent.as_("delivery_note"), - dni.name.as_("item_row"), + dni.si_detail, + Sum(dni.stock_qty * dni.incoming_rate).as_("total_incoming_value"), + Sum(dni.stock_qty).as_("total_qty"), ) - .where((dni.docstatus == 1) & (dni.against_sales_invoice.isin(invoices))) - .groupby(dni.against_sales_invoice, dni.item_code) - .orderby(dni.creation, order=Order.desc) + .where( + (dni.docstatus == 1) + & (dni.against_sales_invoice.isin(invoices)) + & (dni.si_detail.isnotnull()) + & (dni.si_detail != "") + ) + .groupby(dni.si_detail) .run(as_dict=True) ) for entry in delivery_notes: - self.delivery_notes[(entry.sales_invoice, entry.item_code)] = entry + self.delivery_notes[entry.si_detail] = entry def group_items_by_invoice(self): """ diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 2dcee807fec..acb500e940f 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -433,3 +433,4 @@ erpnext.patches.v15_0.replace_http_with_https_in_sales_partner erpnext.patches.v16_0.add_portal_redirects erpnext.patches.v16_0.update_order_qty_and_requested_qty_based_on_mr_and_po erpnext.patches.v16_0.depends_on_inv_dimensions +erpnext.patches.v16_0.clear_procedures_from_receivable_report diff --git a/erpnext/patches/v16_0/clear_procedures_from_receivable_report.py b/erpnext/patches/v16_0/clear_procedures_from_receivable_report.py new file mode 100644 index 00000000000..c362d4ef605 --- /dev/null +++ b/erpnext/patches/v16_0/clear_procedures_from_receivable_report.py @@ -0,0 +1,12 @@ +import frappe + + +def execute(): + frappe.db.sql("drop function if exists ar_genkey") + frappe.db.sql("drop procedure if exists ar_init_tmp_table") + frappe.db.sql("drop procedure if exists ar_allocate_to_tmp_table") + + if frappe.db.get_single_value("Accounts Settings", "receivable_payable_fetch_method") == "Raw SQL": + frappe.db.set_single_value( + "Accounts Settings", "receivable_payable_fetch_method", "UnBuffered Cursor" + ) diff --git a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py index 8c2b559fe52..e6f94689480 100644 --- a/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/test_uae_vat_201.py @@ -6,6 +6,7 @@ import erpnext from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.regional.report.uae_vat_201.uae_vat_201 import ( + execute, get_exempt_total, get_standard_rated_expenses_tax, get_standard_rated_expenses_total, @@ -39,6 +40,13 @@ class TestUaeVat201(TestCase): make_item("_Test UAE VAT Zero Rated Item", properties={"is_zero_rated": 1, "is_exempt": 0}) make_item("_Test UAE VAT Exempt Item", properties={"is_zero_rated": 0, "is_exempt": 1}) + def test_validate_company_region(self): + self.assertRaises( + frappe.exceptions.ValidationError, + execute, + {"company": "_Test Company"}, + ) + def test_uae_vat_201_report(self): make_sales_invoices() create_purchase_invoices() diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.js b/erpnext/regional/report/uae_vat_201/uae_vat_201.js index 49060fdf66a..e62d3395f20 100644 --- a/erpnext/regional/report/uae_vat_201/uae_vat_201.js +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.js @@ -10,6 +10,13 @@ frappe.query_reports["UAE VAT 201"] = { options: "Company", reqd: 1, default: frappe.defaults.get_user_default("Company"), + get_query: function () { + return { + filters: { + country: "United Arab Emirates", + }, + }; + }, }, { fieldname: "from_date", diff --git a/erpnext/regional/report/uae_vat_201/uae_vat_201.py b/erpnext/regional/report/uae_vat_201/uae_vat_201.py index 7cf86adbe01..05f213d18dd 100644 --- a/erpnext/regional/report/uae_vat_201/uae_vat_201.py +++ b/erpnext/regional/report/uae_vat_201/uae_vat_201.py @@ -5,13 +5,25 @@ import frappe from frappe import _ +from erpnext import get_region + def execute(filters=None): + validate_company_region(filters) columns = get_columns() data, emirates, amounts_by_emirate = get_data(filters) return columns, data +def validate_company_region(filters): + if filters.get("company") and get_region(filters.get("company")) != "United Arab Emirates": + frappe.throw( + _( + "The company {0} is not in United Arab Emirates. UAE VAT 201 report is only available for companies in United Arab Emirates." + ).format(frappe.bold(filters.get("company"))) + ) + + def get_columns(): """Creates a list of dictionaries that are used to generate column headers of the data table.""" return [ diff --git a/erpnext/startup/leaderboard.py b/erpnext/startup/leaderboard.py index 06f61fd961e..af168332d4f 100644 --- a/erpnext/startup/leaderboard.py +++ b/erpnext/startup/leaderboard.py @@ -88,7 +88,7 @@ def get_all_customers(date_range, company, field, limit=None): @frappe.whitelist() -def get_all_items(date_range, company, field, limit=None): +def get_all_items(date_range: str, company: str, field: str, limit: int | None = None): if field in ("available_stock_qty", "available_stock_value"): select_field = "sum(actual_qty)" if field == "available_stock_qty" else "sum(stock_value)" results = frappe.db.get_all( @@ -103,21 +103,21 @@ def get_all_items(date_range, company, field, limit=None): else: if field == "total_sales_amount": select_field = "base_net_amount" - select_doctype = "Sales Order" + select_doctype = "Sales Invoice" elif field == "total_purchase_amount": select_field = "base_net_amount" - select_doctype = "Purchase Order" + select_doctype = "Purchase Invoice" elif field == "total_qty_sold": select_field = "stock_qty" - select_doctype = "Sales Order" + select_doctype = "Sales Invoice" elif field == "total_qty_purchased": select_field = "stock_qty" - select_doctype = "Purchase Order" + select_doctype = "Purchase Invoice" filters = [["docstatus", "=", "1"], ["company", "=", company]] from_date, to_date = parse_date_range(date_range) if from_date and to_date: - filters.append(["transaction_date", "between", [from_date, to_date]]) + filters.append(["posting_date", "between", [from_date, to_date]]) child_doctype = f"{select_doctype} Item" return frappe.get_list( diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index ee832b8c6bb..a367dc2dc95 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -774,7 +774,7 @@ class StockEntry(StockController): else: frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx)) - if self.purpose == "Manufacture": + if self.purpose in ["Manufacture", "Repack"]: if d.is_finished_item or d.is_scrap_item: d.s_warehouse = None if not d.t_warehouse: diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 00b5c9bd5a1..dfbbdc9254e 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -82,6 +82,7 @@ class StockReconciliation(StockController): self.set_total_qty_and_amount() self.validate_putaway_capacity() self.validate_inventory_dimension() + self.validate_uom_is_integer("stock_uom", "qty") if self._action == "submit": self.validate_reserved_stock() diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index e055de42adf..8c205a10874 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -1044,7 +1044,7 @@ def insert_item_price(args): ) item_price.insert() frappe.msgprint( - _("Item Price Added for {0} in Price List {1}").format( + _("Item Price added for {0} in Price List - {1}").format( get_link_to_form("Item", args.item_code), args.price_list ), alert=True, @@ -1070,7 +1070,9 @@ def insert_item_price(args): ) item_price.insert() frappe.msgprint( - _("Item Price added for {0} in Price List {1}").format(args.item_code, args.price_list), + _("Item Price added for {0} in Price List - {1}").format( + get_link_to_form("Item", args.item_code), args.price_list + ), alert=True, ) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 9cbce196e94..ffdb97ef9a6 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -2480,7 +2480,9 @@ def get_stock_value_difference( ) if voucher_detail_no: - query = query.where(table.voucher_detail_no != voucher_detail_no) + query = query.where( + (table.voucher_detail_no != voucher_detail_no) | (table.voucher_detail_no.isnull()) + ) elif voucher_no: query = query.where(table.voucher_no != voucher_no)