feat: Unit Price Items in Buying (RFQ, SQ, PO)
- chore: Extract `set_unit_price_items_note` into a util
(cherry picked from commit e403d3f153)
# Conflicts:
# erpnext/buying/doctype/buying_settings/buying_settings.json
# erpnext/buying/doctype/purchase_order/purchase_order.json
# erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
# erpnext/selling/doctype/quotation/quotation.json
# erpnext/selling/doctype/selling_settings/selling_settings.json
This commit is contained in:
@@ -25,6 +25,9 @@
|
||||
"disable_last_purchase_rate",
|
||||
"show_pay_button",
|
||||
"use_transaction_date_exchange_rate",
|
||||
"allow_zero_qty_in_request_for_quotation",
|
||||
"allow_zero_qty_in_supplier_quotation",
|
||||
"allow_zero_qty_in_purchase_order",
|
||||
"subcontract",
|
||||
"backflush_raw_materials_of_subcontract_based_on",
|
||||
"column_break_11",
|
||||
@@ -207,14 +210,37 @@
|
||||
"fieldtype": "Select",
|
||||
"label": "Update frequency of Project",
|
||||
"options": "Each Transaction\nManual"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_zero_qty_in_purchase_order",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow 0 Qty in Purchase Order (Unit Price Items)"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_zero_qty_in_request_for_quotation",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow 0 Qty in Request for Quotation (Unit Price Items)"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_zero_qty_in_supplier_quotation",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow 0 Qty in Supplier Quotation (Unit Price Items)"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"icon": "fa fa-cog",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
<<<<<<< HEAD
|
||||
"modified": "2024-01-31 13:34:18.101256",
|
||||
=======
|
||||
"modified": "2025-03-03 17:32:25.939482",
|
||||
>>>>>>> e403d3f153 (feat: Unit Price Items in Buying (RFQ, SQ, PO))
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Buying Settings",
|
||||
@@ -260,7 +286,12 @@
|
||||
"role": "Purchase User"
|
||||
}
|
||||
],
|
||||
<<<<<<< HEAD
|
||||
"sort_field": "modified",
|
||||
=======
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
>>>>>>> e403d3f153 (feat: Unit Price Items in Buying (RFQ, SQ, PO))
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
|
||||
@@ -18,6 +18,9 @@ class BuyingSettings(Document):
|
||||
from frappe.types import DF
|
||||
|
||||
allow_multiple_items: DF.Check
|
||||
allow_zero_qty_in_purchase_order: DF.Check
|
||||
allow_zero_qty_in_request_for_quotation: DF.Check
|
||||
allow_zero_qty_in_supplier_quotation: DF.Check
|
||||
auto_create_purchase_receipt: DF.Check
|
||||
auto_create_subcontracting_order: DF.Check
|
||||
backflush_raw_materials_of_subcontract_based_on: DF.Literal[
|
||||
|
||||
@@ -26,7 +26,15 @@ frappe.ui.form.on("Purchase Order", {
|
||||
}
|
||||
|
||||
frm.set_indicator_formatter("item_code", function (doc) {
|
||||
return doc.qty <= doc.received_qty ? "green" : "orange";
|
||||
let color;
|
||||
if (!doc.qty && frm.doc.has_unit_price_items) {
|
||||
color = "yellow";
|
||||
} else if (doc.qty <= doc.received_qty) {
|
||||
color = "green";
|
||||
} else {
|
||||
color = "orange";
|
||||
}
|
||||
return color;
|
||||
});
|
||||
|
||||
frm.set_query("expense_account", "items", function () {
|
||||
@@ -63,6 +71,10 @@ frappe.ui.form.on("Purchase Order", {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (frm.doc.docstatus == 0) {
|
||||
erpnext.set_unit_price_items_note(frm);
|
||||
}
|
||||
},
|
||||
|
||||
supplier: function (frm) {
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"apply_tds",
|
||||
"tax_withholding_category",
|
||||
"is_subcontracted",
|
||||
"has_unit_price_items",
|
||||
"supplier_warehouse",
|
||||
"amended_from",
|
||||
"accounting_dimensions_section",
|
||||
@@ -1280,11 +1281,20 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
<<<<<<< HEAD
|
||||
"fieldname": "dispatch_address_display",
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "Dispatch Address Details",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
=======
|
||||
"default": "0",
|
||||
"fieldname": "has_unit_price_items",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Has Unit Price Items",
|
||||
"no_copy": 1
|
||||
>>>>>>> e403d3f153 (feat: Unit Price Items in Buying (RFQ, SQ, PO))
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
@@ -1292,7 +1302,11 @@
|
||||
"idx": 105,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
<<<<<<< HEAD
|
||||
"modified": "2025-04-09 16:54:08.836106",
|
||||
=======
|
||||
"modified": "2025-03-03 16:48:08.697520",
|
||||
>>>>>>> e403d3f153 (feat: Unit Price Items in Buying (RFQ, SQ, PO))
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order",
|
||||
|
||||
@@ -97,6 +97,7 @@ class PurchaseOrder(BuyingController):
|
||||
from_date: DF.Date | None
|
||||
grand_total: DF.Currency
|
||||
group_same_items: DF.Check
|
||||
has_unit_price_items: DF.Check
|
||||
ignore_pricing_rule: DF.Check
|
||||
in_words: DF.Data | None
|
||||
incoterm: DF.Link | None
|
||||
@@ -191,6 +192,10 @@ class PurchaseOrder(BuyingController):
|
||||
self.set_onload("supplier_tds", supplier_tds)
|
||||
self.set_onload("can_update_items", self.can_update_items())
|
||||
|
||||
def before_validate(self):
|
||||
self.set_has_unit_price_items()
|
||||
self.flags.allow_zero_qty = self.has_unit_price_items
|
||||
|
||||
def validate(self):
|
||||
super().validate()
|
||||
|
||||
@@ -223,6 +228,17 @@ class PurchaseOrder(BuyingController):
|
||||
)
|
||||
self.reset_default_field_value("set_warehouse", "items", "warehouse")
|
||||
|
||||
def set_has_unit_price_items(self):
|
||||
"""
|
||||
If permitted in settings and any item has 0 qty, the PO has unit price items.
|
||||
"""
|
||||
if not frappe.db.get_single_value("Buying Settings", "allow_zero_qty_in_purchase_order"):
|
||||
return
|
||||
|
||||
self.has_unit_price_items = any(
|
||||
not row.qty for row in self.get("items") if (row.item_code and not row.qty)
|
||||
)
|
||||
|
||||
def validate_with_previous_doc(self):
|
||||
mri_compare_fields = [["project", "="], ["item_code", "="]]
|
||||
if self.is_subcontracted:
|
||||
|
||||
@@ -28,6 +28,10 @@ frappe.ui.form.on("Request for Quotation", {
|
||||
is_group: 0,
|
||||
},
|
||||
}));
|
||||
|
||||
frm.set_indicator_formatter("item_code", function (doc) {
|
||||
return !doc.qty && frm.doc.has_unit_price_items ? "yellow" : "";
|
||||
});
|
||||
},
|
||||
|
||||
onload: function (frm) {
|
||||
@@ -163,6 +167,10 @@ frappe.ui.form.on("Request for Quotation", {
|
||||
__("View")
|
||||
);
|
||||
}
|
||||
|
||||
if (frm.doc.docstatus === 0) {
|
||||
erpnext.set_unit_price_items_note(frm);
|
||||
}
|
||||
},
|
||||
|
||||
show_supplier_quotation_comparison(frm) {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"transaction_date",
|
||||
"schedule_date",
|
||||
"status",
|
||||
"has_unit_price_items",
|
||||
"amended_from",
|
||||
"suppliers_section",
|
||||
"suppliers",
|
||||
@@ -306,13 +307,26 @@
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Billing Address Details",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "has_unit_price_items",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Has Unit Price Items",
|
||||
"no_copy": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"icon": "fa fa-shopping-cart",
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
<<<<<<< HEAD
|
||||
"modified": "2023-11-06 12:45:28.898706",
|
||||
=======
|
||||
"modified": "2025-03-03 16:48:39.856779",
|
||||
>>>>>>> e403d3f153 (feat: Unit Price Items in Buying (RFQ, SQ, PO))
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Request for Quotation",
|
||||
@@ -377,6 +391,7 @@
|
||||
"role": "All"
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"search_fields": "status, transaction_date",
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "modified",
|
||||
|
||||
@@ -42,6 +42,7 @@ class RequestforQuotation(BuyingController):
|
||||
billing_address_display: DF.SmallText | None
|
||||
company: DF.Link
|
||||
email_template: DF.Link | None
|
||||
has_unit_price_items: DF.Check
|
||||
incoterm: DF.Link | None
|
||||
items: DF.Table[RequestforQuotationItem]
|
||||
letter_head: DF.Link | None
|
||||
@@ -61,6 +62,10 @@ class RequestforQuotation(BuyingController):
|
||||
vendor: DF.Link | None
|
||||
# end: auto-generated types
|
||||
|
||||
def before_validate(self):
|
||||
self.set_has_unit_price_items()
|
||||
self.flags.allow_zero_qty = self.has_unit_price_items
|
||||
|
||||
def validate(self):
|
||||
self.validate_duplicate_supplier()
|
||||
self.validate_supplier_list()
|
||||
@@ -72,6 +77,17 @@ class RequestforQuotation(BuyingController):
|
||||
# after amend and save, status still shows as cancelled, until submit
|
||||
self.db_set("status", "Draft")
|
||||
|
||||
def set_has_unit_price_items(self):
|
||||
"""
|
||||
If permitted in settings and any item has 0 qty, the RFQ has unit price items.
|
||||
"""
|
||||
if not frappe.db.get_single_value("Buying Settings", "allow_zero_qty_in_request_for_quotation"):
|
||||
return
|
||||
|
||||
self.has_unit_price_items = any(
|
||||
not row.qty for row in self.get("items") if (row.item_code and not row.qty)
|
||||
)
|
||||
|
||||
def validate_duplicate_supplier(self):
|
||||
supplier_list = [d.supplier for d in self.suppliers]
|
||||
if len(supplier_list) != len(set(supplier_list)):
|
||||
|
||||
@@ -11,6 +11,11 @@ erpnext.buying.SupplierQuotationController = class SupplierQuotationController e
|
||||
Quotation: "Quotation",
|
||||
};
|
||||
|
||||
const me = this;
|
||||
this.frm.set_indicator_formatter("item_code", function (doc) {
|
||||
return !doc.qty && me.frm.doc.has_unit_price_items ? "yellow" : "";
|
||||
});
|
||||
|
||||
super.setup();
|
||||
}
|
||||
|
||||
@@ -26,6 +31,8 @@ erpnext.buying.SupplierQuotationController = class SupplierQuotationController e
|
||||
cur_frm.page.set_inner_btn_group_as_primary(__("Create"));
|
||||
cur_frm.add_custom_button(__("Quotation"), this.make_quotation, __("Create"));
|
||||
} else if (this.frm.doc.docstatus === 0) {
|
||||
erpnext.set_unit_price_items_note(this.frm);
|
||||
|
||||
this.frm.add_custom_button(
|
||||
__("Material Request"),
|
||||
function () {
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"transaction_date",
|
||||
"valid_till",
|
||||
"quotation_number",
|
||||
"has_unit_price_items",
|
||||
"amended_from",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
@@ -921,14 +922,23 @@
|
||||
"fieldname": "accounting_dimensions_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Accounting Dimensions"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "has_unit_price_items",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Has Unit Price Items",
|
||||
"no_copy": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"icon": "fa fa-shopping-cart",
|
||||
"idx": 29,
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-28 10:20:30.231915",
|
||||
"modified": "2025-03-03 17:39:38.459977",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Supplier Quotation",
|
||||
@@ -989,6 +999,7 @@
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"search_fields": "status, transaction_date, supplier,grand_total",
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "modified",
|
||||
|
||||
@@ -60,6 +60,7 @@ class SupplierQuotation(BuyingController):
|
||||
discount_amount: DF.Currency
|
||||
grand_total: DF.Currency
|
||||
group_same_items: DF.Check
|
||||
has_unit_price_items: DF.Check
|
||||
ignore_pricing_rule: DF.Check
|
||||
in_words: DF.Data | None
|
||||
incoterm: DF.Link | None
|
||||
@@ -103,6 +104,10 @@ class SupplierQuotation(BuyingController):
|
||||
valid_till: DF.Date | None
|
||||
# end: auto-generated types
|
||||
|
||||
def before_validate(self):
|
||||
self.set_has_unit_price_items()
|
||||
self.flags.allow_zero_qty = self.has_unit_price_items
|
||||
|
||||
def validate(self):
|
||||
super().validate()
|
||||
|
||||
@@ -129,6 +134,17 @@ class SupplierQuotation(BuyingController):
|
||||
def on_trash(self):
|
||||
pass
|
||||
|
||||
def set_has_unit_price_items(self):
|
||||
"""
|
||||
If permitted in settings and any item has 0 qty, the SQ has unit price items.
|
||||
"""
|
||||
if not frappe.db.get_single_value("Buying Settings", "allow_zero_qty_in_supplier_quotation"):
|
||||
return
|
||||
|
||||
self.has_unit_price_items = any(
|
||||
not row.qty for row in self.get("items") if (row.item_code and not row.qty)
|
||||
)
|
||||
|
||||
def validate_with_previous_doc(self):
|
||||
super().validate_with_previous_doc(
|
||||
{
|
||||
|
||||
@@ -2773,3 +2773,13 @@ erpnext.apply_putaway_rule = (frm, purpose=null) => {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
erpnext.set_unit_price_items_note = (frm) => {
|
||||
if (frm.doc.has_unit_price_items && !frm.is_new()) {
|
||||
frm.dashboard.set_headline_alert(
|
||||
__("The {0} contains Unit Price Items with 0 Qty.", [__(frm.doc.doctype)]),
|
||||
"yellow",
|
||||
true
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -46,7 +46,7 @@ frappe.ui.form.on("Quotation", {
|
||||
frm.trigger("set_dynamic_field_label");
|
||||
|
||||
if (frm.doc.docstatus === 0) {
|
||||
frm.trigger("set_unit_price_items_note");
|
||||
erpnext.set_unit_price_items_note(frm);
|
||||
}
|
||||
|
||||
let sbb_field = frm.get_docfield("packed_items", "serial_and_batch_bundle");
|
||||
@@ -72,16 +72,6 @@ frappe.ui.form.on("Quotation", {
|
||||
set_label: function (frm) {
|
||||
frm.fields_dict.customer_address.set_label(__(frm.doc.quotation_to + " Address"));
|
||||
},
|
||||
|
||||
set_unit_price_items_note: function (frm) {
|
||||
if (frm.doc.has_unit_price_items) {
|
||||
frm.dashboard.set_headline_alert(
|
||||
__("The Quotation contains Unit Price Items with 0 Qty."),
|
||||
"yellow",
|
||||
true
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
erpnext.selling.QuotationController = class QuotationController extends erpnext.selling.SellingController {
|
||||
|
||||
@@ -1091,18 +1091,23 @@
|
||||
"fieldname": "has_unit_price_items",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Has Unit Price Items"
|
||||
"label": "Has Unit Price Items",
|
||||
"no_copy": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-shopping-cart",
|
||||
"idx": 82,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
"modified": "2024-11-26 12:43:29.293637",
|
||||
=======
|
||||
"modified": "2025-02-28 18:52:44.063265",
|
||||
>>>>>>> c1e4e7af28 (feat: Unit Price Contract)
|
||||
=======
|
||||
"modified": "2025-03-03 16:49:20.050303",
|
||||
>>>>>>> e403d3f153 (feat: Unit Price Items in Buying (RFQ, SQ, PO))
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Quotation",
|
||||
|
||||
@@ -106,7 +106,7 @@ frappe.ui.form.on("Sales Order", {
|
||||
}
|
||||
|
||||
if (frm.doc.docstatus === 0) {
|
||||
frm.trigger("set_unit_price_items_note");
|
||||
erpnext.set_unit_price_items_note(frm);
|
||||
|
||||
if (frm.doc.is_internal_customer) {
|
||||
frm.events.get_items_from_internal_purchase_order(frm);
|
||||
@@ -539,16 +539,6 @@ frappe.ui.form.on("Sales Order", {
|
||||
};
|
||||
frappe.set_route("query-report", "Reserved Stock");
|
||||
},
|
||||
|
||||
set_unit_price_items_note: function (frm) {
|
||||
if (frm.doc.has_unit_price_items && !frm.is_new()) {
|
||||
frm.dashboard.set_headline_alert(
|
||||
__("The Sales Order contains Unit Price Items with 0 Qty."),
|
||||
"yellow",
|
||||
true
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
frappe.ui.form.on("Sales Order Item", {
|
||||
|
||||
@@ -1656,14 +1656,15 @@
|
||||
"fieldname": "has_unit_price_items",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Has Unit Price Items"
|
||||
"label": "Has Unit Price Items",
|
||||
"no_copy": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 105,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-02-28 18:52:01.932669",
|
||||
"modified": "2025-03-03 16:49:00.676927",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Sales Order",
|
||||
|
||||
@@ -37,8 +37,8 @@
|
||||
=======
|
||||
"enable_discount_accounting",
|
||||
"enable_cutoff_date_on_bulk_delivery_note_creation",
|
||||
"allow_zero_qty_in_sales_order",
|
||||
"allow_zero_qty_in_quotation",
|
||||
"allow_zero_qty_in_sales_order",
|
||||
"experimental_section",
|
||||
"use_server_side_reactivity"
|
||||
>>>>>>> c1e4e7af28 (feat: Unit Price Contract)
|
||||
@@ -233,14 +233,18 @@
|
||||
"default": "0",
|
||||
"fieldname": "allow_zero_qty_in_sales_order",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow 0 Qty in Sales Order (Unit Price Contract)"
|
||||
"label": "Allow 0 Qty in Sales Order (Unit Price Items)"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_zero_qty_in_quotation",
|
||||
"fieldtype": "Check",
|
||||
<<<<<<< HEAD
|
||||
"label": "Allow 0 Qty in Quotation (Unit Price Contract)"
|
||||
>>>>>>> c1e4e7af28 (feat: Unit Price Contract)
|
||||
=======
|
||||
"label": "Allow 0 Qty in Quotation (Unit Price Items)"
|
||||
>>>>>>> e403d3f153 (feat: Unit Price Items in Buying (RFQ, SQ, PO))
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
@@ -249,11 +253,15 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD
|
||||
"modified": "2023-10-25 14:03:03.966701",
|
||||
=======
|
||||
"modified": "2025-02-28 18:19:46.436595",
|
||||
>>>>>>> c1e4e7af28 (feat: Unit Price Contract)
|
||||
=======
|
||||
"modified": "2025-03-03 16:39:16.360823",
|
||||
>>>>>>> e403d3f153 (feat: Unit Price Items in Buying (RFQ, SQ, PO))
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Selling Settings",
|
||||
|
||||
Reference in New Issue
Block a user