Skip to content

Commit

Permalink
Merge pull request frappe#30762 from ankush/bunle_pickng
Browse files Browse the repository at this point in the history
feat: support product bundles in picklist
  • Loading branch information
marination authored Apr 27, 2022
2 parents 3ae9fa9 + ebd5f0b commit 2fffc68
Show file tree
Hide file tree
Showing 11 changed files with 363 additions and 106 deletions.
4 changes: 3 additions & 1 deletion erpnext/selling/doctype/sales_order/sales_order.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,9 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
}
}

this.frm.add_custom_button(__('Pick List'), () => this.create_pick_list(), __('Create'));
if (flt(doc.per_picked, 6) < 100 && flt(doc.per_delivered, 6) < 100) {
this.frm.add_custom_button(__('Pick List'), () => this.create_pick_list(), __('Create'));
}

const order_is_a_sale = ["Sales", "Shopping Cart"].indexOf(doc.order_type) !== -1;
const order_is_maintenance = ["Maintenance"].indexOf(doc.order_type) !== -1;
Expand Down
3 changes: 2 additions & 1 deletion erpnext/selling/doctype/sales_order/sales_order.json
Original file line number Diff line number Diff line change
Expand Up @@ -1520,14 +1520,15 @@
"fieldname": "per_picked",
"fieldtype": "Percent",
"label": "% Picked",
"no_copy": 1,
"read_only": 1
}
],
"icon": "fa fa-file-text",
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2022-03-15 21:38:31.437586",
"modified": "2022-04-21 08:16:48.316074",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order",
Expand Down
50 changes: 45 additions & 5 deletions erpnext/selling/doctype/sales_order/sales_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,16 @@ def update_delivery_status(self):
if tot_qty != 0:
self.db_set("per_delivered", flt(delivered_qty / tot_qty) * 100, update_modified=False)

def update_picking_status(self):
total_picked_qty = 0.0
total_qty = 0.0
for so_item in self.items:
total_picked_qty += flt(so_item.picked_qty)
total_qty += flt(so_item.stock_qty)
per_picked = total_picked_qty / total_qty * 100

self.db_set("per_picked", flt(per_picked), update_modified=False)

def set_indicator(self):
"""Set indicator for portal"""
if self.per_billed < 100 and self.per_delivered < 100:
Expand Down Expand Up @@ -1232,9 +1242,30 @@ def make_inter_company_purchase_order(source_name, target_doc=None):

@frappe.whitelist()
def create_pick_list(source_name, target_doc=None):
def update_item_quantity(source, target, source_parent):
target.qty = flt(source.qty) - flt(source.delivered_qty)
target.stock_qty = (flt(source.qty) - flt(source.delivered_qty)) * flt(source.conversion_factor)
from erpnext.stock.doctype.packed_item.packed_item import is_product_bundle

def update_item_quantity(source, target, source_parent) -> None:
picked_qty = flt(source.picked_qty) / (flt(source.conversion_factor) or 1)
qty_to_be_picked = flt(source.qty) - max(picked_qty, flt(source.delivered_qty))

target.qty = qty_to_be_picked
target.stock_qty = qty_to_be_picked * flt(source.conversion_factor)

def update_packed_item_qty(source, target, source_parent) -> None:
qty = flt(source.qty)
for item in source_parent.items:
if source.parent_detail_docname == item.name:
picked_qty = flt(item.picked_qty) / (flt(item.conversion_factor) or 1)
pending_percent = (item.qty - max(picked_qty, item.delivered_qty)) / item.qty
target.qty = target.stock_qty = qty * pending_percent
return

def should_pick_order_item(item) -> bool:
return (
abs(item.delivered_qty) < abs(item.qty)
and item.delivered_by_supplier != 1
and not is_product_bundle(item.item_code)
)

