mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-14 07:01:56 +00:00
Merge pull request #58096 from khushi8112/refactor/item-prices-embedded-list
refactor: use EmbeddedList for item prices
This commit is contained in:
@@ -856,57 +856,107 @@ $.extend(erpnext.item, {
|
||||
}
|
||||
frm.toggle_display("prices_html", true);
|
||||
|
||||
const requested_item = frm.doc.name;
|
||||
const container = frm.fields_dict["prices_html"].$wrapper;
|
||||
frappe.require("embedded_list.bundle.js", () => erpnext.item.build_prices_list(frm));
|
||||
},
|
||||
|
||||
container.html(
|
||||
`<div class="text-muted text-center" style="padding: 20px;">${__("Loading...")}</div>`
|
||||
);
|
||||
build_prices_list: function (frm) {
|
||||
const item_code = frm.doc.name;
|
||||
const container = frm.fields_dict["prices_html"].$wrapper.empty();
|
||||
|
||||
frappe.call({
|
||||
method: "erpnext.stock.doctype.item.item.get_item_prices",
|
||||
args: { item_code: requested_item },
|
||||
|
||||
callback: function (r) {
|
||||
if (requested_item !== frm.doc.name) return;
|
||||
|
||||
if (!r.message) return;
|
||||
|
||||
const { prices, has_more } = r.message;
|
||||
|
||||
const html = frappe.render_template("item_prices", {
|
||||
prices,
|
||||
has_more,
|
||||
item_code: requested_item,
|
||||
stock_uom: frm.doc.stock_uom,
|
||||
});
|
||||
|
||||
container.html(html);
|
||||
|
||||
container.find(".add-price-btn").on("click", () => {
|
||||
const filters = {};
|
||||
if (frm.doc.is_sales_item && !frm.doc.is_purchase_item) {
|
||||
filters.selling = 1;
|
||||
} else if (frm.doc.is_purchase_item && !frm.doc.is_sales_item) {
|
||||
filters.buying = 1;
|
||||
}
|
||||
frappe.new_doc(
|
||||
"Item Price",
|
||||
{ item_code: requested_item, uom: frm.doc.stock_uom },
|
||||
(dialog) => {
|
||||
if (Object.keys(filters).length) {
|
||||
dialog.fields_dict.price_list.get_query = () => ({ filters });
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
container.find(".price-row").on("click", function (e) {
|
||||
if ($(e.target).is("a")) return;
|
||||
|
||||
frappe.set_route("Form", "Item Price", $(this).data("name"));
|
||||
const list = new frappe.ui.EmbeddedList({
|
||||
wrapper: $("<div></div>").appendTo(container),
|
||||
description: __("All active prices for this item across buying and selling price lists."),
|
||||
show_index: true,
|
||||
show_search: false,
|
||||
empty_icon: "tag",
|
||||
empty_message: __("No active item prices found."),
|
||||
add_button: {
|
||||
label: __("Add Price"),
|
||||
action: () => erpnext.item.new_item_price(frm),
|
||||
},
|
||||
on_row_click: (row) => frappe.set_route("Form", "Item Price", row.name),
|
||||
get_data() {
|
||||
return frappe
|
||||
.xcall("erpnext.stock.doctype.item.item.get_item_prices", { item_code })
|
||||
.then((r) => {
|
||||
this._has_more = r.has_more;
|
||||
return r.prices;
|
||||
});
|
||||
},
|
||||
before_render() {
|
||||
this._all_data.forEach((row) => {
|
||||
row.price_type =
|
||||
row.buying && row.selling
|
||||
? __("Buy & Sell")
|
||||
: row.buying
|
||||
? __("Buying")
|
||||
: __("Selling");
|
||||
});
|
||||
},
|
||||
columns: [
|
||||
{ label: __("Price List"), fieldname: "price_list" },
|
||||
{
|
||||
label: __("Type"),
|
||||
type: "badge",
|
||||
fieldname: "price_type",
|
||||
},
|
||||
{
|
||||
label: __("Party"),
|
||||
type: "link",
|
||||
text: (row) => row.customer || row.supplier || "",
|
||||
route: (row) => [
|
||||
"Form",
|
||||
row.customer ? "Customer" : "Supplier",
|
||||
row.customer || row.supplier,
|
||||
],
|
||||
},
|
||||
{
|
||||
label: __("Rate"),
|
||||
fieldname: "price_list_rate",
|
||||
render: (row) => format_currency(row.price_list_rate, row.currency),
|
||||
},
|
||||
{
|
||||
label: __("UOM"),
|
||||
fieldname: "uom",
|
||||
render: (row) => frappe.utils.escape_html(row.uom || frm.doc.stock_uom || ""),
|
||||
},
|
||||
{
|
||||
label: __("Valid Upto"),
|
||||
fieldname: "valid_upto",
|
||||
render: (row) => (row.valid_upto ? frappe.datetime.str_to_user(row.valid_upto) : ""),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
list.refresh().then(() => {
|
||||
if (!list._has_more) return;
|
||||
frappe.ui
|
||||
.button({
|
||||
label: __("View All Prices"),
|
||||
variant: "subtle",
|
||||
size: "sm",
|
||||
onclick: () => {
|
||||
frappe.route_options = { item_code };
|
||||
frappe.set_route("List", "Item Price");
|
||||
},
|
||||
})
|
||||
.appendTo(
|
||||
$('<div class="flex justify-end" style="margin-bottom: 8px;"></div>').appendTo(container)
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
new_item_price: function (frm) {
|
||||
const filters = {};
|
||||
if (frm.doc.is_sales_item && !frm.doc.is_purchase_item) {
|
||||
filters.selling = 1;
|
||||
} else if (frm.doc.is_purchase_item && !frm.doc.is_sales_item) {
|
||||
filters.buying = 1;
|
||||
}
|
||||
frappe.new_doc("Item Price", { item_code: frm.doc.name, uom: frm.doc.stock_uom }, (dialog) => {
|
||||
if (Object.keys(filters).length) {
|
||||
dialog.fields_dict.price_list.get_query = () => ({ filters });
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -1594,6 +1594,9 @@ def get_child_warehouses(warehouse):
|
||||
return get_child_warehouses(warehouse)
|
||||
|
||||
|
||||
ITEM_PRICES_LIMIT = 10
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_item_prices(item_code: str):
|
||||
"""Fetch valid item prices for the item prices tab."""
|
||||
@@ -1621,14 +1624,13 @@ def get_item_prices(item_code: str):
|
||||
.where(ItemPrice.docstatus != 2)
|
||||
.where((ItemPrice.valid_upto.isnull()) | (ItemPrice.valid_upto >= today))
|
||||
.orderby(ItemPrice.price_list)
|
||||
.limit(11)
|
||||
.limit(ITEM_PRICES_LIMIT + 1)
|
||||
.run(as_dict=True)
|
||||
)
|
||||
|
||||
has_more = len(prices) == 11
|
||||
return {
|
||||
"prices": prices[:10],
|
||||
"has_more": has_more,
|
||||
"prices": prices[:ITEM_PRICES_LIMIT],
|
||||
"has_more": len(prices) > ITEM_PRICES_LIMIT,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
<!-- Item Prices Tab -->
|
||||
<style>
|
||||
.item-price-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: var(--text-md);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.item-price-table thead tr {
|
||||
background: var(--subtle-fg);
|
||||
}
|
||||
|
||||
.item-price-table th {
|
||||
padding: 8px 10px;
|
||||
font-weight: 400;
|
||||
color: var(--text-muted);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.item-price-table td {
|
||||
padding: 9px 10px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.item-price-table tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.item-price-table tbody tr.price-row {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.item-price-table tbody tr.price-row:hover {
|
||||
background: var(--fg-hover-color);
|
||||
}
|
||||
|
||||
.item-price-table .col-no {
|
||||
width: 42px;
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.item-price-table .col-rate {
|
||||
text-align: right;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.item-price-table th.col-rate {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.item-price-table .col-muted {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.item-price-table a {
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.item-prices-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
<div style="margin-bottom: 12px;">
|
||||
<div class="text-extra-muted" style="font-size: var(--text-sm);">{{ __("All active prices for this item across buying and selling price lists.") }}</div>
|
||||
</div>
|
||||
{% if (prices && prices.length) { %}
|
||||
|
||||
<div style="border: 1px solid var(--border-color); border-radius: var(--border-radius-md); overflow: hidden; margin-bottom: 16px;">
|
||||
<table class="item-price-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-no">{{ __("No.") }}</th>
|
||||
<th>{{ __("Price List") }}</th>
|
||||
<th>{{ __("Type") }}</th>
|
||||
<th>{{ __("Party") }}</th>
|
||||
<th class="col-rate">{{ __("Rate") }}</th>
|
||||
<th>{{ __("UOM") }}</th>
|
||||
<th>{{ __("Valid Upto") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for (var i=0; i < prices.length; i++) { var p = prices[i]; %}
|
||||
<tr class="price-row" data-name="{{ p.name }}">
|
||||
<td class="col-no">{{ i + 1 }}</td>
|
||||
<td>{{ p.price_list }}</td>
|
||||
<td>
|
||||
{% if (p.buying && p.selling) { %}
|
||||
{{ __("Buy & Sell") }}
|
||||
{% } else if (p.buying) { %}
|
||||
{{ __("Buying") }}
|
||||
{% } else if (p.selling) { %}
|
||||
{{ __("Selling") }}
|
||||
{% } %}
|
||||
</td>
|
||||
<td>
|
||||
{% if (p.customer) { %}
|
||||
<a href="/app/customer/{{ encodeURIComponent(p.customer) }}" onclick="event.stopPropagation()">{{ p.customer }}</a>
|
||||
{% } else if (p.supplier) { %}
|
||||
<a href="/app/supplier/{{ encodeURIComponent(p.supplier) }}" onclick="event.stopPropagation()">{{ p.supplier }}</a>
|
||||
{% } %}
|
||||
</td>
|
||||
<td class="col-rate">{{ format_currency(p.price_list_rate, p.currency) }}</td>
|
||||
<td class="col-muted">{{ p.uom || stock_uom }}</td>
|
||||
<td class="col-muted">{{ p.valid_upto ? frappe.datetime.str_to_user(p.valid_upto) : "" }}</td>
|
||||
</tr>
|
||||
{% } %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="item-prices-footer">
|
||||
<div>
|
||||
{% if (has_more) { %}
|
||||
<a href="/app/item-price?item_code={{ encodeURIComponent(item_code) }}" class="btn btn-xs btn-default">
|
||||
{{ __("View All Prices") }}
|
||||
</a>
|
||||
{% } %}
|
||||
</div>
|
||||
<button class="btn btn-xs btn-default add-price-btn">
|
||||
{{ __("+ Add Price") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{% } else { %}
|
||||
|
||||
<div style="text-align: center; padding: 40px 20px; color: var(--text-muted); border: 1px dashed var(--border-color); border-radius: var(--border-radius-md); margin-bottom: 14px;">
|
||||
<p style="margin-bottom: 12px;">{{ __("No active item prices found.") }}</p>
|
||||
<button class="btn btn-sm btn-default add-price-btn">{{ __("+ Add Price") }}</button>
|
||||
</div>
|
||||
|
||||
{% } %}
|
||||
Reference in New Issue
Block a user