From 453cc374d49b3fac61e2c1cea6119eb62950866b Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 29 Jul 2015 12:21:03 +0530 Subject: [PATCH] - Fixed logic of reserved qty calculation while delivered product bundle via Sales Invoice - Delete stock ledger entries on cancellation of Sales Invoice while product bundle delivered - Make packing list on validate --- .../doctype/sales_invoice/sales_invoice.py | 29 ++++++++----------- erpnext/change_log/current/packing_list.md | 2 ++ erpnext/controllers/selling_controller.py | 23 +++++++++++---- erpnext/controllers/stock_controller.py | 19 ++++++++++++ erpnext/patches.txt | 1 + erpnext/patches/v5_4/__init__.py | 0 ...x_reserved_qty_and_sle_for_packed_items.py | 24 +++++++++++++++ .../doctype/delivery_note/delivery_note.py | 18 ------------ 8 files changed, 75 insertions(+), 41 deletions(-) create mode 100644 erpnext/change_log/current/packing_list.md create mode 100644 erpnext/patches/v5_4/__init__.py create mode 100644 erpnext/patches/v5_4/fix_reserved_qty_and_sle_for_packed_items.py diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 12e199f74c..f581faab13 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe import frappe.defaults -from frappe.utils import cint, cstr, flt +from frappe.utils import cint, flt from frappe import _, msgprint, throw from erpnext.accounts.party import get_party_account, get_due_date from erpnext.controllers.stock_controller import update_gl_entries_after @@ -66,6 +66,7 @@ class SalesInvoice(SellingController): self.validate_c_form() self.validate_time_logs_are_submitted() self.validate_multiple_billing("Delivery Note", "dn_detail", "amount", "items") + self.update_packing_list() def on_submit(self): super(SalesInvoice, self).on_submit() @@ -363,6 +364,13 @@ class SalesInvoice(SellingController): d.actual_qty = bin and flt(bin[0]['actual_qty']) or 0 d.projected_qty = bin and flt(bin[0]['projected_qty']) or 0 + def update_packing_list(self): + if cint(self.update_stock) == 1: + from erpnext.stock.doctype.packed_item.packed_item import make_packing_list + make_packing_list(self, 'items') + else: + self.set('packed_items', []) + def get_warehouse(self): user_pos_profile = frappe.db.sql("""select name, warehouse from `tabPOS Profile` @@ -381,20 +389,6 @@ class SalesInvoice(SellingController): return warehouse def on_update(self): - if cint(self.update_stock) == 1: - # Set default warehouse from POS Profile - if cint(self.is_pos) == 1: - w = self.get_warehouse() - if w: - for d in self.get('items'): - if not d.warehouse: - d.warehouse = cstr(w) - - from erpnext.stock.doctype.packed_item.packed_item import make_packing_list - make_packing_list(self, 'items') - else: - self.set('packed_items', []) - if cint(self.is_pos) == 1: if flt(self.paid_amount) == 0: if self.cash_bank_account: @@ -424,8 +418,9 @@ class SalesInvoice(SellingController): 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: + 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, diff --git a/erpnext/change_log/current/packing_list.md b/erpnext/change_log/current/packing_list.md new file mode 100644 index 0000000000..d4793be1ea --- /dev/null +++ b/erpnext/change_log/current/packing_list.md @@ -0,0 +1,2 @@ +- Fixed logic of reserved qty calculation while delivered product bundle via Sales Invoice +- Delete stock ledger entries on cancellation of Sales Invoice while product bundle delivered \ No newline at end of file diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 9a0aedf176..a1468e13b6 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -174,12 +174,14 @@ class SellingController(StockController): 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 and not self.is_return: + 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, d.so_detail) + 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: @@ -221,12 +223,21 @@ class SellingController(StockController): return frappe.db.sql("""select name from `tabProduct Bundle` where new_item_code=%s and docstatus != 2""", item_code) - def get_already_delivered_qty(self, dn, so, so_detail): - qty = frappe.db.sql("""select sum(qty) from `tabDelivery Note Item` + def get_already_delivered_qty(self, current_docname, so, so_detail): + delivered_via_dn = frappe.db.sql("""select sum(qty) from `tabDelivery Note Item` where so_detail = %s and docstatus = 1 and against_sales_order = %s - and parent != %s""", (so_detail, so, dn)) - return qty and flt(qty[0][0]) or 0.0 + and parent != %s""", (so_detail, so, current_docname)) + + delivered_via_si = frappe.db.sql("""select sum(qty) from `tabSales Invoice Item` + where so_detail = %s and docstatus = 1 + and sales_order = %s + and parent != %s""", (so_detail, so, current_docname)) + + total_delivered_qty = (flt(delivered_via_dn[0][0]) if delivered_via_dn else 0) \ + + (flt(delivered_via_si[0][0]) if delivered_via_si else 0) + + return total_delivered_qty def get_so_qty_and_warehouse(self, so_detail): so_item = frappe.db.sql("""select qty, warehouse from `tabSales Order Item` diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index a47314bfd6..cebeaf59bf 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -9,6 +9,8 @@ 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 + class StockController(AccountsController): def make_gl_entries(self, repost_future_gle=True): @@ -227,6 +229,23 @@ class StockController(AccountsController): 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(_("Reserved 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_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None, warehouse_account=None): diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 648d5b7c83..ef08098c32 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -180,3 +180,4 @@ execute:frappe.rename_doc("DocType", "Salary Manager", "Process Payroll", force= erpnext.patches.v5_1.rename_roles erpnext.patches.v5_1.default_bom execute:frappe.delete_doc("DocType", "Party Type") +erpnext.patches.v5_4.fix_reserved_qty_and_sle_for_packed_items diff --git a/erpnext/patches/v5_4/__init__.py b/erpnext/patches/v5_4/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..0d46d99c34 --- /dev/null +++ b/erpnext/patches/v5_4/fix_reserved_qty_and_sle_for_packed_items.py @@ -0,0 +1,24 @@ +# 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 +from erpnext.utilities.repost_stock import update_bin_qty, get_reserved_qty, repost_actual_qty + +def execute(): + cancelled_invoices = frappe.db.sql_list("""select name from `tabSales Invoice` + where docstatus = 2 and ifnull(update_stock, 0) = 1""") + + if cancelled_invoices: + frappe.db.sql("""delete from `tabStock Ledger Entry` + where voucher_type = 'Sales Invoice' and voucher_no in (%s)""" + % (', '.join(['%s']*len(cancelled_invoices))), tuple(cancelled_invoices)) + + for item_code, warehouse in frappe.db.sql("select item_code, warehouse from tabBin where ifnull(reserved_qty, 0) < 0"): + + repost_actual_qty(item_code, warehouse) + + update_bin_qty(item_code, warehouse, { + "reserved_qty": get_reserved_qty(item_code, warehouse) + }) + \ No newline at end of file diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index bf7505baa3..5e11962ea5 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -9,7 +9,6 @@ from frappe.utils import flt, cint from frappe import msgprint, _ import frappe.defaults from frappe.model.mapper import get_mapped_doc -from erpnext.stock.utils import update_bin from erpnext.controllers.selling_controller import SellingController form_grid_templates = { @@ -262,23 +261,6 @@ class DeliveryNote(SellingController): self.make_sl_entries(sl_entries) - 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(_("Reserved 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 get_list_context(context=None): from erpnext.controllers.website_list_for_contact import get_list_context list_context = get_list_context(context)