From 534b25c4485079ab4e725c296f31aa65ef9d8f6a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 13 Feb 2025 13:19:34 +0530 Subject: [PATCH 01/19] fix: '0' rate LDC's Invoice net totals should be ignored (backport #45639) (#45783) * fix: '0' rate LDC's Invoice net totals should be ignored (cherry picked from commit 325c4e3536aaf84be0d7fb9ff9850360b2eeb2bd) # Conflicts: # erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py * test: ldc @ 0 rate (cherry picked from commit 0cdd346f8fd2000cba7591d3b03bf0db7b69f158) # Conflicts: # erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py * chore: resolve conflicts * fix: incorrect parameters * fix: ignore 0 rate ldc invoices --------- Co-authored-by: ruthra kumar --- .../tax_withholding_category.py | 58 ++++++++++++++++--- .../test_tax_withholding_category.py | 56 ++++++++++++++++-- 2 files changed, 101 insertions(+), 13 deletions(-) diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 2115d44322d..da329324e95 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -236,7 +236,7 @@ def get_lower_deduction_certificate(company, posting_date, tax_details, pan_no): def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=None): - vouchers, voucher_wise_amount = get_invoice_vouchers( + vouchers, voucher_wise_amount, zero_rate_ldc_invoices = get_invoice_vouchers( parties, tax_details, inv.company, party_type=party_type ) @@ -290,7 +290,8 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N # once tds is deducted, not need to add vouchers in the invoice voucher_wise_amount = {} else: - tax_amount = get_tds_amount(ldc, parties, inv, tax_details, vouchers) + taxable_vouchers = list(set(vouchers) - set(zero_rate_ldc_invoices)) + tax_amount = get_tds_amount(ldc, parties, inv, tax_details, taxable_vouchers) elif party_type == "Customer": if tax_deducted: @@ -309,12 +310,33 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"): doctype = "Purchase Invoice" if party_type == "Supplier" else "Sales Invoice" - field = ( - "base_tax_withholding_net_total as base_net_total" if party_type == "Supplier" else "base_net_total" - ) + field = [ + "name", + "base_tax_withholding_net_total as base_net_total" if party_type == "Supplier" else "base_net_total", + "posting_date", + ] voucher_wise_amount = {} vouchers = [] + ldcs = frappe.db.get_all( + "Lower Deduction Certificate", + filters={ + "valid_from": [">=", tax_details.from_date], + "valid_upto": ["<=", tax_details.to_date], + "company": company, + "supplier": ["in", parties], + }, + fields=["supplier", "valid_from", "valid_upto", "rate"], + ) + + doctype = "Purchase Invoice" if party_type == "Supplier" else "Sales Invoice" + field = [ + "base_tax_withholding_net_total as base_net_total" if party_type == "Supplier" else "base_net_total", + "name", + "grand_total", + "posting_date", + ] + filters = { "company": company, frappe.scrub(party_type): ["in", parties], @@ -328,11 +350,31 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"): {"apply_tds": 1, "tax_withholding_category": tax_details.get("tax_withholding_category")} ) - invoices_details = frappe.get_all(doctype, filters=filters, fields=["name", field]) + invoices_details = frappe.get_all(doctype, filters=filters, fields=field) + ldcs = frappe.db.get_all( + "Lower Deduction Certificate", + filters={ + "valid_from": [">=", tax_details.from_date], + "valid_upto": ["<=", tax_details.to_date], + "company": company, + "supplier": ["in", parties], + "rate": 0, + }, + fields=["name", "supplier", "valid_from", "valid_upto"], + ) + + zero_rate_ldc_invoices = [] for d in invoices_details: vouchers.append(d.name) - voucher_wise_amount.update({d.name: {"amount": d.base_net_total, "voucher_type": doctype}}) + _voucher_detail = {"amount": d.base_net_total, "voucher_type": doctype} + + if ldc := [x for x in ldcs if d.posting_date >= x.valid_from and d.posting_date <= x.valid_upto]: + if ldc[0].supplier in parties: + _voucher_detail.update({"amount": 0}) + zero_rate_ldc_invoices.append(d.name) + + voucher_wise_amount.update({d.name: _voucher_detail}) journal_entries_details = frappe.db.sql( """ @@ -363,7 +405,7 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"): vouchers.append(d.name) voucher_wise_amount.update({d.name: {"amount": d.amount, "voucher_type": "Journal Entry"}}) - return vouchers, voucher_wise_amount + return vouchers, voucher_wise_amount, zero_rate_ldc_invoices def get_payment_entry_vouchers(parties, tax_details, company, party_type="Supplier"): diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py index d459b77865c..6a7fc3c43d2 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py @@ -7,7 +7,7 @@ import unittest import frappe from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from frappe.tests.utils import FrappeTestCase, change_settings -from frappe.utils import add_days, today +from frappe.utils import add_days, add_months, today from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry from erpnext.accounts.utils import get_fiscal_year @@ -614,6 +614,49 @@ class TestTaxWithholdingCategory(FrappeTestCase): pi2.cancel() pi3.cancel() + def test_ldc_at_0_rate(self): + frappe.db.set_value( + "Supplier", + "Test LDC Supplier", + { + "tax_withholding_category": "Test Service Category", + "pan": "ABCTY1234D", + }, + ) + + fiscal_year = get_fiscal_year(today(), company="_Test Company") + valid_from = fiscal_year[1] + valid_upto = add_months(valid_from, 1) + create_lower_deduction_certificate( + supplier="Test LDC Supplier", + certificate_no="1AE0423AAJ", + tax_withholding_category="Test Service Category", + tax_rate=0, + limit=50000, + valid_from=valid_from, + valid_upto=valid_upto, + ) + + pi1 = create_purchase_invoice( + supplier="Test LDC Supplier", rate=35000, posting_date=valid_from, set_posting_time=True + ) + pi1.submit() + self.assertEqual(pi1.taxes, []) + + pi2 = create_purchase_invoice( + supplier="Test LDC Supplier", + rate=35000, + posting_date=add_days(valid_upto, 1), + set_posting_time=True, + ) + pi2.submit() + self.assertEqual(len(pi2.taxes), 1) + # pi1 net total shouldn't be included as it lies within LDC at rate of '0' + self.assertEqual(pi2.taxes[0].tax_amount, 3500) + + pi1.cancel() + pi2.cancel() + def set_previous_fy_and_tax_category(self): test_company = "_Test Company" category = "Cumulative Threshold TDS" @@ -771,7 +814,8 @@ def create_purchase_invoice(**args): pi = frappe.get_doc( { "doctype": "Purchase Invoice", - "posting_date": today(), + "set_posting_time": args.set_posting_time or False, + "posting_date": args.posting_date or today(), "apply_tds": 0 if args.do_not_apply_tds else 1, "supplier": args.supplier, "company": "_Test Company", @@ -1099,7 +1143,9 @@ def create_tax_withholding_category( ).insert() -def create_lower_deduction_certificate(supplier, tax_withholding_category, tax_rate, certificate_no, limit): +def create_lower_deduction_certificate( + supplier, tax_withholding_category, tax_rate, certificate_no, limit, valid_from=None, valid_upto=None +): fiscal_year = get_fiscal_year(today(), company="_Test Company") if not frappe.db.exists("Lower Deduction Certificate", certificate_no): frappe.get_doc( @@ -1110,8 +1156,8 @@ def create_lower_deduction_certificate(supplier, tax_withholding_category, tax_r "certificate_no": certificate_no, "tax_withholding_category": tax_withholding_category, "fiscal_year": fiscal_year[0], - "valid_from": fiscal_year[1], - "valid_upto": fiscal_year[2], + "valid_from": valid_from or fiscal_year[1], + "valid_upto": valid_upto or fiscal_year[2], "rate": tax_rate, "certificate_limit": limit, } From aa0ada9670936ec483eb03d9d584967452909253 Mon Sep 17 00:00:00 2001 From: venkat102 Date: Sat, 8 Feb 2025 22:49:35 +0530 Subject: [PATCH 02/19] fix(report): add options to multiselectlist fields (cherry picked from commit 8785342fcee1c80662ab1a2bcf06280b69979fe5) # Conflicts: # erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js # erpnext/accounts/report/gross_profit/gross_profit.js # erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js # erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.js --- .../accounts_payable/accounts_payable.js | 1 + .../accounts_payable_summary.js | 1 + .../accounts_receivable.js | 1 + .../accounts_receivable_summary.js | 1 + .../budget_variance_report.js | 1 + .../customer_ledger_summary.js | 25 +++++ .../report/general_ledger/general_ledger.js | 3 + .../report/gross_profit/gross_profit.js | 37 +++++++ .../report/payment_ledger/payment_ledger.js | 1 + .../supplier_ledger_summary.js | 25 +++++ .../purchase_order_analysis.js | 1 + .../supplier_quotation_comparison.js | 2 + .../opportunity_summary_by_sales_stage.js | 1 + .../job_card_summary/job_card_summary.js | 2 + .../production_planning_report.js | 2 +- .../work_order_summary/work_order_summary.js | 2 + .../payment_terms_status_for_sales_order.js | 1 + .../sales_order_analysis.js | 1 + .../item_shortage_report.js | 1 + .../serial_and_batch_summary.js | 96 +++++++++++++++++++ 20 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.js diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js index 171a94e1151..44fa96e3c0d 100644 --- a/erpnext/accounts/report/accounts_payable/accounts_payable.js +++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js @@ -111,6 +111,7 @@ frappe.query_reports["Accounts Payable"] = { fieldname: "party", label: __("Party"), fieldtype: "MultiSelectList", + options: "party_type", get_data: function (txt) { if (!frappe.query_report.filters) return; diff --git a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js index 92ea9e8f598..24b82c56d89 100644 --- a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js +++ b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js @@ -88,6 +88,7 @@ frappe.query_reports["Accounts Payable Summary"] = { fieldname: "party", label: __("Party"), fieldtype: "MultiSelectList", + options: "party_type", get_data: function (txt) { if (!frappe.query_report.filters) return; diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js index 7e4563ee85e..4b60ba8adc0 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js @@ -56,6 +56,7 @@ frappe.query_reports["Accounts Receivable"] = { fieldname: "party", label: __("Party"), fieldtype: "MultiSelectList", + options: "party_type", get_data: function (txt) { if (!frappe.query_report.filters) return; diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js index 964abc23747..df141d0732a 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js @@ -88,6 +88,7 @@ frappe.query_reports["Accounts Receivable Summary"] = { fieldname: "party", label: __("Party"), fieldtype: "MultiSelectList", + options: "party_type", get_data: function (txt) { if (!frappe.query_report.filters) return; diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.js b/erpnext/accounts/report/budget_variance_report/budget_variance_report.js index 83bd48c71f3..c74450191aa 100644 --- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.js +++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.js @@ -91,6 +91,7 @@ function get_filters() { fieldname: "budget_against_filter", label: __("Dimension Filter"), fieldtype: "MultiSelectList", + options: "budget_against", get_data: function (txt) { if (!frappe.query_report.filters) return; diff --git a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js index dec2ebd2520..fac7ffeb27f 100644 --- a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js +++ b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js @@ -93,5 +93,30 @@ frappe.query_reports["Customer Ledger Summary"] = { fieldtype: "Data", hidden: 1, }, +<<<<<<< HEAD +======= + { + fieldname: "cost_center", + label: __("Cost Center"), + fieldtype: "MultiSelectList", + options: "Cost Center", + get_data: function (txt) { + return frappe.db.get_link_options("Cost Center", txt, { + company: frappe.query_report.get_filter_value("company"), + }); + }, + }, + { + fieldname: "project", + label: __("Project"), + fieldtype: "MultiSelectList", + options: "Project", + get_data: function (txt) { + return frappe.db.get_link_options("Project", txt, { + company: frappe.query_report.get_filter_value("company"), + }); + }, + }, +>>>>>>> 8785342fce (fix(report): add options to multiselectlist fields) ], }; diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index 3431b76dfb1..40455fa882d 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -68,6 +68,7 @@ frappe.query_reports["General Ledger"] = { fieldname: "party", label: __("Party"), fieldtype: "MultiSelectList", + options: "party_type", get_data: function (txt) { if (!frappe.query_report.filters) return; @@ -146,6 +147,7 @@ frappe.query_reports["General Ledger"] = { fieldname: "cost_center", label: __("Cost Center"), fieldtype: "MultiSelectList", + options: "Cost Center", get_data: function (txt) { return frappe.db.get_link_options("Cost Center", txt, { company: frappe.query_report.get_filter_value("company"), @@ -156,6 +158,7 @@ frappe.query_reports["General Ledger"] = { fieldname: "project", label: __("Project"), fieldtype: "MultiSelectList", + options: "Project", get_data: function (txt) { return frappe.db.get_link_options("Project", txt, { company: frappe.query_report.get_filter_value("company"), diff --git a/erpnext/accounts/report/gross_profit/gross_profit.js b/erpnext/accounts/report/gross_profit/gross_profit.js index 64a9838f54b..1f5acdae98c 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.js +++ b/erpnext/accounts/report/gross_profit/gross_profit.js @@ -51,6 +51,43 @@ frappe.query_reports["Gross Profit"] = { fieldtype: "Link", options: "Sales Person", }, +<<<<<<< HEAD +======= + { + fieldname: "warehouse", + label: __("Warehouse"), + fieldtype: "Link", + options: "Warehouse", + get_query: function () { + var company = frappe.query_report.get_filter_value("company"); + return { + filters: [["Warehouse", "company", "=", company]], + }; + }, + }, + { + fieldname: "cost_center", + label: __("Cost Center"), + fieldtype: "MultiSelectList", + options: "Cost Center", + get_data: function (txt) { + return frappe.db.get_link_options("Cost Center", txt, { + company: frappe.query_report.get_filter_value("company"), + }); + }, + }, + { + fieldname: "project", + label: __("Project"), + fieldtype: "MultiSelectList", + options: "Project", + get_data: function (txt) { + return frappe.db.get_link_options("Project", txt, { + company: frappe.query_report.get_filter_value("company"), + }); + }, + }, +>>>>>>> 8785342fce (fix(report): add options to multiselectlist fields) ], tree: true, name_field: "parent", diff --git a/erpnext/accounts/report/payment_ledger/payment_ledger.js b/erpnext/accounts/report/payment_ledger/payment_ledger.js index e0ea7522b12..0121580858f 100644 --- a/erpnext/accounts/report/payment_ledger/payment_ledger.js +++ b/erpnext/accounts/report/payment_ledger/payment_ledger.js @@ -51,6 +51,7 @@ function get_filters() { fieldname: "party", label: __("Party"), fieldtype: "MultiSelectList", + options: "party_type", get_data: function (txt) { if (!frappe.query_report.filters) return; diff --git a/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js b/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js index 1244a1b0168..cbf3bbcc738 100644 --- a/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js +++ b/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js @@ -75,5 +75,30 @@ frappe.query_reports["Supplier Ledger Summary"] = { fieldtype: "Data", hidden: 1, }, +<<<<<<< HEAD +======= + { + fieldname: "cost_center", + label: __("Cost Center"), + fieldtype: "MultiSelectList", + options: "Cost Center", + get_data: function (txt) { + return frappe.db.get_link_options("Cost Center", txt, { + company: frappe.query_report.get_filter_value("company"), + }); + }, + }, + { + fieldname: "project", + label: __("Project"), + fieldtype: "MultiSelectList", + options: "Project", + get_data: function (txt) { + return frappe.db.get_link_options("Project", txt, { + company: frappe.query_report.get_filter_value("company"), + }); + }, + }, +>>>>>>> 8785342fce (fix(report): add options to multiselectlist fields) ], }; diff --git a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js index ad2a9e88737..2d4e4be59dc 100644 --- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js +++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js @@ -53,6 +53,7 @@ frappe.query_reports["Purchase Order Analysis"] = { label: __("Status"), fieldtype: "MultiSelectList", width: "80", + options: ["To Pay", "To Bill", "To Receive", "To Receive and Bill", "Completed"], get_data: function (txt) { let status = ["To Bill", "To Receive", "To Receive and Bill", "Completed"]; let options = []; diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js index f7d0d947b61..05cd88633bc 100644 --- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js +++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js @@ -50,6 +50,7 @@ frappe.query_reports["Supplier Quotation Comparison"] = { fieldname: "supplier", label: __("Supplier"), fieldtype: "MultiSelectList", + options: "Supplier", get_data: function (txt) { return frappe.db.get_link_options("Supplier", txt); }, @@ -58,6 +59,7 @@ frappe.query_reports["Supplier Quotation Comparison"] = { fieldtype: "MultiSelectList", label: __("Supplier Quotation"), fieldname: "supplier_quotation", + options: "Supplier Quotation", default: "", get_data: function (txt) { return frappe.db.get_link_options("Supplier Quotation", txt, { docstatus: ["<", 2] }); diff --git a/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js index 5c449284ab6..50b554d2341 100644 --- a/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js +++ b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js @@ -32,6 +32,7 @@ frappe.query_reports["Opportunity Summary by Sales Stage"] = { fieldname: "status", label: __("Status"), fieldtype: "MultiSelectList", + options: ["Open", "Converted", "Quotation", "Replied"], get_data: function () { return [ { value: "Open", description: "Status" }, diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.js b/erpnext/manufacturing/report/job_card_summary/job_card_summary.js index aac687c1413..bd19211d82a 100644 --- a/erpnext/manufacturing/report/job_card_summary/job_card_summary.js +++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.js @@ -57,6 +57,7 @@ frappe.query_reports["Job Card Summary"] = { label: __("Work Orders"), fieldname: "work_order", fieldtype: "MultiSelectList", + options: "Work Order", get_data: function (txt) { return frappe.db.get_link_options("Work Order", txt); }, @@ -65,6 +66,7 @@ frappe.query_reports["Job Card Summary"] = { label: __("Production Item"), fieldname: "production_item", fieldtype: "MultiSelectList", + options: "Item", get_data: function (txt) { return frappe.db.get_link_options("Item", txt); }, diff --git a/erpnext/manufacturing/report/production_planning_report/production_planning_report.js b/erpnext/manufacturing/report/production_planning_report/production_planning_report.js index bde90504e67..6ace52b35e9 100644 --- a/erpnext/manufacturing/report/production_planning_report/production_planning_report.js +++ b/erpnext/manufacturing/report/production_planning_report/production_planning_report.js @@ -42,7 +42,7 @@ frappe.query_reports["Production Planning Report"] = { fieldname: "docnames", label: __("Document Name"), fieldtype: "MultiSelectList", - options: "Sales Order", + options: "based_on", get_data: function (txt) { if (!frappe.query_report.filters) return; diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.js b/erpnext/manufacturing/report/work_order_summary/work_order_summary.js index 67e6e706c59..2fa1bec92a0 100644 --- a/erpnext/manufacturing/report/work_order_summary/work_order_summary.js +++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.js @@ -43,6 +43,7 @@ frappe.query_reports["Work Order Summary"] = { label: __("Sales Orders"), fieldname: "sales_order", fieldtype: "MultiSelectList", + options: "Sales Order", get_data: function (txt) { return frappe.db.get_link_options("Sales Order", txt); }, @@ -51,6 +52,7 @@ frappe.query_reports["Work Order Summary"] = { label: __("Production Item"), fieldname: "production_item", fieldtype: "MultiSelectList", + options: "Item", get_data: function (txt) { return frappe.db.get_link_options("Item", txt); }, diff --git a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.js b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.js index 32c84b2538b..13d41a60961 100644 --- a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.js +++ b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.js @@ -88,6 +88,7 @@ function get_filters() { fieldname: "status", label: __("Status"), fieldtype: "MultiSelectList", + options: ["Overdue", "Unpaid", "Completed", "Partly Paid"], width: 100, get_data: function (txt) { let status = ["Overdue", "Unpaid", "Completed", "Partly Paid"]; diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js index 25089c4b870..672d28209cc 100644 --- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js +++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js @@ -48,6 +48,7 @@ frappe.query_reports["Sales Order Analysis"] = { fieldname: "status", label: __("Status"), fieldtype: "MultiSelectList", + options: ["To Pay", "To Bill", "To Deliver", "To Deliver and Bill", "Completed"], width: "80", get_data: function (txt) { let status = ["To Bill", "To Deliver", "To Deliver and Bill", "Completed"]; diff --git a/erpnext/stock/report/item_shortage_report/item_shortage_report.js b/erpnext/stock/report/item_shortage_report/item_shortage_report.js index 5a6a54734a4..4cacfdcccb6 100644 --- a/erpnext/stock/report/item_shortage_report/item_shortage_report.js +++ b/erpnext/stock/report/item_shortage_report/item_shortage_report.js @@ -17,6 +17,7 @@ frappe.query_reports["Item Shortage Report"] = { fieldname: "warehouse", label: __("Warehouse"), fieldtype: "MultiSelectList", + options: "Warehouse", width: "100", get_data: function (txt) { return frappe.db.get_link_options("Warehouse", txt); diff --git a/erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.js b/erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.js new file mode 100644 index 00000000000..ab1e2babc80 --- /dev/null +++ b/erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.js @@ -0,0 +1,96 @@ +// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Serial and Batch Summary"] = { + filters: [ + { + fieldname: "company", + label: __("Company"), + fieldtype: "Link", + options: "Company", + default: frappe.defaults.get_user_default("Company"), + }, + { + fieldname: "from_date", + label: __("From Date"), + fieldtype: "Date", + default: frappe.datetime.add_months(frappe.datetime.get_today(), -1), + }, + { + fieldname: "to_date", + label: __("To Date"), + fieldtype: "Date", + default: frappe.datetime.get_today(), + }, + { + fieldname: "item_code", + label: __("Item"), + fieldtype: "Link", + options: "Item", + }, + { + fieldname: "warehouse", + label: __("Warehouse"), + fieldtype: "Link", + options: "Warehouse", + }, + { + fieldname: "voucher_type", + label: __("Voucher Type"), + fieldtype: "Link", + options: "DocType", + get_query: function () { + return { + query: "erpnext.stock.report.serial_and_batch_summary.serial_and_batch_summary.get_voucher_type", + }; + }, + }, + { + fieldname: "voucher_no", + label: __("Voucher No"), + fieldtype: "MultiSelectList", + options: "voucher_type", + get_data: function (txt) { + if (!frappe.query_report.filters) return; + + let voucher_type = frappe.query_report.get_filter_value("voucher_type"); + if (!voucher_type) return; + + return frappe.db.get_link_options(voucher_type, txt); + }, + }, + { + fieldname: "serial_no", + label: __("Serial No"), + fieldtype: "Link", + options: "Serial No", + get_query: function () { + return { + query: "erpnext.stock.report.serial_and_batch_summary.serial_and_batch_summary.get_serial_nos", + filters: { + item_code: frappe.query_report.get_filter_value("item_code"), + voucher_type: frappe.query_report.get_filter_value("voucher_type"), + voucher_no: frappe.query_report.get_filter_value("voucher_no"), + }, + }; + }, + }, + { + fieldname: "batch_no", + label: __("Batch No"), + fieldtype: "Link", + options: "Batch", + get_query: function () { + return { + query: "erpnext.stock.report.serial_and_batch_summary.serial_and_batch_summary.get_batch_nos", + filters: { + item_code: frappe.query_report.get_filter_value("item_code"), + voucher_type: frappe.query_report.get_filter_value("voucher_type"), + voucher_no: frappe.query_report.get_filter_value("voucher_no"), + }, + }; + }, + }, + ], +}; From 5c6028340f4fb95a7661140f638f087f365c1e19 Mon Sep 17 00:00:00 2001 From: ruthra kumar Date: Thu, 13 Feb 2025 14:22:51 +0530 Subject: [PATCH 03/19] chore: resolve conflicts --- .../customer_ledger_summary.js | 25 ----- .../report/gross_profit/gross_profit.js | 37 ------- .../supplier_ledger_summary.js | 25 ----- .../serial_and_batch_summary.js | 96 ------------------- 4 files changed, 183 deletions(-) delete mode 100644 erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.js diff --git a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js index fac7ffeb27f..dec2ebd2520 100644 --- a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js +++ b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js @@ -93,30 +93,5 @@ frappe.query_reports["Customer Ledger Summary"] = { fieldtype: "Data", hidden: 1, }, -<<<<<<< HEAD -======= - { - fieldname: "cost_center", - label: __("Cost Center"), - fieldtype: "MultiSelectList", - options: "Cost Center", - get_data: function (txt) { - return frappe.db.get_link_options("Cost Center", txt, { - company: frappe.query_report.get_filter_value("company"), - }); - }, - }, - { - fieldname: "project", - label: __("Project"), - fieldtype: "MultiSelectList", - options: "Project", - get_data: function (txt) { - return frappe.db.get_link_options("Project", txt, { - company: frappe.query_report.get_filter_value("company"), - }); - }, - }, ->>>>>>> 8785342fce (fix(report): add options to multiselectlist fields) ], }; diff --git a/erpnext/accounts/report/gross_profit/gross_profit.js b/erpnext/accounts/report/gross_profit/gross_profit.js index 1f5acdae98c..64a9838f54b 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.js +++ b/erpnext/accounts/report/gross_profit/gross_profit.js @@ -51,43 +51,6 @@ frappe.query_reports["Gross Profit"] = { fieldtype: "Link", options: "Sales Person", }, -<<<<<<< HEAD -======= - { - fieldname: "warehouse", - label: __("Warehouse"), - fieldtype: "Link", - options: "Warehouse", - get_query: function () { - var company = frappe.query_report.get_filter_value("company"); - return { - filters: [["Warehouse", "company", "=", company]], - }; - }, - }, - { - fieldname: "cost_center", - label: __("Cost Center"), - fieldtype: "MultiSelectList", - options: "Cost Center", - get_data: function (txt) { - return frappe.db.get_link_options("Cost Center", txt, { - company: frappe.query_report.get_filter_value("company"), - }); - }, - }, - { - fieldname: "project", - label: __("Project"), - fieldtype: "MultiSelectList", - options: "Project", - get_data: function (txt) { - return frappe.db.get_link_options("Project", txt, { - company: frappe.query_report.get_filter_value("company"), - }); - }, - }, ->>>>>>> 8785342fce (fix(report): add options to multiselectlist fields) ], tree: true, name_field: "parent", diff --git a/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js b/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js index cbf3bbcc738..1244a1b0168 100644 --- a/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js +++ b/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js @@ -75,30 +75,5 @@ frappe.query_reports["Supplier Ledger Summary"] = { fieldtype: "Data", hidden: 1, }, -<<<<<<< HEAD -======= - { - fieldname: "cost_center", - label: __("Cost Center"), - fieldtype: "MultiSelectList", - options: "Cost Center", - get_data: function (txt) { - return frappe.db.get_link_options("Cost Center", txt, { - company: frappe.query_report.get_filter_value("company"), - }); - }, - }, - { - fieldname: "project", - label: __("Project"), - fieldtype: "MultiSelectList", - options: "Project", - get_data: function (txt) { - return frappe.db.get_link_options("Project", txt, { - company: frappe.query_report.get_filter_value("company"), - }); - }, - }, ->>>>>>> 8785342fce (fix(report): add options to multiselectlist fields) ], }; diff --git a/erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.js b/erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.js deleted file mode 100644 index ab1e2babc80..00000000000 --- a/erpnext/stock/report/serial_and_batch_summary/serial_and_batch_summary.js +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt -/* eslint-disable */ - -frappe.query_reports["Serial and Batch Summary"] = { - filters: [ - { - fieldname: "company", - label: __("Company"), - fieldtype: "Link", - options: "Company", - default: frappe.defaults.get_user_default("Company"), - }, - { - fieldname: "from_date", - label: __("From Date"), - fieldtype: "Date", - default: frappe.datetime.add_months(frappe.datetime.get_today(), -1), - }, - { - fieldname: "to_date", - label: __("To Date"), - fieldtype: "Date", - default: frappe.datetime.get_today(), - }, - { - fieldname: "item_code", - label: __("Item"), - fieldtype: "Link", - options: "Item", - }, - { - fieldname: "warehouse", - label: __("Warehouse"), - fieldtype: "Link", - options: "Warehouse", - }, - { - fieldname: "voucher_type", - label: __("Voucher Type"), - fieldtype: "Link", - options: "DocType", - get_query: function () { - return { - query: "erpnext.stock.report.serial_and_batch_summary.serial_and_batch_summary.get_voucher_type", - }; - }, - }, - { - fieldname: "voucher_no", - label: __("Voucher No"), - fieldtype: "MultiSelectList", - options: "voucher_type", - get_data: function (txt) { - if (!frappe.query_report.filters) return; - - let voucher_type = frappe.query_report.get_filter_value("voucher_type"); - if (!voucher_type) return; - - return frappe.db.get_link_options(voucher_type, txt); - }, - }, - { - fieldname: "serial_no", - label: __("Serial No"), - fieldtype: "Link", - options: "Serial No", - get_query: function () { - return { - query: "erpnext.stock.report.serial_and_batch_summary.serial_and_batch_summary.get_serial_nos", - filters: { - item_code: frappe.query_report.get_filter_value("item_code"), - voucher_type: frappe.query_report.get_filter_value("voucher_type"), - voucher_no: frappe.query_report.get_filter_value("voucher_no"), - }, - }; - }, - }, - { - fieldname: "batch_no", - label: __("Batch No"), - fieldtype: "Link", - options: "Batch", - get_query: function () { - return { - query: "erpnext.stock.report.serial_and_batch_summary.serial_and_batch_summary.get_batch_nos", - filters: { - item_code: frappe.query_report.get_filter_value("item_code"), - voucher_type: frappe.query_report.get_filter_value("voucher_type"), - voucher_no: frappe.query_report.get_filter_value("voucher_no"), - }, - }; - }, - }, - ], -}; From a344b8b9ae9de1828db42ee6343bb899914830e4 Mon Sep 17 00:00:00 2001 From: Navin-S-R Date: Fri, 14 Feb 2025 13:14:27 +0530 Subject: [PATCH 04/19] fix: include missing payment_gateway parameter in Payment Request URL (cherry picked from commit dbac8cfc948a90c00379832d9b9888406fdf4bc6) --- erpnext/accounts/doctype/payment_request/payment_request.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 68055546c24..1012ff56ebf 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -246,6 +246,7 @@ class PaymentRequest(Document): "payer_name": data.customer_name, "order_id": self.name, "currency": self.currency, + "payment_gateway": self.payment_gateway, } ) From 84432fc0353923bb54219a5ee3031030daaf6ac0 Mon Sep 17 00:00:00 2001 From: Diptanil Saha Date: Fri, 14 Feb 2025 17:11:03 +0530 Subject: [PATCH 05/19] fix: pos return validation on v14 (#45859) fix: pos return validation v-14 --- erpnext/controllers/accounts_controller.py | 2 +- erpnext/controllers/sales_and_purchase_return.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 14dc038b0d6..1d4bd3b01f3 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -158,7 +158,7 @@ class AccountsController(TransactionBase): self.validate_qty_is_not_zero() if ( - self.doctype in ["Sales Invoice", "Purchase Invoice"] + self.doctype in ["Sales Invoice", "Purchase Invoice", "POS Invoice"] and self.get("is_return") and self.get("update_stock") ): diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 839bd5e154f..057cac85ae6 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -285,7 +285,7 @@ def get_already_returned_items(doc): field = ( frappe.scrub(doc.doctype) + "_item" - if doc.doctype in ["Purchase Invoice", "Purchase Receipt", "Sales Invoice"] + if doc.doctype in ["Purchase Invoice", "Purchase Receipt", "Sales Invoice", "POS Invoice"] else "dn_detail" ) data = frappe.db.sql( @@ -653,6 +653,7 @@ def get_return_against_item_fields(voucher_type): "Delivery Note": "dn_detail", "Sales Invoice": "sales_invoice_item", "Subcontracting Receipt": "subcontracting_receipt_item", + "POS Invoice": "sales_invoice_item", } return return_against_item_fields[voucher_type] From 44a16bb544061b07778ae8e150e9fc575226e688 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 14 Feb 2025 17:26:50 +0530 Subject: [PATCH 06/19] fix: on selection of batch qty is not fetching --- erpnext/public/js/utils/serial_no_batch_selector.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 6fd7f7f4588..ee6b5de28cd 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -381,7 +381,7 @@ erpnext.SerialNoBatchSelector = class SerialNoBatchSelector { query: "erpnext.controllers.queries.get_batch_no", }; }, - change: function () { + onchange: function () { const batch_no = this.get_value(); if (!batch_no) { this.grid_row.on_grid_fields_dict.available_qty.set_value(0); From 6f6133f2e29cf6bb13fd60534e55fd5af940c6c0 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 15 Feb 2025 15:33:41 +0530 Subject: [PATCH 07/19] fix: serial no is mandatory for zero qty validation --- .../doctype/stock_reconciliation/stock_reconciliation.js | 5 +++++ .../doctype/stock_reconciliation/stock_reconciliation.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js index 6ba2f0ac051..7f406e5d8c3 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js @@ -262,6 +262,11 @@ frappe.ui.form.on("Stock Reconciliation Item", { qty: function (frm, cdt, cdn) { frm.events.set_amount_quantity(frm, cdt, cdn); + + let row = locals[cdt][cdn]; + if (!row.qty) { + frappe.model.set_value(cdt, cdn, "serial_no", ""); + } }, valuation_rate: function (frm, cdt, cdn) { diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index adc67c6bb44..d1b81aa2ebe 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -250,7 +250,7 @@ class StockReconciliation(StockController): validate_is_stock_item(item_code, item.is_stock_item) # item should not be serialized - if item.has_serial_no and not row.serial_no and not item.serial_no_series: + if item.has_serial_no and row.qty and not row.serial_no and not item.serial_no_series: raise frappe.ValidationError( _("Serial no(s) required for serialized item {0}").format(item_code) ) From fc6f568a6c624d3c28e52011459e1215b7bb9017 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 18 Feb 2025 16:45:45 +0530 Subject: [PATCH 08/19] fix: millisecond issue for posting datetime --- erpnext/patches.txt | 2 ++ erpnext/patches/v14_0/update_posting_datetime.py | 10 ++++++++++ erpnext/stock/utils.py | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 erpnext/patches/v14_0/update_posting_datetime.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 94e48ef135d..ab842095412 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -370,3 +370,5 @@ erpnext.patches.v14_0.update_currency_exchange_settings_for_frankfurter erpnext.patches.v14_0.update_stock_uom_in_work_order_item 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 + diff --git a/erpnext/patches/v14_0/update_posting_datetime.py b/erpnext/patches/v14_0/update_posting_datetime.py new file mode 100644 index 00000000000..cb28193b74b --- /dev/null +++ b/erpnext/patches/v14_0/update_posting_datetime.py @@ -0,0 +1,10 @@ +import frappe + + +def execute(): + frappe.db.sql( + """ + UPDATE `tabStock Ledger Entry` + SET posting_datetime = timestamp(posting_date, posting_time) + """ + ) diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index f951ac019f5..636d620fad3 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -631,4 +631,4 @@ def get_combine_datetime(posting_date, posting_time): if isinstance(posting_time, datetime.timedelta): posting_time = (datetime.datetime.min + posting_time).time() - return datetime.datetime.combine(posting_date, posting_time).replace(microsecond=0) + return datetime.datetime.combine(posting_date, posting_time) From 2edf083c3589a45c74d89f26138b23072d33bef0 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 5 Feb 2025 18:48:52 +0530 Subject: [PATCH 09/19] feat: added option to enforce free item qty in pricing rule (cherry picked from commit 19c01b145765fbbd2be95e914c818c44697b7d76) # Conflicts: # erpnext/accounts/doctype/pricing_rule/pricing_rule.py --- .../doctype/pricing_rule/pricing_rule.json | 10 +- .../doctype/pricing_rule/pricing_rule.py | 112 ++++++++++++++++++ .../accounts/doctype/pricing_rule/utils.py | 3 +- 3 files changed, 123 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json index ee9dd2be8c3..473e9b837ae 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json @@ -53,6 +53,7 @@ "column_break_42", "free_item_uom", "round_free_qty", + "enforce_free_item_qty", "is_recursive", "recurse_for", "apply_recursion_over", @@ -643,12 +644,19 @@ "fieldname": "has_priority", "fieldtype": "Check", "label": "Has Priority" + }, + { + "default": "0", + "depends_on": "eval:doc.price_or_product_discount == 'Product'", + "fieldname": "enforce_free_item_qty", + "fieldtype": "Check", + "label": "Enforce Free Item Qty" } ], "icon": "fa fa-gift", "idx": 1, "links": [], - "modified": "2024-09-16 18:14:51.314765", + "modified": "2025-02-05 18:05:03.886828", "modified_by": "Administrator", "module": "Accounts", "name": "Pricing Rule", diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index 902af5c2a77..4913bcb8ed9 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -18,6 +18,118 @@ other_fields = ["other_item_code", "other_item_group", "other_brand"] class PricingRule(Document): +<<<<<<< HEAD +======= + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + from erpnext.accounts.doctype.pricing_rule_brand.pricing_rule_brand import PricingRuleBrand + from erpnext.accounts.doctype.pricing_rule_item_code.pricing_rule_item_code import PricingRuleItemCode + from erpnext.accounts.doctype.pricing_rule_item_group.pricing_rule_item_group import ( + PricingRuleItemGroup, + ) + + applicable_for: DF.Literal[ + "", + "Customer", + "Customer Group", + "Territory", + "Sales Partner", + "Campaign", + "Supplier", + "Supplier Group", + ] + apply_discount_on: DF.Literal["Grand Total", "Net Total"] + apply_discount_on_rate: DF.Check + apply_multiple_pricing_rules: DF.Check + apply_on: DF.Literal["", "Item Code", "Item Group", "Brand", "Transaction"] + apply_recursion_over: DF.Float + apply_rule_on_other: DF.Literal["", "Item Code", "Item Group", "Brand"] + brands: DF.Table[PricingRuleBrand] + buying: DF.Check + campaign: DF.Link | None + company: DF.Link | None + condition: DF.Code | None + coupon_code_based: DF.Check + currency: DF.Link + customer: DF.Link | None + customer_group: DF.Link | None + disable: DF.Check + discount_amount: DF.Currency + discount_percentage: DF.Float + enforce_free_item_qty: DF.Check + for_price_list: DF.Link | None + free_item: DF.Link | None + free_item_rate: DF.Currency + free_item_uom: DF.Link | None + free_qty: DF.Float + has_priority: DF.Check + is_cumulative: DF.Check + is_recursive: DF.Check + item_groups: DF.Table[PricingRuleItemGroup] + items: DF.Table[PricingRuleItemCode] + margin_rate_or_amount: DF.Float + margin_type: DF.Literal["", "Percentage", "Amount"] + max_amt: DF.Currency + max_qty: DF.Float + min_amt: DF.Currency + min_qty: DF.Float + mixed_conditions: DF.Check + naming_series: DF.Literal["PRLE-.####"] + other_brand: DF.Link | None + other_item_code: DF.Link | None + other_item_group: DF.Link | None + price_or_product_discount: DF.Literal["Price", "Product"] + priority: DF.Literal[ + "", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12", + "13", + "14", + "15", + "16", + "17", + "18", + "19", + "20", + ] + promotional_scheme: DF.Link | None + promotional_scheme_id: DF.Data | None + rate: DF.Currency + rate_or_discount: DF.Literal["", "Rate", "Discount Percentage", "Discount Amount"] + recurse_for: DF.Float + round_free_qty: DF.Check + rule_description: DF.SmallText | None + sales_partner: DF.Link | None + same_item: DF.Check + selling: DF.Check + supplier: DF.Link | None + supplier_group: DF.Link | None + territory: DF.Link | None + threshold_percentage: DF.Percent + title: DF.Data + valid_from: DF.Date | None + valid_upto: DF.Date | None + validate_applied_rule: DF.Check + warehouse: DF.Link | None + # end: auto-generated types + +>>>>>>> 19c01b1457 (feat: added option to enforce free item qty in pricing rule) def validate(self): self.validate_mandatory() self.validate_duplicate_apply_on() diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index bec4d2407de..f0f8f0698e1 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -691,7 +691,8 @@ def apply_pricing_rule_for_free_items(doc, pricing_rule_args): args.pop((item.item_code, item.pricing_rules)) for free_item in args.values(): - doc.append("items", free_item) + if frappe.get_value("Pricing Rule", free_item["pricing_rules"], "enforce_free_item_qty"): + doc.append("items", free_item) def get_pricing_rule_items(pr_doc, other_items=False) -> list: From e515b919885abbc4dec276a121019c9b8c421d48 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 5 Feb 2025 19:18:01 +0530 Subject: [PATCH 10/19] fix: tests (cherry picked from commit 366ae85d855c130c89fa1f151a2cca5b1e7dbdd8) # Conflicts: # erpnext/stock/doctype/pick_list/test_pick_list.py --- .../doctype/pricing_rule/pricing_rule.py | 2 +- .../doctype/pricing_rule/test_pricing_rule.py | 5 + .../stock/doctype/pick_list/test_pick_list.py | 96 +++++++++++++++++++ 3 files changed, 102 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index 4913bcb8ed9..fa84c4c0e6b 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -665,7 +665,7 @@ def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None, ra if pricing_rule.margin_type in ["Percentage", "Amount"]: item_details.margin_rate_or_amount = 0.0 item_details.margin_type = None - elif pricing_rule.get("free_item"): + elif pricing_rule.get("free_item") and pricing_rule.get("enforce_free_item_qty"): item_details.remove_free_item = ( item_code if pricing_rule.get("same_item") else pricing_rule.get("free_item") ) diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index 57f2d791199..62f37a78c11 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -386,6 +386,7 @@ class TestPricingRule(FrappeTestCase): "price_or_product_discount": "Product", "same_item": 1, "free_qty": 1, + "enforce_free_item_qty": 1, "company": "_Test Company", } frappe.get_doc(test_record.copy()).insert() @@ -418,6 +419,7 @@ class TestPricingRule(FrappeTestCase): "same_item": 0, "free_item": "_Test Item 2", "free_qty": 1, + "enforce_free_item_qty": 1, "company": "_Test Company", } frappe.get_doc(test_record.copy()).insert() @@ -961,6 +963,7 @@ class TestPricingRule(FrappeTestCase): "price_or_product_discount": "Product", "same_item": 1, "free_qty": 1, + "enforce_free_item_qty": 1, "round_free_qty": 1, "is_recursive": 1, "recurse_for": 2, @@ -1006,6 +1009,7 @@ class TestPricingRule(FrappeTestCase): "price_or_product_discount": "Product", "same_item": 1, "free_qty": 10, + "enforce_free_item_qty": 1, "round_free_qty": 1, "is_recursive": 1, "recurse_for": 100, @@ -1239,6 +1243,7 @@ def make_pricing_rule(**args): "discount_amount": args.discount_amount or 0.0, "apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0, "has_priority": args.has_priority or 0, + "enforce_free_item_qty": args.enforce_free_item_qty or 1, } ) diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py index f679d28c2b2..95a79f6caea 100644 --- a/erpnext/stock/doctype/pick_list/test_pick_list.py +++ b/erpnext/stock/doctype/pick_list/test_pick_list.py @@ -846,6 +846,102 @@ class TestPickList(FrappeTestCase): self.assertRaises(frappe.ValidationError, pl.save) +<<<<<<< HEAD +======= + def test_over_allowance_picking(self): + warehouse = "_Test Warehouse - _TC" + item = make_item( + "Test Over Allowance Picking Item", + properties={ + "is_stock_item": 1, + }, + ).name + + make_stock_entry(item=item, to_warehouse=warehouse, qty=100) + + so = make_sales_order(item_code=item, qty=10, rate=100) + + pl_doc = create_pick_list(so.name) + pl_doc.save() + self.assertEqual(pl_doc.locations[0].qty, 10) + + pl_doc.locations[0].qty = 15 + pl_doc.locations[0].stock_qty = 15 + pl_doc.save() + + self.assertEqual(pl_doc.locations[0].qty, 15) + self.assertRaises(frappe.ValidationError, pl_doc.submit) + + frappe.db.set_single_value("Stock Settings", "over_picking_allowance", 50) + + pl_doc.reload() + pl_doc.submit() + + frappe.db.set_single_value("Stock Settings", "over_picking_allowance", 0) + + def test_ignore_pricing_rule_in_pick_list(self): + frappe.flags.print_stmt = False + warehouse = "_Test Warehouse - _TC" + item = make_item( + properties={ + "is_stock_item": 1, + "has_batch_no": 1, + "batch_number_series": "IPR-PICKLT-.######", + "create_new_batch": 1, + } + ).name + + make_stock_entry( + item=item, + to_warehouse=warehouse, + qty=2, + basic_rate=100, + ) + + pricing_rule = frappe.get_doc( + { + "doctype": "Pricing Rule", + "title": "Same Free Item", + "price_or_product_discount": "Product", + "selling": 1, + "apply_on": "Item Code", + "items": [ + { + "item_code": item, + } + ], + "same_item": 1, + "is_recursive": 1, + "recurse_for": 2, + "free_qty": 1, + "enforce_free_item_qty": 1, + "company": "_Test Company", + "customer": "_Test Customer", + } + ) + + pricing_rule.save() + frappe.flags.print_stmt = True + + so = make_sales_order(item_code=item, qty=2, rate=100, do_not_save=True) + so.set_warehouse = warehouse + so.submit() + + self.assertEqual(len(so.items), 2) + self.assertTrue(so.items[1].is_free_item) + + pl = create_pick_list(so.name) + pl.ignore_pricing_rule = 1 + pl.save() + pl.submit() + + self.assertEqual(len(pl.locations), 1) + + delivery_note = create_delivery_note(pl.name) + + self.assertEqual(len(delivery_note.items), 1) + +>>>>>>> 366ae85d85 (fix: tests) def test_pick_list_not_reset_batch(self): warehouse = "_Test Warehouse - _TC" item = make_item( From 116798df9645bd30a33368d934fe5d790f9422ac Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Wed, 5 Feb 2025 21:34:19 +0530 Subject: [PATCH 11/19] test: added test (cherry picked from commit ac3259b8f156131e2d5ab27e411f9d735f835626) --- .../doctype/pricing_rule/pricing_rule.json | 4 +- .../doctype/pricing_rule/test_pricing_rule.py | 52 +++++++++++++++++-- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json index 473e9b837ae..3b743c796af 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json @@ -646,7 +646,7 @@ "label": "Has Priority" }, { - "default": "0", + "default": "1", "depends_on": "eval:doc.price_or_product_discount == 'Product'", "fieldname": "enforce_free_item_qty", "fieldtype": "Check", @@ -656,7 +656,7 @@ "icon": "fa fa-gift", "idx": 1, "links": [], - "modified": "2025-02-05 18:05:03.886828", + "modified": "2025-02-05 21:03:22.103044", "modified_by": "Administrator", "module": "Accounts", "name": "Pricing Rule", diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index 62f37a78c11..8729bd6b81d 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -386,7 +386,6 @@ class TestPricingRule(FrappeTestCase): "price_or_product_discount": "Product", "same_item": 1, "free_qty": 1, - "enforce_free_item_qty": 1, "company": "_Test Company", } frappe.get_doc(test_record.copy()).insert() @@ -419,7 +418,6 @@ class TestPricingRule(FrappeTestCase): "same_item": 0, "free_item": "_Test Item 2", "free_qty": 1, - "enforce_free_item_qty": 1, "company": "_Test Company", } frappe.get_doc(test_record.copy()).insert() @@ -430,6 +428,54 @@ class TestPricingRule(FrappeTestCase): self.assertEqual(so.items[1].is_free_item, 1) self.assertEqual(so.items[1].item_code, "_Test Item 2") + def test_enforce_free_item_qty(self): + # this test is only for testing non-enforcement as all other tests in this file already test with enforcement + frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule") + test_record = { + "doctype": "Pricing Rule", + "title": "_Test Pricing Rule", + "apply_on": "Item Code", + "currency": "USD", + "items": [ + { + "item_code": "_Test Item", + } + ], + "selling": 1, + "rate_or_discount": "Discount Percentage", + "rate": 0, + "min_qty": 0, + "max_qty": 7, + "discount_percentage": 17.5, + "price_or_product_discount": "Product", + "same_item": 0, + "free_item": "_Test Item 2", + "free_qty": 1, + "company": "_Test Company", + } + pricing_rule = frappe.get_doc(test_record.copy()).insert() + + # With enforcement + so = make_sales_order(item_code="_Test Item", qty=1, do_not_submit=True) + self.assertEqual(so.items[1].is_free_item, 1) + self.assertEqual(so.items[1].item_code, "_Test Item 2") + + # Test 1 : Saving a document with an item with pricing list without it's corresponding free item will cause it the free item to be refetched on save + so.items.pop(1) + so.save() + so.reload() + self.assertEqual(len(so.items), 2) + + # Without enforcement + pricing_rule.enforce_free_item_qty = 0 + pricing_rule.save() + + # Test 2 : Deleted free item will not be fetched again on save without enfrocement + so.items.pop(1) + so.save() + so.reload() + self.assertEqual(len(so.items), 1) + def test_cumulative_pricing_rule(self): frappe.delete_doc_if_exists("Pricing Rule", "_Test Cumulative Pricing Rule") test_record = { @@ -963,7 +1009,6 @@ class TestPricingRule(FrappeTestCase): "price_or_product_discount": "Product", "same_item": 1, "free_qty": 1, - "enforce_free_item_qty": 1, "round_free_qty": 1, "is_recursive": 1, "recurse_for": 2, @@ -1009,7 +1054,6 @@ class TestPricingRule(FrappeTestCase): "price_or_product_discount": "Product", "same_item": 1, "free_qty": 10, - "enforce_free_item_qty": 1, "round_free_qty": 1, "is_recursive": 1, "recurse_for": 100, From ef37388993686243924b445e3d215d500762bdea Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Tue, 11 Feb 2025 16:29:01 +0530 Subject: [PATCH 12/19] fix: add is_new in if condition (cherry picked from commit 4dcac564863ee3fed391adccc5898f3cd07f4ccf) --- erpnext/accounts/doctype/pricing_rule/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index f0f8f0698e1..7fb26402125 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -691,7 +691,9 @@ def apply_pricing_rule_for_free_items(doc, pricing_rule_args): args.pop((item.item_code, item.pricing_rules)) for free_item in args.values(): - if frappe.get_value("Pricing Rule", free_item["pricing_rules"], "enforce_free_item_qty"): + if doc.is_new() or frappe.get_value( + "Pricing Rule", free_item["pricing_rules"], "enforce_free_item_qty" + ): doc.append("items", free_item) From 1ff0edd492146633d89503ce2fae3ef778450d05 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Mon, 17 Feb 2025 18:21:22 +0530 Subject: [PATCH 13/19] refactor: rename field (cherry picked from commit f3d598881c9b1135d5afca01059e27056a62b305) --- erpnext/accounts/doctype/pricing_rule/pricing_rule.json | 8 ++++---- erpnext/accounts/doctype/pricing_rule/pricing_rule.py | 4 ++-- .../accounts/doctype/pricing_rule/test_pricing_rule.py | 8 ++++---- erpnext/accounts/doctype/pricing_rule/utils.py | 4 ++-- erpnext/stock/doctype/pick_list/test_pick_list.py | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json index 3b743c796af..d1697f10903 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json @@ -53,7 +53,7 @@ "column_break_42", "free_item_uom", "round_free_qty", - "enforce_free_item_qty", + "dont_enforce_free_item_qty", "is_recursive", "recurse_for", "apply_recursion_over", @@ -648,15 +648,15 @@ { "default": "1", "depends_on": "eval:doc.price_or_product_discount == 'Product'", - "fieldname": "enforce_free_item_qty", + "fieldname": "dont_enforce_free_item_qty", "fieldtype": "Check", - "label": "Enforce Free Item Qty" + "label": "Don't Enforce Free Item Qty" } ], "icon": "fa fa-gift", "idx": 1, "links": [], - "modified": "2025-02-05 21:03:22.103044", + "modified": "2025-02-17 18:15:39.824639", "modified_by": "Administrator", "module": "Accounts", "name": "Pricing Rule", diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index fa84c4c0e6b..ba52735cf7e 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -62,7 +62,7 @@ class PricingRule(Document): disable: DF.Check discount_amount: DF.Currency discount_percentage: DF.Float - enforce_free_item_qty: DF.Check + dont_enforce_free_item_qty: DF.Check for_price_list: DF.Link | None free_item: DF.Link | None free_item_rate: DF.Currency @@ -665,7 +665,7 @@ def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None, ra if pricing_rule.margin_type in ["Percentage", "Amount"]: item_details.margin_rate_or_amount = 0.0 item_details.margin_type = None - elif pricing_rule.get("free_item") and pricing_rule.get("enforce_free_item_qty"): + elif pricing_rule.get("free_item") and not pricing_rule.get("dont_enforce_free_item_qty"): item_details.remove_free_item = ( item_code if pricing_rule.get("same_item") else pricing_rule.get("free_item") ) diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index 8729bd6b81d..d4e39a1315b 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -428,7 +428,7 @@ class TestPricingRule(FrappeTestCase): self.assertEqual(so.items[1].is_free_item, 1) self.assertEqual(so.items[1].item_code, "_Test Item 2") - def test_enforce_free_item_qty(self): + def test_dont_enforce_free_item_qty(self): # this test is only for testing non-enforcement as all other tests in this file already test with enforcement frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule") test_record = { @@ -467,10 +467,10 @@ class TestPricingRule(FrappeTestCase): self.assertEqual(len(so.items), 2) # Without enforcement - pricing_rule.enforce_free_item_qty = 0 + pricing_rule.dont_enforce_free_item_qty = 1 pricing_rule.save() - # Test 2 : Deleted free item will not be fetched again on save without enfrocement + # Test 2 : Deleted free item will not be fetched again on save without enforcement so.items.pop(1) so.save() so.reload() @@ -1287,7 +1287,7 @@ def make_pricing_rule(**args): "discount_amount": args.discount_amount or 0.0, "apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0, "has_priority": args.has_priority or 0, - "enforce_free_item_qty": args.enforce_free_item_qty or 1, + "enforce_free_item_qty": args.dont_enforce_free_item_qty or 0, } ) diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 7fb26402125..fe81945ad41 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -691,8 +691,8 @@ def apply_pricing_rule_for_free_items(doc, pricing_rule_args): args.pop((item.item_code, item.pricing_rules)) for free_item in args.values(): - if doc.is_new() or frappe.get_value( - "Pricing Rule", free_item["pricing_rules"], "enforce_free_item_qty" + if doc.is_new() or not frappe.get_value( + "Pricing Rule", free_item["pricing_rules"], "dont_enforce_free_item_qty" ): doc.append("items", free_item) diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py index 95a79f6caea..3a043ff9fca 100644 --- a/erpnext/stock/doctype/pick_list/test_pick_list.py +++ b/erpnext/stock/doctype/pick_list/test_pick_list.py @@ -914,7 +914,7 @@ class TestPickList(FrappeTestCase): "is_recursive": 1, "recurse_for": 2, "free_qty": 1, - "enforce_free_item_qty": 1, + "dont_enforce_free_item_qty": 0, "company": "_Test Company", "customer": "_Test Customer", } From 4b16272a018ef78c1842ca254411f4284f494bec Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Mon, 17 Feb 2025 18:47:35 +0530 Subject: [PATCH 14/19] fix: set default value to 0 as per new logic (cherry picked from commit 844f1636c0a82dd6e0d0e6e6e3a86bc6652e3ad9) --- erpnext/accounts/doctype/pricing_rule/pricing_rule.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json index d1697f10903..c4825a6d519 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json @@ -646,7 +646,7 @@ "label": "Has Priority" }, { - "default": "1", + "default": "0", "depends_on": "eval:doc.price_or_product_discount == 'Product'", "fieldname": "dont_enforce_free_item_qty", "fieldtype": "Check", From e7d97865e50747d6a30ef956956ed4e6a909d80e Mon Sep 17 00:00:00 2001 From: Bhavansathru <122002510+Bhavan23@users.noreply.github.com> Date: Wed, 19 Feb 2025 13:57:31 +0530 Subject: [PATCH 15/19] fix: fetch child account data for selected parent (#45904) * fix: fetch child account data for selected parent * fix: change reference name --------- Co-authored-by: venkat102 (cherry picked from commit 73e82b7afa1bef110194bee78e5edb31c6fe092c) --- .../trial_balance_for_party.js | 14 +-- .../trial_balance_for_party.py | 103 ++++++++++-------- 2 files changed, 63 insertions(+), 54 deletions(-) diff --git a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.js b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.js index 50578d314e3..62482ac162c 100644 --- a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.js +++ b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.js @@ -68,16 +68,12 @@ frappe.query_reports["Trial Balance for Party"] = { { fieldname: "account", label: __("Account"), - fieldtype: "Link", + fieldtype: "MultiSelectList", options: "Account", - get_query: function () { - var company = frappe.query_report.get_filter_value("company"); - return { - doctype: "Account", - filters: { - company: company, - }, - }; + get_data: function (txt) { + return frappe.db.get_link_options("Account", txt, { + company: frappe.query_report.get_filter_value("company"), + }); }, }, { diff --git a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py index dd1a12514e2..f6c79eb6c45 100644 --- a/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py +++ b/erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.py @@ -4,8 +4,10 @@ import frappe from frappe import _ +from frappe.query_builder.functions import Sum from frappe.utils import cint, flt +from erpnext.accounts.report.general_ledger.general_ledger import get_accounts_with_children from erpnext.accounts.report.trial_balance.trial_balance import validate_filters @@ -35,9 +37,14 @@ def get_data(filters, show_party_name): filters=party_filters, order_by="name", ) + + account_filter = [] + if filters.get("account"): + account_filter = get_accounts_with_children(filters.get("account")) + company_currency = frappe.get_cached_value("Company", filters.company, "default_currency") - opening_balances = get_opening_balances(filters) - balances_within_period = get_balances_within_period(filters) + opening_balances = get_opening_balances(filters, account_filter) + balances_within_period = get_balances_within_period(filters, account_filter) data = [] # total_debit, total_credit = 0, 0 @@ -89,30 +96,34 @@ def get_data(filters, show_party_name): return data -def get_opening_balances(filters): - account_filter = "" - if filters.get("account"): - account_filter = "and account = %s" % (frappe.db.escape(filters.get("account"))) +def get_opening_balances(filters, account_filter=None): + GL_Entry = frappe.qb.DocType("GL Entry") - gle = frappe.db.sql( - f""" - select party, sum(debit) as opening_debit, sum(credit) as opening_credit - from `tabGL Entry` - where company=%(company)s - and is_cancelled=0 - and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != '' - and (posting_date < %(from_date)s or (ifnull(is_opening, 'No') = 'Yes' and posting_date <= %(to_date)s)) - {account_filter} - group by party""", - { - "company": filters.company, - "from_date": filters.from_date, - "to_date": filters.to_date, - "party_type": filters.party_type, - }, - as_dict=True, + query = ( + frappe.qb.from_(GL_Entry) + .select( + GL_Entry.party, + Sum(GL_Entry.debit).as_("opening_debit"), + Sum(GL_Entry.credit).as_("opening_credit"), + ) + .where( + (GL_Entry.company == filters.company) + & (GL_Entry.is_cancelled == 0) + & (GL_Entry.party_type == filters.party_type) + & (GL_Entry.party != "") + & ( + (GL_Entry.posting_date < filters.from_date) + | ((GL_Entry.is_opening == "Yes") & (GL_Entry.posting_date <= filters.to_date)) + ) + ) + .groupby(GL_Entry.party) ) + if account_filter: + query = query.where(GL_Entry.account.isin(account_filter)) + + gle = query.run(as_dict=True) + opening = frappe._dict() for d in gle: opening_debit, opening_credit = toggle_debit_credit(d.opening_debit, d.opening_credit) @@ -121,31 +132,33 @@ def get_opening_balances(filters): return opening -def get_balances_within_period(filters): - account_filter = "" - if filters.get("account"): - account_filter = "and account = %s" % (frappe.db.escape(filters.get("account"))) +def get_balances_within_period(filters, account_filter=None): + GL_Entry = frappe.qb.DocType("GL Entry") - gle = frappe.db.sql( - f""" - select party, sum(debit) as debit, sum(credit) as credit - from `tabGL Entry` - where company=%(company)s - and is_cancelled = 0 - and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != '' - and posting_date >= %(from_date)s and posting_date <= %(to_date)s - and ifnull(is_opening, 'No') = 'No' - {account_filter} - group by party""", - { - "company": filters.company, - "from_date": filters.from_date, - "to_date": filters.to_date, - "party_type": filters.party_type, - }, - as_dict=True, + query = ( + frappe.qb.from_(GL_Entry) + .select( + GL_Entry.party, + Sum(GL_Entry.debit).as_("debit"), + Sum(GL_Entry.credit).as_("credit"), + ) + .where( + (GL_Entry.company == filters.company) + & (GL_Entry.is_cancelled == 0) + & (GL_Entry.party_type == filters.party_type) + & (GL_Entry.party != "") + & (GL_Entry.posting_date >= filters.from_date) + & (GL_Entry.posting_date <= filters.to_date) + & (GL_Entry.is_opening == "No") + ) + .groupby(GL_Entry.party) ) + if account_filter: + query = query.where(GL_Entry.account.isin(account_filter)) + + gle = query.run(as_dict=True) + balances_within_period = frappe._dict() for d in gle: balances_within_period.setdefault(d.party, [d.debit, d.credit]) From 0cf9c94a3703d129495c22fc683bbba2f1c48d60 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 19 Feb 2025 15:10:34 +0530 Subject: [PATCH 16/19] chore: fix conflicts --- .../doctype/pricing_rule/pricing_rule.py | 112 ------------------ 1 file changed, 112 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index ba52735cf7e..afd27325820 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -18,118 +18,6 @@ other_fields = ["other_item_code", "other_item_group", "other_brand"] class PricingRule(Document): -<<<<<<< HEAD -======= - # begin: auto-generated types - # This code is auto-generated. Do not modify anything in this block. - - from typing import TYPE_CHECKING - - if TYPE_CHECKING: - from frappe.types import DF - - from erpnext.accounts.doctype.pricing_rule_brand.pricing_rule_brand import PricingRuleBrand - from erpnext.accounts.doctype.pricing_rule_item_code.pricing_rule_item_code import PricingRuleItemCode - from erpnext.accounts.doctype.pricing_rule_item_group.pricing_rule_item_group import ( - PricingRuleItemGroup, - ) - - applicable_for: DF.Literal[ - "", - "Customer", - "Customer Group", - "Territory", - "Sales Partner", - "Campaign", - "Supplier", - "Supplier Group", - ] - apply_discount_on: DF.Literal["Grand Total", "Net Total"] - apply_discount_on_rate: DF.Check - apply_multiple_pricing_rules: DF.Check - apply_on: DF.Literal["", "Item Code", "Item Group", "Brand", "Transaction"] - apply_recursion_over: DF.Float - apply_rule_on_other: DF.Literal["", "Item Code", "Item Group", "Brand"] - brands: DF.Table[PricingRuleBrand] - buying: DF.Check - campaign: DF.Link | None - company: DF.Link | None - condition: DF.Code | None - coupon_code_based: DF.Check - currency: DF.Link - customer: DF.Link | None - customer_group: DF.Link | None - disable: DF.Check - discount_amount: DF.Currency - discount_percentage: DF.Float - dont_enforce_free_item_qty: DF.Check - for_price_list: DF.Link | None - free_item: DF.Link | None - free_item_rate: DF.Currency - free_item_uom: DF.Link | None - free_qty: DF.Float - has_priority: DF.Check - is_cumulative: DF.Check - is_recursive: DF.Check - item_groups: DF.Table[PricingRuleItemGroup] - items: DF.Table[PricingRuleItemCode] - margin_rate_or_amount: DF.Float - margin_type: DF.Literal["", "Percentage", "Amount"] - max_amt: DF.Currency - max_qty: DF.Float - min_amt: DF.Currency - min_qty: DF.Float - mixed_conditions: DF.Check - naming_series: DF.Literal["PRLE-.####"] - other_brand: DF.Link | None - other_item_code: DF.Link | None - other_item_group: DF.Link | None - price_or_product_discount: DF.Literal["Price", "Product"] - priority: DF.Literal[ - "", - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "10", - "11", - "12", - "13", - "14", - "15", - "16", - "17", - "18", - "19", - "20", - ] - promotional_scheme: DF.Link | None - promotional_scheme_id: DF.Data | None - rate: DF.Currency - rate_or_discount: DF.Literal["", "Rate", "Discount Percentage", "Discount Amount"] - recurse_for: DF.Float - round_free_qty: DF.Check - rule_description: DF.SmallText | None - sales_partner: DF.Link | None - same_item: DF.Check - selling: DF.Check - supplier: DF.Link | None - supplier_group: DF.Link | None - territory: DF.Link | None - threshold_percentage: DF.Percent - title: DF.Data - valid_from: DF.Date | None - valid_upto: DF.Date | None - validate_applied_rule: DF.Check - warehouse: DF.Link | None - # end: auto-generated types - ->>>>>>> 19c01b1457 (feat: added option to enforce free item qty in pricing rule) def validate(self): self.validate_mandatory() self.validate_duplicate_apply_on() From 57a0717778abd8eead806f2a17fcae1839e6edd3 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 19 Feb 2025 15:12:08 +0530 Subject: [PATCH 17/19] chore: fix conflicts --- .../stock/doctype/pick_list/test_pick_list.py | 96 ------------------- 1 file changed, 96 deletions(-) diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py index 3a043ff9fca..f679d28c2b2 100644 --- a/erpnext/stock/doctype/pick_list/test_pick_list.py +++ b/erpnext/stock/doctype/pick_list/test_pick_list.py @@ -846,102 +846,6 @@ class TestPickList(FrappeTestCase): self.assertRaises(frappe.ValidationError, pl.save) -<<<<<<< HEAD -======= - def test_over_allowance_picking(self): - warehouse = "_Test Warehouse - _TC" - item = make_item( - "Test Over Allowance Picking Item", - properties={ - "is_stock_item": 1, - }, - ).name - - make_stock_entry(item=item, to_warehouse=warehouse, qty=100) - - so = make_sales_order(item_code=item, qty=10, rate=100) - - pl_doc = create_pick_list(so.name) - pl_doc.save() - self.assertEqual(pl_doc.locations[0].qty, 10) - - pl_doc.locations[0].qty = 15 - pl_doc.locations[0].stock_qty = 15 - pl_doc.save() - - self.assertEqual(pl_doc.locations[0].qty, 15) - self.assertRaises(frappe.ValidationError, pl_doc.submit) - - frappe.db.set_single_value("Stock Settings", "over_picking_allowance", 50) - - pl_doc.reload() - pl_doc.submit() - - frappe.db.set_single_value("Stock Settings", "over_picking_allowance", 0) - - def test_ignore_pricing_rule_in_pick_list(self): - frappe.flags.print_stmt = False - warehouse = "_Test Warehouse - _TC" - item = make_item( - properties={ - "is_stock_item": 1, - "has_batch_no": 1, - "batch_number_series": "IPR-PICKLT-.######", - "create_new_batch": 1, - } - ).name - - make_stock_entry( - item=item, - to_warehouse=warehouse, - qty=2, - basic_rate=100, - ) - - pricing_rule = frappe.get_doc( - { - "doctype": "Pricing Rule", - "title": "Same Free Item", - "price_or_product_discount": "Product", - "selling": 1, - "apply_on": "Item Code", - "items": [ - { - "item_code": item, - } - ], - "same_item": 1, - "is_recursive": 1, - "recurse_for": 2, - "free_qty": 1, - "dont_enforce_free_item_qty": 0, - "company": "_Test Company", - "customer": "_Test Customer", - } - ) - - pricing_rule.save() - frappe.flags.print_stmt = True - - so = make_sales_order(item_code=item, qty=2, rate=100, do_not_save=True) - so.set_warehouse = warehouse - so.submit() - - self.assertEqual(len(so.items), 2) - self.assertTrue(so.items[1].is_free_item) - - pl = create_pick_list(so.name) - pl.ignore_pricing_rule = 1 - pl.save() - pl.submit() - - self.assertEqual(len(pl.locations), 1) - - delivery_note = create_delivery_note(pl.name) - - self.assertEqual(len(delivery_note.items), 1) - ->>>>>>> 366ae85d85 (fix: tests) def test_pick_list_not_reset_batch(self): warehouse = "_Test Warehouse - _TC" item = make_item( From a0cd08e9ea07d69df664bb152faf4dd5dfac50e3 Mon Sep 17 00:00:00 2001 From: Steve Wilson Date: Wed, 19 Feb 2025 15:19:36 +0530 Subject: [PATCH 18/19] feat: added ability to use custom html format for process statement of accounts (#45746) * feat: added ability to use custom print format for process statement of accounts documents. * fix: handles missing hook issues * chore: linter changes --------- Co-authored-by: Boy4099 Co-authored-by: ruthra kumar --- .../process_statement_of_accounts.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py index a6a2b2410c9..511c89dfc8f 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py @@ -177,17 +177,21 @@ def get_ar_filters(doc, entry): def get_html(doc, filters, entry, col, res, ageing): base_template_path = "frappe/www/printview.html" - template_path = ( - "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html" - if doc.report == "General Ledger" - else "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html" - ) + template_path = "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts_accounts_receivable.html" + if doc.report == "General Ledger": + template_path = ( + "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html" + ) + + process_soa_html = frappe.get_hooks("process_soa_html") + # fetching custom print format for Process Statement of Accounts + if process_soa_html and process_soa_html.get(doc.report): + template_path = process_soa_html[doc.report][-1] if doc.letter_head: from frappe.www.printview import get_letter_head letter_head = get_letter_head(doc, 0) - html = frappe.render_template( template_path, { @@ -203,7 +207,6 @@ def get_html(doc, filters, entry, col, res, ageing): else None, }, ) - html = frappe.render_template( base_template_path, {"body": html, "css": get_print_style(), "title": "Statement For " + entry.customer}, From 703fd816d1d54071f89d86fd3c041ac32fea6fa5 Mon Sep 17 00:00:00 2001 From: Akhil Narang Date: Wed, 19 Feb 2025 12:23:10 +0530 Subject: [PATCH 19/19] fix(send_message): escape HTML in the text Signed-off-by: Akhil Narang (cherry picked from commit 448a5db20f2959fd6ce809a8894d32c8345a76fc) --- erpnext/templates/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/templates/utils.py b/erpnext/templates/utils.py index 57750a56f6f..15af9f0f014 100644 --- a/erpnext/templates/utils.py +++ b/erpnext/templates/utils.py @@ -3,6 +3,7 @@ import frappe +from frappe.utils import escape_html @frappe.whitelist(allow_guest=True) @@ -11,6 +12,8 @@ def send_message(sender, message, subject="Website Query"): website_send_message(sender, message, subject) + message = escape_html(message) + lead = customer = None customer = frappe.db.sql( """select distinct dl.link_name from `tabDynamic Link` dl