Merge pull request #2313 from nabinhait/stock_reco

Fix gl entries for stock transactions
This commit is contained in:
Nabin Hait 2014-10-15 12:44:59 +05:30
commit 147918ed66
10 changed files with 69 additions and 92 deletions

View File

@ -7,7 +7,7 @@ import frappe.defaults
from frappe.utils import cint, cstr, 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, block_negative_stock
from erpnext.controllers.stock_controller import update_gl_entries_after
from frappe.model.mapper import get_mapped_doc
from erpnext.controllers.selling_controller import SellingController
@ -456,8 +456,8 @@ class SalesInvoice(SellingController):
self.make_sl_entries(sl_entries)
def make_gl_entries(self, repost_future_gle=True, allow_negative_stock=False):
gl_entries = self.get_gl_entries(allow_negative_stock=allow_negative_stock)
def make_gl_entries(self, repost_future_gle=True):
gl_entries = self.get_gl_entries()
if gl_entries:
from erpnext.accounts.general_ledger import make_gl_entries
@ -476,7 +476,7 @@ class SalesInvoice(SellingController):
items, warehouses = self.get_items_and_warehouses()
update_gl_entries_after(self.posting_date, self.posting_time, warehouses, items)
def get_gl_entries(self, warehouse_account=None, allow_negative_stock=False):
def get_gl_entries(self, warehouse_account=None):
from erpnext.accounts.general_ledger import merge_similar_entries
gl_entries = []
@ -485,7 +485,7 @@ class SalesInvoice(SellingController):
self.make_tax_gl_entries(gl_entries)
self.make_item_gl_entries(gl_entries, allow_negative_stock)
self.make_item_gl_entries(gl_entries)
# merge gl entries before adding pos entries
gl_entries = merge_similar_entries(gl_entries)
@ -520,7 +520,7 @@ class SalesInvoice(SellingController):
})
)
def make_item_gl_entries(self, gl_entries, allow_negative_stock=False):
def make_item_gl_entries(self, gl_entries):
# income account gl entries
for item in self.get("entries"):
if flt(item.base_amount):
@ -537,7 +537,7 @@ class SalesInvoice(SellingController):
# expense account gl entries
if cint(frappe.defaults.get_global_default("auto_accounting_for_stock")) \
and cint(self.update_stock):
gl_entries += super(SalesInvoice, self).get_gl_entries(allow_negative_stock=allow_negative_stock)
gl_entries += super(SalesInvoice, self).get_gl_entries()
def make_pos_gl_entries(self, gl_entries):
if cint(self.is_pos) and self.cash_bank_account and self.paid_amount:

View File

