From f4729fa6cdbc14bf6a70e1170fb96029f74fa94b Mon Sep 17 00:00:00 2001 From: Nabin Hait <nabinhait@gmail.com> Date: Mon, 24 Aug 2015 14:32:38 +0530 Subject: [PATCH 1/4] Update delivered / ordered qty in sales/purchase order from return entry Reserved Qty logic cleaned up --- .../doctype/sales_invoice/sales_invoice.py | 50 +++++----------- .../doctype/purchase_order/purchase_order.py | 19 ++---- .../controllers/sales_and_purchase_return.py | 17 +++++- erpnext/controllers/selling_controller.py | 29 +-------- erpnext/controllers/stock_controller.py | 59 ++++++++++++------- .../doctype/sales_order/sales_order.py | 47 ++++++++------- .../doctype/delivery_note/delivery_note.py | 30 ++-------- .../material_request/material_request.py | 19 ++---- .../purchase_receipt/purchase_receipt.py | 16 ++--- 9 files changed, 120 insertions(+), 166 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index f82590a60947..20c766aaa9d4 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -81,9 +81,13 @@ def on_submit(self): self.check_prev_docstatus() + if self.is_return: + self.status_updater = [] + + self.update_status_updater_args() + self.update_prevdoc_status() + if not self.is_return: - self.update_status_updater_args() - self.update_prevdoc_status() self.update_billing_status_for_zero_amount_refdoc("Sales Order") self.check_credit_limit() @@ -107,9 +111,13 @@ def on_cancel(self): from erpnext.accounts.utils import remove_against_link_from_jv remove_against_link_from_jv(self.doctype, self.name) + if self.is_return: + self.status_updater = [] + + self.update_status_updater_args() + self.update_prevdoc_status() + if not self.is_return: - self.update_status_updater_args() - self.update_prevdoc_status() self.update_billing_status_for_zero_amount_refdoc("Sales Order") self.validate_c_form_on_cancel() @@ -294,8 +302,6 @@ def add_remarks(self): def so_dn_required(self): """check in manage account if sales order / delivery note required or not.""" - if self.is_return: - return dic = {'Sales Order':'so_required','Delivery Note':'dn_required'} for i in dic: if frappe.db.get_value('Selling Settings', None, dic[i]) == 'Yes': @@ -408,35 +414,11 @@ def on_update(self): def check_prev_docstatus(self): for d in self.get('items'): - if d.sales_order: - submitted = frappe.db.sql("""select name from `tabSales Order` - where docstatus = 1 and name = %s""", d.sales_order) - if not submitted: - frappe.throw(_("Sales Order {0} is not submitted").format(d.sales_order)) + if d.sales_order and frappe.db.get_value("Sales Order", d.sales_order, "docstatus") != 1: + frappe.throw(_("Sales Order {0} is not submitted").format(d.sales_order)) - if d.delivery_note: - submitted = frappe.db.sql("""select name from `tabDelivery Note` - where docstatus = 1 and name = %s""", d.delivery_note) - if not submitted: - throw(_("Delivery Note {0} is not submitted").format(d.delivery_note)) - - def update_stock_ledger(self): - sl_entries = [] - for d in self.get_item_list(): - if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1 and d.warehouse and flt(d['qty']): - self.update_reserved_qty(d) - - incoming_rate = 0 - if cint(self.is_return) and self.return_against and self.docstatus==1: - incoming_rate = self.get_incoming_rate_for_sales_return(d.item_code, - self.return_against) - - sl_entries.append(self.get_sl_entries(d, { - "actual_qty": -1*flt(d.qty), - "stock_uom": frappe.db.get_value("Item", d.item_code, "stock_uom"), - "incoming_rate": incoming_rate - })) - self.make_sl_entries(sl_entries) + if d.delivery_note and frappe.db.get_value("Delivery Note", d.delivery_note, "docstatus") != 1: + throw(_("Delivery Note {0} is not submitted").format(d.delivery_note)) def make_gl_entries(self, repost_future_gle=True): gl_entries = self.get_gl_entries() diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 9887ddb41687..b1c0a58a7717 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -9,6 +9,7 @@ from frappe.model.mapper import get_mapped_doc from erpnext.controllers.buying_controller import BuyingController from erpnext.stock.doctype.item.item import get_last_purchase_details +from erpnext.utilities.repost_stock import update_bin_qty, get_ordered_qty form_grid_templates = { @@ -136,20 +137,6 @@ def update_requested_qty(self): def update_ordered_qty(self, po_item_rows=None): """update requested qty (before ordered_qty is updated)""" - from erpnext.stock.utils import get_bin - - def _update_ordered_qty(item_code, warehouse): - ordered_qty = frappe.db.sql(""" - select sum((po_item.qty - ifnull(po_item.received_qty, 0))*po_item.conversion_factor) - from `tabPurchase Order Item` po_item, `tabPurchase Order` po - where po_item.item_code=%s and po_item.warehouse=%s - and po_item.qty > ifnull(po_item.received_qty, 0) and po_item.parent=po.name - and po.status!='Stopped' and po.docstatus=1""", (item_code, warehouse)) - - bin_doc = get_bin(item_code, warehouse) - bin_doc.ordered_qty = flt(ordered_qty[0][0]) if ordered_qty else 0 - bin_doc.save() - item_wh_list = [] for d in self.get("items"): if (not po_item_rows or d.name in po_item_rows) and [d.item_code, d.warehouse] not in item_wh_list \ @@ -157,7 +144,9 @@ def _update_ordered_qty(item_code, warehouse): item_wh_list.append([d.item_code, d.warehouse]) for item_code, warehouse in item_wh_list: - _update_ordered_qty(item_code, warehouse) + update_bin_qty(item_code, warehouse, { + "ordered_qty": get_ordered_qty(item_code, warehouse) + }) def check_modified_date(self): mod_db = frappe.db.sql("select modified from `tabPurchase Order` where name = %s", diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index f429c67983d0..2e4ecaa8c118 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -137,9 +137,24 @@ def update_item(source_doc, target_doc, source_parent): target_doc.qty = -1* source_doc.qty if doctype == "Purchase Receipt": target_doc.received_qty = -1* source_doc.qty + target_doc.prevdoc_doctype = source_doc.prevdoc_doctype + target_doc.prevdoc_docname = source_doc.prevdoc_docname + target_doc.prevdoc_detail_docname = source_doc.prevdoc_detail_docname elif doctype == "Purchase Invoice": + target_doc.purchase_order = source_doc.purchase_order target_doc.purchase_receipt = source_doc.purchase_receipt + target_doc.po_detail = source_doc.po_detail target_doc.pr_detail = source_doc.pr_detail + elif doctype == "Delivery Note": + target_doc.against_sales_order = source_doc.against_sales_order + target_doc.against_sales_invoice = source_doc.against_sales_invoice + target_doc.so_detail = source_doc.so_detail + target_doc.si_detail = source_doc.si_detail + elif doctype == "Sales Invoice": + target_doc.sales_order = source_doc.sales_order + target_doc.delivery_note = source_doc.delivery_note + target_doc.so_detail = source_doc.so_detail + target_doc.dn_detail = source_doc.dn_detail doclist = get_mapped_doc(doctype, source_name, { doctype: { @@ -152,8 +167,6 @@ def update_item(source_doc, target_doc, source_parent): doctype +" Item": { "doctype": doctype + " Item", "field_map": { - "purchase_order": "purchase_order", - "purchase_receipt": "purchase_receipt", "serial_no": "serial_no", "batch_no": "batch_no" }, diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 9e2bf1d76be2..569bb82afef9 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -163,42 +163,17 @@ def validate_max_discount(self): def get_item_list(self): il = [] for d in self.get("items"): - reserved_warehouse = "" - reserved_qty_for_main_item = 0 - if d.qty is None: frappe.throw(_("Row {0}: Qty is mandatory").format(d.idx)) - - if self.doctype == "Sales Order": - reserved_warehouse = d.warehouse - if flt(d.qty) > flt(d.delivered_qty): - reserved_qty_for_main_item = flt(d.qty) - flt(d.delivered_qty) - - elif (((self.doctype == "Delivery Note" and d.against_sales_order) - or (self.doctype == "Sales Invoice" and d.sales_order and self.update_stock)) - and not self.is_return): - # if SO qty is 10 and there is tolerance of 20%, then it will allow DN of 12. - # But in this case reserved qty should only be reduced by 10 and not 12 - - already_delivered_qty = self.get_already_delivered_qty(self.name, - d.against_sales_order if self.doctype=="Delivery Note" else d.sales_order, d.so_detail) - so_qty, reserved_warehouse = self.get_so_qty_and_warehouse(d.so_detail) - - if already_delivered_qty + d.qty > so_qty: - reserved_qty_for_main_item = -(so_qty - already_delivered_qty) - else: - reserved_qty_for_main_item = -flt(d.qty) - + if self.has_product_bundle(d.item_code): for p in self.get("packed_items"): if p.parent_detail_docname == d.name and p.parent_item == d.item_code: # the packing details table's qty is already multiplied with parent's qty il.append(frappe._dict({ 'warehouse': p.warehouse, - 'reserved_warehouse': reserved_warehouse, 'item_code': p.item_code, 'qty': flt(p.qty), - 'reserved_qty': (flt(p.qty)/flt(d.qty)) * reserved_qty_for_main_item, 'uom': p.uom, 'batch_no': cstr(p.batch_no).strip(), 'serial_no': cstr(p.serial_no).strip(), @@ -207,10 +182,8 @@ def get_item_list(self): else: il.append(frappe._dict({ 'warehouse': d.warehouse, - 'reserved_warehouse': reserved_warehouse, 'item_code': d.item_code, 'qty': d.qty, - 'reserved_qty': reserved_qty_for_main_item, 'uom': d.stock_uom, 'stock_uom': d.stock_uom, 'batch_no': cstr(d.get("batch_no")).strip(), diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index cfde04b57200..396d088a53a3 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -6,11 +6,9 @@ from frappe.utils import cint, flt, cstr from frappe import msgprint, _ import frappe.defaults - -from erpnext.controllers.accounts_controller import AccountsController from erpnext.accounts.general_ledger import make_gl_entries, delete_gl_entries, process_gl_map -from erpnext.stock.utils import update_bin +from erpnext.controllers.accounts_controller import AccountsController class StockController(AccountsController): def make_gl_entries(self, repost_future_gle=True): @@ -229,23 +227,44 @@ def get_incoming_rate_for_sales_return(self, item_code, against_document): incoming_rate = incoming_rate[0][0] if incoming_rate else 0.0 return incoming_rate - - def update_reserved_qty(self, d): - if d['reserved_qty'] < 0 : - # Reduce reserved qty from reserved warehouse mentioned in so - if not d["reserved_warehouse"]: - frappe.throw(_("Delivery Warehouse is missing in Sales Order")) - - args = { - "item_code": d['item_code'], - "warehouse": d["reserved_warehouse"], - "voucher_type": self.doctype, - "voucher_no": self.name, - "reserved_qty": (self.docstatus==1 and 1 or -1)*flt(d['reserved_qty']), - "posting_date": self.posting_date, - "is_amended": self.amended_from and 'Yes' or 'No' - } - update_bin(args) + + def update_reserved_qty(self): + so_map = {} + for d in self.get("items"): + if d.so_detail: + if self.doctype == "Delivery Note" and d.against_sales_order: + so_map.setdefault(d.against_sales_order, []).append(d.so_detail) + elif self.doctype == "Sales Invoice" and d.sales_order and self.update_stock: + so_map.setdefault(d.sales_order, []).append(d.so_detail) + + for so, so_item_rows in so_map.items(): + if so and so_item_rows: + sales_order = frappe.get_doc("Sales Order", so) + + if sales_order.status in ["Stopped", "Cancelled"]: + frappe.throw(_("Sales Order {0} is cancelled or stopped").format(so), frappe.InvalidStatusError) + + sales_order.update_reserved_qty(so_item_rows) + + def update_stock_ledger(self): + self.update_reserved_qty() + + sl_entries = [] + for d in self.get_item_list(): + if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1 \ + and d.warehouse and flt(d['qty']): + + incoming_rate = 0 + if cint(self.is_return) and self.return_against and self.docstatus==1: + incoming_rate = self.get_incoming_rate_for_sales_return(d.item_code, self.return_against) + + sl_entries.append(self.get_sl_entries(d, { + "actual_qty": -1*flt(d['qty']), + "stock_uom": frappe.db.get_value("Item", d.item_code, "stock_uom"), + "incoming_rate": incoming_rate + })) + + self.make_sl_entries(sl_entries) def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None, warehouse_account=None): diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index b05c009fa0d7..2e57a115355c 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -8,6 +8,7 @@ from frappe.utils import cstr, flt, getdate, comma_and from frappe import _ from frappe.model.mapper import get_mapped_doc +from erpnext.utilities.repost_stock import update_bin_qty, get_reserved_qty from erpnext.controllers.selling_controller import SellingController @@ -151,7 +152,7 @@ def on_submit(self): super(SalesOrder, self).on_submit() self.check_credit_limit() - self.update_stock_ledger(update_stock = 1) + self.update_reserved_qty() frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.base_grand_total, self) @@ -164,7 +165,7 @@ def on_cancel(self): frappe.throw(_("Stopped order cannot be cancelled. Unstop to cancel.")) self.check_nextdoc_docstatus() - self.update_stock_ledger(update_stock = -1) + self.update_reserved_qty() self.update_prevdoc_status('cancel') @@ -213,32 +214,38 @@ def check_modified_date(self): def stop_sales_order(self): self.check_modified_date() - self.update_stock_ledger(-1) + self.update_reserved_qty() frappe.db.set(self, 'status', 'Stopped') frappe.msgprint(_("{0} {1} status is Stopped").format(self.doctype, self.name)) self.notify_modified() def unstop_sales_order(self): self.check_modified_date() - self.update_stock_ledger(1) + self.update_reserved_qty() frappe.db.set(self, 'status', 'Submitted') frappe.msgprint(_("{0} {1} status is Unstopped").format(self.doctype, self.name)) - - - def update_stock_ledger(self, update_stock): - from erpnext.stock.utils import update_bin - for d in self.get_item_list(): - if frappe.db.get_value("Item", d['item_code'], "is_stock_item")==1: - args = { - "item_code": d['item_code'], - "warehouse": d['reserved_warehouse'], - "reserved_qty": flt(update_stock) * flt(d['reserved_qty']), - "posting_date": self.transaction_date, - "voucher_type": self.doctype, - "voucher_no": self.name, - "is_amended": self.amended_from and 'Yes' or 'No' - } - update_bin(args) + + def update_reserved_qty(self, so_item_rows=None): + """update requested qty (before ordered_qty is updated)""" + item_wh_list = [] + def _valid_for_reserve(item_code, warehouse): + if item_code and warehouse and [item_code, warehouse] not in item_wh_list \ + and frappe.db.get_value("Item", item_code, "is_stock_item"): + item_wh_list.append([item_code, warehouse]) + + for d in self.get("items"): + if (not so_item_rows or d.name in so_item_rows): + _valid_for_reserve(d.item_code, d.warehouse) + + if self.has_product_bundle(d.item_code): + for p in self.get("packed_items"): + if p.parent_detail_docname == d.name and p.parent_item == d.item_code: + _valid_for_reserve(p.item_code, p.warehouse) + + for item_code, warehouse in item_wh_list: + update_bin_qty(item_code, warehouse, { + "reserved_qty": get_reserved_qty(item_code, warehouse) + }) def on_update(self): pass diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 5e11962ea56e..201546e45739 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -83,7 +83,7 @@ def set_actual_qty(self): def so_required(self): """check in manage account if sales order required or not""" - if not self.is_return and frappe.db.get_value("Selling Settings", None, 'so_required') == 'Yes': + if frappe.db.get_value("Selling Settings", None, 'so_required') == 'Yes': for d in self.get('items'): if not d.against_sales_order: frappe.throw(_("Sales Order required for Item {0}").format(d.item_code)) @@ -174,10 +174,10 @@ def on_submit(self): # Check for Approving Authority frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.company, self.base_grand_total, self) + # update delivered qty in sales order + self.update_prevdoc_status() + if not self.is_return: - # update delivered qty in sales order - self.update_prevdoc_status() - self.check_credit_limit() self.update_stock_ledger() @@ -190,8 +190,7 @@ def on_cancel(self): self.check_stop_sales_order("against_sales_order") self.check_next_docstatus() - if not self.is_return: - self.update_prevdoc_status() + self.update_prevdoc_status() self.update_stock_ledger() @@ -242,25 +241,6 @@ def cancel_packing_slips(self): ps.cancel() frappe.msgprint(_("Packing Slip(s) cancelled")) - - def update_stock_ledger(self): - sl_entries = [] - for d in self.get_item_list(): - if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1 \ - and d.warehouse and flt(d['qty']): - self.update_reserved_qty(d) - - incoming_rate = 0 - if cint(self.is_return) and self.return_against and self.docstatus==1: - incoming_rate = self.get_incoming_rate_for_sales_return(d.item_code, self.return_against) - - sl_entries.append(self.get_sl_entries(d, { - "actual_qty": -1*flt(d['qty']), - "incoming_rate": incoming_rate - })) - - self.make_sl_entries(sl_entries) - def get_list_context(context=None): from erpnext.controllers.website_list_for_contact import get_list_context list_context = get_list_context(context) diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index fd6f943a954f..03af0722a341 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -10,9 +10,11 @@ from frappe.utils import cstr, flt, getdate from frappe import _ from frappe.model.mapper import get_mapped_doc +from erpnext.utilities.repost_stock import update_bin_qty, get_indented_qty from erpnext.controllers.buying_controller import BuyingController + form_grid_templates = { "items": "templates/form_grid/material_request_grid.html" } @@ -136,19 +138,6 @@ def update_completed_qty(self, mr_items=None): def update_requested_qty(self, mr_item_rows=None): """update requested qty (before ordered_qty is updated)""" - from erpnext.stock.utils import get_bin - - def _update_requested_qty(item_code, warehouse): - requested_qty = frappe.db.sql("""select sum(mr_item.qty - ifnull(mr_item.ordered_qty, 0)) - from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr - where mr_item.item_code=%s and mr_item.warehouse=%s - and mr_item.qty > ifnull(mr_item.ordered_qty, 0) and mr_item.parent=mr.name - and mr.status!='Stopped' and mr.docstatus=1""", (item_code, warehouse)) - - bin_doc = get_bin(item_code, warehouse) - bin_doc.indented_qty = flt(requested_qty[0][0]) if requested_qty else 0 - bin_doc.save() - item_wh_list = [] for d in self.get("items"): if (not mr_item_rows or d.name in mr_item_rows) and [d.item_code, d.warehouse] not in item_wh_list \ @@ -156,7 +145,9 @@ def _update_requested_qty(item_code, warehouse): item_wh_list.append([d.item_code, d.warehouse]) for item_code, warehouse in item_wh_list: - _update_requested_qty(item_code, warehouse) + update_bin_qty(item_code, warehouse, { + "indented_qty": get_indented_qty(item_code, warehouse) + }) def update_completed_and_requested_qty(stock_entry, method): if stock_entry.doctype == "Stock Entry": diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 2979c124525b..f4273b27ed74 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -117,7 +117,7 @@ def validate_with_previous_doc(self): self.validate_rate_with_reference_doc([["Purchase Order", "prevdoc_docname", "prevdoc_detail_docname"]]) def po_required(self): - if not self.is_return and frappe.db.get_value("Buying Settings", None, "po_required") == 'Yes': + if frappe.db.get_value("Buying Settings", None, "po_required") == 'Yes': for d in self.get('items'): if not d.prevdoc_docname: frappe.throw(_("Purchase Order number required for Item {0}").format(d.item_code)) @@ -221,9 +221,10 @@ def on_submit(self): # Set status as Submitted frappe.db.set(self, 'status', 'Submitted') + self.update_prevdoc_status() + self.update_ordered_qty() + if not self.is_return: - self.update_prevdoc_status() - self.update_ordered_qty() purchase_controller.update_last_purchase_rate(self, 1) self.update_stock_ledger() @@ -257,12 +258,11 @@ def on_cancel(self): self.update_stock_ledger() + self.update_prevdoc_status() + # Must be called after updating received qty in PO + self.update_ordered_qty() + if not self.is_return: - self.update_prevdoc_status() - - # Must be called after updating received qty in PO - self.update_ordered_qty() - pc_obj.update_last_purchase_rate(self, 0) self.make_gl_entries_on_cancel() From bdb735df14399e598282fe6271ba5fb6f97cf8f6 Mon Sep 17 00:00:00 2001 From: Nabin Hait <nabinhait@gmail.com> Date: Mon, 24 Aug 2015 16:10:43 +0530 Subject: [PATCH 2/4] Update order reference in return entries and repost reserved / ordered qty --- erpnext/patches.txt | 1 + ...pdate_order_reference_in_return_entries.py | 76 +++++++++++++++++++ .../doctype/sales_order/sales_order.py | 4 +- 3 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 erpnext/patches/v5_7/update_order_reference_in_return_entries.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 21656c6d6594..ea10054d525e 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -193,3 +193,4 @@ execute:frappe.db.sql("update `tabProduction Order` pro set description = (selec erpnext.patches.v5_7.item_template_attributes erpnext.patches.v4_2.repost_reserved_qty #2015-08-20 erpnext.patches.v5_4.update_purchase_cost_against_project +erpnext.patches.v5_7.update_order_reference_in_return_entries \ No newline at end of file diff --git a/erpnext/patches/v5_7/update_order_reference_in_return_entries.py b/erpnext/patches/v5_7/update_order_reference_in_return_entries.py new file mode 100644 index 000000000000..c957242260b6 --- /dev/null +++ b/erpnext/patches/v5_7/update_order_reference_in_return_entries.py @@ -0,0 +1,76 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + # sales return + return_entries = list(frappe.db.sql(""" + select dn.name as name, dn_item.name as row_id, dn.return_against, + dn_item.item_code, "Delivery Note" as doctype + from `tabDelivery Note Item` dn_item, `tabDelivery Note` dn + where dn_item.parent=dn.name and dn.is_return=1 and dn.docstatus < 2 + """, as_dict=1)) + + return_entries += list(frappe.db.sql(""" + select si.name as name, si_item.name as row_id, si.return_against, + si_item.item_code, "Sales Invoice" as doctype + from `tabSales Invoice Item` si_item, `tabSales Invoice` si + where si_item.parent=si.name and si.is_return=1 and si.update_stock=1 and si.docstatus < 2 + """, as_dict=1)) + + for d in return_entries: + ref_field = "against_sales_order" if d.doctype == "Delivery Note" else "sales_order" + order_details = frappe.db.sql(""" + select {0} as sales_order, so_detail + from `tab{1} Item` item + where + parent=%s and item_code=%s + and ifnull(so_detail, '') !='' + order by + (select transaction_date from `tabSales Order` where name=item.{3}) DESC + """.format(ref_field, d.doctype, ref_field, ref_field), (d.return_against, d.item_code), as_dict=1) + + if order_details: + frappe.db.sql(""" + update `tab{0} Item` + set {1}=%s, so_detail=%s + where name=%s + """.format(d.doctype, ref_field), + (order_details[0].sales_order, order_details[0].so_detail, d.row_id)) + + doc = frappe.get_doc(d.doctype, d.name) + doc.update_reserved_qty() + + + #-------------------------- + # purchase return + return_entries = frappe.db.sql(""" + select pr.name as name, pr_item.name as row_id, pr.return_against, pr_item.item_code + from `tabPurchase Receipt Item` pr_item, `tabPurchase Receipt` pr + where pr_item.parent=pr.name and pr.is_return=1 and pr.docstatus < 2 + """, as_dict=1) + + for d in return_entries: + order_details = frappe.db.sql(""" + select prevdoc_docname as purchase_order, prevdoc_detail_docname as po_detail + from `tabPurchase Receipt Item` item + where + parent=%s and item_code=%s + and ifnull(prevdoc_detail_docname, '') !='' + and ifnull(prevdoc_doctype, '') = 'Purchase Order' and ifnull(prevdoc_detail_docname, '') != '' + order by + (select transaction_date from `tabPurchase Order` where name=item.prevdoc_detail_docname) DESC + """, (d.return_against, d.item_code), as_dict=1) + + if order_details: + frappe.db.sql(""" + update `tabPurchase Receipt Item` + set prevdoc_doctype='Purchase Order', prevdoc_docname=%s, prevdoc_detail_docname=%s + where name=%s + """, (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() + \ No newline at end of file diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 2e57a115355c..4f9d54dfbf21 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -214,15 +214,15 @@ def check_modified_date(self): def stop_sales_order(self): self.check_modified_date() - self.update_reserved_qty() frappe.db.set(self, 'status', 'Stopped') + self.update_reserved_qty() frappe.msgprint(_("{0} {1} status is Stopped").format(self.doctype, self.name)) self.notify_modified() def unstop_sales_order(self): self.check_modified_date() - self.update_reserved_qty() frappe.db.set(self, 'status', 'Submitted') + self.update_reserved_qty() frappe.msgprint(_("{0} {1} status is Unstopped").format(self.doctype, self.name)) def update_reserved_qty(self, so_item_rows=None): From 30bb2df0a464452814800b396321f8da6d17dcdc Mon Sep 17 00:00:00 2001 From: Nabin Hait <nabinhait@gmail.com> Date: Mon, 24 Aug 2015 16:16:29 +0530 Subject: [PATCH 3/4] utilities/repost_stock file renamed to stock/stock_balance --- erpnext/buying/doctype/purchase_order/purchase_order.py | 2 +- .../patches/repair_tools/set_stock_balance_as_per_serial_no.py | 2 +- erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py | 2 +- erpnext/patches/v4_2/repost_reserved_qty.py | 2 +- erpnext/patches/v4_2/update_requested_and_ordered_qty.py | 2 +- erpnext/patches/v5_0/repost_requested_qty.py | 2 +- .../patches/v5_4/fix_reserved_qty_and_sle_for_packed_items.py | 2 +- erpnext/selling/doctype/sales_order/sales_order.py | 2 +- erpnext/stock/doctype/item/item.py | 2 +- erpnext/stock/doctype/material_request/material_request.py | 2 +- erpnext/stock/doctype/warehouse/warehouse.py | 2 +- erpnext/{utilities/repost_stock.py => stock/stock_balance.py} | 0 12 files changed, 11 insertions(+), 11 deletions(-) rename erpnext/{utilities/repost_stock.py => stock/stock_balance.py} (100%) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index b1c0a58a7717..58b1d1938dc0 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -9,7 +9,7 @@ from frappe.model.mapper import get_mapped_doc from erpnext.controllers.buying_controller import BuyingController from erpnext.stock.doctype.item.item import get_last_purchase_details -from erpnext.utilities.repost_stock import update_bin_qty, get_ordered_qty +from erpnext.stock.stock_balance import update_bin_qty, get_ordered_qty form_grid_templates = { diff --git a/erpnext/patches/repair_tools/set_stock_balance_as_per_serial_no.py b/erpnext/patches/repair_tools/set_stock_balance_as_per_serial_no.py index 4a4266e7fd21..5a421d146fec 100644 --- a/erpnext/patches/repair_tools/set_stock_balance_as_per_serial_no.py +++ b/erpnext/patches/repair_tools/set_stock_balance_as_per_serial_no.py @@ -5,7 +5,7 @@ import frappe def execute(): - from erpnext.utilities.repost_stock import set_stock_balance_as_per_serial_no + from erpnext.stock.stock_balance import set_stock_balance_as_per_serial_no frappe.db.auto_commit_on_many_writes = 1 set_stock_balance_as_per_serial_no() diff --git a/erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py b/erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py index 73a56a8e9875..0df5801c4257 100644 --- a/erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py +++ b/erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py @@ -6,7 +6,7 @@ from frappe.utils import flt def execute(): - from erpnext.utilities.repost_stock import repost + from erpnext.stock.stock_balance import repost repost(allow_zero_rate=True, only_actual=True) frappe.reload_doctype("Account") diff --git a/erpnext/patches/v4_2/repost_reserved_qty.py b/erpnext/patches/v4_2/repost_reserved_qty.py index a2cd4d8ad676..74f4b79f93bf 100644 --- a/erpnext/patches/v4_2/repost_reserved_qty.py +++ b/erpnext/patches/v4_2/repost_reserved_qty.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import frappe -from erpnext.utilities.repost_stock import update_bin_qty, get_reserved_qty +from erpnext.stock.stock_balance import update_bin_qty, get_reserved_qty def execute(): repost_for = frappe.db.sql(""" diff --git a/erpnext/patches/v4_2/update_requested_and_ordered_qty.py b/erpnext/patches/v4_2/update_requested_and_ordered_qty.py index 240b11dae55f..7bb49e64dfe5 100644 --- a/erpnext/patches/v4_2/update_requested_and_ordered_qty.py +++ b/erpnext/patches/v4_2/update_requested_and_ordered_qty.py @@ -5,7 +5,7 @@ import frappe def execute(): - from erpnext.utilities.repost_stock import update_bin_qty, get_indented_qty, get_ordered_qty + from erpnext.stock.stock_balance import update_bin_qty, get_indented_qty, get_ordered_qty count=0 for item_code, warehouse in frappe.db.sql("""select distinct item_code, warehouse from diff --git a/erpnext/patches/v5_0/repost_requested_qty.py b/erpnext/patches/v5_0/repost_requested_qty.py index 8c1d6f7fbb18..6af71f3fc49a 100644 --- a/erpnext/patches/v5_0/repost_requested_qty.py +++ b/erpnext/patches/v5_0/repost_requested_qty.py @@ -5,7 +5,7 @@ import frappe def execute(): - from erpnext.utilities.repost_stock import update_bin_qty, get_indented_qty + from erpnext.stock.stock_balance import update_bin_qty, get_indented_qty count=0 for item_code, warehouse in frappe.db.sql("""select distinct item_code, warehouse diff --git a/erpnext/patches/v5_4/fix_reserved_qty_and_sle_for_packed_items.py b/erpnext/patches/v5_4/fix_reserved_qty_and_sle_for_packed_items.py index 251dd56acbe0..6eb3994c7c51 100644 --- a/erpnext/patches/v5_4/fix_reserved_qty_and_sle_for_packed_items.py +++ b/erpnext/patches/v5_4/fix_reserved_qty_and_sle_for_packed_items.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import frappe -from erpnext.utilities.repost_stock import repost_actual_qty +from erpnext.stock.stock_balance import repost_actual_qty def execute(): cancelled_invoices = frappe.db.sql_list("""select name from `tabSales Invoice` diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 4f9d54dfbf21..dd21f0553e9e 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -8,7 +8,7 @@ from frappe.utils import cstr, flt, getdate, comma_and from frappe import _ from frappe.model.mapper import get_mapped_doc -from erpnext.utilities.repost_stock import update_bin_qty, get_reserved_qty +from erpnext.stock.stock_balance import update_bin_qty, get_reserved_qty from erpnext.controllers.selling_controller import SellingController diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index f9b41f16577a..0087f18cbed7 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -269,7 +269,7 @@ def set_last_purchase_rate(self, newdn): frappe.db.set_value("Item", newdn, "last_purchase_rate", last_purchase_rate) def recalculate_bin_qty(self, newdn): - from erpnext.utilities.repost_stock import repost_stock + from erpnext.stock.stock_balance import repost_stock frappe.db.auto_commit_on_many_writes = 1 existing_allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock") frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index 03af0722a341..e4183669bf48 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -10,7 +10,7 @@ from frappe.utils import cstr, flt, getdate from frappe import _ from frappe.model.mapper import get_mapped_doc -from erpnext.utilities.repost_stock import update_bin_qty, get_indented_qty +from erpnext.stock.stock_balance import update_bin_qty, get_indented_qty from erpnext.controllers.buying_controller import BuyingController diff --git a/erpnext/stock/doctype/warehouse/warehouse.py b/erpnext/stock/doctype/warehouse/warehouse.py index 5a3976399fec..610c7b88a3b9 100644 --- a/erpnext/stock/doctype/warehouse/warehouse.py +++ b/erpnext/stock/doctype/warehouse/warehouse.py @@ -140,7 +140,7 @@ def after_rename(self, olddn, newdn, merge=False): self.recalculate_bin_qty(newdn) def recalculate_bin_qty(self, newdn): - from erpnext.utilities.repost_stock import repost_stock + from erpnext.stock.stock_balance import repost_stock frappe.db.auto_commit_on_many_writes = 1 existing_allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock") frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) diff --git a/erpnext/utilities/repost_stock.py b/erpnext/stock/stock_balance.py similarity index 100% rename from erpnext/utilities/repost_stock.py rename to erpnext/stock/stock_balance.py From c14c6a3765fa8e98d8bbb6e65a5ecceeb4ce026a Mon Sep 17 00:00:00 2001 From: Nabin Hait <nabinhait@gmail.com> Date: Mon, 24 Aug 2015 16:46:00 +0530 Subject: [PATCH 4/4] Allow different rate in Return Entry --- erpnext/controllers/sales_and_purchase_return.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 2e4ecaa8c118..bb3b63cdd3b7 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -83,9 +83,10 @@ def validate_returned_items(doc): elif abs(d.qty) > max_return_qty: frappe.throw(_("Row # {0}: Cannot return more than {1} for Item {2}") .format(d.idx, ref.qty, d.item_code), StockOverReturnError) - elif ref.rate and flt(d.rate) != ref.rate: - frappe.throw(_("Row # {0}: Rate must be same as {1} {2}") - .format(d.idx, doc.doctype, doc.return_against)) + elif ref.rate and (doc.doctype in ("Delivery Note", "Purchase Receipt") \ + or (doc.doctype=="Sales Invoice" and doc.update_stock==1)) and flt(d.rate) > ref.rate: + frappe.throw(_("Row # {0}: Rate cannot be greater than {1} {2}") + .format(d.idx, doc.doctype, doc.return_against)) elif ref.batch_no and d.batch_no != ref.batch_no: frappe.throw(_("Row # {0}: Batch No must be same as {1} {2}") .format(d.idx, doc.doctype, doc.return_against))