- 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
This commit is contained in:
Nabin Hait 2015-07-29 12:21:03 +05:30
parent 43e46a8506
commit 453cc374d4
8 changed files with 75 additions and 41 deletions

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
import frappe.defaults import frappe.defaults
from frappe.utils import cint, cstr, flt from frappe.utils import cint, flt
from frappe import _, msgprint, throw from frappe import _, msgprint, throw
from erpnext.accounts.party import get_party_account, get_due_date from erpnext.accounts.party import get_party_account, get_due_date
from erpnext.controllers.stock_controller import update_gl_entries_after from erpnext.controllers.stock_controller import update_gl_entries_after
@ -66,6 +66,7 @@ class SalesInvoice(SellingController):
self.validate_c_form() self.validate_c_form()
self.validate_time_logs_are_submitted() self.validate_time_logs_are_submitted()
self.validate_multiple_billing("Delivery Note", "dn_detail", "amount", "items") self.validate_multiple_billing("Delivery Note", "dn_detail", "amount", "items")
self.update_packing_list()
def on_submit(self): def on_submit(self):
super(SalesInvoice, self).on_submit() 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.actual_qty = bin and flt(bin[0]['actual_qty']) or 0
d.projected_qty = bin and flt(bin[0]['projected_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): def get_warehouse(self):
user_pos_profile = frappe.db.sql("""select name, warehouse from `tabPOS Profile` user_pos_profile = frappe.db.sql("""select name, warehouse from `tabPOS Profile`
@ -381,20 +389,6 @@ class SalesInvoice(SellingController):
return warehouse return warehouse
def on_update(self): 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 cint(self.is_pos) == 1:
if flt(self.paid_amount) == 0: if flt(self.paid_amount) == 0:
if self.cash_bank_account: if self.cash_bank_account:
@ -424,8 +418,9 @@ class SalesInvoice(SellingController):
def update_stock_ledger(self): def update_stock_ledger(self):
sl_entries = [] sl_entries = []
for d in self.get_item_list(): for d in self.get_item_list():
if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1 \ if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1 and d.warehouse and flt(d['qty']):
and d.warehouse: self.update_reserved_qty(d)
incoming_rate = 0 incoming_rate = 0
if cint(self.is_return) and self.return_against and self.docstatus==1: 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, incoming_rate = self.get_incoming_rate_for_sales_return(d.item_code,

View File

@ -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

View File

@ -174,12 +174,14 @@ class SellingController(StockController):
if flt(d.qty) > flt(d.delivered_qty): if flt(d.qty) > flt(d.delivered_qty):
reserved_qty_for_main_item = 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. # 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 # 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, 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) so_qty, reserved_warehouse = self.get_so_qty_and_warehouse(d.so_detail)
if already_delivered_qty + d.qty > so_qty: if already_delivered_qty + d.qty > so_qty:
@ -221,12 +223,21 @@ class SellingController(StockController):
return frappe.db.sql("""select name from `tabProduct Bundle` return frappe.db.sql("""select name from `tabProduct Bundle`
where new_item_code=%s and docstatus != 2""", item_code) where new_item_code=%s and docstatus != 2""", item_code)
def get_already_delivered_qty(self, dn, so, so_detail): def get_already_delivered_qty(self, current_docname, so, so_detail):
qty = frappe.db.sql("""select sum(qty) from `tabDelivery Note Item` delivered_via_dn = frappe.db.sql("""select sum(qty) from `tabDelivery Note Item`
where so_detail = %s and docstatus = 1 where so_detail = %s and docstatus = 1
and against_sales_order = %s and against_sales_order = %s
and parent != %s""", (so_detail, so, dn)) and parent != %s""", (so_detail, so, current_docname))
return qty and flt(qty[0][0]) or 0.0
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): def get_so_qty_and_warehouse(self, so_detail):
so_item = frappe.db.sql("""select qty, warehouse from `tabSales Order Item` so_item = frappe.db.sql("""select qty, warehouse from `tabSales Order Item`

View File

@ -9,6 +9,8 @@ import frappe.defaults
from erpnext.controllers.accounts_controller import AccountsController from erpnext.controllers.accounts_controller import AccountsController
from erpnext.accounts.general_ledger import make_gl_entries, delete_gl_entries, process_gl_map 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): class StockController(AccountsController):
def make_gl_entries(self, repost_future_gle=True): 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 incoming_rate = incoming_rate[0][0] if incoming_rate else 0.0
return incoming_rate 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, def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
warehouse_account=None): warehouse_account=None):

View File

@ -180,3 +180,4 @@ execute:frappe.rename_doc("DocType", "Salary Manager", "Process Payroll", force=
erpnext.patches.v5_1.rename_roles erpnext.patches.v5_1.rename_roles
erpnext.patches.v5_1.default_bom erpnext.patches.v5_1.default_bom
execute:frappe.delete_doc("DocType", "Party Type") execute:frappe.delete_doc("DocType", "Party Type")
erpnext.patches.v5_4.fix_reserved_qty_and_sle_for_packed_items

View File

View File

@ -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)
})

View File

@ -9,7 +9,6 @@ from frappe.utils import flt, cint
from frappe import msgprint, _ from frappe import msgprint, _
import frappe.defaults import frappe.defaults
from frappe.model.mapper import get_mapped_doc from frappe.model.mapper import get_mapped_doc
from erpnext.stock.utils import update_bin
from erpnext.controllers.selling_controller import SellingController from erpnext.controllers.selling_controller import SellingController
form_grid_templates = { form_grid_templates = {
@ -262,23 +261,6 @@ class DeliveryNote(SellingController):
self.make_sl_entries(sl_entries) 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): def get_list_context(context=None):
from erpnext.controllers.website_list_for_contact import get_list_context from erpnext.controllers.website_list_for_contact import get_list_context
list_context = get_list_context(context) list_context = get_list_context(context)