Compare commits

..

6 Commits

Author SHA1 Message Date
Nabin Hait
36f56fa1c3 Merge pull request #56786 from frappe/chore/test-territory-target-variance
test: Territory Target Variance based on Item Group report coverage
2026-07-02 18:09:21 +05:30
Raffael Meyer
5b738b7b0d fix: don't attempt to create SABB for non-serialized / non-batch items (#56627)
* fix: don't attempt to create SABB for non-serialized / non-batch items

* fix(stock): skip serial batch lookup for rows without item code
2026-07-02 12:33:26 +00:00
Shllokkk
7229957107 Merge pull request #56747 from Shllokkk/create-payment-entries-from-payable-report
fix: surface create payment entries as primary action on row selection
2026-07-02 17:33:56 +05:30
Nabin Hait
0888405640 test: reuse shared distribution helper and assert a single territory row 2026-07-02 17:15:05 +05:30
Nabin Hait
8d70385019 test: Territory Target Variance based on Item Group report coverage 2026-07-02 15:43:59 +05:30
Shllokkk
48aef307f9 fix: surface create payment entries as primary action on row selection 2026-07-02 02:19:38 +05:30
4 changed files with 100 additions and 73 deletions

View File

@@ -174,7 +174,17 @@ frappe.query_reports["Accounts Payable"] = {
},
get_datatable_options(options) {
return Object.assign(options, { checkboxColumn: true });
return Object.assign(options, {
checkboxColumn: true,
events: {
onCheckRow: () => erpnext.accounts.toggle_create_pe_primary_action(frappe.query_report),
},
});
},
after_refresh: function (report) {
report.datatable?.rowmanager?.checkAll(false);
report.page.clear_primary_action();
},
onload: function (report) {
@@ -186,20 +196,27 @@ frappe.query_reports["Accounts Payable"] = {
if (frappe.boot.sysdefaults.default_ageing_range) {
report.set_filter_value("range", frappe.boot.sysdefaults.default_ageing_range);
}
if (frappe.model.can_create("Payment Entry")) {
report.page.add_inner_button(
__("Create Payment Entries"),
function () {
erpnext.accounts.create_payment_entries_from_payable_report(report);
},
__("Actions")
);
}
},
};
frappe.provide("erpnext.accounts");
erpnext.accounts.toggle_create_pe_primary_action = function (report) {
if (!report || !report.datatable || !frappe.model.can_create("Payment Entry")) return;
const has_purchase_invoice = report.datatable.rowmanager
.getCheckedRows()
.some((i) => report.datatable.datamanager.data[i]?.voucher_type === "Purchase Invoice");
if (has_purchase_invoice) {
report.page.set_primary_action(__("Create Payment Entries"), () =>
erpnext.accounts.create_payment_entries_from_payable_report(report)
);
} else {
report.page.clear_primary_action();
}
};
erpnext.accounts.create_payment_entries_from_payable_report = function (report) {
const datatable = report.datatable;
if (!datatable) return;

View File

@@ -1,62 +0,0 @@
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
from erpnext.buying.report.supplier_quotation_comparison.supplier_quotation_comparison import execute
from erpnext.tests.utils import ERPNextTestSuite
COMPANY = "_Test Company"
ITEM = "_Test Item"
class TestSupplierQuotationComparison(ERPNextTestSuite):
"""The report lists Supplier Quotation item lines so quotes for the same item can
be compared across suppliers."""
def make_quotation(self, supplier, qty, rate):
sq = frappe.get_doc(
{
"doctype": "Supplier Quotation",
"supplier": supplier,
"company": COMPANY,
"currency": "INR",
"transaction_date": "2026-06-01",
"items": [
{"item_code": ITEM, "qty": qty, "rate": rate, "warehouse": "_Test Warehouse - _TC"}
],
}
)
sq.insert()
sq.submit()
return sq
def run_report(self, **extra):
filters = frappe._dict({"company": COMPANY, "from_date": "2026-01-01", "to_date": "2026-12-31"})
filters.update(extra)
return execute(filters)[1]
def test_no_filters_returns_empty(self):
self.assertEqual(execute(None)[1], [])
def test_quotation_line_listed_with_price(self):
sq = self.make_quotation("_Test Supplier", qty=10, rate=100)
rows = [r for r in self.run_report(item_code=ITEM) if r.get("quotation") == sq.name]
self.assertTrue(rows, "Supplier Quotation line missing from report")
row = rows[0]
self.assertEqual(row["supplier_name"], "_Test Supplier")
self.assertEqual(row["qty"], 10)
self.assertEqual(row["base_rate"], 100)
self.assertEqual(row["base_amount"], 1000)
self.assertEqual(row["price_per_unit"], 100)
def test_compares_multiple_suppliers_for_item(self):
sq1 = self.make_quotation("_Test Supplier", qty=10, rate=100)
sq2 = self.make_quotation("_Test Supplier 1", qty=10, rate=120)
quotes = {r["quotation"]: r for r in self.run_report(item_code=ITEM) if r.get("quotation")}
self.assertIn(sq1.name, quotes)
self.assertIn(sq2.name, quotes)
self.assertEqual(quotes[sq1.name]["base_rate"], 100)
self.assertEqual(quotes[sq2.name]["base_rate"], 120)

View File

@@ -0,0 +1,68 @@
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
from frappe.utils import flt, nowdate
from erpnext.accounts.utils import get_fiscal_year
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
from erpnext.selling.report.sales_person_target_variance_based_on_item_group.test_sales_person_target_variance_based_on_item_group import (
create_target_distribution,
)
from erpnext.selling.report.territory_target_variance_based_on_item_group.territory_target_variance_based_on_item_group import (
execute,
)
from erpnext.tests.utils import ERPNextTestSuite
class TestTerritoryTargetVarianceBasedOnItemGroup(ERPNextTestSuite):
def setUp(self):
self.fiscal_year = get_fiscal_year(nowdate())[0]
def test_achieved_target_and_variance(self):
distribution = create_target_distribution(self.fiscal_year)
territory = create_territory_with_target(
"_Test Target Territory", self.fiscal_year, distribution.name, target_qty=50
)
# a Sales Order in that territory contributes to the achieved quantity
so = make_sales_order(rate=1000, qty=20, do_not_submit=True)
so.territory = territory.name
so.submit()
result = execute(
frappe._dict(
{
"fiscal_year": self.fiscal_year,
"doctype": "Sales Order",
"period": "Yearly",
"target_on": "Quantity",
}
)
)[1]
# no item_group is set on the target, so the report emits exactly one row per
# territory -- assert all three figures against that single row
rows = [frappe._dict(r) for r in result if r.get("territory") == territory.name]
self.assertEqual(len(rows), 1, "expected exactly one row for the target territory")
row = rows[0]
self.assertEqual(flt(row.total_target, 2), 50)
self.assertEqual(flt(row.total_achieved, 2), 20)
self.assertEqual(flt(row.total_variance, 2), -30)
def create_territory_with_target(name, fiscal_year, distribution_id, target_qty=50):
doc = frappe.new_doc("Territory")
doc.territory_name = name
doc.parent_territory = "All Territories"
doc.is_group = 0
doc.append(
"targets",
{
"fiscal_year": fiscal_year,
"target_qty": target_qty,
"target_amount": 30000,
"distribution_id": distribution_id,
},
)
return doc.insert()

View File

@@ -176,6 +176,10 @@ class SerialBatchBundleService:
parent_details = self.get_parent_details_for_packed_items()
for row in self.doc.get(table_name):
item_code = row.get("rm_item_code") or row.get("item_code")
if not item_code or not self.is_serial_batch_item(item_code):
continue
if (
not via_landed_cost_voucher
and row.serial_and_batch_bundle