mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-16 19:49:18 +00:00
[fix] [minor] auto accounting for stock transactions
This commit is contained in:
@@ -12,19 +12,6 @@ class DocType:
|
||||
def __init__(self, d, dl):
|
||||
self.doc, self.doclist = d, dl
|
||||
|
||||
def validate(self):
|
||||
self.validate_auto_accounting_for_stock()
|
||||
|
||||
def validate_auto_accounting_for_stock(self):
|
||||
if cint(self.doc.auto_accounting_for_stock) == 1:
|
||||
previous_val = cint(webnotes.conn.get_value("Accounts Settings",
|
||||
None, "auto_accounting_for_stock"))
|
||||
if cint(self.doc.auto_accounting_for_stock) != previous_val:
|
||||
from accounts.utils import validate_stock_and_account_balance, \
|
||||
create_stock_in_hand_jv
|
||||
validate_stock_and_account_balance()
|
||||
create_stock_in_hand_jv(reverse=cint(self.doc.auto_accounting_for_stock) < previous_val)
|
||||
|
||||
def on_update(self):
|
||||
for key in ["auto_accounting_for_stock"]:
|
||||
webnotes.conn.set_default(key, self.doc.fields.get(key, ''))
|
||||
|
||||
@@ -32,6 +32,18 @@ class TestJournalVoucher(unittest.TestCase):
|
||||
|
||||
self.assertTrue(not webnotes.conn.sql("""select name from `tabJournal Voucher Detail`
|
||||
where against_jv=%s""", jv_invoice.doc.name))
|
||||
|
||||
def test_jv_against_stock_account(self):
|
||||
webnotes.defaults.set_global_default("auto_accounting_for_stock", 1)
|
||||
|
||||
jv = webnotes.bean(copy=test_records[0])
|
||||
jv.doclist[1].account = "_Test Account Stock in Hand - _TC"
|
||||
jv.insert()
|
||||
|
||||
from accounts.general_ledger import StockAccountInvalidTransaction
|
||||
self.assertRaises(StockAccountInvalidTransaction, jv.submit)
|
||||
|
||||
webnotes.defaults.set_global_default("auto_accounting_for_stock", 0)
|
||||
|
||||
def test_monthly_budget_crossed_ignore(self):
|
||||
webnotes.conn.set_value("Company", "_Test Company", "monthly_bgt_flag", "Ignore")
|
||||
|
||||
@@ -90,7 +90,6 @@ class DocType(SellingController):
|
||||
get_obj('Authorization Control').validate_approving_authority(self.doc.doctype,
|
||||
self.doc.company, self.doc.grand_total, self)
|
||||
|
||||
self.set_buying_amount()
|
||||
self.check_prev_docstatus()
|
||||
|
||||
self.update_status_updater_args()
|
||||
|
||||
@@ -330,13 +330,12 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
self.assertFalse(gle)
|
||||
|
||||
def atest_pos_gl_entry_with_aii(self):
|
||||
def test_pos_gl_entry_with_aii(self):
|
||||
webnotes.conn.sql("delete from `tabStock Ledger Entry`")
|
||||
webnotes.conn.sql("delete from `tabGL Entry`")
|
||||
webnotes.conn.sql("delete from `tabBin`")
|
||||
webnotes.defaults.set_global_default("auto_accounting_for_stock", 1)
|
||||
|
||||
old_default_company = webnotes.conn.get_default("company")
|
||||
webnotes.conn.set_default("company", "_Test Company")
|
||||
|
||||
self._insert_purchase_receipt()
|
||||
self._insert_pos_settings()
|
||||
|
||||
@@ -360,20 +359,18 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
["_Test Item", "_Test Warehouse - _TC", -1.0])
|
||||
|
||||
# check gl entries
|
||||
stock_in_hand_account = webnotes.conn.get_value("Company", "_Test Company",
|
||||
"stock_in_hand_account")
|
||||
|
||||
gl_entries = webnotes.conn.sql("""select account, debit, credit
|
||||
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
|
||||
order by account asc, debit asc""", si.doc.name, as_dict=1)
|
||||
self.assertTrue(gl_entries)
|
||||
|
||||
|
||||
expected_gl_entries = sorted([
|
||||
[si.doc.debit_to, 630.0, 0.0],
|
||||
[pos[1]["income_account"], 0.0, 500.0],
|
||||
[pos[2]["account_head"], 0.0, 80.0],
|
||||
[pos[3]["account_head"], 0.0, 50.0],
|
||||
[stock_in_hand_account, 0.0, 75.0],
|
||||
["_Test Account Stock In Hand - _TC", 0.0, 75.0],
|
||||
[pos[1]["expense_account"], 75.0, 0.0],
|
||||
[si.doc.debit_to, 0.0, 600.0],
|
||||
["_Test Account Bank Account - _TC", 600.0, 0.0]
|
||||
@@ -383,6 +380,8 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertEquals(expected_gl_entries[i][1], gle.debit)
|
||||
self.assertEquals(expected_gl_entries[i][2], gle.credit)
|
||||
|
||||
|
||||
|
||||
# cancel
|
||||
si.cancel()
|
||||
gle = webnotes.conn.sql("""select * from `tabGL Entry`
|
||||
@@ -390,12 +389,11 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
self.assertFalse(gle)
|
||||
|
||||
self.assertFalse(get_stock_and_account_difference([si.doclist[1].warehouse]))
|
||||
self.assertFalse(get_stock_and_account_difference(["_Test Account Stock In Hand - _TC"]))
|
||||
|
||||
webnotes.defaults.set_global_default("auto_accounting_for_stock", 0)
|
||||
webnotes.conn.set_default("company", old_default_company)
|
||||
|
||||
def atest_sales_invoice_gl_entry_with_aii_no_item_code(self):
|
||||
def test_sales_invoice_gl_entry_with_aii_no_item_code(self):
|
||||
webnotes.defaults.set_global_default("auto_accounting_for_stock", 1)
|
||||
|
||||
si_copy = webnotes.copy_doclist(test_records[1])
|
||||
@@ -422,7 +420,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
webnotes.defaults.set_global_default("auto_accounting_for_stock", 0)
|
||||
|
||||
def atest_sales_invoice_gl_entry_with_aii_non_stock_item(self):
|
||||
def test_sales_invoice_gl_entry_with_aii_non_stock_item(self):
|
||||
webnotes.defaults.set_global_default("auto_accounting_for_stock", 1)
|
||||
|
||||
si_copy = webnotes.copy_doclist(test_records[1])
|
||||
@@ -641,7 +639,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
return new_si
|
||||
|
||||
# if yearly, test 3 repetitions, else test 13 repetitions
|
||||
count = no_of_months == 12 and 3 or 13
|
||||
count = 3 if no_of_months == 12 else 13
|
||||
for i in xrange(count):
|
||||
base_si = _test(i)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{
|
||||
"creation": "2013-06-04 11:02:19",
|
||||
"docstatus": 0,
|
||||
"modified": "2013-07-25 16:32:10",
|
||||
"modified": "2013-08-29 16:58:56",
|
||||
"modified_by": "Administrator",
|
||||
"owner": "Administrator"
|
||||
},
|
||||
@@ -416,17 +416,6 @@
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "buying_amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 1,
|
||||
"label": "Buying Amount",
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"doctype": "DocField",
|
||||
|
||||
@@ -5,8 +5,12 @@ from __future__ import unicode_literals
|
||||
import webnotes
|
||||
from webnotes.utils import flt, cstr, now
|
||||
from webnotes.model.doc import Document
|
||||
from webnotes import msgprint, _
|
||||
from accounts.utils import validate_expense_against_budget
|
||||
|
||||
|
||||
class StockAccountInvalidTransaction(webnotes.ValidationError): pass
|
||||
|
||||
def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True,
|
||||
update_outstanding='Yes'):
|
||||
if gl_map:
|
||||
@@ -47,8 +51,8 @@ def merge_similar_entries(gl_map):
|
||||
merged_gl_map = filter(lambda x: flt(x.debit)!=0 or flt(x.credit)!=0, merged_gl_map)
|
||||
return merged_gl_map
|
||||
|
||||
def check_if_in_list(gle, gl_mqp):
|
||||
for e in gl_mqp:
|
||||
def check_if_in_list(gle, gl_map):
|
||||
for e in gl_map:
|
||||
if e.account == gle.account and \
|
||||
cstr(e.get('against_voucher'))==cstr(gle.get('against_voucher')) \
|
||||
and cstr(e.get('against_voucher_type')) == \
|
||||
@@ -57,11 +61,14 @@ def check_if_in_list(gle, gl_mqp):
|
||||
return e
|
||||
|
||||
def save_entries(gl_map, adv_adj, update_outstanding):
|
||||
validate_account_for_auto_accounting_for_stock(gl_map)
|
||||
|
||||
total_debit = total_credit = 0.0
|
||||
for entry in gl_map:
|
||||
make_entry(entry, adv_adj, update_outstanding)
|
||||
# check against budget
|
||||
validate_expense_against_budget(entry)
|
||||
|
||||
|
||||
# update total debit / credit
|
||||
total_debit += flt(entry.debit)
|
||||
@@ -79,8 +86,20 @@ def make_entry(args, adv_adj, update_outstanding):
|
||||
|
||||
def validate_total_debit_credit(total_debit, total_credit):
|
||||
if abs(total_debit - total_credit) > 0.005:
|
||||
webnotes.throw(webnotes._("Debit and Credit not equal for this voucher: Diff (Debit) is ") +
|
||||
webnotes.throw(_("Debit and Credit not equal for this voucher: Diff (Debit) is ") +
|
||||
cstr(total_debit - total_credit))
|
||||
|
||||
def validate_account_for_auto_accounting_for_stock(gl_map):
|
||||
if gl_map[0].voucher_type=="Journal Voucher":
|
||||
aii_accounts = [d[0] for d in webnotes.conn.sql("""select account from tabWarehouse
|
||||
where ifnull(account, '')!=''""")]
|
||||
|
||||
for entry in gl_map:
|
||||
if entry.account in aii_accounts:
|
||||
webnotes.throw(_("Account") + ": " + entry.account +
|
||||
_(" can only be debited/credited through Stock transactions"),
|
||||
StockAccountInvalidTransaction)
|
||||
|
||||
|
||||
def delete_gl_entries(gl_entries=None, voucher_type=None, voucher_no=None,
|
||||
adv_adj=False, update_outstanding="Yes"):
|
||||
|
||||
@@ -246,79 +246,6 @@ def get_company_default(company, fieldname):
|
||||
_("' in Company: ") + company), raise_exception=True)
|
||||
|
||||
return value
|
||||
|
||||
def create_stock_in_hand_jv(reverse=False):
|
||||
from webnotes.utils import nowdate
|
||||
today = nowdate()
|
||||
fiscal_year = get_fiscal_year(today)[0]
|
||||
jv_list = []
|
||||
|
||||
for company in webnotes.conn.sql_list("select name from `tabCompany`"):
|
||||
stock_rbnb_value = get_stock_rbnb_value(company)
|
||||
stock_rbnb_value = reverse and -1*stock_rbnb_value or stock_rbnb_value
|
||||
if stock_rbnb_value:
|
||||
jv = webnotes.bean([
|
||||
{
|
||||
"doctype": "Journal Voucher",
|
||||
"naming_series": "JV-AUTO-",
|
||||
"company": company,
|
||||
"posting_date": today,
|
||||
"fiscal_year": fiscal_year,
|
||||
"voucher_type": "Journal Entry",
|
||||
"user_remark": (_("Perpetual Accounting") + ": " +
|
||||
(_("Disabled") if reverse else _("Enabled")) + ". " +
|
||||
_("Journal Entry for inventory that is received but not yet invoiced"))
|
||||
},
|
||||
{
|
||||
"doctype": "Journal Voucher Detail",
|
||||
"parentfield": "entries",
|
||||
"account": get_company_default(company, "stock_received_but_not_billed"),
|
||||
(stock_rbnb_value > 0 and "credit" or "debit"): abs(stock_rbnb_value)
|
||||
},
|
||||
{
|
||||
"doctype": "Journal Voucher Detail",
|
||||
"parentfield": "entries",
|
||||
"account": get_company_default(company, "stock_adjustment_account"),
|
||||
(stock_rbnb_value > 0 and "debit" or "credit"): abs(stock_rbnb_value),
|
||||
"cost_center": get_company_default(company, "stock_adjustment_cost_center")
|
||||
},
|
||||
])
|
||||
jv.insert()
|
||||
|
||||
jv_list.append(jv.doc.name)
|
||||
|
||||
if jv_list:
|
||||
msgprint(_("Following Journal Vouchers have been created automatically") + \
|
||||
":\n%s" % ("\n".join([("<a href=\"#Form/Journal Voucher/%s\">%s</a>" % (jv, jv)) for jv in jv_list]),))
|
||||
|
||||
msgprint(_("""These adjustment vouchers book the difference between \
|
||||
the total value of received items and the total value of invoiced items, \
|
||||
as a required step to use Perpetual Accounting.
|
||||
This is an approximation to get you started.
|
||||
You will need to submit these vouchers after checking if the values are correct.
|
||||
For more details, read: \
|
||||
<a href="http://erpnext.com/auto-inventory-accounting" target="_blank">\
|
||||
Perpetual Accounting</a>"""))
|
||||
|
||||
webnotes.msgprint("""Please refresh the system to get effect of Perpetual Accounting""")
|
||||
|
||||
|
||||
def get_stock_rbnb_value(company):
|
||||
total_received_amount = webnotes.conn.sql("""select sum(valuation_rate*qty*conversion_factor)
|
||||
from `tabPurchase Receipt Item` pr_item where docstatus=1
|
||||
and exists(select name from `tabItem` where name = pr_item.item_code
|
||||
and is_stock_item='Yes')
|
||||
and exists(select name from `tabPurchase Receipt`
|
||||
where name = pr_item.parent and company = %s)""", company)
|
||||
|
||||
total_billed_amount = webnotes.conn.sql("""select sum(valuation_rate*qty*conversion_factor)
|
||||
from `tabPurchase Invoice Item` pi_item where docstatus=1
|
||||
and exists(select name from `tabItem` where name = pi_item.item_code
|
||||
and is_stock_item='Yes')
|
||||
and exists(select name from `tabPurchase Invoice`
|
||||
where name = pi_item.parent and company = %s)""", company)
|
||||
return flt(total_received_amount[0][0]) - flt(total_billed_amount[0][0])
|
||||
|
||||
|
||||
def fix_total_debit_credit():
|
||||
vouchers = webnotes.conn.sql("""select voucher_type, voucher_no,
|
||||
@@ -335,14 +262,6 @@ def fix_total_debit_credit():
|
||||
where voucher_type = %s and voucher_no = %s and %s > 0 limit 1""" %
|
||||
(dr_or_cr, dr_or_cr, '%s', '%s', '%s', dr_or_cr),
|
||||
(d.diff, d.voucher_type, d.voucher_no))
|
||||
|
||||
def validate_stock_and_account_balance():
|
||||
difference = get_stock_and_account_difference()
|
||||
if difference:
|
||||
msgprint(_("Account balance must be synced with stock balance, \
|
||||
to enable perpetual accounting." +
|
||||
_(" Following accounts are not synced with stock balance") + ": \n" +
|
||||
"\n".join(difference.keys())), raise_exception=1)
|
||||
|
||||
def get_stock_and_account_difference(account_list=None, posting_date=None):
|
||||
from stock.utils import get_stock_balance_on
|
||||
|
||||
Reference in New Issue
Block a user