Skip to content

Commit ed6a103

Browse files
Nishka GosaliaNishka Gosalia
authored andcommitted
feat: Allowing closing individual items in sales order
1 parent 02a9c54 commit ed6a103

File tree

13 files changed

+437
-28
lines changed

13 files changed

+437
-28
lines changed

erpnext/manufacturing/doctype/production_plan/production_plan.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@ def get_so_items(self):
368368
(so_item.parent.isin(so_list))
369369
& (so_item.docstatus == 1)
370370
& ((so_item.stock_qty - so_item.stock_reserved_qty) > so_item.work_order_qty)
371+
& ((so_item.is_closed == 0) | (so_item.is_closed.isnull()))
371372
)
372373
)
373374

@@ -432,6 +433,7 @@ def get_so_items(self):
432433
.where((bom.item == pi.item_code) & (bom.is_active == 1))
433434
)
434435
)
436+
& ((so_item.is_closed == 0) | (so_item.is_closed.isnull()))
435437
)
436438
)
437439

@@ -1542,6 +1544,7 @@ def get_sales_orders(self):
15421544
& (so.status.notin(["Stopped", "Closed"]))
15431545
& (so.company == self.company)
15441546
& (so_item.qty > so_item.production_plan_qty)
1547+
& ((so_item.is_closed == 0) | (so_item.is_closed.isnull()))
15451548
)
15461549
)
15471550

