diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index b0a59d00cdf..b3d1d554b42 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -169,10 +169,10 @@ class Account(NestedSet): # Add company abbr if not provided from erpnext.setup.doctype.company.company import get_name_with_abbr new_account = get_name_with_abbr(new, self.company) - new_account = get_name_with_number(new_account, self.account_number) - - # Validate properties before merging - if merge: + if not merge: + new_account = get_name_with_number(new_account, self.account_number) + else: + # Validate properties before merging if not frappe.db.exists("Account", new): throw(_("Account {0} does not exist").format(new)) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 07853d0e4a7..3f9bca7cf1a 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -350,7 +350,6 @@ class PurchaseInvoice(BuyingController): self.negative_expense_to_be_booked = 0.0 gl_entries = [] - self.make_supplier_gl_entry(gl_entries) self.make_item_gl_entries(gl_entries) self.make_tax_gl_entries(gl_entries) @@ -424,7 +423,10 @@ class PurchaseInvoice(BuyingController): # sub-contracting warehouse if flt(item.rm_supp_cost): - supplier_warehouse_account = warehouse_account[self.supplier_warehouse]["name"] + supplier_warehouse_account = warehouse_account[self.supplier_warehouse]["account"] + if not supplier_warehouse_account: + frappe.throw(_("Please set account in Warehouse {0}") + .format(self.supplier_warehouse)) gl_entries.append(self.get_gl_dict({ "account": supplier_warehouse_account, "against": item.expense_account, diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index 0394f74b714..ef59cd85042 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -12,6 +12,15 @@ frappe.ui.form.on("Purchase Order", { 'Purchase Invoice': 'Invoice', 'Stock Entry': 'Material to Supplier' } + + frm.set_query("reserve_warehouse", "supplied_items", function() { + return { + filters: { + "company": frm.doc.company, + "is_group": 0 + } + } + }); }, onload: function(frm) { @@ -289,7 +298,8 @@ cur_frm.fields_dict['items'].grid.get_field('bom').get_query = function(doc, cdt filters: [ ['BOM', 'item', '=', d.item_code], ['BOM', 'is_active', '=', '1'], - ['BOM', 'docstatus', '=', '1'] + ['BOM', 'docstatus', '=', '1'], + ['BOM', 'company', '=', doc.company] ] } } diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index 2191a556c41..2325a8c3db3 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -3071,6 +3071,38 @@ "bold": 0, "collapsible": 0, "columns": 0, + "depends_on": "eval:doc.is_subcontracted", + "fieldname": "supplied_items_section", + "fieldtype": "Section Break", + "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": "Supplied Items", + "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, + "depends_on": "", "fieldname": "supplied_items", "fieldtype": "Table", "hidden": 0, @@ -3261,8 +3293,8 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-12-19 14:53:03.986840", - "modified_by": "nabinhait@gmail.com", + "modified": "2018-01-05 14:44:56.132189", + "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", "owner": "Administrator", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index de77b24352d..c6e21ada679 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -71,8 +71,10 @@ class PurchaseOrder(BuyingController): def validate_supplier(self): prevent_po = frappe.db.get_value("Supplier", self.supplier, 'prevent_pos') if prevent_po: - standing = frappe.db.get_value("Supplier Scorecard",self.supplier, 'status') - frappe.throw(_("Purchase Orders are not allowed for {0} due to a scorecard standing of {1}.").format(self.supplier, standing)) + standing = frappe.db.get_value("Supplier Scorecard", self.supplier, 'status') + if standing: + frappe.throw(_("Purchase Orders are not allowed for {0} due to a scorecard standing of {1}.") + .format(self.supplier, standing)) warn_po = frappe.db.get_value("Supplier", self.supplier, 'warn_pos') if warn_po: @@ -184,6 +186,9 @@ class PurchaseOrder(BuyingController): self.set_status(update=True, status=status) self.update_requested_qty() self.update_ordered_qty() + if self.is_subcontracted == "Yes": + self.update_reserved_qty_for_subcontract() + self.notify_update() clear_doctype_notifications(self) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index cb7d2264899..5ae4d3b3441 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -6,8 +6,8 @@ import unittest import frappe import frappe.defaults from frappe.utils import flt, add_days, nowdate -from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt, make_purchase_invoice - +from erpnext.buying.doctype.purchase_order.purchase_order import (make_purchase_receipt, make_purchase_invoice, make_stock_entry as make_subcontract_transfer_entry) +from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry class TestPurchaseOrder(unittest.TestCase): def test_make_purchase_receipt(self): @@ -183,37 +183,128 @@ class TestPurchaseOrder(unittest.TestCase): self.assertTrue(pi.get('payment_schedule')) def test_reserved_qty_subcontract_po(self): - bin = frappe.get_all("Bin", filters={"warehouse": "_Test Warehouse - _TC"}, - fields=["item_code","reserved_qty_for_sub_contract"]) + # Make stock available for raw materials + make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100) + make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test Item Home Desktop 100", + qty=20, basic_rate=100) - item_supplied = frappe.get_all("Purchase Order Item Supplied", filters={"main_item_code": "_Test FG Item", "reserve_warehouse": "_Test Warehouse - _TC"}, - fields=["rm_item_code","required_qty"]) + bin1 = frappe.db.get_value("Bin", + filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, + fieldname=["reserved_qty_for_sub_contract", "projected_qty"], as_dict=1) - for item_bin in bin: - total_sup_qty = 0 - for item_sup in item_supplied: - if item_bin["item_code"] == item_sup["rm_item_code"]: - total_sup_qty = total_sup_qty + item_sup["required_qty"] + # Submit PO + po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes") - self.assertEquals(item_bin["reserved_qty_for_sub_contract"],total_sup_qty) + bin2 = frappe.db.get_value("Bin", + filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, + fieldname=["reserved_qty_for_sub_contract", "projected_qty"], as_dict=1) + self.assertEquals(bin2.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10) + self.assertEquals(bin2.projected_qty, bin1.projected_qty - 10) + + # Create stock transfer + se = frappe.get_doc(make_subcontract_transfer_entry(po.name, "_Test FG Item")) + se.to_warehouse = "_Test Warehouse 1 - _TC" + for d in se.get("items"): + if d.item_code == "_Test Item": + d.qty = 6 + se.save() + se.submit() + + bin3 = frappe.db.get_value("Bin", + filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, + fieldname="reserved_qty_for_sub_contract", as_dict=1) + + self.assertEquals(bin3.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) + + # close PO + po.update_status("Closed") + bin4 = frappe.db.get_value("Bin", + filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, + fieldname="reserved_qty_for_sub_contract", as_dict=1) + + self.assertEquals(bin4.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) + + # Re-open PO + po.update_status("Submitted") + bin5 = frappe.db.get_value("Bin", + filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, + fieldname="reserved_qty_for_sub_contract", as_dict=1) + + self.assertEquals(bin5.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) + + # make Purchase Receipt against PO + pr = make_purchase_receipt(po.name) + pr.supplier_warehouse = "_Test Warehouse 1 - _TC" + pr.save() + pr.submit() + + bin6 = frappe.db.get_value("Bin", + filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, + fieldname="reserved_qty_for_sub_contract", as_dict=1) + + self.assertEquals(bin6.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) + + # Cancel PR + pr.cancel() + bin7 = frappe.db.get_value("Bin", + filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, + fieldname="reserved_qty_for_sub_contract", as_dict=1) + + self.assertEquals(bin7.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) + + # Make Purchase Invoice + pi = make_purchase_invoice(po.name) + pi.update_stock = 1 + pi.supplier_warehouse = "_Test Warehouse 1 - _TC" + pi.insert() + pi.submit() + bin8 = frappe.db.get_value("Bin", + filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, + fieldname="reserved_qty_for_sub_contract", as_dict=1) + + self.assertEquals(bin8.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) + + # Cancel PR + pi.cancel() + bin9 = frappe.db.get_value("Bin", + filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, + fieldname="reserved_qty_for_sub_contract", as_dict=1) + + self.assertEquals(bin9.reserved_qty_for_sub_contract, bin2.reserved_qty_for_sub_contract - 6) + + # Cancel Stock Entry + se.cancel() + bin10 = frappe.db.get_value("Bin", + filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, + fieldname="reserved_qty_for_sub_contract", as_dict=1) + self.assertEquals(bin10.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10) + + # Cancel PO + po.reload() + po.cancel() + bin11 = frappe.db.get_value("Bin", + filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"}, + fieldname="reserved_qty_for_sub_contract", as_dict=1) + + self.assertEquals(bin11.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract) def get_same_items(): return [ - { - "item_code": "_Test FG Item", - "warehouse": "_Test Warehouse - _TC", - "qty": 1, - "rate": 500, - "schedule_date": add_days(nowdate(), 1) - }, - { - "item_code": "_Test FG Item", - "warehouse": "_Test Warehouse - _TC", - "qty": 4, - "rate": 500, - "schedule_date": add_days(nowdate(), 1) - } - ] + { + "item_code": "_Test FG Item", + "warehouse": "_Test Warehouse - _TC", + "qty": 1, + "rate": 500, + "schedule_date": add_days(nowdate(), 1) + }, + { + "item_code": "_Test FG Item", + "warehouse": "_Test Warehouse - _TC", + "qty": 4, + "rate": 500, + "schedule_date": add_days(nowdate(), 1) + } + ] def create_purchase_order(**args): po = frappe.new_doc("Purchase Order") diff --git a/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.json b/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.json index 5e8754fad33..d4a02fbb197 100644 --- a/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.json +++ b/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.json @@ -15,7 +15,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "columns": 0, + "columns": 2, "fieldname": "main_item_code", "fieldtype": "Data", "hidden": 0, @@ -46,7 +46,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "columns": 0, + "columns": 2, "fieldname": "rm_item_code", "fieldtype": "Data", "hidden": 0, @@ -77,7 +77,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "columns": 0, + "columns": 2, "fieldname": "required_qty", "fieldtype": "Float", "hidden": 0, @@ -108,7 +108,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "columns": 0, + "columns": 2, "fieldname": "rate", "fieldtype": "Currency", "hidden": 0, @@ -209,7 +209,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 1, + "in_list_view": 0, "in_standard_filter": 0, "label": "BOM Detail No", "length": 0, @@ -240,7 +240,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 1, + "in_list_view": 0, "in_standard_filter": 0, "label": "Reference Name", "length": 0, @@ -326,7 +326,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "columns": 0, + "columns": 2, "fieldname": "reserve_warehouse", "fieldtype": "Link", "hidden": 0, @@ -334,7 +334,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 0, "label": "Reserve Warehouse", "length": 0, @@ -363,7 +363,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2017-11-29 21:10:40.431423", + "modified": "2018-01-05 14:47:15.400785", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item Supplied", diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index c533202150c..d3bcebce479 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -160,9 +160,10 @@ class BuyingController(StockController): if item in self.sub_contracted_items and not item.bom: frappe.throw(_("Please select BOM in BOM field for Item {0}").format(item.item_code)) - for supplied_item in self.get("supplied_items"): - if not supplied_item.reserve_warehouse: - frappe.throw(_("Reserved Warehouse is mandatory for Item {0} in Raw Materials supplied").format(frappe.bold(supplied_item.rm_item_code))) + if self.doctype == "Purchase Order": + for supplied_item in self.get("supplied_items"): + if not supplied_item.reserve_warehouse: + frappe.throw(_("Reserved Warehouse is mandatory for Item {0} in Raw Materials supplied").format(frappe.bold(supplied_item.rm_item_code))) else: for item in self.get("items"): @@ -195,9 +196,14 @@ class BuyingController(StockController): raw_materials_cost = 0 items = list(set([d.item_code for d in bom_items])) item_wh = frappe._dict(frappe.db.sql("""select item_code, default_warehouse - from `tabItem` where name in ({0})""".format(", ".join(["%s"] * len(items))), items)) + from `tabItem` where name in ({0})""".format(", ".join(["%s"] * len(items))), items)) for bom_item in bom_items: + if self.doctype == "Purchase Order": + reserve_warehouse = bom_item.source_warehouse or item_wh.get(bom_item.item_code) + if frappe.db.get_value("Warehouse", reserve_warehouse, "company") != self.company: + reserve_warehouse = None + # check if exists exists = 0 for d in self.get(raw_material_table): @@ -217,7 +223,8 @@ class BuyingController(StockController): rm.rm_item_code = bom_item.item_code rm.stock_uom = bom_item.stock_uom rm.required_qty = required_qty - rm.reserve_warehouse = bom_item.source_warehouse or item_wh.get(bom_item.item_code) + if self.doctype == "Purchase Order" and not rm.reserve_warehouse: + rm.reserve_warehouse = reserve_warehouse rm.conversion_factor = item.conversion_factor @@ -344,7 +351,7 @@ class BuyingController(StockController): frappe.get_meta(item_row.doctype).get_label(fieldname), item_row['item_code']))) def update_stock_ledger(self, allow_negative_stock=False, via_landed_cost_voucher=False): - self.update_ordered_qty() + self.update_ordered_and_reserved_qty() sl_entries = [] stock_items = self.get_stock_items() @@ -386,7 +393,7 @@ class BuyingController(StockController): self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher) - def update_ordered_qty(self): + def update_ordered_and_reserved_qty(self): po_map = {} for d in self.get("items"): if self.doctype=="Purchase Receipt" \ @@ -405,6 +412,8 @@ class BuyingController(StockController): frappe.InvalidStatusError) po_obj.update_ordered_qty(po_item_rows) + if self.is_subcontracted: + po_obj.update_reserved_qty_for_subcontract() def make_sl_entries_for_supplier_warehouse(self, sl_entries): if hasattr(self, 'supplied_items'): diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 5976915ede5..5c2c1122366 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -484,4 +484,5 @@ erpnext.patches.v10_0.copy_projects_renamed_fields erpnext.patches.v10_0.enabled_regional_print_format_based_on_country erpnext.patches.v10_0.update_asset_calculate_depreciation erpnext.patches.v10_0.add_guardian_role_for_parent_portal -erpnext.patches.v10_0.set_numeric_ranges_in_template_if_blank \ No newline at end of file +erpnext.patches.v10_0.set_numeric_ranges_in_template_if_blank +erpnext.patches.v10_0.update_reserved_qty_for_purchase_order \ No newline at end of file diff --git a/erpnext/patches/v5_8/update_order_reference_in_return_entries.py b/erpnext/patches/v5_8/update_order_reference_in_return_entries.py index d7972dc64a8..503263834c0 100644 --- a/erpnext/patches/v5_8/update_order_reference_in_return_entries.py +++ b/erpnext/patches/v5_8/update_order_reference_in_return_entries.py @@ -10,6 +10,7 @@ def execute(): frappe.reload_doctype("Purchase Receipt") frappe.reload_doctype("Sales Order Item") frappe.reload_doctype("Purchase Order Item") + frappe.reload_doctype("Purchase Order Item Supplied") # sales return return_entries = list(frappe.db.sql(""" @@ -86,6 +87,6 @@ def execute(): """, (order_details[0].purchase_order, order_details[0].po_detail, d.row_id)) pr = frappe.get_doc("Purchase Receipt", d.name) - pr.update_ordered_qty() + pr.update_ordered_and_reserved_qty() pr.update_prevdoc_status() diff --git a/erpnext/patches/v9_2/update_reserved_qty_for_purchase_order.py b/erpnext/patches/v9_2/update_reserved_qty_for_purchase_order.py deleted file mode 100644 index 805c347c418..00000000000 --- a/erpnext/patches/v9_2/update_reserved_qty_for_purchase_order.py +++ /dev/null @@ -1,33 +0,0 @@ -import frappe -from frappe import _ -from erpnext.stock.utils import get_bin - -def execute(): - def_warehouse = frappe.db.sql("""select min(name) from `tabWarehouse` where is_group = 0""")[0][0] - - po_item = list(frappe.db.sql(("""select distinct po.name as poname, poitem.rm_item_code as rm_item_code - from `tabPurchase Order` po, `tabPurchase Order Item Supplied` poitem - where po.name = poitem.parent - and po.is_subcontracted = "Yes" - and po.docstatus = 1"""),as_dict=1)) - - items = list(set([d.rm_item_code for d in po_item])) - item_wh = frappe._dict(frappe.db.sql("""select item_code, default_warehouse - from `tabItem` where name in ({0})""".format(", ".join(["%s"] * len(items))), items)) - #Update reserved warehouse - for item in po_item: - reserve_warehouse = item_wh.get(item.rm_item_code) or def_warehouse - update_res_warehouse = frappe.db.sql("""update `tabPurchase Order Item Supplied` - set reserve_warehouse = %s - where parent = %s and rm_item_code = %s""",(reserve_warehouse - ,item["poname"],item["rm_item_code"])) - #Update bin - item_wh_bin = frappe.db.sql(("""select distinct poitemsup.rm_item_code as rm_item_code, - poitemsup.reserve_warehouse as reserve_warehouse - from `tabPurchase Order` po, `tabPurchase Order Item Supplied` poitemsup - where po.name = poitemsup.parent - and po.is_subcontracted = "Yes" - and po.docstatus = 1"""),as_dict=1) - for d in item_wh_bin: - stock_bin = get_bin(d["rm_item_code"], d["reserve_warehouse"]) - stock_bin.update_reserved_qty_for_sub_contracting() \ No newline at end of file diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py index e5b4fdfdff9..fd344234838 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -64,7 +64,7 @@ class Bin(Document): 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(""" @@ -95,28 +95,39 @@ class Bin(Document): 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) + 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(qty),0) + ifnull(sum(transfer_qty),0) from `tabStock Entry` se, `tabStock Entry Detail` sed, `tabPurchase Order` po where - sed.item_code = %s - and se.name = sed.parent and se.docstatus=1 and se.purpose='Subcontract' + 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 ifnull(se.purchase_order, '') !=''""", (self.item_code))[0][0] + and po.docstatus = 1 + and po.is_subcontracted = 'Yes' + and po.status != 'Closed' + and po.per_received < 100 + """, (self.item_code))[0][0] - self.set_projected_qty() 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 update_item_projected_qty(item_code): diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 29f3553deb0..fb12f19ad66 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -121,8 +121,10 @@ class PurchaseReceipt(BuyingController): if self.per_billed < 100: 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 d33fc64d7d3..3af8d4e7156 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -586,13 +586,15 @@ class StockEntry(StockController): 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)) + 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 @@ -602,11 +604,13 @@ class StockEntry(StockController): 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": @@ -846,6 +850,20 @@ class StockEntry(StockController): 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): @@ -881,18 +899,6 @@ def move_sample_to_retention_warehouse(company, items): if stock_entry.get('items'): return stock_entry.as_dict() - 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(item.item_code) - stock_bin = get_bin(d.item_code, reserve_warehouse) - stock_bin.update_reserved_qty_for_sub_contracting() - @frappe.whitelist() def get_production_order_details(production_order): production_order = frappe.get_doc("Production Order", production_order) 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 21287b9d9ee..89a256cacfa 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 @@ def get_columns(): _("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 @@ def get_data(filters): 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 @@ def get_data(filters): 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 @@ def get_bin_list(filters): 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)