Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return improvements and Reserved Qty Cleanup #3940

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 16 additions & 34 deletions erpnext/accounts/doctype/sales_invoice/sales_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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()
Expand Down Expand Up @@ -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':
Expand Down Expand Up @@ -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()
Expand Down
19 changes: 4 additions & 15 deletions erpnext/buying/doctype/purchase_order/purchase_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.stock.stock_balance import update_bin_qty, get_ordered_qty


form_grid_templates = {
Expand Down Expand Up @@ -136,28 +137,16 @@ 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 \
and frappe.db.get_value("Item", d.item_code, "is_stock_item") and d.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",
Expand Down
24 changes: 19 additions & 5 deletions erpnext/controllers/sales_and_purchase_return.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -137,9 +138,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: {
Expand All @@ -152,8 +168,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"
},
Expand Down
29 changes: 1 addition & 28 deletions erpnext/controllers/selling_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -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(),
Expand Down
59 changes: 39 additions & 20 deletions erpnext/controllers/stock_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down
1 change: 1 addition & 0 deletions erpnext/patches.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion erpnext/patches/v4_2/repost_reserved_qty.py
Original file line number Diff line number Diff line change
Expand Up @@ -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("""
Expand Down
2 changes: 1 addition & 1 deletion erpnext/patches/v4_2/update_requested_and_ordered_qty.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion erpnext/patches/v5_0/repost_requested_qty.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
Loading