mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-20 21:49:18 +00:00
Merge pull request #55052 from frappe/version-15-hotfix
chore: release v15
This commit is contained in:
@@ -30,16 +30,6 @@ frappe.ui.form.on("Accounts Settings", {
|
|||||||
add_taxes_from_item_tax_template(frm) {
|
add_taxes_from_item_tax_template(frm) {
|
||||||
toggle_tax_settings(frm, "add_taxes_from_item_tax_template");
|
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) {
|
function toggle_tax_settings(frm, field_name) {
|
||||||
|
|||||||
@@ -95,7 +95,6 @@
|
|||||||
"receivable_payable_fetch_method",
|
"receivable_payable_fetch_method",
|
||||||
"default_ageing_range",
|
"default_ageing_range",
|
||||||
"column_break_ntmi",
|
"column_break_ntmi",
|
||||||
"drop_ar_procedures",
|
|
||||||
"legacy_section",
|
"legacy_section",
|
||||||
"ignore_is_opening_check_for_reporting",
|
"ignore_is_opening_check_for_reporting",
|
||||||
"payment_request_settings",
|
"payment_request_settings",
|
||||||
@@ -561,7 +560,7 @@
|
|||||||
"fieldname": "receivable_payable_fetch_method",
|
"fieldname": "receivable_payable_fetch_method",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
"label": "Data Fetch Method",
|
"label": "Data Fetch Method",
|
||||||
"options": "Buffered Cursor\nUnBuffered Cursor\nRaw SQL"
|
"options": "Buffered Cursor\nUnBuffered Cursor"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "accounts_receivable_payable_tuning_section",
|
"fieldname": "accounts_receivable_payable_tuning_section",
|
||||||
@@ -623,13 +622,6 @@
|
|||||||
"fieldname": "column_break_ntmi",
|
"fieldname": "column_break_ntmi",
|
||||||
"fieldtype": "Column Break"
|
"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",
|
"default": "0",
|
||||||
"fieldname": "fetch_valuation_rate_for_internal_transaction",
|
"fieldname": "fetch_valuation_rate_for_internal_transaction",
|
||||||
@@ -671,7 +663,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2026-03-06 14:49:11.467716",
|
"modified": "2026-05-18 12:16:33.679345",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounts Settings",
|
"name": "Accounts Settings",
|
||||||
@@ -701,4 +693,4 @@
|
|||||||
"sort_order": "ASC",
|
"sort_order": "ASC",
|
||||||
"states": [],
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class AccountsSettings(Document):
|
|||||||
merge_similar_account_heads: DF.Check
|
merge_similar_account_heads: DF.Check
|
||||||
over_billing_allowance: DF.Currency
|
over_billing_allowance: DF.Currency
|
||||||
post_change_gl_entries: DF.Check
|
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
|
receivable_payable_remarks_length: DF.Int
|
||||||
reconciliation_queue_size: DF.Int
|
reconciliation_queue_size: DF.Int
|
||||||
role_allowed_to_over_bill: DF.Link | None
|
role_allowed_to_over_bill: DF.Link | None
|
||||||
@@ -170,11 +170,3 @@ class AccountsSettings(Document):
|
|||||||
),
|
),
|
||||||
title=_("Auto Tax Settings Error"),
|
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}")
|
|
||||||
|
|||||||
@@ -725,31 +725,12 @@ frappe.ui.form.on("Payment Entry", {
|
|||||||
if (!frm.doc.paid_from_account_currency || !frm.doc.company) return;
|
if (!frm.doc.paid_from_account_currency || !frm.doc.company) return;
|
||||||
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
|
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
|
||||||
|
|
||||||
if (frm.doc.paid_from_account_currency == company_currency) {
|
frm.events.set_current_exchange_rate(
|
||||||
frm.set_value("source_exchange_rate", 1);
|
frm,
|
||||||
} else if (frm.doc.paid_from) {
|
"source_exchange_rate",
|
||||||
if (["Internal Transfer", "Pay"].includes(frm.doc.payment_type)) {
|
frm.doc.paid_from_account_currency,
|
||||||
let company_currency = frappe.get_doc(":Company", frm.doc.company)?.default_currency;
|
company_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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
paid_to_account_currency: function (frm) {
|
paid_to_account_currency: function (frm) {
|
||||||
@@ -781,49 +762,24 @@ frappe.ui.form.on("Payment Entry", {
|
|||||||
|
|
||||||
posting_date: function (frm) {
|
posting_date: function (frm) {
|
||||||
frm.events.paid_from_account_currency(frm);
|
frm.events.paid_from_account_currency(frm);
|
||||||
|
frm.events.paid_to_account_currency(frm);
|
||||||
},
|
},
|
||||||
|
|
||||||
source_exchange_rate: function (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;
|
frm.set_paid_amount_based_on_received_amount = true;
|
||||||
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
|
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
|
||||||
|
|
||||||
if (frm.doc.received_amount) {
|
if (frm.doc.base_received_amount && frm.doc.source_exchange_rate) {
|
||||||
frm.set_value(
|
frm.set_value("base_paid_amount", frm.doc.base_received_amount);
|
||||||
"base_received_amount",
|
|
||||||
flt(frm.doc.received_amount) * flt(frm.doc.target_exchange_rate)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
// target exchange rate should always be same as source if both account currencies is same
|
||||||
!frm.doc.source_exchange_rate &&
|
if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) {
|
||||||
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("source_exchange_rate", frm.doc.target_exchange_rate);
|
frm.set_value(
|
||||||
frm.set_value("base_paid_amount", frm.doc.base_received_amount);
|
"paid_amount",
|
||||||
} else if (company_currency == frm.doc.paid_from_account_currency) {
|
flt(frm.doc.base_paid_amount) / flt(frm.doc.source_exchange_rate)
|
||||||
frm.set_value("paid_amount", frm.doc.base_received_amount);
|
);
|
||||||
frm.set_value("base_paid_amount", frm.doc.base_received_amount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// set_unallocated_amount is called by below method,
|
// 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;
|
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
|
// 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);
|
frm.set_df_property("target_exchange_rate", "read_only", erpnext.stale_rate_allowed() ? 0 : 1);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -350,7 +350,7 @@
|
|||||||
"reqd": 1
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "doc.received_amount",
|
"depends_on": "eval:doc.received_amount;",
|
||||||
"fieldname": "base_received_amount",
|
"fieldname": "base_received_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Received Amount (Company Currency)",
|
"label": "Received Amount (Company Currency)",
|
||||||
@@ -800,7 +800,7 @@
|
|||||||
"table_fieldname": "payment_entries"
|
"table_fieldname": "payment_entries"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2025-05-15 18:01:04.013025",
|
"modified": "2026-05-15 13:31:01.166010",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Payment Entry",
|
"name": "Payment Entry",
|
||||||
|
|||||||
@@ -669,7 +669,7 @@ def validate_due_date_with_template(posting_date, due_date, bill_date, template_
|
|||||||
if not default_due_date:
|
if not default_due_date:
|
||||||
return
|
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():
|
if frappe.db.get_single_value("Accounts Settings", "credit_controller") in frappe.get_roles():
|
||||||
party_type = "supplier" if doctype == "Purchase Invoice" else "customer"
|
party_type = "supplier" if doctype == "Purchase Invoice" else "customer"
|
||||||
|
|
||||||
|
|||||||
@@ -129,8 +129,6 @@ class ReceivablePayableReport:
|
|||||||
self.fetch_ple_in_buffered_cursor()
|
self.fetch_ple_in_buffered_cursor()
|
||||||
elif self.ple_fetch_method == "UnBuffered Cursor":
|
elif self.ple_fetch_method == "UnBuffered Cursor":
|
||||||
self.fetch_ple_in_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
|
# Build delivery note map against all sales invoices
|
||||||
self.build_delivery_note_map()
|
self.build_delivery_note_map()
|
||||||
@@ -321,81 +319,6 @@ class ReceivablePayableReport:
|
|||||||
row.paid -= amount
|
row.paid -= amount
|
||||||
row.paid_in_account_currency -= amount_in_account_currency
|
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):
|
def update_sub_total_row(self, row, party):
|
||||||
total_row = self.total_row_map.get(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))
|
frappe.throw(_("{0}: {1} does not exist").format(group_dtype, d))
|
||||||
|
|
||||||
return list(set(all_party_groups))
|
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)
|
|
||||||
|
|||||||
@@ -786,19 +786,11 @@ class GrossProfitGenerator:
|
|||||||
return self.calculate_buying_amount_from_sle(
|
return self.calculate_buying_amount_from_sle(
|
||||||
row, my_sle, parenttype, parent, row.item_row, item_code
|
row, my_sle, parenttype, parent, row.item_row, item_code
|
||||||
)
|
)
|
||||||
elif self.delivery_notes.get((row.parent, row.item_code), None):
|
elif row.item_row and self.delivery_notes.get(row.item_row):
|
||||||
# check if Invoice has delivery notes
|
dn = self.delivery_notes[row.item_row]
|
||||||
dn = self.delivery_notes.get((row.parent, row.item_code))
|
if flt(dn.total_qty):
|
||||||
parenttype, parent, item_row, dn_warehouse = (
|
return flt(row.qty) * flt(dn.total_incoming_value) / flt(dn.total_qty)
|
||||||
"Delivery Note",
|
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
|
||||||
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.sales_order and row.so_detail:
|
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)
|
incoming_amount = self.get_buying_amount_from_so_dn(row.sales_order, row.so_detail, item_code)
|
||||||
if incoming_amount:
|
if incoming_amount:
|
||||||
@@ -1049,25 +1041,29 @@ class GrossProfitGenerator:
|
|||||||
def get_delivery_notes(self):
|
def get_delivery_notes(self):
|
||||||
self.delivery_notes = frappe._dict({})
|
self.delivery_notes = frappe._dict({})
|
||||||
if self.si_list:
|
if self.si_list:
|
||||||
|
from frappe.query_builder.functions import Sum
|
||||||
|
|
||||||
invoices = [x.parent for x in self.si_list]
|
invoices = [x.parent for x in self.si_list]
|
||||||
dni = qb.DocType("Delivery Note Item")
|
dni = qb.DocType("Delivery Note Item")
|
||||||
delivery_notes = (
|
delivery_notes = (
|
||||||
qb.from_(dni)
|
qb.from_(dni)
|
||||||
.select(
|
.select(
|
||||||
dni.against_sales_invoice.as_("sales_invoice"),
|
dni.si_detail,
|
||||||
dni.item_code,
|
Sum(dni.stock_qty * dni.incoming_rate).as_("total_incoming_value"),
|
||||||
dni.warehouse,
|
Sum(dni.stock_qty).as_("total_qty"),
|
||||||
dni.parent.as_("delivery_note"),
|
|
||||||
dni.name.as_("item_row"),
|
|
||||||
)
|
)
|
||||||
.where((dni.docstatus == 1) & (dni.against_sales_invoice.isin(invoices)))
|
.where(
|
||||||
.groupby(dni.against_sales_invoice, dni.item_code)
|
(dni.docstatus == 1)
|
||||||
.orderby(dni.creation, order=Order.desc)
|
& (dni.against_sales_invoice.isin(invoices))
|
||||||
|
& (dni.si_detail.isnotnull())
|
||||||
|
& (dni.si_detail != "")
|
||||||
|
)
|
||||||
|
.groupby(dni.si_detail)
|
||||||
.run(as_dict=True)
|
.run(as_dict=True)
|
||||||
)
|
)
|
||||||
|
|
||||||
for entry in delivery_notes:
|
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):
|
def group_items_by_invoice(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -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.add_portal_redirects
|
||||||
erpnext.patches.v16_0.update_order_qty_and_requested_qty_based_on_mr_and_po
|
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.depends_on_inv_dimensions
|
||||||
|
erpnext.patches.v16_0.clear_procedures_from_receivable_report
|
||||||
|
|||||||
@@ -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"
|
||||||
|
)
|
||||||
@@ -6,6 +6,7 @@ import erpnext
|
|||||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
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.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
from erpnext.regional.report.uae_vat_201.uae_vat_201 import (
|
from erpnext.regional.report.uae_vat_201.uae_vat_201 import (
|
||||||
|
execute,
|
||||||
get_exempt_total,
|
get_exempt_total,
|
||||||
get_standard_rated_expenses_tax,
|
get_standard_rated_expenses_tax,
|
||||||
get_standard_rated_expenses_total,
|
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 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})
|
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):
|
def test_uae_vat_201_report(self):
|
||||||
make_sales_invoices()
|
make_sales_invoices()
|
||||||
create_purchase_invoices()
|
create_purchase_invoices()
|
||||||
|
|||||||
@@ -10,6 +10,13 @@ frappe.query_reports["UAE VAT 201"] = {
|
|||||||
options: "Company",
|
options: "Company",
|
||||||
reqd: 1,
|
reqd: 1,
|
||||||
default: frappe.defaults.get_user_default("Company"),
|
default: frappe.defaults.get_user_default("Company"),
|
||||||
|
get_query: function () {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
country: "United Arab Emirates",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: "from_date",
|
fieldname: "from_date",
|
||||||
|
|||||||
@@ -5,13 +5,25 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
|
||||||
|
from erpnext import get_region
|
||||||
|
|
||||||
|
|
||||||
def execute(filters=None):
|
def execute(filters=None):
|
||||||
|
validate_company_region(filters)
|
||||||
columns = get_columns()
|
columns = get_columns()
|
||||||
data, emirates, amounts_by_emirate = get_data(filters)
|
data, emirates, amounts_by_emirate = get_data(filters)
|
||||||
return columns, data
|
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():
|
def get_columns():
|
||||||
"""Creates a list of dictionaries that are used to generate column headers of the data table."""
|
"""Creates a list of dictionaries that are used to generate column headers of the data table."""
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ def get_all_customers(date_range, company, field, limit=None):
|
|||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@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"):
|
if field in ("available_stock_qty", "available_stock_value"):
|
||||||
select_field = "sum(actual_qty)" if field == "available_stock_qty" else "sum(stock_value)"
|
select_field = "sum(actual_qty)" if field == "available_stock_qty" else "sum(stock_value)"
|
||||||
results = frappe.db.get_all(
|
results = frappe.db.get_all(
|
||||||
@@ -103,21 +103,21 @@ def get_all_items(date_range, company, field, limit=None):
|
|||||||
else:
|
else:
|
||||||
if field == "total_sales_amount":
|
if field == "total_sales_amount":
|
||||||
select_field = "base_net_amount"
|
select_field = "base_net_amount"
|
||||||
select_doctype = "Sales Order"
|
select_doctype = "Sales Invoice"
|
||||||
elif field == "total_purchase_amount":
|
elif field == "total_purchase_amount":
|
||||||
select_field = "base_net_amount"
|
select_field = "base_net_amount"
|
||||||
select_doctype = "Purchase Order"
|
select_doctype = "Purchase Invoice"
|
||||||
elif field == "total_qty_sold":
|
elif field == "total_qty_sold":
|
||||||
select_field = "stock_qty"
|
select_field = "stock_qty"
|
||||||
select_doctype = "Sales Order"
|
select_doctype = "Sales Invoice"
|
||||||
elif field == "total_qty_purchased":
|
elif field == "total_qty_purchased":
|
||||||
select_field = "stock_qty"
|
select_field = "stock_qty"
|
||||||
select_doctype = "Purchase Order"
|
select_doctype = "Purchase Invoice"
|
||||||
|
|
||||||
filters = [["docstatus", "=", "1"], ["company", "=", company]]
|
filters = [["docstatus", "=", "1"], ["company", "=", company]]
|
||||||
from_date, to_date = parse_date_range(date_range)
|
from_date, to_date = parse_date_range(date_range)
|
||||||
if from_date and to_date:
|
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"
|
child_doctype = f"{select_doctype} Item"
|
||||||
return frappe.get_list(
|
return frappe.get_list(
|
||||||
|
|||||||
@@ -774,7 +774,7 @@ class StockEntry(StockController):
|
|||||||
else:
|
else:
|
||||||
frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
|
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:
|
if d.is_finished_item or d.is_scrap_item:
|
||||||
d.s_warehouse = None
|
d.s_warehouse = None
|
||||||
if not d.t_warehouse:
|
if not d.t_warehouse:
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ class StockReconciliation(StockController):
|
|||||||
self.set_total_qty_and_amount()
|
self.set_total_qty_and_amount()
|
||||||
self.validate_putaway_capacity()
|
self.validate_putaway_capacity()
|
||||||
self.validate_inventory_dimension()
|
self.validate_inventory_dimension()
|
||||||
|
self.validate_uom_is_integer("stock_uom", "qty")
|
||||||
|
|
||||||
if self._action == "submit":
|
if self._action == "submit":
|
||||||
self.validate_reserved_stock()
|
self.validate_reserved_stock()
|
||||||
|
|||||||
@@ -1044,7 +1044,7 @@ def insert_item_price(args):
|
|||||||
)
|
)
|
||||||
item_price.insert()
|
item_price.insert()
|
||||||
frappe.msgprint(
|
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
|
get_link_to_form("Item", args.item_code), args.price_list
|
||||||
),
|
),
|
||||||
alert=True,
|
alert=True,
|
||||||
@@ -1070,7 +1070,9 @@ def insert_item_price(args):
|
|||||||
)
|
)
|
||||||
item_price.insert()
|
item_price.insert()
|
||||||
frappe.msgprint(
|
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,
|
alert=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -2480,7 +2480,9 @@ def get_stock_value_difference(
|
|||||||
)
|
)
|
||||||
|
|
||||||
if voucher_detail_no:
|
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:
|
elif voucher_no:
|
||||||
query = query.where(table.voucher_no != voucher_no)
|
query = query.where(table.voucher_no != voucher_no)
|
||||||
|
|||||||
Reference in New Issue
Block a user