Block negative stock in perpetual inventory
This commit is contained in:
parent
bb19b91ef9
commit
adeb976a1b
@ -14,6 +14,9 @@ class AccountsSettings(Document):
|
||||
frappe.db.set_default("auto_accounting_for_stock", self.auto_accounting_for_stock)
|
||||
|
||||
if cint(self.auto_accounting_for_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"))
|
||||
|
||||
# set default perpetual account in company
|
||||
for company in frappe.db.sql("select name from tabCompany"):
|
||||
frappe.get_doc("Company", company[0]).save()
|
||||
|
@ -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
|
||||
from erpnext.controllers.stock_controller import update_gl_entries_after, block_negative_stock
|
||||
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):
|
||||
gl_entries = self.get_gl_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)
|
||||
|
||||
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):
|
||||
def get_gl_entries(self, warehouse_account=None, allow_negative_stock=False):
|
||||
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)
|
||||
self.make_item_gl_entries(gl_entries, allow_negative_stock)
|
||||
|
||||
# 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):
|
||||
def make_item_gl_entries(self, gl_entries, allow_negative_stock=False):
|
||||
# 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()
|
||||
gl_entries += super(SalesInvoice, self).get_gl_entries(allow_negative_stock=allow_negative_stock)
|
||||
|
||||
def make_pos_gl_entries(self, gl_entries):
|
||||
if cint(self.is_pos) and self.cash_bank_account and self.paid_amount:
|
||||
|
@ -97,8 +97,7 @@ def validate_account_for_auto_accounting_for_stock(gl_map):
|
||||
|
||||
for entry in gl_map:
|
||||
if entry.account in aii_accounts:
|
||||
frappe.throw(_("Account: {0} can only be updated via \
|
||||
Stock Transactions").format(entry.account), StockAccountInvalidTransaction)
|
||||
frappe.throw(_("Account: {0} can only be updated via Stock Transactions").format(entry.account), StockAccountInvalidTransaction)
|
||||
|
||||
|
||||
def delete_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None,
|
||||
|
@ -8,10 +8,10 @@ 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
|
||||
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):
|
||||
def make_gl_entries(self, repost_future_gle=True, allow_negative_stock=False):
|
||||
if self.docstatus == 2:
|
||||
delete_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
|
||||
|
||||
@ -19,16 +19,19 @@ class StockController(AccountsController):
|
||||
warehouse_account = get_warehouse_account()
|
||||
|
||||
if self.docstatus==1:
|
||||
gl_entries = self.get_gl_entries(warehouse_account)
|
||||
gl_entries = self.get_gl_entries(warehouse_account, allow_negative_stock)
|
||||
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)
|
||||
update_gl_entries_after(self.posting_date, self.posting_time, warehouses, items,
|
||||
warehouse_account, allow_negative_stock)
|
||||
|
||||
def get_gl_entries(self, warehouse_account=None, default_expense_account=None,
|
||||
default_cost_center=None):
|
||||
from erpnext.accounts.general_ledger import process_gl_map
|
||||
default_cost_center=None, allow_negative_stock=False):
|
||||
|
||||
block_negative_stock(allow_negative_stock)
|
||||
|
||||
if not warehouse_account:
|
||||
warehouse_account = get_warehouse_account()
|
||||
|
||||
@ -46,12 +49,17 @@ 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, sle.posting_date)
|
||||
stock_value_difference = flt(sle.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": flt(sle.stock_value_difference, 2)
|
||||
"debit": stock_value_difference
|
||||
}))
|
||||
|
||||
# to target warehouse / expense account
|
||||
@ -60,7 +68,7 @@ class StockController(AccountsController):
|
||||
"against": warehouse_account[sle.warehouse],
|
||||
"cost_center": detail.cost_center,
|
||||
"remarks": self.get("remarks") or "Accounting Entry for Stock",
|
||||
"credit": flt(sle.stock_value_difference, 2)
|
||||
"credit": stock_value_difference
|
||||
}))
|
||||
elif sle.warehouse not in warehouse_with_no_account:
|
||||
warehouse_with_no_account.append(sle.warehouse)
|
||||
@ -214,7 +222,8 @@ class StockController(AccountsController):
|
||||
|
||||
return serialized_items
|
||||
|
||||
def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None, warehouse_account=None):
|
||||
def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
|
||||
warehouse_account=None, allow_negative_stock=False):
|
||||
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))
|
||||
@ -228,12 +237,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)
|
||||
expected_gle = voucher_obj.get_gl_entries(warehouse_account, allow_negative_stock)
|
||||
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)
|
||||
voucher_obj.make_gl_entries(repost_future_gle=False, allow_negative_stock=allow_negative_stock)
|
||||
else:
|
||||
_delete_gl_entries(voucher_type, voucher_no)
|
||||
|
||||
@ -285,3 +294,22 @@ 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, posting_date):
|
||||
last_valuation_rate = frappe.db.sql("""select valuation_rate
|
||||
from `tabStock Ledger Entry`
|
||||
where item_code = %s and warehouse = %s
|
||||
and ifnull(qty_after_transaction, 0) > 0 and posting_date < %s
|
||||
order by posting_date desc limit 1""", (item_code, warehouse, posting_date))
|
||||
|
||||
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")
|
||||
|
||||
return valuation_rate
|
||||
|
@ -203,12 +203,12 @@ class StockReconciliation(StockController):
|
||||
"posting_time": self.posting_time
|
||||
})
|
||||
|
||||
def get_gl_entries(self, warehouse_account=None):
|
||||
def get_gl_entries(self, warehouse_account=None, allow_negative_stock=False):
|
||||
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)
|
||||
self.expense_account, self.cost_center, allow_negative_stock=allow_negative_stock)
|
||||
|
||||
def validate_expense_account(self):
|
||||
if not cint(frappe.defaults.get_global_default("auto_accounting_for_stock")):
|
||||
|
@ -6,14 +6,16 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
|
||||
from frappe.utils import cint
|
||||
from frappe.model.document import Document
|
||||
|
||||
class StockSettings(Document):
|
||||
|
||||
def validate(self):
|
||||
for key in ["item_naming_by", "item_group", "stock_uom",
|
||||
"allow_negative_stock"]:
|
||||
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, ""))
|
||||
|
||||
from erpnext.setup.doctype.naming_series.naming_series import set_by_naming_series
|
||||
@ -25,3 +27,5 @@ class StockSettings(Document):
|
||||
if submitted_stock_frozen > stock_frozen_limit:
|
||||
self.stock_frozen_upto_days = stock_frozen_limit
|
||||
frappe.msgprint (_("`Freeze Stocks Older Than` should be smaller than %d days.") %stock_frozen_limit)
|
||||
|
||||
|
||||
|
@ -36,9 +36,9 @@ def make_sl_entries(sl_entries, is_amended=None):
|
||||
"is_amended": is_amended
|
||||
})
|
||||
update_bin(args)
|
||||
|
||||
if cancel:
|
||||
delete_cancelled_entry(sl_entries[0].get('voucher_type'),
|
||||
sl_entries[0].get('voucher_no'))
|
||||
delete_cancelled_entry(sl_entries[0].get('voucher_type'), sl_entries[0].get('voucher_no'))
|
||||
|
||||
def set_as_cancel(voucher_type, voucher_no):
|
||||
frappe.db.sql("""update `tabStock Ledger Entry` set is_cancelled='Yes',
|
||||
|
Loading…
Reference in New Issue
Block a user