erpnext/manufacturing/doctype/work_order/work_order.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2396,6 +2396,7 @@ def query_sales_order(doctype, txt, searchfield, start, page_len, filters) -> li
23962396
fields=["name"],
23972397
filters=[
23982398
["Sales Order", "docstatus", "=", 1],
2399+
["Sales Order", "status", "!=", "Closed"],
23992400
],
24002401
or_filters=[
24012402
["Sales Order Item", "item_code", "=", filters.get("production_item")],

erpnext/patches.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,4 +456,5 @@ erpnext.patches.v16_0.update_tax_withholding_field_in_payment_entry
456456
erpnext.patches.v16_0.migrate_tax_withholding_data
457457
erpnext.patches.v16_0.update_corrected_cancelled_status
458458
erpnext.patches.v16_0.fix_barcode_typo
459-
erpnext.patches.v16_0.set_post_change_gl_entries_on_pos_settings
459+
erpnext.patches.v16_0.set_post_change_gl_entries_on_pos_settings
460+
erpnext.patches.v16_0.update_sales_order_item_status
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import frappe
2+
3+
4+
def execute():
5+
frappe.db.auto_commit_on_many_writes = 1
6+
sales_order = frappe.qb.DocType("Sales Order")
7+
sales_order_item = frappe.qb.DocType("Sales Order Item")
8+
9+
try:
10+
frappe.qb.update(sales_order_item).join(sales_order).on(
11+
sales_order.name == sales_order_item.parent
12+
).set(sales_order_item.is_closed, 1).where(
13+
(sales_order.name == sales_order_item.parent)
14+
& (sales_order.status == "Closed")
15+
& (sales_order_item.is_closed == 0)
16+
).run()
17+
finally:
18+
frappe.db.auto_commit_on_many_writes = 0

erpnext/public/js/utils.js

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -610,23 +610,31 @@ erpnext.utils.update_child_items = function (opts) {
610610
const has_reserved_stock = opts.has_reserved_stock ? true : false;
611611
const get_precision = (fieldname) => child_meta.fields.find((f) => f.fieldname == fieldname).precision;
612612

613-
this.data = frm.doc[opts.child_docname].map((d) => {
614-
return {
615-
docname: d.name,
616-
name: d.name,
617-
item_code: d.item_code,
618-
item_name: d.item_name,
619-
delivery_date: d.delivery_date,
620-
schedule_date: d.schedule_date,
621-
conversion_factor: d.conversion_factor,
622-
qty: d.qty,
623-
rate: d.rate,
624-
uom: d.uom,
625-
fg_item: d.fg_item,
626-
fg_item_qty: d.fg_item_qty,
627-
description: d.description,
628-
};
629-
});
613+
const is_sales_order = frm.doc.doctype === "Sales Order";
614+
this.data = frm.doc[opts.child_docname]
615+
.filter((d) => {
616+
if (is_sales_order) {
617+
return !d.is_closed;
618+
}
619+
return true;
620+
})
621+
.map((d) => {
622+
return {
623+
docname: d.name,
624+
name: d.name,
625+
item_code: d.item_code,
626+
item_name: d.item_name,
627+
delivery_date: d.delivery_date,
628+
schedule_date: d.schedule_date,
629+
conversion_factor: d.conversion_factor,
630+
qty: d.qty,
631+
rate: d.rate,
632+
uom: d.uom,
633+
fg_item: d.fg_item,
634+
fg_item_qty: d.fg_item_qty,
635+
description: d.description,
636+
};
637+
});
630638

631639
const fields = [
632640
{

erpnext/selling/doctype/sales_order/sales_order.js

Lines changed: 187 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ frappe.ui.form.on("Sales Order", {
2828
color = "yellow";
2929
} else if (doc.stock_qty <= doc.delivered_qty) {
3030
color = "green";
31+
} else if (doc.is_closed) {
32+
color = "grey";
3133
} else {
3234
color = "orange";
3335
}
@@ -996,6 +998,18 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
996998
() => this.close_sales_order(),
997999
__("Status")
9981000
);
1001+
1002+
this.frm.add_custom_button(
1003+
__("Close selected items"),
1004+
() => this.close_selected_items(),
1005+
__("Status")
1006+
);
1007+
1008+
this.frm.add_custom_button(
1009+
__("Re-open selected items"),
1010+
() => this.reopen_selected_items(),
1011+
__("Status")
1012+
);
9991013
}
10001014
}
10011015

@@ -1709,7 +1723,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
17091723
me.frm.doc.items.forEach((d) => {
17101724
let ordered_qty = me.get_ordered_qty(d, me.frm.doc);
17111725
let pending_qty = (flt(d.stock_qty) - ordered_qty) / flt(d.conversion_factor);
1712-
if (pending_qty > 0) {
1726+
if (pending_qty > 0 && !d.is_closed) {
17131727
po_items.push({
17141728
name: d.name,
17151729
item_name: d.item_name,
@@ -1779,6 +1793,178 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
17791793
});
17801794
d.show();
17811795
}
1796+
reopen_selected_items() {
1797+
var me = this;
1798+
this.data = this.frm.doc.items
1799+
.filter((d) => d.is_closed)
1800+
.map((d) => {
1801+
return {
1802+
docname: d.name,
1803+
item_code: d.item_code,
1804+
qty: d.qty,
1805+
is_close: d.is_closed,
1806+
delivered_qty: d.delivered_qty,
1807+
};
1808+
});
1809+
const reopen_item_fields = [
1810+
{
1811+
fieldtype: "Link",
1812+
fieldname: "item_code",
1813+
options: "Item",
1814+
in_list_view: 1,
1815+
read_only: 1,
1816+
},
1817+
{
1818+
fieldtype: "Float",
1819+
fieldname: "qty",
1820+
read_only: 1,
1821+
in_list_view: 1,
1822+
label: __("Qty"),
1823+
},
1824+
];
1825+
var d = new frappe.ui.Dialog({
1826+
title: __("Re-open Selected Items"),
1827+
size: "large",
1828+
fields: [
1829+
{
1830+
fieldname: "reopen_items",
1831+
fieldtype: "Table",
1832+
label: __("Items"),
1833+
cannot_add_rows: true,
1834+
in_place_edit: true,
1835+
fields: reopen_item_fields,
1836+
data: this.data,
1837+
get_data: () => {
1838+
return this.data;
1839+
},
1840+
},
1841+
],
1842+
primary_action_label: __("Re-open"),
1843+
primary_action: function () {
1844+
let values = d.get_values();
1845+
1846+
let selected_items = (values.reopen_items || []).filter((row) => row.__checked);
1847+
if (!selected_items.length) {
1848+
frappe.msgprint(__("Please select one item to re-open"));
1849+
return;
1850+
}
1851+
frappe.call({
1852+
method: "erpnext.selling.doctype.sales_order.sales_order.close_or_reopen_selected_items",
1853+
args: { sales_order: me.frm.doc.name, selected_items: selected_items, status: "Re-open" },
1854+
callback: (r) => {
1855+
if (!r.exc) {
1856+
d.hide();
1857+
me.frm.reload_doc();
1858+
frappe.show_alert({
1859+
message: __("Selected items re-opened"),
1860+
indicator: "green",
1861+
});
1862+
}
1863+
},
1864+
});
1865+
},
1866+
});
1867+
1868+
d.show();
1869+
}
1870+
1871+
close_selected_items() {
1872+
var me = this;
1873+
this.data = this.frm.doc.items
1874+
.filter((d) => d.qty > flt(d.delivered_qty) && !d.is_closed)
1875+
.map((d) => {
1876+
return {
1877+
docname: d.name,
1878+
item_code: d.item_code,
1879+
qty: d.qty,
1880+
is_close: d.is_closed,
1881+
delivered_qty: d.delivered_qty,
1882+
};
1883+
});
1884+
const close_item_fields = [
1885+
{
1886+
fieldtype: "Link",
1887+
fieldname: "item_code",
1888+
options: "Item",
1889+
in_list_view: 1,
1890+
read_only: 1,
1891+
},
1892+
{
1893+
fieldtype: "Float",
1894+
fieldname: "qty",
1895+
read_only: 1,
1896+
in_list_view: 1,
1897+
label: __("Qty"),
1898+
},
1899+
];
1900+
var d = new frappe.ui.Dialog({
1901+
title: __("Close Selected Items"),
1902+
size: "large",
1903+
fields: [
1904+
{
1905+
fieldname: "select_all",
1906+
fieldtype: "Check",
1907+
label: __("Select all items"),
1908+
default: 1,
1909+
onchange: function () {
1910+
const table_field = d.get_field("close_items");
1911+
table_field.df.hidden = this.get_value();
1912+
table_field.refresh();
1913+
},
1914+
},
1915+
{
1916+
fieldname: "close_items",
1917+
fieldtype: "Table",
1918+
label: __("Items"),
1919+
cannot_add_rows: true,
1920+
in_place_edit: true,
1921+
fields: close_item_fields,
1922+
data: this.data,
1923+
hidden: true,
1924+
get_data: () => {
1925+
return this.data;
1926+
},
1927+
},
1928+
],
1929+
primary_action_label: __("Close"),
1930+
primary_action: function () {
1931+
let values = d.get_values();
1932+
1933+
if (values.select_all) {
1934+
me.close_sales_order();
1935+
d.hide();
1936+
return;
1937+
}
1938+
let selected_items = (values.close_items || []).filter((row) => row.__checked);
1939+
if (selected_items.length == me.data.length) {
1940+
me.close_sales_order();
1941+
d.hide();
1942+
return;
1943+
}
1944+
if (!selected_items.length) {
1945+
frappe.msgprint(__("Please select one item to close"));
1946+
return;
1947+
}
1948+
frappe.call({
1949+
method: "erpnext.selling.doctype.sales_order.sales_order.close_or_reopen_selected_items",
1950+
args: { sales_order: me.frm.doc.name, selected_items: selected_items, status: "Close" },
1951+
callback: (r) => {
1952+
if (!r.exc) {
1953+
d.hide();
1954+
me.frm.reload_doc();
1955+
frappe.show_alert({
1956+
message: __("Selected items closed"),
1957+
indicator: "green",
1958+
});
1959+
}
1960+
},
1961+
});
1962+
},
1963+
});
1964+
1965+
d.show();
1966+
}
1967+
17821968
close_sales_order() {
17831969
this.frm.cscript.update_status("Close", "Closed");
17841970
}

0 commit comments

Comments
 (0)