@ -269,7 +269,7 @@ class BuyingController(StockController):
# get raw materials rate
if self.doctype == "Purchase Receipt":
from erpnext.stock.utils import get_incoming_rate
item_rate = get_incoming_rate({
rm.rate = get_incoming_rate({
"item_code": bom_item.item_code,
"warehouse": self.supplier_warehouse,
"posting_date": self.posting_date,
@ -277,10 +277,9 @@ class BuyingController(StockController):
"qty": -1 * required_qty,
"serial_no": rm.serial_no
})
if not item_rate:
from erpnext.controllers.stock_controller import get_valuation_rate
item_rate = get_valuation_rate(bom_item.item_code, self.supplier_warehouse)
rm.rate = item_rate or bom_item.rate
if not rm.rate:
from erpnext.stock.stock_ledger import get_valuation_rate
rm.rate = get_valuation_rate(bom_item.item_code, self.supplier_warehouse)
else:
rm.rate = bom_item.rate

View File

@ -11,7 +11,7 @@ from erpnext.controllers.accounts_controller import AccountsController
from erpnext.accounts.general_ledger import make_gl_entries, delete_gl_entries, process_gl_map
class StockController(AccountsController):
def make_gl_entries(self, repost_future_gle=True, allow_negative_stock=False):
def make_gl_entries(self, repost_future_gle=True):
if self.docstatus == 2:
delete_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
@ -19,18 +19,16 @@ class StockController(AccountsController):
warehouse_account = get_warehouse_account()
if self.docstatus==1:
gl_entries = self.get_gl_entries(warehouse_account, allow_negative_stock=allow_negative_stock)
gl_entries = self.get_gl_entries(warehouse_account)
make_gl_entries(gl_entries)
if repost_future_gle:
items, warehouses = self.get_items_and_warehouses()
update_gl_entries_after(self.posting_date, self.posting_time, warehouses, items,
warehouse_account, allow_negative_stock)
warehouse_account)
def get_gl_entries(self, warehouse_account=None, default_expense_account=None,
default_cost_center=None, allow_negative_stock=False):
# block_negative_stock(allow_negative_stock)
default_cost_center=None):
if not warehouse_account:
warehouse_account = get_warehouse_account()
@ -49,17 +47,12 @@ class StockController(AccountsController):
self.check_expense_account(detail)
stock_value_difference = flt(sle.stock_value_difference, 2)
if not stock_value_difference:
valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse)
stock_value_difference = flt(sle.actual_qty)*flt(valuation_rate)
gl_list.append(self.get_gl_dict({
"account": warehouse_account[sle.warehouse],
"against": detail.expense_account,
"cost_center": detail.cost_center,
"remarks": self.get("remarks") or "Accounting Entry for Stock",
"debit": stock_value_difference
"debit": flt(sle.stock_value_difference, 2)
}))
# to target warehouse / expense account
@ -68,7 +61,7 @@ class StockController(AccountsController):
"against": warehouse_account[sle.warehouse],
"cost_center": detail.cost_center,
"remarks": self.get("remarks") or "Accounting Entry for Stock",
"credit": stock_value_difference
"credit": flt(sle.stock_value_difference, 2)
}))
elif sle.warehouse not in warehouse_with_no_account:
warehouse_with_no_account.append(sle.warehouse)
@ -224,7 +217,7 @@ class StockController(AccountsController):
return serialized_items
def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
warehouse_account=None, allow_negative_stock=False):
warehouse_account=None):
def _delete_gl_entries(voucher_type, voucher_no):
frappe.db.sql("""delete from `tabGL Entry`
where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no))
@ -238,12 +231,12 @@ def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for
for voucher_type, voucher_no in future_stock_vouchers:
existing_gle = gle.get((voucher_type, voucher_no), [])
voucher_obj = frappe.get_doc(voucher_type, voucher_no)
expected_gle = voucher_obj.get_gl_entries(warehouse_account, allow_negative_stock=allow_negative_stock)
expected_gle = voucher_obj.get_gl_entries(warehouse_account)
if expected_gle:
if not existing_gle or not compare_existing_and_expected_gle(existing_gle,
expected_gle):
_delete_gl_entries(voucher_type, voucher_no)
voucher_obj.make_gl_entries(repost_future_gle=False, allow_negative_stock=allow_negative_stock)
voucher_obj.make_gl_entries(repost_future_gle=False)
else:
_delete_gl_entries(voucher_type, voucher_no)
@ -295,31 +288,3 @@ def get_warehouse_account():
warehouse_account = dict(frappe.db.sql("""select master_name, name from tabAccount
where account_type = 'Warehouse' and ifnull(master_name, '') != ''"""))
return warehouse_account
def block_negative_stock(allow_negative_stock=False):
if cint(frappe.defaults.get_global_default("auto_accounting_for_stock")) and not allow_negative_stock:
if cint(frappe.db.get_value("Stock Settings", None, "allow_negative_stock")):
frappe.throw(_("Negative stock is not allowed in case of Perpetual Inventory, please disable it from Stock Settings"))
def get_valuation_rate(item_code, warehouse):
last_valuation_rate = frappe.db.sql("""select valuation_rate
from `tabStock Ledger Entry`
where item_code = %s and warehouse = %s
and ifnull(valuation_rate, 0) > 0
order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, warehouse))
if not last_valuation_rate:
last_valuation_rate = frappe.db.sql("""select valuation_rate
from `tabStock Ledger Entry`
where item_code = %s and ifnull(valuation_rate, 0) > 0
order by posting_date desc, posting_time desc, name desc limit 1""", item_code)
valuation_rate = flt(last_valuation_rate[0][0]) if last_valuation_rate else 0
if not valuation_rate:
valuation_rate = frappe.db.get_value("Item Price", {"item_code": item_code, "buying": 1}, "price_list_rate")
if not valuation_rate:
frappe.throw(_("Purchase rate for item: {0} not found, which is required to book accounting entry. Please mention item price against a buying price list.").format(item_code))
return valuation_rate

View File

@ -85,4 +85,5 @@ erpnext.patches.v4_2.seprate_manufacture_and_repack
execute:frappe.delete_doc("Report", "Warehouse-Wise Stock Balance")
execute:frappe.delete_doc("DocType", "Purchase Request")
execute:frappe.delete_doc("DocType", "Purchase Request Item")
erpnext.patches.v4_2.recalculate_bom_cost
erpnext.patches.v4_2.recalculate_bom_cost
erpnext.patches.v4_2.fix_gl_entries_for_stock_transactions

View File