doc = get_mapped_doc(
"Sales Order",
Expand All @@ -1245,8 +1276,17 @@ def update_item_quantity(source, target, source_parent):
"doctype": "Pick List Item",
"field_map": {"parent": "sales_order", "name": "sales_order_item"},
"postprocess": update_item_quantity,
"condition": lambda doc: abs(doc.delivered_qty) < abs(doc.qty)
and doc.delivered_by_supplier != 1,
"condition": should_pick_order_item,
},
"Packed Item": {
"doctype": "Pick List Item",
"field_map": {
"parent": "sales_order",
"name": "sales_order_item",
"parent_detail_docname": "product_bundle_item",
},
"field_no_map": ["picked_qty"],
"postprocess": update_packed_item_qty,
},
},
target_doc,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -803,13 +803,15 @@
{
"fieldname": "picked_qty",
"fieldtype": "Float",
"label": "Picked Qty"
"label": "Picked Qty (in Stock UOM)",
"no_copy": 1,
"read_only": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2022-03-15 20:17:33.984799",
"modified": "2022-04-27 03:15:34.366563",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order Item",
Expand Down
10 changes: 9 additions & 1 deletion erpnext/stock/doctype/packed_item/packed_item.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"ordered_qty",
"column_break_16",
"incoming_rate",
"picked_qty",
"page_break",
"prevdoc_doctype",
"parent_detail_docname"
Expand Down Expand Up @@ -234,13 +235,20 @@
"label": "Ordered Qty",
"no_copy": 1,
"read_only": 1
},
{
"fieldname": "picked_qty",
"fieldtype": "Float",
"label": "Picked Qty",
"no_copy": 1,
"read_only": 1
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2022-03-10 15:42:00.265915",
"modified": "2022-04-27 05:23:08.683245",
"modified_by": "Administrator",
"module": "Stock",
"name": "Packed Item",
Expand Down
6 changes: 5 additions & 1 deletion erpnext/stock/doctype/packed_item/packed_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def make_packing_list(doc):
reset = reset_packing_list(doc)

for item_row in doc.get("items"):
if frappe.db.exists("Product Bundle", {"new_item_code": item_row.item_code}):
if is_product_bundle(item_row.item_code):
for bundle_item in get_product_bundle_items(item_row.item_code):
pi_row = add_packed_item_row(
doc=doc,
Expand All @@ -54,6 +54,10 @@ def make_packing_list(doc):
set_product_bundle_rate_amount(doc, parent_items_price) # set price in bundle item


def is_product_bundle(item_code: str) -> bool:
return bool(frappe.db.exists("Product Bundle", {"new_item_code": item_code}))


def get_indexed_packed_items_table(doc):
"""
Create dict from stale packed items table like:
Expand Down
52 changes: 34 additions & 18 deletions erpnext/stock/doctype/packed_item/test_packed_item.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,58 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt

from typing import List, Optional, Tuple

import frappe
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_to_date, nowdate

from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry


def create_product_bundle(
quantities: Optional[List[int]] = None, warehouse: Optional[str] = None
) -> Tuple[str, List[str]]:
"""Get a new product_bundle for use in tests.
Create 10x required stock if warehouse is specified.
"""
if not quantities:
quantities = [2, 2]

bundle = make_item(properties={"is_stock_item": 0}).name

bundle_doc = frappe.get_doc({"doctype": "Product Bundle", "new_item_code": bundle})

components = []
for qty in quantities:
compoenent = make_item().name
components.append(compoenent)
bundle_doc.append("items", {"item_code": compoenent, "qty": qty})
if warehouse:
make_stock_entry(item=compoenent, to_warehouse=warehouse, qty=10 * qty, rate=100)

bundle_doc.insert()

return bundle, components


class TestPackedItem(FrappeTestCase):
"Test impact on Packed Items table in various scenarios."

@classmethod
def setUpClass(cls) -> None:
super().setUpClass()
cls.warehouse = "_Test Warehouse - _TC"
cls.bundle = "_Test Product Bundle X"
cls.bundle_items = ["_Test Bundle Item 1", "_Test Bundle Item 2"]

cls.bundle2 = "_Test Product Bundle Y"
cls.bundle2_items = ["_Test Bundle Item 3", "_Test Bundle Item 4"]

make_item(cls.bundle, {"is_stock_item": 0})
make_item(cls.bundle2, {"is_stock_item": 0})
for item in cls.bundle_items + cls.bundle2_items:
make_item(item, {"is_stock_item": 1})

make_item("_Test Normal Stock Item", {"is_stock_item": 1})

make_product_bundle(cls.bundle, cls.bundle_items, qty=2)
make_product_bundle(cls.bundle2, cls.bundle2_items, qty=2)
cls.bundle, cls.bundle_items = create_product_bundle(warehouse=cls.warehouse)
cls.bundle2, cls.bundle2_items = create_product_bundle(warehouse=cls.warehouse)

for item in cls.bundle_items + cls.bundle2_items:
make_stock_entry(item_code=item, to_warehouse=cls.warehouse, qty=100, rate=100)
cls.normal_item = make_item().name

def test_adding_bundle_item(self):
"Test impact on packed items if bundle item row is added."
Expand All @@ -58,7 +74,7 @@ def test_updating_bundle_item(self):
self.assertEqual(so.packed_items[1].qty, 4)

# change item code to non bundle item
so.items[0].item_code = "_Test Normal Stock Item"
so.items[0].item_code = self.normal_item
so.save()

self.assertEqual(len(so.packed_items), 0)
Expand Down
4 changes: 3 additions & 1 deletion erpnext/stock/doctype/pick_list/pick_list.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@
"set_only_once": 1
},
{
"collapsible": 1,
"fieldname": "print_settings_section",
"fieldtype": "Section Break",
"label": "Print Settings"
Expand All @@ -129,7 +130,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2021-10-05 15:08:40.369957",
"modified": "2022-04-21 07:56:40.646473",
"modified_by": "Administrator",
"module": "Stock",
"name": "Pick List",
Expand Down Expand Up @@ -199,5 +200,6 @@
],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
Loading

0 comments on commit 2fffc68

Please sign in to comment.