From 227db769a0c72631f3148b07c2285c3df113c106 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 8 May 2014 19:06:01 +0530 Subject: [PATCH 1/5] Item valuation rate on material transfer based on fifo --- erpnext/stock/utils.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 526b7c2d6f..8fe5284c47 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -79,8 +79,7 @@ def get_incoming_rate(args): if not previous_sle: return 0.0 previous_stock_queue = json.loads(previous_sle.get('stock_queue', '[]') or '[]') - in_rate = previous_stock_queue and \ - get_fifo_rate(previous_stock_queue, args.get("qty") or 0) or 0 + in_rate = get_fifo_rate(previous_stock_queue, args.get("qty") or 0) if previous_stock_queue else 0 elif valuation_method == 'Moving Average': in_rate = previous_sle.get('valuation_rate') or 0 @@ -107,24 +106,25 @@ def get_fifo_rate(previous_stock_queue, qty): total = sum(f[0] for f in previous_stock_queue) return total and sum(f[0] * f[1] for f in previous_stock_queue) / flt(total) or 0.0 else: - outgoing_cost = 0 + available_qty_for_outgoing, outgoing_cost = 0, 0 qty_to_pop = abs(qty) while qty_to_pop and previous_stock_queue: batch = previous_stock_queue[0] if 0 < batch[0] <= qty_to_pop: # if batch qty > 0 # not enough or exactly same qty in current batch, clear batch + available_qty_for_outgoing += flt(batch[0]) outgoing_cost += flt(batch[0]) * flt(batch[1]) qty_to_pop -= batch[0] previous_stock_queue.pop(0) else: # all from current batch + available_qty_for_outgoing += flt(qty_to_pop) outgoing_cost += flt(qty_to_pop) * flt(batch[1]) batch[0] -= qty_to_pop qty_to_pop = 0 - # if queue gets blank and qty_to_pop remaining, get average rate of full queue - return outgoing_cost / (abs(qty) - qty_to_pop) + return outgoing_cost / available_qty_for_outgoing def get_valid_serial_nos(sr_nos, qty=0, item_code=''): """split serial nos, validate and return list of valid serial nos""" From 3c5bc544ea9d17ce8909e03e73b7b133ba686c43 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 8 May 2014 19:07:02 +0530 Subject: [PATCH 2/5] Serial no and batch no field added in purchase receipt item supplied table --- .../purchase_receipt_item_supplied.json | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json b/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json index 25ff1da98f..5a9f4b6ac8 100644 --- a/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json +++ b/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json @@ -1,5 +1,5 @@ { - "creation": "2013-02-22 01:27:42.000000", + "creation": "2013-02-22 01:27:42", "docstatus": 0, "doctype": "DocType", "fields": [ @@ -35,6 +35,21 @@ "read_only": 1, "width": "300px" }, + { + "fieldname": "batch_no", + "fieldtype": "Link", + "label": "Batch No", + "no_copy": 1, + "options": "Batch", + "permlevel": 0 + }, + { + "fieldname": "serial_no", + "fieldtype": "Text", + "label": "Serial No", + "no_copy": 1, + "permlevel": 0 + }, { "fieldname": "col_break1", "fieldtype": "Column Break", @@ -57,6 +72,7 @@ "oldfieldname": "consumed_qty", "oldfieldtype": "Currency", "permlevel": 0, + "read_only": 1, "reqd": 1 }, { @@ -137,9 +153,12 @@ "hide_toolbar": 0, "idx": 1, "istable": 1, - "modified": "2014-02-13 11:29:35.000000", + "modified": "2014-05-08 18:37:42.966473", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Receipt Item Supplied", - "owner": "wasim@webnotestech.com" + "owner": "wasim@webnotestech.com", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file From 844dd36ff9bda1b4abe268d52e22ca3923f3a433 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 8 May 2014 19:07:34 +0530 Subject: [PATCH 3/5] PP tool: get items from SO based on item filter --- .../production_planning_tool.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py b/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py index 1e753196eb..a59e0e9fd9 100644 --- a/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py +++ b/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py @@ -106,14 +106,21 @@ class ProductionPlanningTool(Document): msgprint(_("Please enter sales order in the above table")) return [] + item_condition = "" + if self.fg_item: + item_condition = ' and so_item.item_code = "' + self.fg_item + '"' + items = frappe.db.sql("""select distinct parent, item_code, warehouse, (qty - ifnull(delivered_qty, 0)) as pending_qty from `tabSales Order Item` so_item where parent in (%s) and docstatus = 1 and ifnull(qty, 0) > ifnull(delivered_qty, 0) and exists (select * from `tabItem` item where item.name=so_item.item_code and (ifnull(item.is_pro_applicable, 'No') = 'Yes' - or ifnull(item.is_sub_contracted_item, 'No') = 'Yes'))""" % \ - (", ".join(["%s"] * len(so_list))), tuple(so_list), as_dict=1) + or ifnull(item.is_sub_contracted_item, 'No') = 'Yes')) %s""" % \ + (", ".join(["%s"] * len(so_list)), item_condition), tuple(so_list), as_dict=1) + + if self.fg_item: + item_condition = ' and pi.item_code = "' + self.fg_item + '"' packed_items = frappe.db.sql("""select distinct pi.parent, pi.item_code, pi.warehouse as reserved_warhouse, (((so_item.qty - ifnull(so_item.delivered_qty, 0)) * pi.qty) / so_item.qty) @@ -124,8 +131,8 @@ class ProductionPlanningTool(Document): and so_item.parent in (%s) and ifnull(so_item.qty, 0) > ifnull(so_item.delivered_qty, 0) and exists (select * from `tabItem` item where item.name=pi.item_code and (ifnull(item.is_pro_applicable, 'No') = 'Yes' - or ifnull(item.is_sub_contracted_item, 'No') = 'Yes'))""" % \ - (", ".join(["%s"] * len(so_list))), tuple(so_list), as_dict=1) + or ifnull(item.is_sub_contracted_item, 'No') = 'Yes')) %s""" % \ + (", ".join(["%s"] * len(so_list)), item_condition), tuple(so_list), as_dict=1) return items + packed_items From 5d070753fd563e089f06796767197e5fbc538cdc Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 8 May 2014 19:08:06 +0530 Subject: [PATCH 4/5] Item managed batch-wise, can not be reconciled through stock reconciliation --- .../doctype/stock_reconciliation/stock_reconciliation.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 870fcd0bb2..4bc34d6d59 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -106,6 +106,11 @@ class StockReconciliation(StockController): if item.has_serial_no == "Yes": raise frappe.ValidationError, _("Serialized Item {0} cannot be updated using Stock Reconciliation").format(item_code) + # item managed batch-wise not allowed + if item.has_batch_no == "Yes": + frappe.throw(_("Item: {0} managed batch-wise, can not be reconciled using \ + Stock Reconciliation, instead use Stock Entry").format(item_code)) + # docstatus should be < 2 validate_cancelled_item(item_code, item.docstatus, verbose=0) From 344f4436f19be8dd5720810094b6c236cbd4cc0c Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Thu, 8 May 2014 19:08:20 +0530 Subject: [PATCH 5/5] Create / update raw materials supplied table for sub-contracting --- .../doctype/purchase_order/purchase_order.py | 2 +- erpnext/controllers/buying_controller.py | 86 +++++++++++++------ .../purchase_receipt/purchase_receipt.py | 2 +- 3 files changed, 62 insertions(+), 28 deletions(-) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 69f5c9538c..8d6ba46040 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -45,7 +45,7 @@ class PurchaseOrder(BuyingController): self.validate_with_previous_doc() self.validate_for_subcontracting() - self.update_raw_materials_supplied("po_raw_material_details") + self.create_raw_materials_supplied("po_raw_material_details") def validate_with_previous_doc(self): super(PurchaseOrder, self).validate_with_previous_doc(self.tname, { diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 07227985be..d2fc2bf94d 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -200,48 +200,82 @@ class BuyingController(StockController): and not self.supplier_warehouse: frappe.throw(_("Supplier Warehouse mandatory for sub-contracted Purchase Receipt")) - def update_raw_materials_supplied(self, raw_material_table): - self.set(raw_material_table, []) + def create_raw_materials_supplied(self, raw_material_table): if self.is_subcontracted=="Yes": + parent_items = [] + rm_supplied_idx = 0 for item in self.get(self.fname): if self.doctype == "Purchase Receipt": item.rm_supp_cost = 0.0 if item.item_code in self.sub_contracted_items: - self.add_bom_items(item, raw_material_table) + self.update_raw_materials_supplied(item, raw_material_table, rm_supplied_idx) + + if [item.item_code, item.name] not in parent_items: + parent_items.append([item.item_code, item.name]) + + self.cleanup_raw_materials_supplied(parent_items, raw_material_table) elif self.doctype == "Purchase Receipt": for item in self.get(self.fname): item.rm_supp_cost = 0.0 - def add_bom_items(self, d, raw_material_table): - bom_items = self.get_items_from_default_bom(d.item_code) + def update_raw_materials_supplied(self, item, raw_material_table, rm_supplied_idx): + bom_items = self.get_items_from_default_bom(item.item_code) raw_materials_cost = 0 - for item in bom_items: - required_qty = flt(item.qty_consumed_per_unit) * flt(d.qty) * flt(d.conversion_factor) - rm_doclist = { - "doctype": self.doctype + " Item Supplied", - "reference_name": d.name, - "bom_detail_no": item.name, - "main_item_code": d.item_code, - "rm_item_code": item.item_code, - "stock_uom": item.stock_uom, - "required_qty": required_qty, - "conversion_factor": d.conversion_factor, - "rate": item.rate, - "amount": required_qty * flt(item.rate) - } - if self.doctype == "Purchase Receipt": - rm_doclist.update({ - "consumed_qty": required_qty, - "description": item.description, - }) - self.append(raw_material_table, rm_doclist) + for bom_item in bom_items: + # check if exists + exists = 0 + for d in self.get(raw_material_table): + if d.main_item_code == item.item_code and d.rm_item_code == bom_item.item_code \ + and d.reference_name == item.name: + rm, exists = d, 1 + break + + if not exists: + rm = self.append(raw_material_table, {}) + + required_qty = flt(bom_item.qty_consumed_per_unit) * flt(item.qty) * flt(item.conversion_factor) + rm.reference_name = item.name + rm.bom_detail_no = bom_item.name + rm.main_item_code = item.item_code + rm.rm_item_code = bom_item.item_code + rm.stock_uom = bom_item.stock_uom + rm.required_qty = required_qty + + rm.conversion_factor = item.conversion_factor + rm.rate = bom_item.rate + rm.amount = required_qty * flt(bom_item.rate) + rm.idx = rm_supplied_idx + + if self.doc.doctype == "Purchase Receipt": + rm.consumed_qty = required_qty + rm.description = bom_item.description + if item.batch_no and not rm.batch_no: + rm.batch_no = item.batch_no + + rm_supplied_idx += 1 raw_materials_cost += required_qty * flt(item.rate) if self.doctype == "Purchase Receipt": - d.rm_supp_cost = raw_materials_cost + item.rm_supp_cost = raw_materials_cost + + def cleanup_raw_materials_supplied(self, parent_items, raw_material_table): + """Remove all those child items which are no longer present in main item table""" + delete_list = [] + for d in self.get(raw_material_table): + if [d.main_item_code, d.reference_name] not in parent_items: + # mark for deletion from doclist + delete_list.append([d.main_item_code, d.reference_name]) + + # delete from doclist + if delete_list: + rm_supplied_details = self.get(raw_material_table) + self.set(raw_material_table, []) + for d in rm_supplied_details: + if d not in delete_list: + self.append(raw_material_table, d) def get_items_from_default_bom(self, item_code): bom_items = frappe.db.sql("""select t2.item_code, t2.qty_consumed_per_unit, diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 7f7dd56b7d..90161f5456 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -60,7 +60,7 @@ class PurchaseReceipt(BuyingController): # sub-contracting self.validate_for_subcontracting() - self.update_raw_materials_supplied("pr_raw_material_details") + self.create_raw_materials_supplied("pr_raw_material_details") self.update_valuation_rate("purchase_receipt_details")