mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-20 17:59:57 +00:00
feat: add Purchase Partner and commission functionality
Mirrors the existing Sales Partner / Sales Commission feature for the purchase side, as requested in issue #52298. **New DocTypes:** - Purchase Partner (Setup module) — master for purchase agents/brokers with commission_rate, territory, address & contacts, and targets - Purchase Partner Type (Buying module) — classification for partners **Commission fields added to:** - Purchase Order, Purchase Invoice, Purchase Receipt — commission_section, purchase_partner (Link), commission_rate (fetch_from partner), amount_eligible_for_commission, total_commission - Purchase Order Item, Purchase Invoice Item, Purchase Receipt Item — grant_commission (fetched from Item master, default 0) **Commission calculation:** - Python: BuyingController.calculate_commission() mirrors SellingController logic; triggered via accounts_controller on validate - JS: BuyingController.calculate_purchase_commission() in buying.js; triggered from taxes_and_totals.js after totals recalculate - Event handlers: purchase_partner / commission_rate / total_commission **New Reports:** - Purchase Partner Commission Summary (Buying) — per-document summary - Purchase Partner Transaction Summary (Buying) — item-level breakdown - Purchase Partners Commission (Accounts) — aggregated query report **Workspace:** Purchase Partner card added to Buying workspace **Tests:** test_purchase_partner.py covers commission calculation, grant_commission exclusion, rate validation, and report execution Fixes #52298 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -167,6 +167,12 @@
|
||||
"terms_section_break",
|
||||
"tc_name",
|
||||
"terms",
|
||||
"commission_section",
|
||||
"purchase_partner",
|
||||
"amount_eligible_for_commission",
|
||||
"column_break_commission",
|
||||
"commission_rate",
|
||||
"total_commission",
|
||||
"more_info_tab",
|
||||
"status_section",
|
||||
"status",
|
||||
@@ -1683,6 +1689,50 @@
|
||||
"fieldname": "automation_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Automation"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "purchase_partner",
|
||||
"fieldname": "commission_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Commission",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "purchase_partner",
|
||||
"fieldtype": "Link",
|
||||
"label": "Purchase Partner",
|
||||
"options": "Purchase Partner",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "amount_eligible_for_commission",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Amount Eligible for Commission",
|
||||
"options": "Company:company:default_currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_commission",
|
||||
"fieldtype": "Column Break",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "purchase_partner.commission_rate",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "commission_rate",
|
||||
"fieldtype": "Float",
|
||||
"label": "Commission Rate (%)",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "total_commission",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Total Commission",
|
||||
"options": "Company:company:default_currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
@@ -1755,4 +1805,4 @@
|
||||
"timeline_field": "supplier",
|
||||
"title_field": "supplier_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -121,6 +121,7 @@
|
||||
"dimension_col_break",
|
||||
"cost_center",
|
||||
"section_break_82",
|
||||
"grant_commission",
|
||||
"page_break"
|
||||
],
|
||||
"fields": [
|
||||
@@ -1004,6 +1005,15 @@
|
||||
"label": "Delivered by Supplier",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fetch_from": "item_code.grant_commission",
|
||||
"fieldname": "grant_commission",
|
||||
"fieldtype": "Check",
|
||||
"label": "Grant Commission",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
@@ -1021,4 +1031,4 @@
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"add_translate_data": 0,
|
||||
"columns": [],
|
||||
"creation": "2026-06-15 00:00:00.000000",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2026-06-15 00:00:00.000000",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Partners Commission",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"query": "SELECT\n purchase_partner as \"Purchase Partner:Link/Purchase Partner:220\",\n sum(base_net_total) as \"Invoiced Amount (Excl. Tax):Currency:220\",\n sum(amount_eligible_for_commission) as \"Amount Eligible for Commission:Currency:220\",\n sum(total_commission) as \"Total Commission:Currency:170\",\n sum(total_commission)*100 / sum(amount_eligible_for_commission) as \"Average Commission Rate:Percent:220\"\nFROM\n `tabPurchase Invoice`\nWHERE\n docstatus = 1\n AND IFNULL(base_net_total, 0) > 0\n AND IFNULL(total_commission, 0) > 0\nGROUP BY\n purchase_partner\nORDER BY\n sum(total_commission) DESC",
|
||||
"ref_doctype": "Purchase Invoice",
|
||||
"report_name": "Purchase Partners Commission",
|
||||
"report_type": "Query Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Accounts Manager"
|
||||
},
|
||||
{
|
||||
"role": "Accounts User"
|
||||
}
|
||||
],
|
||||
"timeout": 0
|
||||
}
|
||||
@@ -133,6 +133,12 @@
|
||||
"terms_section_break",
|
||||
"tc_name",
|
||||
"terms",
|
||||
"commission_section",
|
||||
"purchase_partner",
|
||||
"amount_eligible_for_commission",
|
||||
"column_break_commission",
|
||||
"commission_rate",
|
||||
"total_commission",
|
||||
"more_info_tab",
|
||||
"tracking_section",
|
||||
"status",
|
||||
@@ -1291,6 +1297,50 @@
|
||||
"fieldname": "auto_repeat_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Auto Repeat"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "purchase_partner",
|
||||
"fieldname": "commission_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Commission",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "purchase_partner",
|
||||
"fieldtype": "Link",
|
||||
"label": "Purchase Partner",
|
||||
"options": "Purchase Partner",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "amount_eligible_for_commission",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Amount Eligible for Commission",
|
||||
"options": "Company:company:default_currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_commission",
|
||||
"fieldtype": "Column Break",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "purchase_partner.commission_rate",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "commission_rate",
|
||||
"fieldtype": "Float",
|
||||
"label": "Commission Rate (%)",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "total_commission",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Total Commission",
|
||||
"options": "Company:company:default_currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
@@ -1354,4 +1404,4 @@
|
||||
"timeline_field": "supplier",
|
||||
"title_field": "supplier_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -111,6 +111,7 @@
|
||||
"production_plan",
|
||||
"production_plan_item",
|
||||
"production_plan_sub_assembly_item",
|
||||
"grant_commission",
|
||||
"page_break",
|
||||
"column_break_pjyo",
|
||||
"job_card"
|
||||
@@ -934,6 +935,15 @@
|
||||
"non_negative": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fetch_from": "item_code.grant_commission",
|
||||
"fieldname": "grant_commission",
|
||||
"fieldtype": "Check",
|
||||
"label": "Grant Commission",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
@@ -955,4 +965,4 @@
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "field:purchase_partner_type",
|
||||
"creation": "2026-06-15 00:00:00.000000",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"purchase_partner_type"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "purchase_partner_type",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Purchase Partner Type",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2026-06-15 00:00:00.000000",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Partner Type",
|
||||
"naming_rule": "By fieldname",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"translated_doctype": 1
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class PurchasePartnerType(Document):
|
||||
# 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
|
||||
|
||||
purchase_partner_type: DF.Data
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,16 @@
|
||||
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
import frappe
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
|
||||
class TestPurchasePartnerType(ERPNextTestSuite):
|
||||
def test_purchase_partner_type_creation(self):
|
||||
if not frappe.db.exists("Purchase Partner Type", "_Test Purchase Partner Type"):
|
||||
ppt = frappe.new_doc("Purchase Partner Type")
|
||||
ppt.purchase_partner_type = "_Test Purchase Partner Type"
|
||||
ppt.insert(ignore_permissions=True)
|
||||
|
||||
self.assertTrue(frappe.db.exists("Purchase Partner Type", "_Test Purchase Partner Type"))
|
||||
frappe.delete_doc("Purchase Partner Type", "_Test Purchase Partner Type", force=True)
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.query_reports["Purchase Partner Commission Summary"] = {
|
||||
filters: [
|
||||
{
|
||||
fieldname: "company",
|
||||
label: __("Company"),
|
||||
fieldtype: "Link",
|
||||
options: "Company",
|
||||
default: frappe.defaults.get_user_default("Company"),
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
fieldname: "purchase_partner",
|
||||
label: __("Purchase Partner"),
|
||||
fieldtype: "Link",
|
||||
options: "Purchase Partner",
|
||||
},
|
||||
{
|
||||
fieldname: "doctype",
|
||||
label: __("Document Type"),
|
||||
fieldtype: "Select",
|
||||
options: "Purchase Order\nPurchase Receipt\nPurchase Invoice",
|
||||
default: "Purchase Order",
|
||||
},
|
||||
{
|
||||
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: "supplier",
|
||||
label: __("Supplier"),
|
||||
fieldtype: "Link",
|
||||
options: "Supplier",
|
||||
},
|
||||
{
|
||||
fieldname: "territory",
|
||||
label: __("Territory"),
|
||||
fieldtype: "Link",
|
||||
options: "Territory",
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"add_total_row": 1,
|
||||
"creation": "2026-06-15 00:00:00.000000",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2026-06-15 00:00:00.000000",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Partner Commission Summary",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "Purchase Order",
|
||||
"report_name": "Purchase Partner Commission Summary",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Purchase Manager"
|
||||
},
|
||||
{
|
||||
"role": "Purchase User"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.query_builder import DocType, Field, Order
|
||||
from frappe.query_builder.custom import ConstantColumn
|
||||
from frappe.query_builder.utils import QueryBuilder
|
||||
from frappe.utils.data import comma_or
|
||||
|
||||
PURCHASE_TRANSACTION_DOCTYPES = ["Purchase Order", "Purchase Invoice", "Purchase Receipt"]
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
if not filters:
|
||||
filters = {}
|
||||
|
||||
return PurchasePartnerCommissionSummaryReport(filters).run()
|
||||
|
||||
|
||||
class PurchasePartnerSummaryReport:
|
||||
"""Base class for Purchase Partner Summary related Reports."""
|
||||
|
||||
dt: DocType
|
||||
date_field: str
|
||||
date_label: str
|
||||
columns: list
|
||||
data: list
|
||||
query: QueryBuilder
|
||||
filters: dict
|
||||
|
||||
def __init__(self, filters: dict):
|
||||
self.filters = filters
|
||||
self.columns = []
|
||||
|
||||
def run(self):
|
||||
self.validate_filters()
|
||||
self.prepare_columns()
|
||||
self.get_data()
|
||||
return self.columns, self.data
|
||||
|
||||
def validate_filters(self):
|
||||
if not self.filters.get("doctype"):
|
||||
frappe.throw(_("Please select the document type first."))
|
||||
|
||||
if self.filters.get("doctype") not in PURCHASE_TRANSACTION_DOCTYPES:
|
||||
frappe.throw(_("DocType can be one of them {0}").format(comma_or(PURCHASE_TRANSACTION_DOCTYPES)))
|
||||
|
||||
if not self.filters.get("company"):
|
||||
frappe.throw(_("Please select a company."))
|
||||
|
||||
if (
|
||||
self.filters.get("from_date")
|
||||
and self.filters.get("to_date")
|
||||
and self.filters.get("from_date") > self.filters.get("to_date")
|
||||
):
|
||||
frappe.throw(_("From Date cannot be greater than To Date."))
|
||||
|
||||
self._set_date_field_and_label()
|
||||
|
||||
def _set_date_field_and_label(self):
|
||||
self.date_field = (
|
||||
"transaction_date" if self.filters.get("doctype") == "Purchase Order" else "posting_date"
|
||||
)
|
||||
self.date_label = _("Order Date") if self.date_field == "transaction_date" else _("Posting Date")
|
||||
|
||||
def prepare_columns(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_data(self):
|
||||
self.build_report_query()
|
||||
self.data = self.query.run(as_dict=1)
|
||||
|
||||
def build_report_query(self):
|
||||
self._build_report_base_query()
|
||||
self.extend_report_query()
|
||||
self._apply_common_filters()
|
||||
self.apply_filters()
|
||||
|
||||
def _build_report_base_query(self):
|
||||
self.dt = DocType(self.filters.get("doctype"))
|
||||
|
||||
company_currency = frappe.get_cached_value("Company", self.filters.get("company"), "default_currency")
|
||||
|
||||
self.query = (
|
||||
frappe.qb.from_(self.dt)
|
||||
.select(
|
||||
self.dt.name,
|
||||
self.dt.supplier,
|
||||
self.dt.territory,
|
||||
Field(self.date_field, "posting_date", table=self.dt),
|
||||
self.dt.purchase_partner,
|
||||
self.dt.commission_rate,
|
||||
ConstantColumn(company_currency).as_("currency"),
|
||||
)
|
||||
.where(
|
||||
(self.dt.docstatus == 1)
|
||||
& (self.dt.purchase_partner.notnull())
|
||||
& (self.dt.purchase_partner != "")
|
||||
)
|
||||
.orderby(self.dt.name, order=Order.desc)
|
||||
.orderby(self.dt.purchase_partner)
|
||||
)
|
||||
|
||||
def extend_report_query(self):
|
||||
pass
|
||||
|
||||
def _apply_common_filters(self):
|
||||
for field in ["company", "supplier", "territory", "purchase_partner"]:
|
||||
if self.filters.get(field):
|
||||
self.query = self.query.where(Field(field, table=self.dt) == self.filters.get(field))
|
||||
|
||||
if self.filters.get("from_date"):
|
||||
self.query = self.query.where(
|
||||
Field(self.date_field, table=self.dt) >= self.filters.get("from_date")
|
||||
)
|
||||
|
||||
if self.filters.get("to_date"):
|
||||
self.query = self.query.where(
|
||||
Field(self.date_field, table=self.dt) <= self.filters.get("to_date")
|
||||
)
|
||||
|
||||
def apply_filters(self):
|
||||
pass
|
||||
|
||||
def make_column(
|
||||
self,
|
||||
label: str,
|
||||
fieldname: str,
|
||||
fieldtype: str,
|
||||
width: int = 140,
|
||||
options: str = "",
|
||||
hidden: int = 0,
|
||||
):
|
||||
self.columns.append(
|
||||
dict(
|
||||
label=label,
|
||||
fieldname=fieldname,
|
||||
fieldtype=fieldtype,
|
||||
options=options,
|
||||
width=width,
|
||||
hidden=hidden,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class PurchasePartnerCommissionSummaryReport(PurchasePartnerSummaryReport):
|
||||
def prepare_columns(self):
|
||||
self.make_column(
|
||||
_(self.filters.get("doctype")), "name", "Link", options=self.filters.get("doctype")
|
||||
)
|
||||
self.make_column(_("Supplier"), "supplier", "Link", options="Supplier")
|
||||
self.make_column(_("Currency"), "currency", "Data", 80, hidden=1)
|
||||
self.make_column(_("Territory"), "territory", "Link", 100, "Territory")
|
||||
self.make_column(self.date_label, "posting_date", "Date")
|
||||
self.make_column(_("Amount"), "amount", "Currency", 120, "currency")
|
||||
self.make_column(_("Purchase Partner"), "purchase_partner", "Link", options="Purchase Partner")
|
||||
self.make_column(_("Commission Rate %"), "commission_rate", "Data", 100)
|
||||
self.make_column(_("Total Commission"), "total_commission", "Currency", 120, "currency")
|
||||
|
||||
def extend_report_query(self):
|
||||
self.query = self.query.select(
|
||||
self.dt.base_net_total.as_("amount"),
|
||||
self.dt.total_commission,
|
||||
)
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.query_reports["Purchase Partner Transaction Summary"] = {
|
||||
filters: [
|
||||
{
|
||||
fieldname: "company",
|
||||
label: __("Company"),
|
||||
fieldtype: "Link",
|
||||
options: "Company",
|
||||
default: frappe.defaults.get_user_default("Company"),
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
fieldname: "purchase_partner",
|
||||
label: __("Purchase Partner"),
|
||||
fieldtype: "Link",
|
||||
options: "Purchase Partner",
|
||||
},
|
||||
{
|
||||
fieldname: "doctype",
|
||||
label: __("Document Type"),
|
||||
fieldtype: "Select",
|
||||
options: "Purchase Order\nPurchase Receipt\nPurchase Invoice",
|
||||
default: "Purchase Order",
|
||||
},
|
||||
{
|
||||
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: "supplier",
|
||||
label: __("Supplier"),
|
||||
fieldtype: "Link",
|
||||
options: "Supplier",
|
||||
},
|
||||
{
|
||||
fieldname: "territory",
|
||||
label: __("Territory"),
|
||||
fieldtype: "Link",
|
||||
options: "Territory",
|
||||
},
|
||||
{
|
||||
fieldname: "item_group",
|
||||
label: __("Item Group"),
|
||||
fieldtype: "Link",
|
||||
options: "Item Group",
|
||||
},
|
||||
{
|
||||
fieldname: "brand",
|
||||
label: __("Brand"),
|
||||
fieldtype: "Link",
|
||||
options: "Brand",
|
||||
},
|
||||
{
|
||||
fieldname: "show_return_entries",
|
||||
label: __("Show Return Entries"),
|
||||
fieldtype: "Check",
|
||||
default: 0,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"add_total_row": 1,
|
||||
"creation": "2026-06-15 00:00:00.000000",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2026-06-15 00:00:00.000000",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Partner Transaction Summary",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "Purchase Order",
|
||||
"report_name": "Purchase Partner Transaction Summary",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Purchase User"
|
||||
},
|
||||
{
|
||||
"role": "Purchase Manager"
|
||||
},
|
||||
{
|
||||
"role": "Accounts User"
|
||||
},
|
||||
{
|
||||
"role": "Stock User"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.query_builder import Case
|
||||
|
||||
from erpnext.buying.report.purchase_partner_commission_summary.purchase_partner_commission_summary import (
|
||||
PurchasePartnerSummaryReport,
|
||||
)
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
if not filters:
|
||||
filters = {}
|
||||
|
||||
return PurchasePartnerTransactionSummaryReport(filters=filters).run()
|
||||
|
||||
|
||||
class PurchasePartnerTransactionSummaryReport(PurchasePartnerSummaryReport):
|
||||
def prepare_columns(self):
|
||||
self.make_column(
|
||||
_(self.filters.get("doctype")), "name", "Link", options=self.filters.get("doctype")
|
||||
)
|
||||
self.make_column(_("Supplier"), "supplier", "Link", options="Supplier")
|
||||
self.make_column(_("Currency"), "currency", "Data", 80, hidden=1)
|
||||
self.make_column(_("Territory"), "territory", "Link", 100, "Territory")
|
||||
self.make_column(self.date_label, "posting_date", "Date")
|
||||
self.make_column(_("Item Code"), "item_code", "Link", 100, "Item")
|
||||
self.make_column(_("Item Group"), "item_group", "Link", 100, "Item Group")
|
||||
self.make_column(_("Brand"), "brand", "Link", 100, "Brand")
|
||||
self.make_column(_("Quantity"), "qty", "Float", 120)
|
||||
self.make_column(_("Rate"), "rate", "Currency", 120, "currency")
|
||||
self.make_column(_("Amount"), "amount", "Currency", 120, "currency")
|
||||
self.make_column(_("Purchase Partner"), "purchase_partner", "Link", options="Purchase Partner")
|
||||
self.make_column(_("Commission Rate %"), "commission_rate", "Data", 100)
|
||||
self.make_column(_("Commission"), "commission", "Currency", 120, "currency")
|
||||
|
||||
def extend_report_query(self):
|
||||
self.dt_item = frappe.qb.DocType(f"{self.filters['doctype']} Item")
|
||||
|
||||
self.query = (
|
||||
self.query.join(self.dt_item)
|
||||
.on(self.dt.name == self.dt_item.parent)
|
||||
.select(
|
||||
self.dt_item.base_net_rate.as_("rate"),
|
||||
self.dt_item.qty,
|
||||
self.dt_item.base_net_amount.as_("amount"),
|
||||
Case()
|
||||
.when(
|
||||
self.dt_item.grant_commission.eq(1),
|
||||
((self.dt_item.base_net_amount * self.dt.commission_rate) / 100),
|
||||
)
|
||||
.else_(0)
|
||||
.as_("commission"),
|
||||
self.dt_item.brand,
|
||||
self.dt_item.item_group,
|
||||
self.dt_item.item_code,
|
||||
)
|
||||
)
|
||||
|
||||
def apply_filters(self):
|
||||
if not self.filters.get("show_return_entries"):
|
||||
self.query = self.query.where(self.dt_item.qty > 0.0)
|
||||
|
||||
if self.filters.get("brand"):
|
||||
self.query = self.query.where(self.dt_item.brand == self.filters.get("brand"))
|
||||
|
||||
if self.filters.get("item_group"):
|
||||
lft, rgt = frappe.get_cached_value("Item Group", self.filters.get("item_group"), ["lft", "rgt"])
|
||||
if item_groups := frappe.get_all(
|
||||
"Item Group", filters=[["lft", ">=", lft], ["rgt", "<=", rgt]], pluck="name"
|
||||
):
|
||||
self.query = self.query.where(self.dt_item.item_group.isin(item_groups))
|
||||
@@ -385,6 +385,69 @@
|
||||
"onboard": 1,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Purchase Partner",
|
||||
"link_count": 0,
|
||||
"onboard": 0,
|
||||
"type": "Card Break"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Purchase Partner",
|
||||
"link_count": 0,
|
||||
"link_to": "Purchase Partner",
|
||||
"link_type": "DocType",
|
||||
"onboard": 1,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Purchase Partner Type",
|
||||
"link_count": 0,
|
||||
"link_to": "Purchase Partner Type",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Purchase Partner",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Purchase Partners Commission",
|
||||
"link_count": 0,
|
||||
"link_to": "Purchase Partners Commission",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Purchase Partner",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Purchase Partner Commission Summary",
|
||||
"link_count": 0,
|
||||
"link_to": "Purchase Partner Commission Summary",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Purchase Partner",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Purchase Partner Transaction Summary",
|
||||
"link_count": 0,
|
||||
"link_to": "Purchase Partner Transaction Summary",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
@@ -540,4 +603,4 @@
|
||||
"shortcuts": [],
|
||||
"title": "Buying",
|
||||
"type": "Workspace"
|
||||
}
|
||||
}
|
||||
@@ -639,6 +639,13 @@ class AccountsController(TransactionBase):
|
||||
self.calculate_commission()
|
||||
self.calculate_contribution()
|
||||
|
||||
if self.doctype in (
|
||||
"Purchase Order",
|
||||
"Purchase Receipt",
|
||||
"Purchase Invoice",
|
||||
):
|
||||
self.calculate_commission()
|
||||
|
||||
def validate_date_with_fiscal_year(self):
|
||||
if self.meta.get_field("fiscal_year"):
|
||||
date_field = None
|
||||
|
||||
@@ -384,6 +384,31 @@ class BuyingController(SubcontractingController):
|
||||
item=row,
|
||||
)
|
||||
|
||||
def calculate_commission(self):
|
||||
if not self.meta.get_field("commission_rate"):
|
||||
return
|
||||
|
||||
self.round_floats_in(self, ("amount_eligible_for_commission", "commission_rate"))
|
||||
|
||||
if not (0 <= self.commission_rate <= 100.0):
|
||||
from frappe import throw
|
||||
|
||||
throw(
|
||||
"{} {}".format(
|
||||
_(self.meta.get_label("commission_rate")),
|
||||
_("must be between 0 and 100"),
|
||||
)
|
||||
)
|
||||
|
||||
self.amount_eligible_for_commission = sum(
|
||||
item.base_net_amount for item in self.items if item.grant_commission
|
||||
)
|
||||
|
||||
self.total_commission = flt(
|
||||
self.amount_eligible_for_commission * self.commission_rate / 100.0,
|
||||
self.precision("total_commission"),
|
||||
)
|
||||
|
||||
def set_total_in_words(self):
|
||||
from frappe.utils import money_in_words
|
||||
|
||||
|
||||
@@ -473,7 +473,69 @@ erpnext.buying = {
|
||||
});
|
||||
}
|
||||
|
||||
add_serial_batch_for_rejected_qty(doc, cdt, cdn) {
|
||||
purchase_partner() {
|
||||
this.calculate_purchase_commission();
|
||||
}
|
||||
|
||||
commission_rate() {
|
||||
if (
|
||||
["Purchase Order", "Purchase Receipt", "Purchase Invoice"].includes(
|
||||
this.frm.doc.doctype
|
||||
)
|
||||
) {
|
||||
this.calculate_purchase_commission();
|
||||
}
|
||||
}
|
||||
|
||||
total_commission() {
|
||||
if (
|
||||
!["Purchase Order", "Purchase Receipt", "Purchase Invoice"].includes(
|
||||
this.frm.doc.doctype
|
||||
)
|
||||
)
|
||||
return;
|
||||
frappe.model.round_floats_in(this.frm.doc, [
|
||||
"amount_eligible_for_commission",
|
||||
"total_commission",
|
||||
]);
|
||||
const { amount_eligible_for_commission } = this.frm.doc;
|
||||
if (!amount_eligible_for_commission) return;
|
||||
this.frm.set_value(
|
||||
"commission_rate",
|
||||
flt((this.frm.doc.total_commission * 100.0) / amount_eligible_for_commission)
|
||||
);
|
||||
}
|
||||
|
||||
calculate_purchase_commission() {
|
||||
if (!this.frm.fields_dict.commission_rate || this.frm.doc.docstatus === 1) return;
|
||||
|
||||
if (this.frm.doc.commission_rate > 100) {
|
||||
this.frm.set_value("commission_rate", 100);
|
||||
frappe.throw(
|
||||
`${__(
|
||||
frappe.meta.get_label(
|
||||
this.frm.doc.doctype,
|
||||
"commission_rate",
|
||||
this.frm.doc.name
|
||||
)
|
||||
)} ${__("cannot be greater than 100")}`
|
||||
);
|
||||
}
|
||||
|
||||
this.frm.doc.amount_eligible_for_commission = (this.frm.doc.items || []).reduce(
|
||||
(sum, item) => (item.grant_commission ? sum + item.base_net_amount : sum),
|
||||
0
|
||||
);
|
||||
|
||||
this.frm.doc.total_commission = flt(
|
||||
(this.frm.doc.amount_eligible_for_commission * this.frm.doc.commission_rate) / 100.0,
|
||||
precision("total_commission")
|
||||
);
|
||||
|
||||
refresh_field(["amount_eligible_for_commission", "total_commission"]);
|
||||
}
|
||||
|
||||
add_serial_batch_for_rejected_qty(doc, cdt, cdn) {
|
||||
let item = locals[cdt][cdn];
|
||||
let me = this;
|
||||
let fields = ["has_batch_no", "has_serial_no"];
|
||||
|
||||
@@ -76,6 +76,11 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
this.calculate_contribution();
|
||||
}
|
||||
|
||||
// Purchase partner commission
|
||||
if (["Purchase Order", "Purchase Receipt", "Purchase Invoice"].includes(this.frm.doc.doctype)) {
|
||||
this.calculate_purchase_commission();
|
||||
}
|
||||
|
||||
// Update paid amount on return/debit note creation
|
||||
if (
|
||||
this.frm.doc.doctype === "Purchase Invoice" &&
|
||||
|
||||
0
erpnext/setup/doctype/purchase_partner/__init__.py
Normal file
0
erpnext/setup/doctype/purchase_partner/__init__.py
Normal file
25
erpnext/setup/doctype/purchase_partner/purchase_partner.js
Normal file
25
erpnext/setup/doctype/purchase_partner/purchase_partner.js
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.ui.form.on("Purchase Partner", {
|
||||
refresh: function (frm) {
|
||||
if (frm.doc.__islocal) {
|
||||
hide_field(["address_html", "contact_html", "address_contacts"]);
|
||||
frappe.contacts.clear_address_and_contact(frm);
|
||||
} else {
|
||||
unhide_field(["address_html", "contact_html", "address_contacts"]);
|
||||
frappe.contacts.render_address_and_contact(frm);
|
||||
}
|
||||
},
|
||||
|
||||
setup: function (frm) {
|
||||
frm.fields_dict["targets"].grid.get_field("distribution_id").get_query = function (doc, cdt, cdn) {
|
||||
var row = locals[cdt][cdn];
|
||||
return {
|
||||
filters: {
|
||||
fiscal_year: row.fiscal_year,
|
||||
},
|
||||
};
|
||||
};
|
||||
},
|
||||
});
|
||||
145
erpnext/setup/doctype/purchase_partner/purchase_partner.json
Normal file
145
erpnext/setup/doctype/purchase_partner/purchase_partner.json
Normal file
@@ -0,0 +1,145 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:partner_name",
|
||||
"creation": "2026-06-15 00:00:00",
|
||||
"description": "A third party agent / broker / commission agent who facilitates purchases for a commission.",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"partner_name",
|
||||
"partner_type",
|
||||
"territory",
|
||||
"column_break0",
|
||||
"commission_rate",
|
||||
"address_contacts",
|
||||
"address_desc",
|
||||
"address_html",
|
||||
"column_break1",
|
||||
"contact_desc",
|
||||
"contact_html",
|
||||
"partner_target_details_section_break",
|
||||
"targets"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "partner_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Purchase Partner Name",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "partner_type",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Partner Type",
|
||||
"options": "Purchase Partner Type"
|
||||
},
|
||||
{
|
||||
"fieldname": "territory",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Territory",
|
||||
"options": "Territory",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break0",
|
||||
"fieldtype": "Column Break",
|
||||
"width": "50%"
|
||||
},
|
||||
{
|
||||
"fieldname": "commission_rate",
|
||||
"fieldtype": "Float",
|
||||
"label": "Commission Rate",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "address_contacts",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Address & Contacts"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.__islocal",
|
||||
"fieldname": "address_desc",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Address Desc"
|
||||
},
|
||||
{
|
||||
"fieldname": "address_html",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Address HTML",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break1",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.__islocal",
|
||||
"fieldname": "contact_desc",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Contact Desc"
|
||||
},
|
||||
{
|
||||
"fieldname": "contact_html",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Contact HTML",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "partner_target_details_section_break",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Purchase Partner Target"
|
||||
},
|
||||
{
|
||||
"fieldname": "targets",
|
||||
"fieldtype": "Table",
|
||||
"label": "Targets",
|
||||
"options": "Target Detail"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-user",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2026-06-15 00:00:00.000000",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Purchase Partner",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Purchase Manager"
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Purchase User"
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Purchase Master Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "creation",
|
||||
"sort_order": "ASC",
|
||||
"states": []
|
||||
}
|
||||
28
erpnext/setup/doctype/purchase_partner/purchase_partner.py
Normal file
28
erpnext/setup/doctype/purchase_partner/purchase_partner.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.contacts.address_and_contact import load_address_and_contact
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class PurchasePartner(Document):
|
||||
# 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.setup.doctype.target_detail.target_detail import TargetDetail
|
||||
|
||||
commission_rate: DF.Float
|
||||
partner_name: DF.Data
|
||||
partner_type: DF.Link | None
|
||||
targets: DF.Table[TargetDetail]
|
||||
territory: DF.Link
|
||||
# end: auto-generated types
|
||||
|
||||
def onload(self):
|
||||
load_address_and_contact(self)
|
||||
105
erpnext/setup/doctype/purchase_partner/test_purchase_partner.py
Normal file
105
erpnext/setup/doctype/purchase_partner/test_purchase_partner.py
Normal file
@@ -0,0 +1,105 @@
|
||||
# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
||||
from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
|
||||
class TestPurchasePartner(ERPNextTestSuite):
|
||||
def setUp(self):
|
||||
self.partner = make_purchase_partner()
|
||||
|
||||
def tearDown(self):
|
||||
frappe.delete_doc("Purchase Partner", self.partner.name, force=True)
|
||||
|
||||
def test_purchase_partner_creation(self):
|
||||
self.assertEqual(self.partner.commission_rate, 10.0)
|
||||
self.assertEqual(self.partner.territory, "_Test Territory")
|
||||
|
||||
def test_commission_calculated_on_purchase_order(self):
|
||||
po = create_purchase_order(do_not_submit=True)
|
||||
po.purchase_partner = self.partner.name
|
||||
po.commission_rate = self.partner.commission_rate
|
||||
# grant_commission defaults to 1 fetched from item, ensure it's set
|
||||
for item in po.items:
|
||||
item.grant_commission = 1
|
||||
po.save()
|
||||
|
||||
self.assertEqual(po.commission_rate, 10.0)
|
||||
expected_commission = po.base_net_total * 10.0 / 100.0
|
||||
self.assertAlmostEqual(po.total_commission, expected_commission, places=2)
|
||||
self.assertAlmostEqual(po.amount_eligible_for_commission, po.base_net_total, places=2)
|
||||
|
||||
def test_commission_zero_when_grant_commission_false(self):
|
||||
po = create_purchase_order(do_not_submit=True)
|
||||
po.purchase_partner = self.partner.name
|
||||
po.commission_rate = 10.0
|
||||
for item in po.items:
|
||||
item.grant_commission = 0
|
||||
po.save()
|
||||
|
||||
self.assertEqual(po.total_commission, 0)
|
||||
self.assertEqual(po.amount_eligible_for_commission, 0)
|
||||
|
||||
def test_commission_rate_validation(self):
|
||||
po = create_purchase_order(do_not_submit=True)
|
||||
po.purchase_partner = self.partner.name
|
||||
po.commission_rate = 110.0
|
||||
with self.assertRaises(frappe.ValidationError):
|
||||
po.save()
|
||||
|
||||
def test_commission_on_purchase_invoice(self):
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||
|
||||
pi = make_purchase_invoice(do_not_save=True)
|
||||
pi.purchase_partner = self.partner.name
|
||||
pi.commission_rate = self.partner.commission_rate
|
||||
for item in pi.items:
|
||||
item.grant_commission = 1
|
||||
pi.save()
|
||||
|
||||
self.assertEqual(pi.commission_rate, 10.0)
|
||||
expected_commission = pi.base_net_total * 10.0 / 100.0
|
||||
self.assertAlmostEqual(pi.total_commission, expected_commission, places=2)
|
||||
|
||||
def test_purchase_partner_commission_summary_report(self):
|
||||
from erpnext.buying.report.purchase_partner_commission_summary.purchase_partner_commission_summary import (
|
||||
execute,
|
||||
)
|
||||
|
||||
po = create_purchase_order(do_not_submit=True)
|
||||
po.purchase_partner = self.partner.name
|
||||
po.commission_rate = 10.0
|
||||
for item in po.items:
|
||||
item.grant_commission = 1
|
||||
po.save()
|
||||
po.submit()
|
||||
|
||||
columns, data = execute(
|
||||
{
|
||||
"company": "_Test Company",
|
||||
"doctype": "Purchase Order",
|
||||
"purchase_partner": self.partner.name,
|
||||
}
|
||||
)
|
||||
|
||||
self.assertTrue(len(columns) > 0)
|
||||
self.assertTrue(any(row.get("purchase_partner") == self.partner.name for row in data))
|
||||
|
||||
po.cancel()
|
||||
|
||||
|
||||
def make_purchase_partner(**kwargs):
|
||||
kwargs = frappe._dict(kwargs)
|
||||
partner = frappe.new_doc("Purchase Partner")
|
||||
partner.partner_name = kwargs.partner_name or "_Test Purchase Partner"
|
||||
partner.territory = kwargs.territory or "_Test Territory"
|
||||
partner.commission_rate = kwargs.commission_rate or 10.0
|
||||
if not frappe.db.exists("Purchase Partner", partner.partner_name):
|
||||
partner.insert(ignore_permissions=True)
|
||||
else:
|
||||
partner = frappe.get_doc("Purchase Partner", partner.partner_name)
|
||||
return partner
|
||||
@@ -126,6 +126,12 @@
|
||||
"terms_tab",
|
||||
"tc_name",
|
||||
"terms",
|
||||
"commission_section",
|
||||
"purchase_partner",
|
||||
"amount_eligible_for_commission",
|
||||
"column_break_commission",
|
||||
"commission_rate",
|
||||
"total_commission",
|
||||
"more_info_tab",
|
||||
"status_section",
|
||||
"status",
|
||||
@@ -1286,6 +1292,50 @@
|
||||
{
|
||||
"fieldname": "column_break_ugyv",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "purchase_partner",
|
||||
"fieldname": "commission_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Commission",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "purchase_partner",
|
||||
"fieldtype": "Link",
|
||||
"label": "Purchase Partner",
|
||||
"options": "Purchase Partner",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "amount_eligible_for_commission",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Amount Eligible for Commission",
|
||||
"options": "Company:company:default_currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_commission",
|
||||
"fieldtype": "Column Break",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "purchase_partner.commission_rate",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "commission_rate",
|
||||
"fieldtype": "Float",
|
||||
"label": "Commission Rate (%)",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "total_commission",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Total Commission",
|
||||
"options": "Company:company:default_currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
@@ -1363,4 +1413,4 @@
|
||||
"timeline_field": "supplier",
|
||||
"title_field": "supplier_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -126,6 +126,7 @@
|
||||
"dimension_col_break",
|
||||
"cost_center",
|
||||
"section_break_80",
|
||||
"grant_commission",
|
||||
"page_break",
|
||||
"sales_order",
|
||||
"sales_order_item",
|
||||
@@ -1117,6 +1118,15 @@
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fetch_from": "item_code.grant_commission",
|
||||
"fieldname": "grant_commission",
|
||||
"fieldtype": "Check",
|
||||
"label": "Grant Commission",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
@@ -1134,4 +1144,4 @@
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user