Reserve for subcontracting (#13195)
* [fix] #8427
* review comments changes
* Validation for reserved warhouse
* code improvements
* alignment
* test case
* message changes
* default warehouse / remove validation / change sql
* fix
* patch
* Fixed merge conflict
* Fixes and cleanups of reserve qty for subcontracting
* set from_warehouse only if purchase_order and purpose found (#12398)
* [HotFix] Validation issue for subcontract stock entry (#12127)
* [Fix] Validation issue for subcontract stock entry
* Update stock_entry.py
* Fixes and cleanups of reserve qty for subcontracting
* patch fixed
* Reload bin in patch
* [fix] set source warehouse in stock entry for manufacture
* [fix] #8540
* code alignment
* code alignment
* Move target warehouse validation to submit
* validation code improvement
* code changes for single stock entry
* validation fix
* call make_rm_stock_entry
* remove old stock entry method/rewrite test case
* Don't set bom_no against raw materials while trasferring items for sub-contracting
* minor fix
diff --git a/erpnext/stock/dashboard/item_dashboard.js b/erpnext/stock/dashboard/item_dashboard.js
index 4a52526..2748436 100644
--- a/erpnext/stock/dashboard/item_dashboard.js
+++ b/erpnext/stock/dashboard/item_dashboard.js
@@ -90,7 +90,7 @@
data.forEach(function(d) {
d.actual_or_pending = d.projected_qty + d.reserved_qty + d.reserved_qty_for_production;
d.pending_qty = 0;
- d.total_reserved = d.reserved_qty + d.reserved_qty_for_production;
+ d.total_reserved = d.reserved_qty + d.reserved_qty_for_production + d.reserved_qty_for_sub_contract;
if(d.actual_or_pending > d.actual_qty) {
d.pending_qty = d.actual_or_pending - d.actual_qty;
}
diff --git a/erpnext/stock/dashboard/item_dashboard.py b/erpnext/stock/dashboard/item_dashboard.py
index 0d75a9a..f95daaf 100644
--- a/erpnext/stock/dashboard/item_dashboard.py
+++ b/erpnext/stock/dashboard/item_dashboard.py
@@ -26,13 +26,14 @@
return frappe.db.sql('''
select
b.item_code, b.warehouse, b.projected_qty, b.reserved_qty,
- b.reserved_qty_for_production, b.actual_qty, b.valuation_rate, i.item_name
+ b.reserved_qty_for_production, b.reserved_qty_for_sub_contract, b.actual_qty, b.valuation_rate, i.item_name
from
tabBin b, tabItem i
where
b.item_code = i.name
and
- (b.projected_qty != 0 or b.reserved_qty != 0 or b.reserved_qty_for_production != 0 or b.actual_qty != 0)
+ (b.projected_qty != 0 or b.reserved_qty != 0 or b.reserved_qty_for_production != 0
+ or b.reserved_qty_for_sub_contract != 0 or b.actual_qty != 0)
{conditions}
order by
{sort_by} {sort_order}
diff --git a/erpnext/stock/doctype/bin/bin.json b/erpnext/stock/doctype/bin/bin.json
index 1f6e9e1..def817b 100644
--- a/erpnext/stock/doctype/bin/bin.json
+++ b/erpnext/stock/doctype/bin/bin.json
@@ -302,6 +302,36 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fieldname": "reserved_qty_for_sub_contract",
+ "fieldtype": "Float",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Reserved Qty for sub contract",
+ "length": 0,
+ "no_copy": 0,
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "print_hide_if_no_value": 0,
+ "read_only": 0,
+ "remember_last_selected_value": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"fieldname": "ma_rate",
"fieldtype": "Float",
"hidden": 1,
@@ -463,7 +493,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2017-06-13 13:06:32.601505",
+ "modified": "2017-11-22 08:14:30.615638",
"modified_by": "Administrator",
"module": "Stock",
"name": "Bin",
diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py
index f2c214d..430d9fb 100644
--- a/erpnext/stock/doctype/bin/bin.py
+++ b/erpnext/stock/doctype/bin/bin.py
@@ -61,7 +61,7 @@
def set_projected_qty(self):
self.projected_qty = (flt(self.actual_qty) + flt(self.ordered_qty)
+ flt(self.indented_qty) + flt(self.planned_qty) - flt(self.reserved_qty)
- - flt(self.reserved_qty_for_production))
+ - flt(self.reserved_qty_for_production) - flt(self.reserved_qty_for_sub_contract))
def get_first_sle(self):
sle = frappe.db.sql("""
@@ -90,6 +90,42 @@
self.db_set('reserved_qty_for_production', flt(self.reserved_qty_for_production))
self.db_set('projected_qty', self.projected_qty)
+ def update_reserved_qty_for_sub_contracting(self):
+ #reserved qty
+ reserved_qty_for_sub_contract = frappe.db.sql('''
+ select ifnull(sum(itemsup.required_qty),0)
+ from `tabPurchase Order` po, `tabPurchase Order Item Supplied` itemsup
+ where
+ itemsup.rm_item_code = %s
+ and itemsup.parent = po.name
+ and po.docstatus = 1
+ and po.is_subcontracted = 'Yes'
+ and po.status != 'Closed'
+ and po.per_received < 100
+ and itemsup.reserve_warehouse = %s''', (self.item_code, self.warehouse))[0][0]
+
+ #Get Transferred Entries
+ materials_transferred = frappe.db.sql("""
+ select
+ ifnull(sum(transfer_qty),0)
+ from
+ `tabStock Entry` se, `tabStock Entry Detail` sed, `tabPurchase Order` po
+ where
+ se.docstatus=1
+ and se.purpose='Subcontract'
+ and ifnull(se.purchase_order, '') !=''
+ and sed.item_code = %s
+ and se.name = sed.parent
+ and se.purchase_order = po.name
+ and po.docstatus = 1
+ and po.is_subcontracted = 'Yes'
+ and po.status != 'Closed'
+ and po.per_received < 100
+ """, (self.item_code))[0][0]
+
+ self.db_set('reserved_qty_for_sub_contract', (reserved_qty_for_sub_contract - materials_transferred))
+ self.set_projected_qty()
+ self.db_set('projected_qty', self.projected_qty)
def on_doctype_update():
frappe.db.add_index("Bin", ["item_code", "warehouse"])
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 29f3553..2a962cc 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -122,7 +122,8 @@
self.update_billing_status()
# Updating stock ledger should always be called after updating prevdoc status,
- # because updating ordered qty in bin depends upon updated ordered qty in PO
+ # because updating ordered qty, reserved_qty_for_subcontract in bin
+ # depends upon updated ordered qty in PO
self.update_stock_ledger()
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 7d5af5f..5aeba7c 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -11,6 +11,7 @@
from erpnext.stock.get_item_details import get_bin_details, get_default_cost_center, get_conversion_factor
from erpnext.stock.doctype.batch.batch import get_batch_no, set_batch_nos, get_batch_qty
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
+from erpnext.stock.utils import get_bin
import json
class IncorrectValuationRateError(frappe.ValidationError): pass
@@ -63,17 +64,22 @@
self.calculate_rate_and_amount(update_finished_item_rate=False)
def on_submit(self):
+
self.update_stock_ledger()
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
update_serial_nos_after_submit(self, "items")
self.update_production_order()
self.validate_purchase_order()
+ if self.purchase_order and self.purpose == "Subcontract":
+ self.update_purchase_order_supplied_items()
self.make_gl_entries()
def on_cancel(self):
self.update_stock_ledger()
self.update_production_order()
+ if self.purchase_order and self.purpose == "Subcontract":
+ self.update_purchase_order_supplied_items()
self.make_gl_entries_on_cancel()
def validate_purpose(self):
@@ -403,7 +409,7 @@
"""validation: finished good quantity should be same as manufacturing quantity"""
items_with_target_warehouse = []
for d in self.get('items'):
- if d.bom_no and flt(d.transfer_qty) != flt(self.fg_completed_qty) and (d.t_warehouse != getattr(self, "pro_doc", frappe._dict()).scrap_warehouse):
+ if self.purpose != "Subcontract" and d.bom_no and flt(d.transfer_qty) != flt(self.fg_completed_qty) and (d.t_warehouse != getattr(self, "pro_doc", frappe._dict()).scrap_warehouse):
frappe.throw(_("Quantity in row {0} ({1}) must be same as manufactured quantity {2}"). \
format(d.idx, d.transfer_qty, self.fg_completed_qty))
@@ -582,19 +588,32 @@
frappe.throw(_("Manufacturing Quantity is mandatory"))
item_dict = self.get_bom_raw_materials(self.fg_completed_qty)
+
+ #Get PO Supplied Items Details
+ if self.purchase_order and self.purpose == "Subcontract":
+ #Get PO Supplied Items Details
+ item_wh = frappe._dict(frappe.db.sql("""
+ select rm_item_code, reserve_warehouse
+ from `tabPurchase Order` po, `tabPurchase Order Item Supplied` poitemsup
+ where po.name = poitemsup.parent
+ and po.name = %s""",self.purchase_order))
for item in item_dict.values():
if self.pro_doc and not self.pro_doc.skip_transfer:
item["from_warehouse"] = self.pro_doc.wip_warehouse
-
+ #Get Reserve Warehouse from PO
+ if self.purchase_order and self.purpose=="Subcontract":
+ item["from_warehouse"] = item_wh.get(item.item_code)
item["to_warehouse"] = self.to_warehouse if self.purpose=="Subcontract" else ""
self.add_to_stock_entry_detail(item_dict)
- scrap_item_dict = self.get_bom_scrap_material(self.fg_completed_qty)
- for item in scrap_item_dict.values():
- if self.pro_doc and self.pro_doc.scrap_warehouse:
- item["to_warehouse"] = self.pro_doc.scrap_warehouse
- self.add_to_stock_entry_detail(scrap_item_dict, bom_no=self.bom_no)
+ if self.purpose != "Subcontract":
+ scrap_item_dict = self.get_bom_scrap_material(self.fg_completed_qty)
+ for item in scrap_item_dict.values():
+ if self.pro_doc and self.pro_doc.scrap_warehouse:
+ item["to_warehouse"] = self.pro_doc.scrap_warehouse
+
+ self.add_to_stock_entry_detail(scrap_item_dict, bom_no=self.bom_no)
# fetch the serial_no of the first stock entry for the second stock entry
if self.production_order and self.purpose == "Manufacture":
@@ -782,7 +801,7 @@
def add_to_stock_entry_detail(self, item_dict, bom_no=None):
expense_account, cost_center = frappe.db.get_values("Company", self.company, \
["default_expense_account", "cost_center"])[0]
-
+
for d in item_dict:
stock_uom = item_dict[d].get("stock_uom") or frappe.db.get_value("Item", d, "stock_uom")
@@ -833,7 +852,20 @@
if getdate(self.posting_date) > getdate(expiry_date):
frappe.throw(_("Batch {0} of Item {1} has expired.").format(item.batch_no, item.item_code))
+ def update_purchase_order_supplied_items(self):
+ #Get PO Supplied Items Details
+ item_wh = frappe._dict(frappe.db.sql("""
+ select rm_item_code, reserve_warehouse
+ from `tabPurchase Order` po, `tabPurchase Order Item Supplied` poitemsup
+ where po.name = poitemsup.parent
+ and po.name = %s""", self.purchase_order))
+ #Update reserved sub contracted quantity in bin based on Supplied Item Details
+ for d in self.get("items"):
+ reserve_warehouse = item_wh.get(d.item_code)
+ stock_bin = get_bin(d.item_code, reserve_warehouse)
+ stock_bin.update_reserved_qty_for_sub_contracting()
+
@frappe.whitelist()
def move_sample_to_retention_warehouse(company, items):
if isinstance(items, basestring):
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index b590822..a5814f7 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -562,9 +562,8 @@
rm_cost = 0
for d in stock_entry.get("items"):
- if d.s_warehouse:
+ if d.item_code != "_Test FG Item 2":
rm_cost += flt(d.amount)
-
fg_cost = filter(lambda x: x.item_code=="_Test FG Item 2", stock_entry.get("items"))[0].amount
self.assertEqual(fg_cost,
flt(rm_cost + bom_operation_cost + production_order.additional_operating_cost, 2))
diff --git a/erpnext/stock/page/stock_balance/stock_balance.js b/erpnext/stock/page/stock_balance/stock_balance.js
index 16a85fa..85ea5b1 100644
--- a/erpnext/stock/page/stock_balance/stock_balance.js
+++ b/erpnext/stock/page/stock_balance/stock_balance.js
@@ -48,6 +48,7 @@
{fieldname: 'projected_qty', label: __('Projected qty')},
{fieldname: 'reserved_qty', label: __('Reserved for sale')},
{fieldname: 'reserved_qty_for_production', label: __('Reserved for manufacturing')},
+ {fieldname: 'reserved_qty_for_sub_contract', label: __('Reserved for sub contracting')},
{fieldname: 'actual_qty', label: __('Actual qty in stock')},
]
},
diff --git a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
index 21287b9..89a256c 100644
--- a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
+++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
@@ -16,6 +16,7 @@
_("UOM") + ":Link/UOM:100", _("Actual Qty") + ":Float:100", _("Planned Qty") + ":Float:100",
_("Requested Qty") + ":Float:110", _("Ordered Qty") + ":Float:100",
_("Reserved Qty") + ":Float:100", _("Reserved Qty for Production") + ":Float:100",
+ _("Reserved for sub contracting") + ":Float:100",
_("Projected Qty") + ":Float:100", _("Reorder Level") + ":Float:100", _("Reorder Qty") + ":Float:100",
_("Shortage Qty") + ":Float:100"]
@@ -33,7 +34,8 @@
continue
# item = item_map.setdefault(bin.item_code, get_item(bin.item_code))
- company = warehouse_company.setdefault(bin.warehouse, frappe.db.get_value("Warehouse", bin.warehouse, "company"))
+ company = warehouse_company.setdefault(bin.warehouse,
+ frappe.db.get_value("Warehouse", bin.warehouse, "company"))
if filters.brand and filters.brand != item.brand:
continue
@@ -52,7 +54,8 @@
data.append([item.name, item.item_name, item.description, item.item_group, item.brand, bin.warehouse,
item.stock_uom, bin.actual_qty, bin.planned_qty, bin.indented_qty, bin.ordered_qty,
- bin.reserved_qty, bin.reserved_qty_for_production, bin.projected_qty, re_order_level, re_order_qty, shortage_qty])
+ bin.reserved_qty, bin.reserved_qty_for_production, bin.reserved_qty_for_sub_contract,
+ bin.projected_qty, re_order_level, re_order_qty, shortage_qty])
return data
@@ -71,7 +74,7 @@
warehouse_details.rgt))
bin_list = frappe.db.sql("""select item_code, warehouse, actual_qty, planned_qty, indented_qty,
- ordered_qty, reserved_qty, reserved_qty_for_production, projected_qty
+ ordered_qty, reserved_qty, reserved_qty_for_production, reserved_qty_for_sub_contract, projected_qty
from tabBin bin {conditions} order by item_code, warehouse
""".format(conditions=" where " + " and ".join(conditions) if conditions else ""), as_dict=1)
diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py
index 6a4ac43..49909d9 100644
--- a/erpnext/stock/stock_balance.py
+++ b/erpnext/stock/stock_balance.py
@@ -150,7 +150,7 @@
if mismatch:
bin.projected_qty = (flt(bin.actual_qty) + flt(bin.ordered_qty) +
flt(bin.indented_qty) + flt(bin.planned_qty) - flt(bin.reserved_qty)
- - flt(bin.reserved_qty_for_production))
+ - flt(bin.reserved_qty_for_production)) - flt(bin.reserved_qty_for_sub_contract)
bin.save()