@ -7,7 +7,7 @@ from frappe.utils import flt
def execute():
from erpnext.utilities.repost_stock import repost
repost()
repost(allow_zero_rate=True)
warehouse_account = frappe.db.sql("""select name, master_name from tabAccount
where ifnull(account_type, '') = 'Warehouse'""")
@ -40,7 +40,7 @@ def execute():
where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no))
voucher = frappe.get_doc(voucher_type, voucher_no)
voucher.make_gl_entries(repost_future_gle=False, allow_negative_stock=True)
voucher.make_gl_entries(repost_future_gle=False)
frappe.db.commit()
except Exception, e:
print frappe.get_traceback()

View File

@ -283,11 +283,8 @@ class PurchaseReceipt(BuyingController):
def get_rate(self,arg):
return frappe.get_doc('Purchase Common').get_rate(arg,self)
def get_gl_entries(self, warehouse_account=None, allow_negative_stock=False):
def get_gl_entries(self, warehouse_account=None):
from erpnext.accounts.general_ledger import process_gl_map
from erpnext.controllers.stock_controller import block_negative_stock
# block_negative_stock(allow_negative_stock)
stock_rbnb = self.get_company_default("stock_received_but_not_billed")
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")

View File

@ -198,12 +198,12 @@ class StockReconciliation(StockController):
"posting_time": self.posting_time
})
def get_gl_entries(self, warehouse_account=None, allow_negative_stock=False):
def get_gl_entries(self, warehouse_account=None):
if not self.cost_center:
msgprint(_("Please enter Cost Center"), raise_exception=1)
return super(StockReconciliation, self).get_gl_entries(warehouse_account,
self.expense_account, self.cost_center, allow_negative_stock=allow_negative_stock)
self.expense_account, self.cost_center)
def validate_expense_account(self):
if not cint(frappe.defaults.get_global_default("auto_accounting_for_stock")):

View File

@ -12,9 +12,6 @@ from frappe.model.document import Document
class StockSettings(Document):
def validate(self):
if cint(self.allow_negative_stock) and cint(frappe.defaults.get_global_default("auto_accounting_for_stock")):
frappe.throw(_("Negative stock is not allowed in case of Perpetual Inventory"))
for key in ["item_naming_by", "item_group", "stock_uom", "allow_negative_stock"]:
frappe.db.set_default(key, self.get(key, ""))

View File

@ -6,7 +6,6 @@ import frappe
from frappe import _
from frappe.utils import cint, flt, cstr, now
from erpnext.stock.utils import get_valuation_method
from erpnext.controllers.stock_controller import get_valuation_rate
import json
# future reposting
@ -59,7 +58,7 @@ def delete_cancelled_entry(voucher_type, voucher_no):
frappe.db.sql("""delete from `tabStock Ledger Entry`
where voucher_type=%s and voucher_no=%s""", (voucher_type, voucher_no))
def update_entries_after(args, verbose=1):
def update_entries_after(args, allow_zero_rate=False, verbose=1):
"""
update valution rate and qty after transaction
from the current time-bucket onwards
@ -107,9 +106,9 @@ def update_entries_after(args, verbose=1):
stock_queue = [[qty_after_transaction, valuation_rate]]
else:
if valuation_method == "Moving Average":
valuation_rate = get_moving_average_values(qty_after_transaction, sle, valuation_rate)
valuation_rate = get_moving_average_values(qty_after_transaction, sle, valuation_rate, allow_zero_rate)
else:
valuation_rate = get_fifo_values(qty_after_transaction, sle, stock_queue)
valuation_rate = get_fifo_values(qty_after_transaction, sle, stock_queue, allow_zero_rate)
qty_after_transaction += flt(sle.actual_qty)
@ -252,7 +251,7 @@ def get_serialized_values(qty_after_transaction, sle, valuation_rate):
return valuation_rate
def get_moving_average_values(qty_after_transaction, sle, valuation_rate):
def get_moving_average_values(qty_after_transaction, sle, valuation_rate, allow_zero_rate):
incoming_rate = flt(sle.incoming_rate)
actual_qty = flt(sle.actual_qty)
@ -266,18 +265,19 @@ def get_moving_average_values(qty_after_transaction, sle, valuation_rate):
if new_stock_qty:
valuation_rate = new_stock_value / flt(new_stock_qty)
elif not valuation_rate:
valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse)
elif not valuation_rate and qty_after_transaction <= 0:
valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse, allow_zero_rate)
return abs(flt(valuation_rate))
def get_fifo_values(qty_after_transaction, sle, stock_queue):
def get_fifo_values(qty_after_transaction, sle, stock_queue, allow_zero_rate):
incoming_rate = flt(sle.incoming_rate)
actual_qty = flt(sle.actual_qty)
intialize_stock_queue(stock_queue, sle.item_code, sle.warehouse, actual_qty)
if actual_qty > 0:
if not stock_queue:
stock_queue.append([0, 0])
if stock_queue[-1][0] > 0:
stock_queue.append([actual_qty, incoming_rate])
else:
@ -289,12 +289,12 @@ def get_fifo_values(qty_after_transaction, sle, stock_queue):
else:
qty_to_pop = abs(actual_qty)
while qty_to_pop:
intialize_stock_queue(stock_queue, sle.item_code, sle.warehouse, actual_qty)
if not stock_queue:
stock_queue.append([0, get_valuation_rate(sle.item_code, sle.warehouse, allow_zero_rate)
if qty_after_transaction <= 0 else 0])
batch = stock_queue[0]
# print qty_to_pop, batch
if qty_to_pop >= batch[0]:
# consume current batch
qty_to_pop = qty_to_pop - batch[0]
@ -318,11 +318,6 @@ def get_fifo_values(qty_after_transaction, sle, stock_queue):
return abs(valuation_rate)
def intialize_stock_queue(stock_queue, item_code, warehouse, actual_qty):
if not stock_queue:
estimated_val_rate = get_valuation_rate(item_code, warehouse) if actual_qty < 0 else 0
stock_queue.append([0, estimated_val_rate])
def _raise_exceptions(args, verbose=1):
deficiency = min(e["diff"] for e in _exceptions)
msg = _("Negative Stock Error ({6}) for Item {0} in Warehouse {1} on {2} {3} in {4} {5}").format(args["item_code"],
@ -353,3 +348,26 @@ def get_previous_sle(args, for_update=False):
"timestamp(posting_date, posting_time) <= timestamp(%(posting_date)s, %(posting_time)s)"],
"desc", "limit 1", for_update=for_update)
return sle and sle[0] or {}
def get_valuation_rate(item_code, warehouse, allow_zero_rate=False):
last_valuation_rate = frappe.db.sql("""select valuation_rate
from `tabStock Ledger Entry`
where item_code = %s and warehouse = %s
and ifnull(valuation_rate, 0) > 0
order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, warehouse))
if not last_valuation_rate:
last_valuation_rate = frappe.db.sql("""select valuation_rate
from `tabStock Ledger Entry`
where item_code = %s and ifnull(valuation_rate, 0) > 0
order by posting_date desc, posting_time desc, name desc limit 1""", item_code)
valuation_rate = flt(last_valuation_rate[0][0]) if last_valuation_rate else 0
if not valuation_rate:
valuation_rate = frappe.db.get_value("Item Price", {"item_code": item_code, "buying": 1}, "price_list_rate")
if not allow_zero_rate and not valuation_rate and cint(frappe.db.get_value("Accounts Settings", None, "auto_accounting_for_stock")):
frappe.throw(_("Purchase rate for item: {0} not found, which is required to book accounting entry (expense). Please mention item price against a buying price list.").format(item_code))
return valuation_rate

View File

@ -9,7 +9,7 @@ from erpnext.stock.utils import update_bin
from erpnext.stock.stock_ledger import update_entries_after
from erpnext.accounts.utils import get_fiscal_year
def repost(allow_negative_stock=False):
def repost(allow_negative_stock=False, allow_zero_rate=False):
"""
Repost everything!
"""
@ -23,7 +23,7 @@ def repost(allow_negative_stock=False):
union
select item_code, warehouse from `tabStock Ledger Entry`) a"""):
try:
repost_stock(d[0], d[1])
repost_stock(d[0], d[1], allow_zero_rate)
frappe.db.commit()
except:
frappe.db.rollback()
@ -33,8 +33,8 @@ def repost(allow_negative_stock=False):
frappe.db.get_value("Stock Settings", None, "allow_negative_stock"))
frappe.db.auto_commit_on_many_writes = 0
def repost_stock(item_code, warehouse):
repost_actual_qty(item_code, warehouse)
def repost_stock(item_code, warehouse, allow_zero_rate=False):
repost_actual_qty(item_code, warehouse, allow_zero_rate)
if item_code and warehouse:
update_bin_qty(item_code, warehouse, {
@ -44,9 +44,9 @@ def repost_stock(item_code, warehouse):
"planned_qty": get_planned_qty(item_code, warehouse)
})
def repost_actual_qty(item_code, warehouse):
def repost_actual_qty(item_code, warehouse, allow_zero_rate=False):
try:
update_entries_after({ "item_code": item_code, "warehouse": warehouse })
update_entries_after({ "item_code": item_code, "warehouse": warehouse }, allow_zero_rate)
except:
pass