mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-22 14:39:19 +00:00
Merge branch 'master' of github.com:webnotes/erpnext into wsgi
Conflicts: accounts/doctype/account/account.py accounts/doctype/gl_entry/gl_entry.py accounts/doctype/period_closing_voucher/period_closing_voucher.py stock/doctype/delivery_note/delivery_note.py stock/doctype/landed_cost_wizard/landed_cost_wizard.py stock/doctype/purchase_receipt/purchase_receipt.py stock/doctype/stock_ledger/stock_ledger.py stock/doctype/warehouse/warehouse.py stock/stock_ledger.py
This commit is contained in:
@@ -64,7 +64,6 @@ class DocType:
|
||||
select * from `tabStock Ledger Entry`
|
||||
where item_code = %s
|
||||
and warehouse = %s
|
||||
and ifnull(is_cancelled, 'No') = 'No'
|
||||
order by timestamp(posting_date, posting_time) asc, name asc
|
||||
limit 1
|
||||
""", (self.doc.item_code, self.doc.warehouse), as_dict=1)
|
||||
|
||||
@@ -43,8 +43,8 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend(
|
||||
|
||||
set_print_hide(doc, dt, dn);
|
||||
|
||||
// unhide expense_account and cost_center is auto_inventory_accounting enabled
|
||||
var aii_enabled = cint(sys_defaults.auto_inventory_accounting)
|
||||
// unhide expense_account and cost_center is auto_accounting_for_stock enabled
|
||||
var aii_enabled = cint(sys_defaults.auto_accounting_for_stock)
|
||||
cur_frm.fields_dict[cur_frm.cscript.fname].grid.set_column_disp(["expense_account", "cost_center"], aii_enabled);
|
||||
|
||||
if (this.frm.doc.docstatus===0) {
|
||||
@@ -201,7 +201,7 @@ cur_frm.cscript.on_submit = function(doc, cdt, cdn) {
|
||||
}
|
||||
}
|
||||
|
||||
if (sys_defaults.auto_inventory_accounting) {
|
||||
if (sys_defaults.auto_accounting_for_stock) {
|
||||
|
||||
cur_frm.cscript.expense_account = function(doc, cdt, cdn){
|
||||
var d = locals[cdt][cdn];
|
||||
|
||||
@@ -10,10 +10,7 @@ from webnotes.model.code import get_obj
|
||||
from webnotes import msgprint, _
|
||||
import webnotes.defaults
|
||||
from webnotes.model.mapper import get_mapped_doclist
|
||||
|
||||
|
||||
|
||||
|
||||
from stock.utils import update_bin
|
||||
from controllers.selling_controller import SellingController
|
||||
|
||||
class DocType(SellingController):
|
||||
@@ -129,7 +126,6 @@ class DocType(SellingController):
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
def validate_proj_cust(self):
|
||||
"""check for does customer belong to same project as entered.."""
|
||||
if self.doc.project_name and self.doc.customer:
|
||||
@@ -163,7 +159,7 @@ class DocType(SellingController):
|
||||
if not d['warehouse']:
|
||||
msgprint("Please enter Warehouse for item %s as it is stock item"
|
||||
% d['item_code'], raise_exception=1)
|
||||
|
||||
|
||||
|
||||
def update_current_stock(self):
|
||||
for d in getlist(self.doclist, 'delivery_note_details'):
|
||||
@@ -188,12 +184,11 @@ class DocType(SellingController):
|
||||
self.update_prevdoc_status()
|
||||
|
||||
# create stock ledger entry
|
||||
self.update_stock_ledger(update_stock = 1)
|
||||
self.update_stock_ledger()
|
||||
self.update_serial_nos()
|
||||
|
||||
self.credit_limit()
|
||||
|
||||
self.set_buying_amount()
|
||||
self.make_gl_entries()
|
||||
|
||||
# set DN status
|
||||
@@ -207,7 +202,7 @@ class DocType(SellingController):
|
||||
|
||||
self.update_prevdoc_status()
|
||||
|
||||
self.update_stock_ledger(update_stock = -1)
|
||||
self.update_stock_ledger()
|
||||
self.update_serial_nos(cancel=True)
|
||||
|
||||
webnotes.conn.set(self.doc, 'status', 'Cancelled')
|
||||
@@ -291,60 +286,39 @@ class DocType(SellingController):
|
||||
webnotes.msgprint(_("Packing Slip(s) Cancelled"))
|
||||
|
||||
|
||||
def update_stock_ledger(self, update_stock):
|
||||
self.values = []
|
||||
def update_stock_ledger(self):
|
||||
sl_entries = []
|
||||
for d in self.get_item_list():
|
||||
if webnotes.conn.get_value("Item", d['item_code'], "is_stock_item") == "Yes":
|
||||
# this happens when item is changed from non-stock to stock item
|
||||
if not d["warehouse"]:
|
||||
continue
|
||||
if webnotes.conn.get_value("Item", d.item_code, "is_stock_item") == "Yes" \
|
||||
and d.warehouse:
|
||||
self.update_reserved_qty(d)
|
||||
|
||||
sl_entries.append(self.get_sl_entries(d, {
|
||||
"actual_qty": -1*flt(d['qty']),
|
||||
}))
|
||||
|
||||
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"]:
|
||||
webnotes.throw(_("Reserved Warehouse is missing in Sales Order"))
|
||||
|
||||
if d['reserved_qty'] < 0 :
|
||||
# Reduce reserved qty from reserved warehouse mentioned in so
|
||||
if not d["reserved_warehouse"]:
|
||||
webnotes.throw(_("Reserved Warehouse is missing in Sales Order"))
|
||||
|
||||
args = {
|
||||
"item_code": d['item_code'],
|
||||
"voucher_type": self.doc.doctype,
|
||||
"voucher_no": self.doc.name,
|
||||
"reserved_qty": flt(update_stock) * flt(d['reserved_qty']),
|
||||
"posting_date": self.doc.posting_date,
|
||||
"is_amended": self.doc.amended_from and 'Yes' or 'No'
|
||||
}
|
||||
get_obj("Warehouse", d["reserved_warehouse"]).update_bin(args)
|
||||
|
||||
# Reduce actual qty from warehouse
|
||||
self.make_sl_entry(d, d['warehouse'], - flt(d['qty']) , 0, update_stock)
|
||||
|
||||
get_obj('Stock Ledger', 'Stock Ledger').update_stock(self.values)
|
||||
|
||||
args = {
|
||||
"item_code": d['item_code'],
|
||||
"warehouse": d["reserved_warehouse"],
|
||||
"voucher_type": self.doc.doctype,
|
||||
"voucher_no": self.doc.name,
|
||||
"reserved_qty": (self.doc.docstatus==1 and 1 or -1)*flt(d['reserved_qty']),
|
||||
"posting_date": self.doc.posting_date,
|
||||
"is_amended": self.doc.amended_from and 'Yes' or 'No'
|
||||
}
|
||||
update_bin(args)
|
||||
|
||||
def get_item_list(self):
|
||||
return get_obj('Sales Common').get_item_list(self)
|
||||
|
||||
|
||||
def make_sl_entry(self, d, wh, qty, in_value, update_stock):
|
||||
self.values.append({
|
||||
'item_code' : d['item_code'],
|
||||
'warehouse' : wh,
|
||||
'posting_date' : self.doc.posting_date,
|
||||
'posting_time' : self.doc.posting_time,
|
||||
'voucher_type' : 'Delivery Note',
|
||||
'voucher_no' : self.doc.name,
|
||||
'voucher_detail_no' : d['name'],
|
||||
'actual_qty' : qty,
|
||||
'stock_uom' : d['uom'],
|
||||
'incoming_rate' : in_value,
|
||||
'company' : self.doc.company,
|
||||
'fiscal_year' : self.doc.fiscal_year,
|
||||
'is_cancelled' : (update_stock==1) and 'No' or 'Yes',
|
||||
'batch_no' : d['batch_no'],
|
||||
'serial_no' : d['serial_no'],
|
||||
"project" : self.doc.project_name
|
||||
})
|
||||
|
||||
|
||||
def credit_limit(self):
|
||||
"""check credit limit of items in DN Detail which are not fetched from sales order"""
|
||||
amount, total = 0, 0
|
||||
@@ -354,22 +328,6 @@ class DocType(SellingController):
|
||||
if amount != 0:
|
||||
total = (amount/self.doc.net_total)*self.doc.grand_total
|
||||
get_obj('Sales Common').check_credit(self, total)
|
||||
|
||||
def make_gl_entries(self):
|
||||
if not cint(webnotes.defaults.get_global_default("auto_inventory_accounting")):
|
||||
return
|
||||
|
||||
gl_entries = []
|
||||
for item in self.doclist.get({"parentfield": "delivery_note_details"}):
|
||||
self.check_expense_account(item)
|
||||
|
||||
if item.buying_amount:
|
||||
gl_entries += self.get_gl_entries_for_stock(item.expense_account, -1*item.buying_amount,
|
||||
cost_center=item.cost_center)
|
||||
|
||||
if gl_entries:
|
||||
from accounts.general_ledger import make_gl_entries
|
||||
make_gl_entries(gl_entries, cancel=(self.doc.docstatus == 2))
|
||||
|
||||
def get_invoiced_qty_map(delivery_note):
|
||||
"""returns a map: {dn_detail: invoiced_qty}"""
|
||||
|
||||
@@ -7,20 +7,22 @@ import unittest
|
||||
import webnotes
|
||||
import webnotes.defaults
|
||||
from webnotes.utils import cint
|
||||
from stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries, set_perpetual_inventory, test_records as pr_test_records
|
||||
|
||||
class TestDeliveryNote(unittest.TestCase):
|
||||
def _insert_purchase_receipt(self):
|
||||
from stock.doctype.purchase_receipt.test_purchase_receipt import test_records as pr_test_records
|
||||
def _insert_purchase_receipt(self, item_code=None):
|
||||
pr = webnotes.bean(copy=pr_test_records[0])
|
||||
pr.run_method("calculate_taxes_and_totals")
|
||||
if item_code:
|
||||
pr.doclist[1].item_code = item_code
|
||||
pr.insert()
|
||||
pr.submit()
|
||||
|
||||
def test_over_billing_against_dn(self):
|
||||
self.clear_stock_account_balance()
|
||||
self._insert_purchase_receipt()
|
||||
|
||||
from stock.doctype.delivery_note.delivery_note import make_sales_invoice
|
||||
|
||||
self._insert_purchase_receipt()
|
||||
dn = webnotes.bean(copy=test_records[0]).insert()
|
||||
|
||||
self.assertRaises(webnotes.ValidationError, make_sales_invoice,
|
||||
@@ -38,9 +40,9 @@ class TestDeliveryNote(unittest.TestCase):
|
||||
|
||||
|
||||
def test_delivery_note_no_gl_entry(self):
|
||||
webnotes.conn.sql("""delete from `tabBin`""")
|
||||
webnotes.defaults.set_global_default("auto_inventory_accounting", 0)
|
||||
self.assertEqual(cint(webnotes.defaults.get_global_default("auto_inventory_accounting")), 0)
|
||||
self.clear_stock_account_balance()
|
||||
set_perpetual_inventory(0)
|
||||
self.assertEqual(cint(webnotes.defaults.get_global_default("auto_accounting_for_stock")), 0)
|
||||
|
||||
self._insert_purchase_receipt()
|
||||
|
||||
@@ -48,18 +50,24 @@ class TestDeliveryNote(unittest.TestCase):
|
||||
dn.insert()
|
||||
dn.submit()
|
||||
|
||||
stock_value, stock_value_difference = webnotes.conn.get_value("Stock Ledger Entry",
|
||||
{"voucher_type": "Delivery Note", "voucher_no": dn.doc.name,
|
||||
"item_code": "_Test Item"}, ["stock_value", "stock_value_difference"])
|
||||
self.assertEqual(stock_value, 0)
|
||||
self.assertEqual(stock_value_difference, -375)
|
||||
|
||||
|
||||
gl_entries = webnotes.conn.sql("""select account, debit, credit
|
||||
from `tabGL Entry` where voucher_type='Delivery Note' and voucher_no=%s
|
||||
order by account desc""", dn.doc.name, as_dict=1)
|
||||
|
||||
self.assertTrue(not gl_entries)
|
||||
self.assertFalse(get_gl_entries("Delivery Note", dn.doc.name))
|
||||
|
||||
def test_delivery_note_gl_entry(self):
|
||||
webnotes.conn.sql("""delete from `tabBin`""")
|
||||
webnotes.conn.sql("delete from `tabStock Ledger Entry`")
|
||||
|
||||
webnotes.defaults.set_global_default("auto_inventory_accounting", 1)
|
||||
self.assertEqual(cint(webnotes.defaults.get_global_default("auto_inventory_accounting")), 1)
|
||||
self.clear_stock_account_balance()
|
||||
set_perpetual_inventory()
|
||||
self.assertEqual(cint(webnotes.defaults.get_global_default("auto_accounting_for_stock")), 1)
|
||||
webnotes.conn.set_value("Item", "_Test Item", "valuation_method", "FIFO")
|
||||
|
||||
self._insert_purchase_receipt()
|
||||
|
||||
@@ -67,8 +75,8 @@ class TestDeliveryNote(unittest.TestCase):
|
||||
dn.doclist[1].expense_account = "Cost of Goods Sold - _TC"
|
||||
dn.doclist[1].cost_center = "Main - _TC"
|
||||
|
||||
stock_in_hand_account = webnotes.conn.get_value("Company", dn.doc.company,
|
||||
"stock_in_hand_account")
|
||||
stock_in_hand_account = webnotes.conn.get_value("Account",
|
||||
{"master_name": dn.doclist[1].warehouse})
|
||||
|
||||
from accounts.utils import get_balance_on
|
||||
prev_bal = get_balance_on(stock_in_hand_account, dn.doc.posting_date)
|
||||
@@ -76,26 +84,79 @@ class TestDeliveryNote(unittest.TestCase):
|
||||
dn.insert()
|
||||
dn.submit()
|
||||
|
||||
|
||||
gl_entries = webnotes.conn.sql("""select account, debit, credit
|
||||
from `tabGL Entry` where voucher_type='Delivery Note' and voucher_no=%s
|
||||
order by account asc""", dn.doc.name, as_dict=1)
|
||||
gl_entries = get_gl_entries("Delivery Note", dn.doc.name)
|
||||
self.assertTrue(gl_entries)
|
||||
|
||||
expected_values = sorted([
|
||||
[stock_in_hand_account, 0.0, 375.0],
|
||||
["Cost of Goods Sold - _TC", 375.0, 0.0]
|
||||
])
|
||||
expected_values = {
|
||||
stock_in_hand_account: [0.0, 375.0],
|
||||
"Cost of Goods Sold - _TC": [375.0, 0.0]
|
||||
}
|
||||
for i, gle in enumerate(gl_entries):
|
||||
self.assertEquals(expected_values[i][0], gle.account)
|
||||
self.assertEquals(expected_values[i][1], gle.debit)
|
||||
self.assertEquals(expected_values[i][2], gle.credit)
|
||||
self.assertEquals([gle.debit, gle.credit], expected_values.get(gle.account))
|
||||
|
||||
# check stock in hand balance
|
||||
bal = get_balance_on(stock_in_hand_account, dn.doc.posting_date)
|
||||
self.assertEquals(bal, prev_bal - 375.0)
|
||||
|
||||
# back dated purchase receipt
|
||||
pr = webnotes.bean(copy=pr_test_records[0])
|
||||
pr.doc.posting_date = "2013-01-01"
|
||||
pr.doclist[1].import_rate = 100
|
||||
pr.doclist[1].amount = 100
|
||||
|
||||
webnotes.defaults.set_global_default("auto_inventory_accounting", 0)
|
||||
pr.insert()
|
||||
pr.submit()
|
||||
|
||||
gl_entries = get_gl_entries("Delivery Note", dn.doc.name)
|
||||
self.assertTrue(gl_entries)
|
||||
expected_values = {
|
||||
stock_in_hand_account: [0.0, 666.65],
|
||||
"Cost of Goods Sold - _TC": [666.65, 0.0]
|
||||
}
|
||||
for i, gle in enumerate(gl_entries):
|
||||
self.assertEquals([gle.debit, gle.credit], expected_values.get(gle.account))
|
||||
|
||||
dn.cancel()
|
||||
self.assertFalse(get_gl_entries("Delivery Note", dn.doc.name))
|
||||
set_perpetual_inventory(0)
|
||||
|
||||
def test_delivery_note_gl_entry_packing_item(self):
|
||||
self.clear_stock_account_balance()
|
||||
set_perpetual_inventory()
|
||||
|
||||
self._insert_purchase_receipt()
|
||||
self._insert_purchase_receipt("_Test Item Home Desktop 100")
|
||||
|
||||
dn = webnotes.bean(copy=test_records[0])
|
||||
dn.doclist[1].item_code = "_Test Sales BOM Item"
|
||||
dn.doclist[1].qty = 1
|
||||
|
||||
stock_in_hand_account = webnotes.conn.get_value("Account",
|
||||
{"master_name": dn.doclist[1].warehouse})
|
||||
|
||||
from accounts.utils import get_balance_on
|
||||
prev_bal = get_balance_on(stock_in_hand_account, dn.doc.posting_date)
|
||||
|
||||
dn.insert()
|
||||
dn.submit()
|
||||
|
||||
gl_entries = get_gl_entries("Delivery Note", dn.doc.name)
|
||||
self.assertTrue(gl_entries)
|
||||
|
||||
expected_values = {
|
||||
stock_in_hand_account: [0.0, 525],
|
||||
"Cost of Goods Sold - _TC": [525.0, 0.0]
|
||||
}
|
||||
for i, gle in enumerate(gl_entries):
|
||||
self.assertEquals([gle.debit, gle.credit], expected_values.get(gle.account))
|
||||
|
||||
# check stock in hand balance
|
||||
bal = get_balance_on(stock_in_hand_account, dn.doc.posting_date)
|
||||
self.assertEquals(bal, prev_bal - 525.0)
|
||||
|
||||
dn.cancel()
|
||||
self.assertFalse(get_gl_entries("Delivery Note", dn.doc.name))
|
||||
|
||||
set_perpetual_inventory(0)
|
||||
|
||||
def test_serialized(self):
|
||||
from stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||
@@ -148,7 +209,13 @@ class TestDeliveryNote(unittest.TestCase):
|
||||
dn.insert()
|
||||
|
||||
self.assertRaises(SerialNoStatusError, dn.submit)
|
||||
|
||||
def clear_stock_account_balance(self):
|
||||
webnotes.conn.sql("""delete from `tabBin`""")
|
||||
webnotes.conn.sql("delete from `tabStock Ledger Entry`")
|
||||
webnotes.conn.sql("delete from `tabGL Entry`")
|
||||
|
||||
test_dependencies = ["Sales BOM"]
|
||||
|
||||
test_records = [
|
||||
[
|
||||
@@ -183,8 +250,10 @@ test_records = [
|
||||
"export_rate": 100.0,
|
||||
"amount": 500.0,
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"stock_uom": "_Test UOM"
|
||||
"stock_uom": "_Test UOM",
|
||||
"expense_account": "Cost of Goods Sold - _TC",
|
||||
"cost_center": "Main - _TC"
|
||||
}
|
||||
]
|
||||
|
||||
]
|
||||
]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{
|
||||
"creation": "2013-04-22 13:15:44",
|
||||
"docstatus": 0,
|
||||
"modified": "2013-08-07 14:45:30",
|
||||
"modified": "2013-08-29 16:58:16",
|
||||
"modified_by": "Administrator",
|
||||
"owner": "Administrator"
|
||||
},
|
||||
@@ -420,17 +420,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",
|
||||
|
||||
@@ -67,7 +67,7 @@ class DocType(DocListController):
|
||||
if not self.doc.fields.get("__islocal"):
|
||||
matched=True
|
||||
ref_uom = webnotes.conn.get_value("Stock Ledger Entry",
|
||||
{"item_code": self.doc.name, "is_cancelled": "No"}, "stock_uom")
|
||||
{"item_code": self.doc.name}, "stock_uom")
|
||||
if ref_uom:
|
||||
if cstr(ref_uom) != cstr(self.doc.stock_uom):
|
||||
matched = False
|
||||
@@ -75,8 +75,8 @@ class DocType(DocListController):
|
||||
bin_list = webnotes.conn.sql("select * from tabBin where item_code=%s",
|
||||
self.doc.item_code, as_dict=1)
|
||||
for bin in bin_list:
|
||||
if bin.reserved_qty > 0 or bin.ordered_qty > 0 or bin.indented_qty > 0 \
|
||||
or bin.planned_qty > 0 and cstr(bin.stock_uom) != cstr(self.doc.stock_uom):
|
||||
if (bin.reserved_qty > 0 or bin.ordered_qty > 0 or bin.indented_qty > 0 \
|
||||
or bin.planned_qty > 0) and cstr(bin.stock_uom) != cstr(self.doc.stock_uom):
|
||||
matched = False
|
||||
break
|
||||
|
||||
@@ -199,7 +199,7 @@ class DocType(DocListController):
|
||||
|
||||
def check_if_sle_exists(self):
|
||||
sle = webnotes.conn.sql("""select name from `tabStock Ledger Entry`
|
||||
where item_code = %s and ifnull(is_cancelled, 'No') = 'No'""", self.doc.name)
|
||||
where item_code = %s""", self.doc.name)
|
||||
return sle and 'exists' or 'not exists'
|
||||
|
||||
def validate_name_with_item_group(self):
|
||||
@@ -260,8 +260,6 @@ class DocType(DocListController):
|
||||
|
||||
def on_trash(self):
|
||||
webnotes.conn.sql("""delete from tabBin where item_code=%s""", self.doc.item_code)
|
||||
webnotes.conn.sql("""delete from `tabStock Ledger Entry`
|
||||
where item_code=%s and is_cancelled='Yes' """, self.doc.item_code)
|
||||
|
||||
if self.doc.page_name:
|
||||
from webnotes.webutils import clear_cache
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{
|
||||
"creation": "2013-02-22 01:28:02",
|
||||
"docstatus": 0,
|
||||
"modified": "2013-07-10 14:54:10",
|
||||
"modified": "2013-09-02 17:36:19",
|
||||
"modified_by": "Administrator",
|
||||
"owner": "wasim@webnotestech.com"
|
||||
},
|
||||
@@ -14,7 +14,6 @@
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"in_list_view": 1,
|
||||
"name": "__common__",
|
||||
"parent": "Landed Cost Item",
|
||||
"parentfield": "fields",
|
||||
@@ -30,16 +29,25 @@
|
||||
"doctype": "DocField",
|
||||
"fieldname": "account_head",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Account Head",
|
||||
"oldfieldname": "account_head",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Account",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"label": "Cost Center",
|
||||
"options": "Cost Center"
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Description",
|
||||
"oldfieldname": "description",
|
||||
"oldfieldtype": "Data",
|
||||
@@ -50,6 +58,7 @@
|
||||
"doctype": "DocField",
|
||||
"fieldname": "amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Amount",
|
||||
"oldfieldname": "amount",
|
||||
"oldfieldtype": "Currency",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{
|
||||
"creation": "2013-02-22 01:28:02",
|
||||
"docstatus": 0,
|
||||
"modified": "2013-07-10 14:54:10",
|
||||
"modified": "2013-09-02 13:44:28",
|
||||
"modified_by": "Administrator",
|
||||
"owner": "wasim@webnotestech.com"
|
||||
},
|
||||
@@ -14,36 +14,26 @@
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "purchase_receipt",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Purchase Receipt",
|
||||
"name": "__common__",
|
||||
"oldfieldname": "purchase_receipt_no",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Purchase Receipt",
|
||||
"parent": "Landed Cost Purchase Receipt",
|
||||
"parentfield": "fields",
|
||||
"parenttype": "DocType",
|
||||
"permlevel": 0
|
||||
"permlevel": 0,
|
||||
"print_width": "220px",
|
||||
"width": "220px"
|
||||
},
|
||||
{
|
||||
"doctype": "DocType",
|
||||
"name": "Landed Cost Purchase Receipt"
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "purchase_receipt",
|
||||
"fieldtype": "Link",
|
||||
"label": "Purchase Receipt",
|
||||
"oldfieldname": "purchase_receipt_no",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Purchase Receipt",
|
||||
"print_width": "220px",
|
||||
"width": "220px"
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "select_pr",
|
||||
"fieldtype": "Check",
|
||||
"label": "Select PR",
|
||||
"oldfieldname": "include_in_landed_cost",
|
||||
"oldfieldtype": "Check",
|
||||
"print_width": "120px",
|
||||
"width": "120px"
|
||||
"doctype": "DocField"
|
||||
}
|
||||
]
|
||||
@@ -1,18 +1,48 @@
|
||||
// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
cur_frm.cscript.onload = function(doc, cdt, cdn) {
|
||||
if(!doc.currency){doc.currency = sys_defaults.currency;}
|
||||
}
|
||||
|
||||
wn.provide("erpnext.stock");
|
||||
wn.require("public/app/js/controllers/stock_controller.js");
|
||||
|
||||
cur_frm.fields_dict['landed_cost_details'].grid.get_field("account_head").get_query = function(doc,cdt,cdn) {
|
||||
return{
|
||||
filters:[
|
||||
['Account', 'group_or_ledger', '=', 'Ledger'],
|
||||
['Account', 'account_type', 'in', 'Tax, Chargeable'],
|
||||
['Account', 'is_pl_account', '=', 'Yes'],
|
||||
['Account', 'debit_or_credit', '=', 'Debit']
|
||||
]
|
||||
erpnext.stock.LandedCostWizard = erpnext.stock.StockController.extend({
|
||||
setup: function() {
|
||||
var me = this;
|
||||
this.frm.fields_dict.lc_pr_details.grid.get_field('purchase_receipt').get_query =
|
||||
function() {
|
||||
if(!me.frm.doc.company) msgprint(wn._("Please enter company first"));
|
||||
return {
|
||||
filters:[
|
||||
['Purchase Receipt', 'docstatus', '=', '1'],
|
||||
['Purchase Receipt', 'company', '=', me.frm.doc.company],
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
this.frm.fields_dict.landed_cost_details.grid.get_field('account_head').get_query = function() {
|
||||
if(!me.frm.doc.company) msgprint(wn._("Please enter company first"));
|
||||
return {
|
||||
filters:[
|
||||
['Account', 'group_or_ledger', '=', 'Ledger'],
|
||||
['Account', 'account_type', 'in', 'Tax, Chargeable'],
|
||||
['Account', 'is_pl_account', '=', 'Yes'],
|
||||
['Account', 'debit_or_credit', '=', 'Debit'],
|
||||
['Account', 'company', '=', me.frm.doc.company]
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
this.frm.fields_dict.landed_cost_details.grid.get_field('cost_center').get_query =
|
||||
function() {
|
||||
if(!me.frm.doc.company) msgprint(wn._("Please enter company first"));
|
||||
return {
|
||||
filters:[
|
||||
['Cost Center', 'group_or_ledger', '=', 'Ledger'],
|
||||
['Cost Center', 'company', '=', me.frm.doc.company]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cur_frm.script_manager.make(erpnext.stock.LandedCostWizard);
|
||||
@@ -7,225 +7,95 @@ from webnotes.utils import cint, cstr, flt
|
||||
from webnotes.model.doc import addchild
|
||||
from webnotes.model.bean import getlist
|
||||
from webnotes.model.code import get_obj
|
||||
from webnotes import msgprint
|
||||
|
||||
|
||||
from webnotes import msgprint, _
|
||||
|
||||
class DocType:
|
||||
def __init__(self, doc, doclist=[]):
|
||||
self.doc = doc
|
||||
self.doclist = doclist
|
||||
self.prwise_cost = {}
|
||||
|
||||
def check_mandatory(self):
|
||||
""" Check mandatory fields """
|
||||
if not self.doc.from_pr_date or not self.doc.to_pr_date:
|
||||
msgprint("Please enter From and To PR Date", raise_exception=1)
|
||||
|
||||
if not self.doc.currency:
|
||||
msgprint("Please enter Currency.", raise_exception=1)
|
||||
|
||||
|
||||
def get_purchase_receipts(self):
|
||||
""" Get purchase receipts for given period """
|
||||
|
||||
self.doclist = self.doc.clear_table(self.doclist,'lc_pr_details',1)
|
||||
self.check_mandatory()
|
||||
|
||||
pr = webnotes.conn.sql("select name from `tabPurchase Receipt` where docstatus = 1 and posting_date >= '%s' and posting_date <= '%s' and currency = '%s' order by name " % (self.doc.from_pr_date, self.doc.to_pr_date, self.doc.currency), as_dict = 1)
|
||||
if len(pr)>200:
|
||||
msgprint("Please enter date of shorter duration as there are too many purchase receipt, hence it cannot be loaded.", raise_exception=1)
|
||||
|
||||
for i in pr:
|
||||
ch = addchild(self.doc, 'lc_pr_details', 'Landed Cost Purchase Receipt',
|
||||
self.doclist)
|
||||
ch.purchase_receipt = i and i['name'] or ''
|
||||
ch.save()
|
||||
|
||||
def get_selected_pr(self):
|
||||
""" Get selected purchase receipt no """
|
||||
self.selected_pr = [d.purchase_receipt for d in getlist(self.doclist, 'lc_pr_details') if d.select_pr]
|
||||
if not self.selected_pr:
|
||||
msgprint("Please select atleast one PR to proceed.", raise_exception=1)
|
||||
|
||||
def validate_selected_pr(self):
|
||||
"""Validate selected PR as submitted"""
|
||||
invalid_pr = webnotes.conn.sql("SELECT name FROM `tabPurchase Receipt` WHERE docstatus != 1 and name in (%s)" % ("'" + "', '".join(self.selected_pr) + "'"))
|
||||
if invalid_pr:
|
||||
msgprint("Selected purchase receipts must be submitted. Following PR are not submitted: %s" % invalid_pr, raise_exception=1)
|
||||
def update_landed_cost(self):
|
||||
"""
|
||||
Add extra cost and recalculate all values in pr,
|
||||
Recalculate valuation rate in all sle after pr posting date
|
||||
"""
|
||||
purchase_receipts = [row.purchase_receipt for row in
|
||||
self.doclist.get({"parentfield": "lc_pr_details"})]
|
||||
|
||||
self.validate_purchase_receipts(purchase_receipts)
|
||||
self.cancel_pr(purchase_receipts)
|
||||
self.add_charges_in_pr(purchase_receipts)
|
||||
self.submit_pr(purchase_receipts)
|
||||
msgprint("Landed Cost updated successfully")
|
||||
|
||||
def get_total_amt(self):
|
||||
""" Get sum of net total of all selected PR"""
|
||||
return webnotes.conn.sql("SELECT SUM(net_total) FROM `tabPurchase Receipt` WHERE name in (%s)" % ("'" + "', '".join(self.selected_pr) + "'"))[0][0]
|
||||
|
||||
def validate_purchase_receipts(self, purchase_receipts):
|
||||
for pr in purchase_receipts:
|
||||
if webnotes.conn.get_value("Purchase Receipt", pr, "docstatus") != 1:
|
||||
webnotes.throw(_("Purchase Receipt") + ": " + pr + _(" is not submitted document"))
|
||||
|
||||
def add_charges_in_pr(self):
|
||||
def add_charges_in_pr(self, purchase_receipts):
|
||||
""" Add additional charges in selected pr proportionately"""
|
||||
total_amt = self.get_total_amt()
|
||||
total_amt = self.get_total_pr_amt(purchase_receipts)
|
||||
|
||||
for pr in self.selected_pr:
|
||||
pr_obj = get_obj('Purchase Receipt', pr, with_children = 1)
|
||||
cumulative_grand_total = flt(pr_obj.doc.grand_total)
|
||||
for pr in purchase_receipts:
|
||||
pr_bean = webnotes.bean('Purchase Receipt', pr)
|
||||
idx = max([d.idx for d in pr_bean.doclist.get({"parentfield": "purchase_tax_details"})])
|
||||
|
||||
for lc in getlist(self.doclist, 'landed_cost_details'):
|
||||
amt = flt(lc.amount) * flt(pr_obj.doc.net_total)/ flt(total_amt)
|
||||
self.prwise_cost[pr] = self.prwise_cost.get(pr, 0) + amt
|
||||
cumulative_grand_total += amt
|
||||
for lc in self.doclist.get({"parentfield": "landed_cost_details"}):
|
||||
amt = flt(lc.amount) * flt(pr_bean.doc.net_total)/ flt(total_amt)
|
||||
|
||||
pr_oc_row = webnotes.conn.sql("select name from `tabPurchase Taxes and Charges` where parent = %s and category = 'Valuation' and add_deduct_tax = 'Add' and charge_type = 'Actual' and account_head = %s",(pr, lc.account_head))
|
||||
if not pr_oc_row: # add if not exists
|
||||
ch = addchild(pr_obj.doc, 'purchase_tax_details', 'Purchase Taxes and Charges')
|
||||
matched_row = pr_bean.doclist.get({
|
||||
"parentfield": "purchase_tax_details",
|
||||
"category": "Valuation",
|
||||
"add_deduct_tax": "Add",
|
||||
"charge_type": "Actual",
|
||||
"account_head": lc.account_head
|
||||
})
|
||||
|
||||
if not matched_row: # add if not exists
|
||||
ch = addchild(pr_bean.doc, 'purchase_tax_details', 'Purchase Taxes and Charges')
|
||||
ch.category = 'Valuation'
|
||||
ch.add_deduct_tax = 'Add'
|
||||
ch.charge_type = 'Actual'
|
||||
ch.description = lc.description
|
||||
ch.account_head = lc.account_head
|
||||
ch.cost_center = lc.cost_center
|
||||
ch.rate = amt
|
||||
ch.tax_amount = amt
|
||||
ch.total = cumulative_grand_total
|
||||
ch.docstatus = 1
|
||||
ch.idx = 500 # add at the end
|
||||
ch.idx = idx
|
||||
ch.save(1)
|
||||
idx += 1
|
||||
else: # overwrite if exists
|
||||
webnotes.conn.sql("update `tabPurchase Taxes and Charges` set rate = %s, tax_amount = %s where name = %s and parent = %s ", (amt, amt, pr_oc_row[0][0], pr))
|
||||
|
||||
|
||||
def reset_other_charges(self, pr_obj):
|
||||
""" Reset all calculated values to zero"""
|
||||
for t in getlist(pr_obj.doclist, 'purchase_tax_details'):
|
||||
t.total_tax_amount = 0;
|
||||
t.total_amount = 0;
|
||||
t.tax_amount = 0;
|
||||
t.total = 0;
|
||||
t.save()
|
||||
|
||||
|
||||
def cal_charges_and_item_tax_amt(self):
|
||||
""" Re-calculates other charges values and itemwise tax amount for getting valuation rate"""
|
||||
import json
|
||||
for pr in self.selected_pr:
|
||||
obj = get_obj('Purchase Receipt', pr, with_children = 1)
|
||||
total = 0
|
||||
self.reset_other_charges(obj)
|
||||
|
||||
for prd in getlist(obj.doclist, 'purchase_receipt_details'):
|
||||
prev_total, item_tax = flt(prd.amount), 0
|
||||
total += flt(prd.qty) * flt(prd.purchase_rate)
|
||||
|
||||
try:
|
||||
item_tax_rate = prd.item_tax_rate and json.loads(prd.item_tax_rate) or {}
|
||||
except ValueError:
|
||||
item_tax_rate = prd.item_tax_rate and eval(prd.item_tax_rate) or {}
|
||||
|
||||
|
||||
ocd = getlist(obj.doclist, 'purchase_tax_details')
|
||||
# calculate tax for other charges
|
||||
for oc in range(len(ocd)):
|
||||
# Get rate : consider if diff for this item
|
||||
if item_tax_rate.get(ocd[oc].account_head) and ocd[oc].charge_type != 'Actual':
|
||||
rate = item_tax_rate[ocd[oc].account_head]
|
||||
else:
|
||||
rate = flt(ocd[oc].rate)
|
||||
|
||||
tax_amount = self.cal_tax(ocd, prd, rate, obj.doc.net_total, oc)
|
||||
total, prev_total, item_tax = self.add_deduct_taxes(ocd, oc, tax_amount, total, prev_total, item_tax)
|
||||
|
||||
prd.item_tax_amount = flt(item_tax)
|
||||
prd.save()
|
||||
obj.doc.save()
|
||||
matched_row[0].rate = amt
|
||||
matched_row[0].tax_amount = amt
|
||||
matched_row[0].cost_center = lc.cost_center
|
||||
|
||||
pr_bean.run_method("validate")
|
||||
for d in pr_bean.doclist:
|
||||
d.save()
|
||||
|
||||
|
||||
def cal_tax(self, ocd, prd, rate, net_total, oc):
|
||||
""" Calculates tax amount for one item"""
|
||||
tax_amount = 0
|
||||
if ocd[oc].charge_type == 'Actual':
|
||||
tax_amount = flt(rate) * flt(prd.amount) / flt(net_total)
|
||||
elif ocd[oc].charge_type == 'On Net Total':
|
||||
tax_amount = flt(rate) * flt(prd.amount) / 100
|
||||
elif ocd[oc].charge_type == 'On Previous Row Amount':
|
||||
row_no = cstr(ocd[oc].row_id)
|
||||
row = row_no.split("+")
|
||||
for r in range(0, len(row)):
|
||||
id = cint(row[r])
|
||||
tax_amount += flt((flt(rate) * flt(ocd[id-1].total_amount) / 100))
|
||||
row_id = row_no.find("/")
|
||||
if row_id != -1:
|
||||
rate = ''
|
||||
row = (row_no).split("/")
|
||||
id1 = cint(row[0])
|
||||
id2 = cint(row[1])
|
||||
tax_amount = flt(flt(ocd[id1-1].total_amount) / flt(ocd[id2-1].total_amount))
|
||||
elif ocd[oc].charge_type == 'On Previous Row Total':
|
||||
row = cint(ocd[oc].row_id)
|
||||
if ocd[row-1].add_deduct_tax == 'Add':
|
||||
tax_amount = flt(rate) * (flt(ocd[row-1].total_tax_amount)+flt(ocd[row-1].total_amount)) / 100
|
||||
elif ocd[row-1].add_deduct_tax == 'Deduct':
|
||||
tax_amount = flt(rate) * (flt(ocd[row-1].total_tax_amount)-flt(ocd[row-1].total_amount)) / 100
|
||||
|
||||
return tax_amount
|
||||
|
||||
def add_deduct_taxes(self, ocd, oc, tax_amount, total, prev_total, item_tax):
|
||||
"""Calculates other charges values"""
|
||||
add_ded = ocd[oc].add_deduct_tax == 'Add' and 1 or ocd[oc].add_or_deduct == 'Deduct' and -1
|
||||
ocd[oc].total_amount = flt(tax_amount)
|
||||
ocd[oc].total_tax_amount = flt(prev_total)
|
||||
ocd[oc].tax_amount += flt(tax_amount)
|
||||
|
||||
if ocd[oc].category != "Valuation":
|
||||
prev_total += add_ded * flt(ocd[oc].total_amount)
|
||||
total += add_ded * flt(ocd[oc].tax_amount)
|
||||
ocd[oc].total = total
|
||||
else:
|
||||
prev_total = prev_total
|
||||
ocd[oc].total = flt(total)
|
||||
ocd[oc].save()
|
||||
|
||||
if ocd[oc].category != "Total":
|
||||
item_tax += add_ded * ocd[oc].total_amount
|
||||
|
||||
return total, prev_total, item_tax
|
||||
|
||||
|
||||
def update_sle(self):
|
||||
""" Recalculate valuation rate in all sle after pr posting date"""
|
||||
from stock.stock_ledger import update_entries_after
|
||||
|
||||
for pr in self.selected_pr:
|
||||
pr_obj = get_obj('Purchase Receipt', pr, with_children = 1)
|
||||
def get_total_pr_amt(self, purchase_receipts):
|
||||
return webnotes.conn.sql("""SELECT SUM(net_total) FROM `tabPurchase Receipt`
|
||||
WHERE name in (%s)""" % ', '.join(['%s']*len(purchase_receipts)),
|
||||
tuple(purchase_receipts))[0][0]
|
||||
|
||||
for d in getlist(pr_obj.doclist, 'purchase_receipt_details'):
|
||||
if flt(d.qty):
|
||||
d.valuation_rate = (flt(d.purchase_rate) + (flt(d.rm_supp_cost)/flt(d.qty)) + (flt(d.item_tax_amount)/flt(d.qty))) / flt(d.conversion_factor)
|
||||
d.save()
|
||||
if d.serial_no:
|
||||
self.update_serial_no(d.serial_no, d.valuation_rate)
|
||||
webnotes.conn.sql("update `tabStock Ledger Entry` set incoming_rate = '%s' where voucher_detail_no = '%s'"%(flt(d.valuation_rate), d.name))
|
||||
|
||||
res = webnotes.conn.sql("""select item_code, warehouse, posting_date, posting_time
|
||||
from `tabStock Ledger Entry` where voucher_detail_no = %s LIMIT 1""",
|
||||
d.name, as_dict=1)
|
||||
|
||||
# update valuation rate after pr posting date
|
||||
if res:
|
||||
update_entries_after(res[0])
|
||||
|
||||
|
||||
def update_serial_no(self, sr_no, rate):
|
||||
""" update valuation rate in serial no"""
|
||||
sr_no = map(lambda x: x.strip(), cstr(sr_no).split('\n'))
|
||||
|
||||
webnotes.conn.sql("""update `tabSerial No` set purchase_rate = %s where name in (%s)""" %
|
||||
('%s', ', '.join(['%s']*len(sr_no))), tuple([rate] + sr_no))
|
||||
|
||||
def update_landed_cost(self):
|
||||
"""
|
||||
Add extra cost and recalculate all values in pr,
|
||||
Recalculate valuation rate in all sle after pr posting date
|
||||
"""
|
||||
self.get_selected_pr()
|
||||
self.validate_selected_pr()
|
||||
self.add_charges_in_pr()
|
||||
self.cal_charges_and_item_tax_amt()
|
||||
self.update_sle()
|
||||
msgprint("Landed Cost updated successfully")
|
||||
def cancel_pr(self, purchase_receipts):
|
||||
for pr in purchase_receipts:
|
||||
pr_bean = webnotes.bean("Purchase Receipt", pr)
|
||||
|
||||
pr_bean.run_method("update_ordered_qty", is_cancelled="Yes")
|
||||
pr_bean.run_method("update_serial_nos", cancel=True)
|
||||
|
||||
webnotes.conn.sql("""delete from `tabStock Ledger Entry`
|
||||
where voucher_type='Purchase Receipt' and voucher_no=%s""", pr)
|
||||
webnotes.conn.sql("""delete from `tabGL Entry` where voucher_type='Purchase Receipt'
|
||||
and voucher_no=%s""", pr)
|
||||
|
||||
def submit_pr(self, purchase_receipts):
|
||||
for pr in purchase_receipts:
|
||||
pr_bean = webnotes.bean("Purchase Receipt", pr)
|
||||
pr_bean.run_method("update_ordered_qty")
|
||||
pr_bean.run_method("update_stock")
|
||||
pr_bean.run_method("update_serial_nos")
|
||||
pr_bean.run_method("make_gl_entries")
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{
|
||||
"creation": "2013-01-22 16:50:39",
|
||||
"docstatus": 0,
|
||||
"modified": "2013-07-22 15:31:20",
|
||||
"modified": "2013-09-02 19:13:09",
|
||||
"modified_by": "Administrator",
|
||||
"owner": "wasim@webnotestech.com"
|
||||
},
|
||||
@@ -40,46 +40,19 @@
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "process",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Process",
|
||||
"options": "<div class=\"field_description\"><b>Process:</b><br>1. Fetch and select Purchase Receipt<br>2. Enter extra costs<br>3. Click on Update Landed Cost button<br> 4. Cost will be added into other charges table of selected PR proportionately based on net total<br>5. Item Valuation Rate will be recalculated</div>"
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "section_break0",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Select Purchase Receipts",
|
||||
"options": "Simple"
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "from_pr_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "From PR Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "to_pr_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "To PR Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Currency",
|
||||
"options": "Currency",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "get_purchase_receipt",
|
||||
"fieldtype": "Button",
|
||||
"label": "Get Purchase Receipt",
|
||||
"options": "get_purchase_receipts"
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "lc_pr_details",
|
||||
@@ -91,7 +64,7 @@
|
||||
"doctype": "DocField",
|
||||
"fieldname": "section_break1",
|
||||
"fieldtype": "Section Break",
|
||||
"options": "Simple"
|
||||
"label": "Add Taxes and Charges"
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
@@ -102,9 +75,9 @@
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "update_pr",
|
||||
"fieldname": "update_landed_cost",
|
||||
"fieldtype": "Button",
|
||||
"label": "Update PR",
|
||||
"label": "Update Landed Cost",
|
||||
"options": "update_landed_cost"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -87,6 +87,8 @@ class DocType(BuyingController):
|
||||
|
||||
def update_bin(self, is_submit, is_stopped):
|
||||
""" Update Quantity Requested for Purchase in Bin for Material Request of type 'Purchase'"""
|
||||
|
||||
from stock.utils import update_bin
|
||||
for d in getlist(self.doclist, 'indent_details'):
|
||||
if webnotes.conn.get_value("Item", d.item_code, "is_stock_item") == "Yes":
|
||||
if not d.warehouse:
|
||||
@@ -99,10 +101,11 @@ class DocType(BuyingController):
|
||||
|
||||
args = {
|
||||
"item_code": d.item_code,
|
||||
"warehouse": d.warehouse,
|
||||
"indented_qty": (is_submit and 1 or -1) * flt(qty),
|
||||
"posting_date": self.doc.transaction_date
|
||||
}
|
||||
get_obj('Warehouse', d.warehouse).update_bin(args)
|
||||
update_bin(args)
|
||||
|
||||
def on_submit(self):
|
||||
purchase_controller = webnotes.get_obj("Purchase Common")
|
||||
@@ -200,6 +203,7 @@ def update_completed_qty(controller, caller_method):
|
||||
|
||||
def _update_requested_qty(controller, mr_obj, mr_items):
|
||||
"""update requested qty (before ordered_qty is updated)"""
|
||||
from stock.utils import update_bin
|
||||
for mr_item_name in mr_items:
|
||||
mr_item = mr_obj.doclist.getone({"parentfield": "indent_details", "name": mr_item_name})
|
||||
se_detail = controller.doclist.getone({"parentfield": "mtn_details",
|
||||
@@ -218,8 +222,9 @@ def _update_requested_qty(controller, mr_obj, mr_items):
|
||||
else:
|
||||
add_indented_qty = se_detail.transfer_qty
|
||||
|
||||
webnotes.get_obj("Warehouse", se_detail.t_warehouse).update_bin({
|
||||
update_bin({
|
||||
"item_code": se_detail.item_code,
|
||||
"warehouse": se_detail.t_warehouse,
|
||||
"indented_qty": (se_detail.docstatus==2 and 1 or -1) * add_indented_qty,
|
||||
"posting_date": controller.doc.posting_date,
|
||||
})
|
||||
|
||||
@@ -10,7 +10,7 @@ from webnotes.utils import flt
|
||||
|
||||
class TestMaterialRequest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
webnotes.defaults.set_global_default("auto_inventory_accounting", 0)
|
||||
webnotes.defaults.set_global_default("auto_accounting_for_stock", 0)
|
||||
|
||||
def test_make_purchase_order(self):
|
||||
from stock.doctype.material_request.material_request import make_purchase_order
|
||||
|
||||
@@ -9,7 +9,7 @@ from webnotes.model.bean import getlist
|
||||
from webnotes.model.code import get_obj
|
||||
from webnotes import msgprint
|
||||
import webnotes.defaults
|
||||
|
||||
from stock.utils import update_bin
|
||||
|
||||
from controllers.buying_controller import BuyingController
|
||||
class DocType(BuyingController):
|
||||
@@ -42,16 +42,46 @@ class DocType(BuyingController):
|
||||
def get_bin_details(self, arg = ''):
|
||||
return get_obj(dt='Purchase Common').get_bin_details(arg)
|
||||
|
||||
def validate(self):
|
||||
super(DocType, self).validate()
|
||||
|
||||
self.po_required()
|
||||
|
||||
if not self.doc.status:
|
||||
self.doc.status = "Draft"
|
||||
|
||||
import utilities
|
||||
utilities.validate_status(self.doc.status, ["Draft", "Submitted", "Cancelled"])
|
||||
|
||||
self.validate_with_previous_doc()
|
||||
self.validate_rejected_warehouse()
|
||||
self.validate_accepted_rejected_qty()
|
||||
self.validate_inspection()
|
||||
self.validate_uom_is_integer("uom", ["qty", "received_qty"])
|
||||
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
||||
self.validate_challan_no()
|
||||
|
||||
pc_obj = get_obj(dt='Purchase Common')
|
||||
pc_obj.validate_for_items(self)
|
||||
pc_obj.get_prevdoc_date(self)
|
||||
self.check_for_stopped_status(pc_obj)
|
||||
|
||||
# sub-contracting
|
||||
self.validate_for_subcontracting()
|
||||
self.update_raw_materials_supplied("pr_raw_material_details")
|
||||
|
||||
self.update_valuation_rate("purchase_receipt_details")
|
||||
|
||||
def validate_rejected_warehouse(self):
|
||||
for d in self.doclist.get({"parentfield": "purchase_receipt_details"}):
|
||||
if flt(d.rejected_qty) and not d.rejected_warehouse:
|
||||
d.rejected_warehouse = self.doc.rejected_warehouse
|
||||
if not d.rejected_warehouse:
|
||||
webnotes.throw(_("Rejected Warehouse is mandatory against regected item"))
|
||||
|
||||
# validate accepted and rejected qty
|
||||
def validate_accepted_rejected_qty(self):
|
||||
for d in getlist(self.doclist, "purchase_receipt_details"):
|
||||
|
||||
# If Reject Qty than Rejected warehouse is mandatory
|
||||
if flt(d.rejected_qty) and (not self.doc.rejected_warehouse):
|
||||
msgprint("Rejected Warehouse is necessary if there are rejections.")
|
||||
raise Exception
|
||||
|
||||
if not flt(d.received_qty) and flt(d.qty):
|
||||
d.received_qty = flt(d.qty) - flt(d.rejected_qty)
|
||||
|
||||
@@ -110,109 +140,70 @@ class DocType(BuyingController):
|
||||
msgprint("Purchse Order No. required against item %s"%d.item_code)
|
||||
raise Exception
|
||||
|
||||
def validate(self):
|
||||
super(DocType, self).validate()
|
||||
def update_stock(self):
|
||||
sl_entries = []
|
||||
stock_items = self.get_stock_items()
|
||||
|
||||
self.po_required()
|
||||
|
||||
if not self.doc.status:
|
||||
self.doc.status = "Draft"
|
||||
|
||||
import utilities
|
||||
utilities.validate_status(self.doc.status, ["Draft", "Submitted", "Cancelled"])
|
||||
|
||||
self.validate_with_previous_doc()
|
||||
self.validate_accepted_rejected_qty()
|
||||
self.validate_inspection()
|
||||
self.validate_uom_is_integer("uom", ["qty", "received_qty"])
|
||||
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
||||
self.validate_challan_no()
|
||||
|
||||
pc_obj = get_obj(dt='Purchase Common')
|
||||
pc_obj.validate_for_items(self)
|
||||
pc_obj.get_prevdoc_date(self)
|
||||
self.check_for_stopped_status(pc_obj)
|
||||
|
||||
# sub-contracting
|
||||
self.validate_for_subcontracting()
|
||||
self.update_raw_materials_supplied("pr_raw_material_details")
|
||||
|
||||
self.update_valuation_rate("purchase_receipt_details")
|
||||
|
||||
def on_update(self):
|
||||
if self.doc.rejected_warehouse:
|
||||
for d in getlist(self.doclist,'purchase_receipt_details'):
|
||||
d.rejected_warehouse = self.doc.rejected_warehouse
|
||||
|
||||
def update_stock(self, is_submit):
|
||||
pc_obj = get_obj('Purchase Common')
|
||||
self.values = []
|
||||
for d in getlist(self.doclist, 'purchase_receipt_details'):
|
||||
if webnotes.conn.get_value("Item", d.item_code, "is_stock_item") == "Yes":
|
||||
if not d.warehouse:
|
||||
continue
|
||||
if d.item_code in stock_items and d.warehouse:
|
||||
pr_qty = flt(d.qty) * flt(d.conversion_factor)
|
||||
|
||||
ord_qty = 0
|
||||
if pr_qty:
|
||||
sl_entries.append(self.get_sl_entries(d, {
|
||||
"actual_qty": flt(pr_qty),
|
||||
"serial_no": cstr(d.serial_no).strip(),
|
||||
"incoming_rate": d.valuation_rate
|
||||
}))
|
||||
|
||||
if flt(d.rejected_qty) > 0:
|
||||
sl_entries.append(self.get_sl_entries(d, {
|
||||
"warehouse": d.rejected_warehouse,
|
||||
"actual_qty": flt(d.rejected_qty) * flt(d.conversion_factor),
|
||||
"serial_no": cstr(d.rejected_serial_no).strip(),
|
||||
"incoming_rate": d.valuation_rate
|
||||
}))
|
||||
|
||||
self.bk_flush_supp_wh(sl_entries)
|
||||
self.make_sl_entries(sl_entries)
|
||||
|
||||
def update_ordered_qty(self, is_cancelled="No"):
|
||||
pc_obj = get_obj('Purchase Common')
|
||||
stock_items = self.get_stock_items()
|
||||
for d in getlist(self.doclist, 'purchase_receipt_details'):
|
||||
if d.item_code in stock_items and d.warehouse \
|
||||
and cstr(d.prevdoc_doctype) == 'Purchase Order':
|
||||
pr_qty = flt(d.qty) * flt(d.conversion_factor)
|
||||
|
||||
if cstr(d.prevdoc_doctype) == 'Purchase Order':
|
||||
# get qty and pending_qty of prevdoc
|
||||
curr_ref_qty = pc_obj.get_qty( d.doctype, 'prevdoc_detail_docname',
|
||||
d.prevdoc_detail_docname, 'Purchase Order Item',
|
||||
'Purchase Order - Purchase Receipt', self.doc.name)
|
||||
max_qty, qty, curr_qty = flt(curr_ref_qty.split('~~~')[1]), \
|
||||
flt(curr_ref_qty.split('~~~')[0]), 0
|
||||
|
||||
if flt(qty) + flt(pr_qty) > flt(max_qty):
|
||||
curr_qty = (flt(max_qty) - flt(qty)) * flt(d.conversion_factor)
|
||||
else:
|
||||
curr_qty = flt(pr_qty)
|
||||
|
||||
ord_qty = -flt(curr_qty)
|
||||
|
||||
# update ordered qty in bin
|
||||
args = {
|
||||
"item_code": d.item_code,
|
||||
"posting_date": self.doc.posting_date,
|
||||
"ordered_qty": (is_submit and 1 or -1) * flt(ord_qty)
|
||||
}
|
||||
get_obj("Warehouse", d.warehouse).update_bin(args)
|
||||
|
||||
# UPDATE actual qty to warehouse by pr_qty
|
||||
if pr_qty:
|
||||
self.make_sl_entry(d, d.warehouse, flt(pr_qty), d.valuation_rate, is_submit)
|
||||
|
||||
# UPDATE actual to rejected warehouse by rejected qty
|
||||
if flt(d.rejected_qty) > 0:
|
||||
self.make_sl_entry(d, self.doc.rejected_warehouse, flt(d.rejected_qty) * flt(d.conversion_factor), d.valuation_rate, is_submit, rejected = 1)
|
||||
|
||||
self.bk_flush_supp_wh(is_submit)
|
||||
|
||||
if self.values:
|
||||
get_obj('Stock Ledger', 'Stock Ledger').update_stock(self.values)
|
||||
|
||||
|
||||
# make Stock Entry
|
||||
def make_sl_entry(self, d, wh, qty, in_value, is_submit, rejected = 0):
|
||||
self.values.append({
|
||||
'item_code' : d.fields.has_key('item_code') and d.item_code or d.rm_item_code,
|
||||
'warehouse' : wh,
|
||||
'posting_date' : self.doc.posting_date,
|
||||
'posting_time' : self.doc.posting_time,
|
||||
'voucher_type' : 'Purchase Receipt',
|
||||
'voucher_no' : self.doc.name,
|
||||
'voucher_detail_no' : d.name,
|
||||
'actual_qty' : qty,
|
||||
'stock_uom' : d.stock_uom,
|
||||
'incoming_rate' : in_value,
|
||||
'company' : self.doc.company,
|
||||
'fiscal_year' : self.doc.fiscal_year,
|
||||
'is_cancelled' : (is_submit==1) and 'No' or 'Yes',
|
||||
'batch_no' : cstr(d.batch_no).strip(),
|
||||
'serial_no' : d.serial_no,
|
||||
"project" : d.project_name
|
||||
})
|
||||
# get qty and pending_qty of prevdoc
|
||||
curr_ref_qty = pc_obj.get_qty(d.doctype, 'prevdoc_detail_docname',
|
||||
d.prevdoc_detail_docname, 'Purchase Order Item',
|
||||
'Purchase Order - Purchase Receipt', self.doc.name)
|
||||
max_qty, qty, curr_qty = flt(curr_ref_qty.split('~~~')[1]), \
|
||||
flt(curr_ref_qty.split('~~~')[0]), 0
|
||||
|
||||
if flt(qty) + flt(pr_qty) > flt(max_qty):
|
||||
curr_qty = (flt(max_qty) - flt(qty)) * flt(d.conversion_factor)
|
||||
else:
|
||||
curr_qty = flt(pr_qty)
|
||||
|
||||
args = {
|
||||
"item_code": d.item_code,
|
||||
"warehouse": d.warehouse,
|
||||
"posting_date": self.doc.posting_date,
|
||||
"ordered_qty": (is_cancelled=="Yes" and -1 or 1)*flt(curr_qty)
|
||||
}
|
||||
update_bin(args)
|
||||
|
||||
def bk_flush_supp_wh(self, sl_entries):
|
||||
for d in getlist(self.doclist, 'pr_raw_material_details'):
|
||||
# negative quantity is passed as raw material qty has to be decreased
|
||||
# when PR is submitted and it has to be increased when PR is cancelled
|
||||
sl_entries.append(self.get_sl_entries(d, {
|
||||
"item_code": d.rm_item_code,
|
||||
"warehouse": self.doc.supplier_warehouse,
|
||||
"actual_qty": -1*flt(consumed_qty),
|
||||
"incoming_rate": 0
|
||||
}))
|
||||
|
||||
def validate_inspection(self):
|
||||
for d in getlist(self.doclist, 'purchase_receipt_details'): #Enter inspection date for all items that require inspection
|
||||
@@ -243,12 +234,12 @@ class DocType(BuyingController):
|
||||
|
||||
self.update_prevdoc_status()
|
||||
|
||||
# Update Stock
|
||||
self.update_stock(is_submit = 1)
|
||||
self.update_ordered_qty()
|
||||
|
||||
self.update_stock()
|
||||
|
||||
self.update_serial_nos()
|
||||
|
||||
# Update last purchase rate
|
||||
purchase_controller.update_last_purchase_rate(self, 1)
|
||||
|
||||
self.make_gl_entries()
|
||||
@@ -279,7 +270,7 @@ class DocType(BuyingController):
|
||||
pc_obj = get_obj('Purchase Common')
|
||||
|
||||
self.check_for_stopped_status(pc_obj)
|
||||
# 1.Check if Purchase Invoice has been submitted against current Purchase Order
|
||||
# Check if Purchase Invoice has been submitted against current Purchase Order
|
||||
# pc_obj.check_docstatus(check = 'Next', doctype = 'Purchase Invoice', docname = self.doc.name, detail_doctype = 'Purchase Invoice Item')
|
||||
|
||||
submitted = webnotes.conn.sql("select t1.name from `tabPurchase Invoice` t1,`tabPurchase Invoice Item` t2 where t1.name = t2.parent and t2.purchase_receipt = '%s' and t1.docstatus = 1" % self.doc.name)
|
||||
@@ -287,29 +278,19 @@ class DocType(BuyingController):
|
||||
msgprint("Purchase Invoice : " + cstr(submitted[0][0]) + " has already been submitted !")
|
||||
raise Exception
|
||||
|
||||
# 2.Set Status as Cancelled
|
||||
|
||||
webnotes.conn.set(self.doc,'status','Cancelled')
|
||||
|
||||
# 3. Cancel Serial No
|
||||
|
||||
# 4.Update Bin
|
||||
self.update_stock(is_submit = 0)
|
||||
self.update_ordered_qty(is_cancelled="Yes")
|
||||
|
||||
self.update_stock()
|
||||
self.update_serial_nos(cancel=True)
|
||||
|
||||
self.update_prevdoc_status()
|
||||
|
||||
# 6. Update last purchase rate
|
||||
pc_obj.update_last_purchase_rate(self, 0)
|
||||
|
||||
self.make_cancel_gl_entries()
|
||||
|
||||
def bk_flush_supp_wh(self, is_submit):
|
||||
for d in getlist(self.doclist, 'pr_raw_material_details'):
|
||||
# negative quantity is passed as raw material qty has to be decreased
|
||||
# when PR is submitted and it has to be increased when PR is cancelled
|
||||
consumed_qty = - flt(d.consumed_qty)
|
||||
self.make_sl_entry(d, self.doc.supplier_warehouse, flt(consumed_qty), 0, is_submit)
|
||||
|
||||
|
||||
def get_current_stock(self):
|
||||
for d in getlist(self.doclist, 'pr_raw_material_details'):
|
||||
if self.doc.supplier_warehouse:
|
||||
@@ -319,29 +300,13 @@ class DocType(BuyingController):
|
||||
|
||||
def get_rate(self,arg):
|
||||
return get_obj('Purchase Common').get_rate(arg,self)
|
||||
|
||||
def make_gl_entries(self):
|
||||
if not cint(webnotes.defaults.get_global_default("auto_inventory_accounting")):
|
||||
return
|
||||
|
||||
from accounts.general_ledger import make_gl_entries
|
||||
|
||||
|
||||
def get_gl_entries_for_stock(self, warehouse_account=None):
|
||||
against_stock_account = self.get_company_default("stock_received_but_not_billed")
|
||||
total_valuation_amount = self.get_total_valuation_amount()
|
||||
gl_entries = self.get_gl_entries_for_stock(against_stock_account, total_valuation_amount)
|
||||
|
||||
if gl_entries:
|
||||
make_gl_entries(gl_entries, cancel=(self.doc.docstatus == 2))
|
||||
|
||||
def get_total_valuation_amount(self):
|
||||
total_valuation_amount = 0.0
|
||||
|
||||
for item in self.doclist.get({"parentfield": "purchase_receipt_details"}):
|
||||
if item.item_code in self.stock_items:
|
||||
total_valuation_amount += flt(item.valuation_rate) * \
|
||||
flt(item.qty) * flt(item.conversion_factor)
|
||||
|
||||
return total_valuation_amount
|
||||
gl_entries = super(DocType, self).get_gl_entries_for_stock(warehouse_account,
|
||||
against_stock_account)
|
||||
return gl_entries
|
||||
|
||||
|
||||
@webnotes.whitelist()
|
||||
|
||||
@@ -10,6 +10,8 @@ from webnotes.utils import cint
|
||||
|
||||
class TestPurchaseReceipt(unittest.TestCase):
|
||||
def test_make_purchase_invoice(self):
|
||||
self._clear_stock_account_balance()
|
||||
set_perpetual_inventory(0)
|
||||
from stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice
|
||||
|
||||
pr = webnotes.bean(copy=test_records[0]).insert()
|
||||
@@ -29,45 +31,63 @@ class TestPurchaseReceipt(unittest.TestCase):
|
||||
self.assertRaises(webnotes.ValidationError, webnotes.bean(pi).submit)
|
||||
|
||||
def test_purchase_receipt_no_gl_entry(self):
|
||||
self._clear_stock_account_balance()
|
||||
set_perpetual_inventory(0)
|
||||
pr = webnotes.bean(copy=test_records[0])
|
||||
pr.run_method("calculate_taxes_and_totals")
|
||||
pr.insert()
|
||||
pr.submit()
|
||||
|
||||
gl_entries = webnotes.conn.sql("""select account, debit, credit
|
||||
from `tabGL Entry` where voucher_type='Purchase Receipt' and voucher_no=%s
|
||||
order by account desc""", pr.doc.name, as_dict=1)
|
||||
|
||||
self.assertTrue(not gl_entries)
|
||||
stock_value, stock_value_difference = webnotes.conn.get_value("Stock Ledger Entry",
|
||||
{"voucher_type": "Purchase Receipt", "voucher_no": pr.doc.name,
|
||||
"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"},
|
||||
["stock_value", "stock_value_difference"])
|
||||
self.assertEqual(stock_value, 375)
|
||||
self.assertEqual(stock_value_difference, 375)
|
||||
|
||||
bin_stock_value = webnotes.conn.get_value("Bin", {"item_code": "_Test Item",
|
||||
"warehouse": "_Test Warehouse - _TC"}, "stock_value")
|
||||
self.assertEqual(bin_stock_value, 375)
|
||||
|
||||
self.assertFalse(get_gl_entries("Purchase Receipt", pr.doc.name))
|
||||
|
||||
def test_purchase_receipt_gl_entry(self):
|
||||
webnotes.defaults.set_global_default("auto_inventory_accounting", 1)
|
||||
self.assertEqual(cint(webnotes.defaults.get_global_default("auto_inventory_accounting")), 1)
|
||||
self._clear_stock_account_balance()
|
||||
|
||||
set_perpetual_inventory()
|
||||
self.assertEqual(cint(webnotes.defaults.get_global_default("auto_accounting_for_stock")), 1)
|
||||
|
||||
pr = webnotes.bean(copy=test_records[0])
|
||||
pr.run_method("calculate_taxes_and_totals")
|
||||
pr.insert()
|
||||
pr.submit()
|
||||
|
||||
gl_entries = webnotes.conn.sql("""select account, debit, credit
|
||||
from `tabGL Entry` where voucher_type='Purchase Receipt' and voucher_no=%s
|
||||
order by account desc""", pr.doc.name, as_dict=1)
|
||||
gl_entries = get_gl_entries("Purchase Receipt", pr.doc.name)
|
||||
|
||||
self.assertTrue(gl_entries)
|
||||
|
||||
stock_in_hand_account = webnotes.conn.get_value("Company", pr.doc.company,
|
||||
"stock_in_hand_account")
|
||||
stock_in_hand_account = webnotes.conn.get_value("Account",
|
||||
{"master_name": pr.doclist[1].warehouse})
|
||||
fixed_asset_account = webnotes.conn.get_value("Account",
|
||||
{"master_name": pr.doclist[2].warehouse})
|
||||
|
||||
expected_values = [
|
||||
[stock_in_hand_account, 750.0, 0.0],
|
||||
["Stock Received But Not Billed - _TC", 0.0, 750.0]
|
||||
]
|
||||
expected_values = {
|
||||
stock_in_hand_account: [375.0, 0.0],
|
||||
fixed_asset_account: [375.0, 0.0],
|
||||
"Stock Received But Not Billed - _TC": [0.0, 750.0]
|
||||
}
|
||||
|
||||
for i, gle in enumerate(gl_entries):
|
||||
self.assertEquals(expected_values[i][0], gle.account)
|
||||
self.assertEquals(expected_values[i][1], gle.debit)
|
||||
self.assertEquals(expected_values[i][2], gle.credit)
|
||||
for gle in gl_entries:
|
||||
self.assertEquals(expected_values[gle.account][0], gle.debit)
|
||||
self.assertEquals(expected_values[gle.account][1], gle.credit)
|
||||
|
||||
webnotes.defaults.set_global_default("auto_inventory_accounting", 0)
|
||||
pr.cancel()
|
||||
self.assertFalse(get_gl_entries("Purchase Receipt", pr.doc.name))
|
||||
|
||||
set_perpetual_inventory(0)
|
||||
|
||||
def _clear_stock_account_balance(self):
|
||||
webnotes.conn.sql("delete from `tabStock Ledger Entry`")
|
||||
webnotes.conn.sql("""delete from `tabBin`""")
|
||||
webnotes.conn.sql("""delete from `tabGL Entry`""")
|
||||
|
||||
def test_subcontracting(self):
|
||||
pr = webnotes.bean(copy=test_records[1])
|
||||
@@ -95,7 +115,16 @@ class TestPurchaseReceipt(unittest.TestCase):
|
||||
|
||||
self.assertFalse(webnotes.conn.get_value("Serial No", pr.doclist[1].serial_no,
|
||||
"warehouse"))
|
||||
|
||||
def get_gl_entries(voucher_type, voucher_no):
|
||||
return webnotes.conn.sql("""select account, debit, credit
|
||||
from `tabGL Entry` where voucher_type=%s and voucher_no=%s
|
||||
order by account desc""", (voucher_type, voucher_no), as_dict=1)
|
||||
|
||||
def set_perpetual_inventory(enable=1):
|
||||
accounts_settings = webnotes.bean("Accounts Settings")
|
||||
accounts_settings.doc.auto_accounting_for_stock = enable
|
||||
accounts_settings.save()
|
||||
|
||||
|
||||
test_dependencies = ["BOM"]
|
||||
@@ -122,15 +151,31 @@ test_records = [
|
||||
"item_code": "_Test Item",
|
||||
"item_name": "_Test Item",
|
||||
"parentfield": "purchase_receipt_details",
|
||||
"received_qty": 10.0,
|
||||
"qty": 10.0,
|
||||
"received_qty": 5.0,
|
||||
"qty": 5.0,
|
||||
"rejected_qty": 0.0,
|
||||
"import_rate": 50.0,
|
||||
"amount": 500.0,
|
||||
"amount": 250.0,
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"stock_uom": "Nos",
|
||||
"uom": "_Test UOM",
|
||||
},
|
||||
{
|
||||
"conversion_factor": 1.0,
|
||||
"description": "_Test Item",
|
||||
"doctype": "Purchase Receipt Item",
|
||||
"item_code": "_Test Item",
|
||||
"item_name": "_Test Item",
|
||||
"parentfield": "purchase_receipt_details",
|
||||
"received_qty": 5.0,
|
||||
"qty": 5.0,
|
||||
"rejected_qty": 0.0,
|
||||
"import_rate": 50.0,
|
||||
"amount": 250.0,
|
||||
"warehouse": "_Test Warehouse 1 - _TC",
|
||||
"stock_uom": "Nos",
|
||||
"uom": "_Test UOM",
|
||||
},
|
||||
{
|
||||
"account_head": "_Test Account Shipping Charges - _TC",
|
||||
"add_deduct_tax": "Add",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{
|
||||
"creation": "2013-05-24 19:29:10",
|
||||
"docstatus": 0,
|
||||
"modified": "2013-08-07 14:45:23",
|
||||
"modified": "2013-09-20 11:36:55",
|
||||
"modified_by": "Administrator",
|
||||
"owner": "Administrator"
|
||||
},
|
||||
@@ -310,7 +310,7 @@
|
||||
"doctype": "DocField",
|
||||
"fieldname": "rejected_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"hidden": 0,
|
||||
"label": "Rejected Warehouse",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "rejected_warehouse",
|
||||
@@ -318,7 +318,7 @@
|
||||
"options": "Warehouse",
|
||||
"print_hide": 1,
|
||||
"print_width": "100px",
|
||||
"read_only": 1,
|
||||
"read_only": 0,
|
||||
"width": "100px"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{
|
||||
"creation": "2013-05-16 10:59:15",
|
||||
"docstatus": 0,
|
||||
"modified": "2013-08-21 13:37:01",
|
||||
"modified": "2013-08-28 19:13:09",
|
||||
"modified_by": "Administrator",
|
||||
"owner": "Administrator"
|
||||
},
|
||||
@@ -35,7 +35,8 @@
|
||||
"parenttype": "DocType",
|
||||
"permlevel": 0,
|
||||
"read": 1,
|
||||
"write": 1
|
||||
"report": 1,
|
||||
"submit": 0
|
||||
},
|
||||
{
|
||||
"doctype": "DocType",
|
||||
@@ -449,22 +450,23 @@
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"doctype": "DocPerm",
|
||||
"report": 1,
|
||||
"role": "Material Manager",
|
||||
"submit": 0
|
||||
"role": "Material Master Manager",
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"doctype": "DocPerm",
|
||||
"report": 1,
|
||||
"role": "Material User",
|
||||
"submit": 0
|
||||
},
|
||||
{
|
||||
"create": 0,
|
||||
"doctype": "DocPerm",
|
||||
"role": "Accounts User"
|
||||
"role": "Material Manager",
|
||||
"write": 0
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"doctype": "DocPerm",
|
||||
"role": "Material User",
|
||||
"write": 0
|
||||
}
|
||||
]
|
||||
@@ -24,6 +24,6 @@ class TestSerialNo(unittest.TestCase):
|
||||
sr.doc.warehouse = None
|
||||
sr.insert()
|
||||
self.assertTrue(sr.doc.name)
|
||||
|
||||
|
||||
sr.doc.warehouse = "_Test Warehouse - _TC"
|
||||
self.assertTrue(SerialNoCannotCannotChangeError, sr.doc.save)
|
||||
@@ -38,10 +38,10 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
|
||||
}
|
||||
};
|
||||
|
||||
if(cint(wn.defaults.get_default("auto_inventory_accounting"))) {
|
||||
this.frm.add_fetch("company", "stock_adjustment_account", "expense_adjustment_account");
|
||||
|
||||
this.frm.fields_dict["expense_adjustment_account"].get_query = function() {
|
||||
if(cint(wn.defaults.get_default("auto_accounting_for_stock"))) {
|
||||
this.frm.add_fetch("company", "stock_adjustment_account", "expense_account");
|
||||
this.frm.fields_dict.mtn_details.grid.get_field('expense_account').get_query =
|
||||
function() {
|
||||
return {
|
||||
filters: {
|
||||
"company": me.frm.doc.company,
|
||||
@@ -88,7 +88,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
|
||||
set_default_account: function() {
|
||||
var me = this;
|
||||
|
||||
if (cint(wn.defaults.get_default("auto_inventory_accounting")) && !this.frm.doc.expense_adjustment_account) {
|
||||
if(cint(wn.defaults.get_default("auto_accounting_for_stock"))) {
|
||||
var account_for = "stock_adjustment_account";
|
||||
if (this.frm.doc.purpose == "Sales Return")
|
||||
account_for = "stock_in_hand_account";
|
||||
@@ -102,12 +102,22 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
|
||||
"company": this.frm.doc.company
|
||||
},
|
||||
callback: function(r) {
|
||||
if (!r.exc) me.frm.set_value("expense_adjustment_account", r.message);
|
||||
if (!r.exc) {
|
||||
for(d in getchildren('Stock Entry Detail',doc.name,'mtn_details')) {
|
||||
if(!d.expense_account) d.expense_account = r.message;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
entries_add: function(doc, cdt, cdn) {
|
||||
var row = wn.model.get_doc(cdt, cdn);
|
||||
this.frm.script_manager.copy_from_first_row("mtn_details", row,
|
||||
["expense_account", "cost_center"]);
|
||||
},
|
||||
|
||||
clean_up: function() {
|
||||
// Clear Production Order record from locals, because it is updated via Stock Entry
|
||||
if(this.frm.doc.production_order &&
|
||||
|
||||
@@ -38,7 +38,6 @@ class DocType(StockController):
|
||||
self.validate_item()
|
||||
self.validate_uom_is_integer("uom", "qty")
|
||||
self.validate_uom_is_integer("stock_uom", "transfer_qty")
|
||||
|
||||
self.validate_warehouse(pro_obj)
|
||||
self.validate_production_order(pro_obj)
|
||||
self.get_stock_and_rate()
|
||||
@@ -51,13 +50,13 @@ class DocType(StockController):
|
||||
self.set_total_amount()
|
||||
|
||||
def on_submit(self):
|
||||
self.update_stock_ledger(0)
|
||||
self.update_stock_ledger()
|
||||
self.update_serial_no(1)
|
||||
self.update_production_order(1)
|
||||
self.make_gl_entries()
|
||||
|
||||
def on_cancel(self):
|
||||
self.update_stock_ledger(1)
|
||||
self.update_stock_ledger()
|
||||
self.update_serial_no(0)
|
||||
self.update_production_order(0)
|
||||
self.make_cancel_gl_entries()
|
||||
@@ -75,8 +74,9 @@ class DocType(StockController):
|
||||
raise_exception=True)
|
||||
|
||||
def validate_item(self):
|
||||
stock_items = self.get_stock_items()
|
||||
for item in self.doclist.get({"parentfield": "mtn_details"}):
|
||||
if item.item_code not in self.stock_items:
|
||||
if item.item_code not in stock_items:
|
||||
msgprint(_("""Only Stock Items are allowed for Stock Entry"""),
|
||||
raise_exception=True)
|
||||
|
||||
@@ -176,33 +176,6 @@ class DocType(StockController):
|
||||
def set_total_amount(self):
|
||||
self.doc.total_amount = sum([flt(item.amount) for item in self.doclist.get({"parentfield": "mtn_details"})])
|
||||
|
||||
def make_gl_entries(self):
|
||||
if not cint(webnotes.defaults.get_global_default("auto_inventory_accounting")):
|
||||
return
|
||||
|
||||
if not self.doc.expense_adjustment_account:
|
||||
webnotes.msgprint(_("Please enter Expense/Adjustment Account"), raise_exception=1)
|
||||
|
||||
from accounts.general_ledger import make_gl_entries
|
||||
|
||||
total_valuation_amount = self.get_total_valuation_amount()
|
||||
|
||||
gl_entries = self.get_gl_entries_for_stock(self.doc.expense_adjustment_account,
|
||||
total_valuation_amount)
|
||||
if gl_entries:
|
||||
make_gl_entries(gl_entries, cancel=self.doc.docstatus == 2)
|
||||
|
||||
def get_total_valuation_amount(self):
|
||||
total_valuation_amount = 0
|
||||
for item in self.doclist.get({"parentfield": "mtn_details"}):
|
||||
if item.t_warehouse and not item.s_warehouse:
|
||||
total_valuation_amount += flt(item.incoming_rate, 2) * flt(item.transfer_qty)
|
||||
|
||||
if item.s_warehouse and not item.t_warehouse:
|
||||
total_valuation_amount -= flt(item.incoming_rate, 2) * flt(item.transfer_qty)
|
||||
|
||||
return total_valuation_amount
|
||||
|
||||
def get_stock_and_rate(self):
|
||||
"""get stock and incoming rate on posting date"""
|
||||
for d in getlist(self.doclist, 'mtn_details'):
|
||||
@@ -231,7 +204,7 @@ class DocType(StockController):
|
||||
sle = webnotes.conn.sql("""select name, posting_date, posting_time,
|
||||
actual_qty, stock_value, warehouse from `tabStock Ledger Entry`
|
||||
where voucher_type = %s and voucher_no = %s and
|
||||
item_code = %s and ifnull(is_cancelled, 'No') = 'No' limit 1""",
|
||||
item_code = %s limit 1""",
|
||||
((self.doc.delivery_note_no and "Delivery Note" or "Sales Invoice"),
|
||||
self.doc.delivery_note_no or self.doc.sales_invoice_no, args.item_code), as_dict=1)
|
||||
if sle:
|
||||
@@ -339,20 +312,34 @@ class DocType(StockController):
|
||||
sr.doc.status = "Sales Returned" if is_submit else "Delivered"
|
||||
sr.save()
|
||||
|
||||
def update_stock_ledger(self, is_cancelled=0):
|
||||
self.values = []
|
||||
def update_stock_ledger(self):
|
||||
sl_entries = []
|
||||
for d in getlist(self.doclist, 'mtn_details'):
|
||||
if cstr(d.s_warehouse) and not is_cancelled:
|
||||
self.add_to_values(d, cstr(d.s_warehouse), -flt(d.transfer_qty), is_cancelled)
|
||||
if cstr(d.s_warehouse) and self.doc.docstatus == 1:
|
||||
sl_entries.append(self.get_sl_entries(d, {
|
||||
"warehouse": cstr(d.s_warehouse),
|
||||
"actual_qty": -flt(d.transfer_qty),
|
||||
"incoming_rate": 0
|
||||
}))
|
||||
|
||||
if cstr(d.t_warehouse):
|
||||
self.add_to_values(d, cstr(d.t_warehouse), flt(d.transfer_qty), is_cancelled)
|
||||
sl_entries.append(self.get_sl_entries(d, {
|
||||
"warehouse": cstr(d.t_warehouse),
|
||||
"actual_qty": flt(d.transfer_qty),
|
||||
"incoming_rate": flt(d.incoming_rate)
|
||||
}))
|
||||
|
||||
# On cancellation, make stock ledger entry for
|
||||
# target warehouse first, to update serial no values properly
|
||||
|
||||
if cstr(d.s_warehouse) and self.doc.docstatus == 2:
|
||||
sl_entries.append(self.get_sl_entries(d, {
|
||||
"warehouse": cstr(d.s_warehouse),
|
||||
"actual_qty": -flt(d.transfer_qty),
|
||||
"incoming_rate": 0
|
||||
}))
|
||||
|
||||
if cstr(d.s_warehouse) and is_cancelled:
|
||||
self.add_to_values(d, cstr(d.s_warehouse), -flt(d.transfer_qty), is_cancelled)
|
||||
|
||||
get_obj('Stock Ledger', 'Stock Ledger').update_stock(self.values,
|
||||
self.doc.amended_from and 'Yes' or 'No')
|
||||
self.make_sl_entries(sl_entries, self.doc.amended_from and 'Yes' or 'No')
|
||||
|
||||
def update_production_order(self, is_submit):
|
||||
if self.doc.production_order:
|
||||
@@ -370,14 +357,16 @@ class DocType(StockController):
|
||||
|
||||
# update bin
|
||||
if self.doc.purpose == "Manufacture/Repack":
|
||||
from stock.utils import update_bin
|
||||
pro_obj.doc.produced_qty = flt(pro_obj.doc.produced_qty) + \
|
||||
(is_submit and 1 or -1 ) * flt(self.doc.fg_completed_qty)
|
||||
args = {
|
||||
"item_code": pro_obj.doc.production_item,
|
||||
"warehouse": pro_obj.doc.fg_warehouse,
|
||||
"posting_date": self.doc.posting_date,
|
||||
"planned_qty": (is_submit and -1 or 1 ) * flt(self.doc.fg_completed_qty)
|
||||
}
|
||||
get_obj('Warehouse', pro_obj.doc.fg_warehouse).update_bin(args)
|
||||
update_bin(args)
|
||||
|
||||
# update production order status
|
||||
pro_obj.doc.status = (flt(pro_obj.doc.qty)==flt(pro_obj.doc.produced_qty)) \
|
||||
@@ -413,7 +402,7 @@ class DocType(StockController):
|
||||
arg, ret = eval(arg), {}
|
||||
uom = webnotes.conn.sql("""select conversion_factor from `tabUOM Conversion Detail`
|
||||
where parent = %s and uom = %s""", (arg['item_code'], arg['uom']), as_dict = 1)
|
||||
if not uom:
|
||||
if not uom or not flt(uom[0].conversion_factor):
|
||||
msgprint("There is no Conversion Factor for UOM '%s' in Item '%s'" % (arg['uom'],
|
||||
arg['item_code']))
|
||||
ret = {'uom' : ''}
|
||||
@@ -629,26 +618,6 @@ class DocType(StockController):
|
||||
# to be assigned for finished item
|
||||
se_child.bom_no = bom_no
|
||||
|
||||
def add_to_values(self, d, wh, qty, is_cancelled):
|
||||
self.values.append({
|
||||
'item_code': d.item_code,
|
||||
'warehouse': wh,
|
||||
'posting_date': self.doc.posting_date,
|
||||
'posting_time': self.doc.posting_time,
|
||||
'voucher_type': 'Stock Entry',
|
||||
'voucher_no': self.doc.name,
|
||||
'voucher_detail_no': d.name,
|
||||
'actual_qty': qty,
|
||||
'incoming_rate': flt(d.incoming_rate, 2) or 0,
|
||||
'stock_uom': d.stock_uom,
|
||||
'company': self.doc.company,
|
||||
'is_cancelled': (is_cancelled ==1) and 'Yes' or 'No',
|
||||
'batch_no': cstr(d.batch_no).strip(),
|
||||
'serial_no': cstr(d.serial_no).strip(),
|
||||
"project": self.doc.project_name,
|
||||
"fiscal_year": self.doc.fiscal_year,
|
||||
})
|
||||
|
||||
def get_cust_values(self):
|
||||
"""fetches customer details"""
|
||||
if self.doc.delivery_note_no:
|
||||
@@ -787,7 +756,6 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
||||
from `tabStock Ledger Entry` sle
|
||||
where item_code = '%(item_code)s'
|
||||
and warehouse = '%(s_warehouse)s'
|
||||
and ifnull(is_cancelled, 'No') = 'No'
|
||||
and batch_no like '%(txt)s'
|
||||
and exists(select * from `tabBatch`
|
||||
where name = sle.batch_no
|
||||
@@ -885,7 +853,8 @@ def make_return_jv(stock_entry):
|
||||
"account": r.get("account"),
|
||||
"against_invoice": r.get("against_invoice"),
|
||||
"against_voucher": r.get("against_voucher"),
|
||||
"balance": get_balance_on(r.get("account"), se.doc.posting_date)
|
||||
"balance": get_balance_on(r.get("account"), se.doc.posting_date) \
|
||||
if r.get("account") else 0
|
||||
})
|
||||
|
||||
return jv_list
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{
|
||||
"creation": "2013-04-09 11:43:55",
|
||||
"docstatus": 0,
|
||||
"modified": "2013-08-08 14:22:31",
|
||||
"modified": "2013-08-24 15:16:34",
|
||||
"modified_by": "Administrator",
|
||||
"owner": "Administrator"
|
||||
},
|
||||
@@ -199,16 +199,6 @@
|
||||
"reqd": 1,
|
||||
"search_index": 0
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:sys_defaults.auto_inventory_accounting",
|
||||
"doctype": "DocField",
|
||||
"fieldname": "expense_adjustment_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Expense/Adjustment Account",
|
||||
"options": "Account",
|
||||
"print_hide": 1,
|
||||
"read_only": 0
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "items_section",
|
||||
|
||||
@@ -8,17 +8,19 @@ from __future__ import unicode_literals
|
||||
import webnotes, unittest
|
||||
from webnotes.utils import flt
|
||||
from stock.doctype.stock_ledger_entry.stock_ledger_entry import *
|
||||
from stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
|
||||
|
||||
|
||||
class TestStockEntry(unittest.TestCase):
|
||||
def tearDown(self):
|
||||
webnotes.defaults.set_global_default("auto_inventory_accounting", 0)
|
||||
set_perpetual_inventory(0)
|
||||
if hasattr(self, "old_default_company"):
|
||||
webnotes.conn.set_default("company", self.old_default_company)
|
||||
|
||||
def test_auto_material_request(self):
|
||||
webnotes.conn.sql("""delete from `tabMaterial Request Item`""")
|
||||
webnotes.conn.sql("""delete from `tabMaterial Request`""")
|
||||
self._clear_stock()
|
||||
self._clear_stock_account_balance()
|
||||
|
||||
webnotes.conn.set_value("Stock Settings", None, "auto_indent", True)
|
||||
|
||||
@@ -41,6 +43,7 @@ class TestStockEntry(unittest.TestCase):
|
||||
webnotes.conn.set_default("company", self.old_default_company)
|
||||
|
||||
def test_warehouse_company_validation(self):
|
||||
self._clear_stock_account_balance()
|
||||
webnotes.session.user = "test2@example.com"
|
||||
webnotes.bean("Profile", "test2@example.com").get_controller()\
|
||||
.add_roles("Sales User", "Sales Manager", "Material User", "Material Manager")
|
||||
@@ -79,15 +82,15 @@ class TestStockEntry(unittest.TestCase):
|
||||
webnotes.session.user = "Administrator"
|
||||
|
||||
def test_material_receipt_gl_entry(self):
|
||||
webnotes.conn.sql("delete from `tabStock Ledger Entry`")
|
||||
webnotes.defaults.set_global_default("auto_inventory_accounting", 1)
|
||||
self._clear_stock_account_balance()
|
||||
set_perpetual_inventory()
|
||||
|
||||
mr = webnotes.bean(copy=test_records[0])
|
||||
mr.insert()
|
||||
mr.submit()
|
||||
|
||||
stock_in_hand_account = webnotes.conn.get_value("Company", "_Test Company",
|
||||
"stock_in_hand_account")
|
||||
stock_in_hand_account = webnotes.conn.get_value("Account", {"account_type": "Warehouse",
|
||||
"master_name": mr.doclist[1].t_warehouse})
|
||||
|
||||
self.check_stock_ledger_entries("Stock Entry", mr.doc.name,
|
||||
[["_Test Item", "_Test Warehouse - _TC", 50.0]])
|
||||
@@ -100,37 +103,30 @@ class TestStockEntry(unittest.TestCase):
|
||||
)
|
||||
|
||||
mr.cancel()
|
||||
self.check_stock_ledger_entries("Stock Entry", mr.doc.name,
|
||||
sorted([["_Test Item", "_Test Warehouse - _TC", 50.0],
|
||||
["_Test Item", "_Test Warehouse - _TC", -50.0]]))
|
||||
|
||||
self.check_gl_entries("Stock Entry", mr.doc.name,
|
||||
sorted([
|
||||
[stock_in_hand_account, 5000.0, 0.0],
|
||||
["Stock Adjustment - _TC", 0.0, 5000.0],
|
||||
[stock_in_hand_account, 0.0, 5000.0],
|
||||
["Stock Adjustment - _TC", 5000.0, 0.0]
|
||||
])
|
||||
)
|
||||
|
||||
self.assertFalse(webnotes.conn.sql("""select * from `tabStock Ledger Entry`
|
||||
where voucher_type='Stock Entry' and voucher_no=%s""", mr.doc.name))
|
||||
|
||||
self.assertFalse(webnotes.conn.sql("""select * from `tabGL Entry`
|
||||
where voucher_type='Stock Entry' and voucher_no=%s""", mr.doc.name))
|
||||
|
||||
|
||||
def test_material_issue_gl_entry(self):
|
||||
self._clear_stock()
|
||||
webnotes.defaults.set_global_default("auto_inventory_accounting", 1)
|
||||
self._clear_stock_account_balance()
|
||||
set_perpetual_inventory()
|
||||
|
||||
mr = webnotes.bean(copy=test_records[0])
|
||||
mr.insert()
|
||||
mr.submit()
|
||||
self._insert_material_receipt()
|
||||
|
||||
mi = webnotes.bean(copy=test_records[1])
|
||||
mi.insert()
|
||||
mi.submit()
|
||||
|
||||
stock_in_hand_account = webnotes.conn.get_value("Company", "_Test Company",
|
||||
"stock_in_hand_account")
|
||||
|
||||
self.check_stock_ledger_entries("Stock Entry", mi.doc.name,
|
||||
[["_Test Item", "_Test Warehouse - _TC", -40.0]])
|
||||
|
||||
|
||||
stock_in_hand_account = webnotes.conn.get_value("Account", {"account_type": "Warehouse",
|
||||
"master_name": mi.doclist[1].s_warehouse})
|
||||
|
||||
self.check_gl_entries("Stock Entry", mi.doc.name,
|
||||
sorted([
|
||||
[stock_in_hand_account, 0.0, 4000.0],
|
||||
@@ -139,28 +135,24 @@ class TestStockEntry(unittest.TestCase):
|
||||
)
|
||||
|
||||
mi.cancel()
|
||||
self.assertFalse(webnotes.conn.sql("""select * from `tabStock Ledger Entry`
|
||||
where voucher_type='Stock Entry' and voucher_no=%s""", mi.doc.name))
|
||||
|
||||
self.check_stock_ledger_entries("Stock Entry", mi.doc.name,
|
||||
sorted([["_Test Item", "_Test Warehouse - _TC", -40.0],
|
||||
["_Test Item", "_Test Warehouse - _TC", 40.0]]))
|
||||
self.assertFalse(webnotes.conn.sql("""select * from `tabGL Entry`
|
||||
where voucher_type='Stock Entry' and voucher_no=%s""", mi.doc.name))
|
||||
|
||||
self.check_gl_entries("Stock Entry", mi.doc.name,
|
||||
sorted([
|
||||
[stock_in_hand_account, 0.0, 4000.0],
|
||||
["Stock Adjustment - _TC", 4000.0, 0.0],
|
||||
[stock_in_hand_account, 4000.0, 0.0],
|
||||
["Stock Adjustment - _TC", 0.0, 4000.0],
|
||||
])
|
||||
)
|
||||
self.assertEquals(webnotes.conn.get_value("Bin", {"warehouse": mi.doclist[1].s_warehouse,
|
||||
"item_code": mi.doclist[1].item_code}, "actual_qty"), 50)
|
||||
|
||||
self.assertEquals(webnotes.conn.get_value("Bin", {"warehouse": mi.doclist[1].s_warehouse,
|
||||
"item_code": mi.doclist[1].item_code}, "stock_value"), 5000)
|
||||
|
||||
def test_material_transfer_gl_entry(self):
|
||||
self._clear_stock()
|
||||
webnotes.defaults.set_global_default("auto_inventory_accounting", 1)
|
||||
|
||||
mr = webnotes.bean(copy=test_records[0])
|
||||
mr.insert()
|
||||
mr.submit()
|
||||
self._clear_stock_account_balance()
|
||||
set_perpetual_inventory()
|
||||
|
||||
self._insert_material_receipt()
|
||||
|
||||
mtn = webnotes.bean(copy=test_records[2])
|
||||
mtn.insert()
|
||||
mtn.submit()
|
||||
@@ -168,57 +160,104 @@ class TestStockEntry(unittest.TestCase):
|
||||
self.check_stock_ledger_entries("Stock Entry", mtn.doc.name,
|
||||
[["_Test Item", "_Test Warehouse - _TC", -45.0], ["_Test Item", "_Test Warehouse 1 - _TC", 45.0]])
|
||||
|
||||
# no gl entry
|
||||
gl_entries = webnotes.conn.sql("""select * from `tabGL Entry`
|
||||
where voucher_type = 'Stock Entry' and voucher_no=%s""", mtn.doc.name)
|
||||
self.assertFalse(gl_entries)
|
||||
stock_in_hand_account = webnotes.conn.get_value("Account", {"account_type": "Warehouse",
|
||||
"master_name": mtn.doclist[1].s_warehouse})
|
||||
|
||||
fixed_asset_account = webnotes.conn.get_value("Account", {"account_type": "Warehouse",
|
||||
"master_name": mtn.doclist[1].t_warehouse})
|
||||
|
||||
|
||||
self.check_gl_entries("Stock Entry", mtn.doc.name,
|
||||
sorted([
|
||||
[stock_in_hand_account, 0.0, 4500.0],
|
||||
[fixed_asset_account, 4500.0, 0.0],
|
||||
])
|
||||
)
|
||||
|
||||
|
||||
mtn.cancel()
|
||||
self.check_stock_ledger_entries("Stock Entry", mtn.doc.name,
|
||||
sorted([["_Test Item", "_Test Warehouse - _TC", 45.0],
|
||||
["_Test Item", "_Test Warehouse 1 - _TC", -45.0],
|
||||
["_Test Item", "_Test Warehouse - _TC", -45.0],
|
||||
["_Test Item", "_Test Warehouse 1 - _TC", 45.0]]))
|
||||
self.assertFalse(webnotes.conn.sql("""select * from `tabStock Ledger Entry`
|
||||
where voucher_type='Stock Entry' and voucher_no=%s""", mtn.doc.name))
|
||||
|
||||
self.assertFalse(webnotes.conn.sql("""select * from `tabGL Entry`
|
||||
where voucher_type='Stock Entry' and voucher_no=%s""", mtn.doc.name))
|
||||
|
||||
|
||||
def test_repack_no_change_in_valuation(self):
|
||||
self._clear_stock_account_balance()
|
||||
set_perpetual_inventory()
|
||||
|
||||
# no gl entry
|
||||
gl_entries = webnotes.conn.sql("""select * from `tabGL Entry`
|
||||
where voucher_type = 'Stock Entry' and voucher_no=%s""", mtn.doc.name)
|
||||
self._insert_material_receipt()
|
||||
|
||||
repack = webnotes.bean(copy=test_records[3])
|
||||
repack.insert()
|
||||
repack.submit()
|
||||
|
||||
self.check_stock_ledger_entries("Stock Entry", repack.doc.name,
|
||||
[["_Test Item", "_Test Warehouse - _TC", -50.0],
|
||||
["_Test Item Home Desktop 100", "_Test Warehouse - _TC", 1]])
|
||||
|
||||
gl_entries = webnotes.conn.sql("""select account, debit, credit
|
||||
from `tabGL Entry` where voucher_type='Stock Entry' and voucher_no=%s
|
||||
order by account desc""", repack.doc.name, as_dict=1)
|
||||
self.assertFalse(gl_entries)
|
||||
|
||||
set_perpetual_inventory(0)
|
||||
|
||||
def test_repack_with_change_in_valuation(self):
|
||||
self._clear_stock_account_balance()
|
||||
set_perpetual_inventory()
|
||||
|
||||
self._insert_material_receipt()
|
||||
|
||||
repack = webnotes.bean(copy=test_records[3])
|
||||
repack.doclist[2].incoming_rate = 6000
|
||||
repack.insert()
|
||||
repack.submit()
|
||||
|
||||
stock_in_hand_account = webnotes.conn.get_value("Account", {"account_type": "Warehouse",
|
||||
"master_name": repack.doclist[2].t_warehouse})
|
||||
|
||||
self.check_gl_entries("Stock Entry", repack.doc.name,
|
||||
sorted([
|
||||
[stock_in_hand_account, 1000.0, 0.0],
|
||||
["Stock Adjustment - _TC", 0.0, 1000.0],
|
||||
])
|
||||
)
|
||||
set_perpetual_inventory(0)
|
||||
|
||||
def check_stock_ledger_entries(self, voucher_type, voucher_no, expected_sle):
|
||||
# check stock ledger entries
|
||||
sle = webnotes.conn.sql("""select * from `tabStock Ledger Entry` where voucher_type = %s
|
||||
and voucher_no = %s order by item_code, warehouse, actual_qty""",
|
||||
(voucher_type, voucher_no), as_dict=1)
|
||||
self.assertTrue(sle)
|
||||
expected_sle.sort(key=lambda x: x[0])
|
||||
|
||||
# check stock ledger entries
|
||||
sle = webnotes.conn.sql("""select item_code, warehouse, actual_qty
|
||||
from `tabStock Ledger Entry` where voucher_type = %s
|
||||
and voucher_no = %s order by item_code, warehouse, actual_qty""",
|
||||
(voucher_type, voucher_no), as_list=1)
|
||||
self.assertTrue(sle)
|
||||
sle.sort(key=lambda x: x[0])
|
||||
|
||||
for i, sle in enumerate(sle):
|
||||
self.assertEquals(expected_sle[i][0], sle.item_code)
|
||||
self.assertEquals(expected_sle[i][1], sle.warehouse)
|
||||
self.assertEquals(expected_sle[i][2], sle.actual_qty)
|
||||
self.assertEquals(expected_sle[i][0], sle[0])
|
||||
self.assertEquals(expected_sle[i][1], sle[1])
|
||||
self.assertEquals(expected_sle[i][2], sle[2])
|
||||
|
||||
def check_gl_entries(self, voucher_type, voucher_no, expected_gl_entries):
|
||||
# check gl entries
|
||||
expected_gl_entries.sort(key=lambda x: x[0])
|
||||
|
||||
gl_entries = webnotes.conn.sql("""select account, debit, credit
|
||||
from `tabGL Entry` where voucher_type=%s and voucher_no=%s
|
||||
order by account asc, debit asc""", (voucher_type, voucher_no), as_dict=1)
|
||||
order by account asc, debit asc""", (voucher_type, voucher_no), as_list=1)
|
||||
self.assertTrue(gl_entries)
|
||||
for i, gle in enumerate(gl_entries):
|
||||
self.assertEquals(expected_gl_entries[i][0], gle.account)
|
||||
self.assertEquals(expected_gl_entries[i][1], gle.debit)
|
||||
self.assertEquals(expected_gl_entries[i][2], gle.credit)
|
||||
|
||||
def _clear_stock(self):
|
||||
webnotes.conn.sql("delete from `tabStock Ledger Entry`")
|
||||
webnotes.conn.sql("""delete from `tabBin`""")
|
||||
webnotes.conn.sql("""delete from `tabSerial No`""")
|
||||
gl_entries.sort(key=lambda x: x[0])
|
||||
|
||||
for i, gle in enumerate(gl_entries):
|
||||
self.assertEquals(expected_gl_entries[i][0], gle[0])
|
||||
self.assertEquals(expected_gl_entries[i][1], gle[1])
|
||||
self.assertEquals(expected_gl_entries[i][2], gle[2])
|
||||
|
||||
self.old_default_company = webnotes.conn.get_default("company")
|
||||
webnotes.conn.set_default("company", "_Test Company")
|
||||
|
||||
def _insert_material_receipt(self):
|
||||
self._clear_stock()
|
||||
self._clear_stock_account_balance()
|
||||
se1 = webnotes.bean(copy=test_records[0])
|
||||
se1.insert()
|
||||
se1.submit()
|
||||
@@ -305,9 +344,11 @@ class TestStockEntry(unittest.TestCase):
|
||||
return se
|
||||
|
||||
def test_sales_invoice_return_of_non_packing_item(self):
|
||||
self._clear_stock_account_balance()
|
||||
self._test_sales_invoice_return("_Test Item", 5, 2)
|
||||
|
||||
def test_sales_invoice_return_of_packing_item(self):
|
||||
self._clear_stock_account_balance()
|
||||
self._test_sales_invoice_return("_Test Sales BOM Item", 25, 20)
|
||||
|
||||
def _test_delivery_note_return(self, item_code, delivered_qty, returned_qty):
|
||||
@@ -319,7 +360,6 @@ class TestStockEntry(unittest.TestCase):
|
||||
from stock.doctype.delivery_note.delivery_note import make_sales_invoice
|
||||
|
||||
actual_qty_0 = self._get_actual_qty()
|
||||
|
||||
# make a delivery note based on this invoice
|
||||
dn = webnotes.bean(copy=delivery_note_test_records[0])
|
||||
dn.doclist[1].item_code = item_code
|
||||
@@ -358,9 +398,11 @@ class TestStockEntry(unittest.TestCase):
|
||||
return se
|
||||
|
||||
def test_delivery_note_return_of_non_packing_item(self):
|
||||
self._clear_stock_account_balance()
|
||||
self._test_delivery_note_return("_Test Item", 5, 2)
|
||||
|
||||
def test_delivery_note_return_of_packing_item(self):
|
||||
self._clear_stock_account_balance()
|
||||
self._test_delivery_note_return("_Test Sales BOM Item", 25, 20)
|
||||
|
||||
def _test_sales_return_jv(self, se):
|
||||
@@ -375,14 +417,17 @@ class TestStockEntry(unittest.TestCase):
|
||||
self.assertTrue(jv_list[1].get("against_invoice"))
|
||||
|
||||
def test_make_return_jv_for_sales_invoice_non_packing_item(self):
|
||||
self._clear_stock_account_balance()
|
||||
se = self._test_sales_invoice_return("_Test Item", 5, 2)
|
||||
self._test_sales_return_jv(se)
|
||||
|
||||
def test_make_return_jv_for_sales_invoice_packing_item(self):
|
||||
self._clear_stock_account_balance()
|
||||
se = self._test_sales_invoice_return("_Test Sales BOM Item", 25, 20)
|
||||
self._test_sales_return_jv(se)
|
||||
|
||||
def test_make_return_jv_for_delivery_note_non_packing_item(self):
|
||||
self._clear_stock_account_balance()
|
||||
se = self._test_delivery_note_return("_Test Item", 5, 2)
|
||||
self._test_sales_return_jv(se)
|
||||
|
||||
@@ -390,6 +435,7 @@ class TestStockEntry(unittest.TestCase):
|
||||
self._test_sales_return_jv(se)
|
||||
|
||||
def test_make_return_jv_for_delivery_note_packing_item(self):
|
||||
self._clear_stock_account_balance()
|
||||
se = self._test_delivery_note_return("_Test Sales BOM Item", 25, 20)
|
||||
self._test_sales_return_jv(se)
|
||||
|
||||
@@ -450,7 +496,7 @@ class TestStockEntry(unittest.TestCase):
|
||||
return se
|
||||
|
||||
def test_purchase_receipt_return(self):
|
||||
self._clear_stock()
|
||||
self._clear_stock_account_balance()
|
||||
|
||||
actual_qty_0 = self._get_actual_qty()
|
||||
|
||||
@@ -466,7 +512,7 @@ class TestStockEntry(unittest.TestCase):
|
||||
|
||||
actual_qty_1 = self._get_actual_qty()
|
||||
|
||||
self.assertEquals(actual_qty_0 + 10, actual_qty_1)
|
||||
self.assertEquals(actual_qty_0 + 5, actual_qty_1)
|
||||
|
||||
pi_doclist = make_purchase_invoice(pr.doc.name)
|
||||
|
||||
@@ -506,6 +552,7 @@ class TestStockEntry(unittest.TestCase):
|
||||
|
||||
def test_over_stock_return(self):
|
||||
from stock.doctype.stock_entry.stock_entry import StockOverReturnError
|
||||
self._clear_stock_account_balance()
|
||||
|
||||
# out of 10, 5 gets returned
|
||||
prev_se, pr_docname = self.test_purchase_receipt_return()
|
||||
@@ -533,6 +580,7 @@ class TestStockEntry(unittest.TestCase):
|
||||
self.assertTrue(jv_list[1].get("against_voucher"))
|
||||
|
||||
def test_make_return_jv_for_purchase_receipt(self):
|
||||
self._clear_stock_account_balance()
|
||||
se, pr_name = self.test_purchase_receipt_return()
|
||||
self._test_purchase_return_jv(se)
|
||||
|
||||
@@ -540,7 +588,7 @@ class TestStockEntry(unittest.TestCase):
|
||||
self._test_purchase_return_jv(se)
|
||||
|
||||
def _test_purchase_return_return_against_purchase_order(self):
|
||||
self._clear_stock()
|
||||
self._clear_stock_account_balance()
|
||||
|
||||
actual_qty_0 = self._get_actual_qty()
|
||||
|
||||
@@ -604,6 +652,14 @@ class TestStockEntry(unittest.TestCase):
|
||||
|
||||
return se, pr.doc.name
|
||||
|
||||
def _clear_stock_account_balance(self):
|
||||
webnotes.conn.sql("delete from `tabStock Ledger Entry`")
|
||||
webnotes.conn.sql("""delete from `tabBin`""")
|
||||
webnotes.conn.sql("""delete from `tabGL Entry`""")
|
||||
|
||||
self.old_default_company = webnotes.conn.get_default("company")
|
||||
webnotes.conn.set_default("company", "_Test Company")
|
||||
|
||||
def test_serial_no_not_reqd(self):
|
||||
se = webnotes.bean(copy=test_records[0])
|
||||
se.doclist[1].serial_no = "ABCD"
|
||||
@@ -637,6 +693,7 @@ class TestStockEntry(unittest.TestCase):
|
||||
self.assertRaises(SerialNoQtyError, se.submit)
|
||||
|
||||
def test_serial_no_transfer_in(self):
|
||||
self._clear_stock_account_balance()
|
||||
se = webnotes.bean(copy=test_records[0])
|
||||
se.doclist[1].item_code = "_Test Serialized Item"
|
||||
se.doclist[1].qty = 2
|
||||
@@ -652,6 +709,7 @@ class TestStockEntry(unittest.TestCase):
|
||||
self.assertFalse(webnotes.conn.get_value("Serial No", "ABCD", "warehouse"))
|
||||
|
||||
def test_serial_no_not_exists(self):
|
||||
self._clear_stock_account_balance()
|
||||
se = webnotes.bean(copy=test_records[0])
|
||||
se.doc.purpose = "Material Issue"
|
||||
se.doclist[1].item_code = "_Test Serialized Item"
|
||||
@@ -664,6 +722,7 @@ class TestStockEntry(unittest.TestCase):
|
||||
self.assertRaises(SerialNoNotExistsError, se.submit)
|
||||
|
||||
def test_serial_by_series(self):
|
||||
self._clear_stock_account_balance()
|
||||
se = make_serialized_item()
|
||||
|
||||
serial_nos = get_serial_nos(se.doclist[1].serial_no)
|
||||
@@ -674,6 +733,7 @@ class TestStockEntry(unittest.TestCase):
|
||||
return se
|
||||
|
||||
def test_serial_item_error(self):
|
||||
self._clear_stock_account_balance()
|
||||
self.test_serial_by_series()
|
||||
|
||||
se = webnotes.bean(copy=test_records[0])
|
||||
@@ -688,6 +748,7 @@ class TestStockEntry(unittest.TestCase):
|
||||
self.assertRaises(SerialNoItemError, se.submit)
|
||||
|
||||
def test_serial_move(self):
|
||||
self._clear_stock_account_balance()
|
||||
se = make_serialized_item()
|
||||
serial_no = get_serial_nos(se.doclist[1].serial_no)[0]
|
||||
|
||||
@@ -707,6 +768,7 @@ class TestStockEntry(unittest.TestCase):
|
||||
self.assertTrue(webnotes.conn.get_value("Serial No", serial_no, "warehouse"), "_Test Warehouse - _TC")
|
||||
|
||||
def test_serial_warehouse_error(self):
|
||||
self._clear_stock_account_balance()
|
||||
make_serialized_item()
|
||||
|
||||
se = webnotes.bean(copy=test_records[0])
|
||||
@@ -721,6 +783,7 @@ class TestStockEntry(unittest.TestCase):
|
||||
self.assertRaises(SerialNoWarehouseError, se.submit)
|
||||
|
||||
def test_serial_cancel(self):
|
||||
self._clear_stock_account_balance()
|
||||
se = self.test_serial_by_series()
|
||||
se.cancel()
|
||||
|
||||
@@ -745,7 +808,6 @@ test_records = [
|
||||
"posting_time": "17:14:24",
|
||||
"purpose": "Material Receipt",
|
||||
"fiscal_year": "_Test Fiscal Year 2013",
|
||||
"expense_adjustment_account": "Stock Adjustment - _TC"
|
||||
},
|
||||
{
|
||||
"conversion_factor": 1.0,
|
||||
@@ -758,6 +820,8 @@ test_records = [
|
||||
"transfer_qty": 50.0,
|
||||
"uom": "_Test UOM",
|
||||
"t_warehouse": "_Test Warehouse - _TC",
|
||||
"expense_account": "Stock Adjustment - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC"
|
||||
},
|
||||
],
|
||||
[
|
||||
@@ -768,7 +832,6 @@ test_records = [
|
||||
"posting_time": "17:15",
|
||||
"purpose": "Material Issue",
|
||||
"fiscal_year": "_Test Fiscal Year 2013",
|
||||
"expense_adjustment_account": "Stock Adjustment - _TC"
|
||||
},
|
||||
{
|
||||
"conversion_factor": 1.0,
|
||||
@@ -781,6 +844,8 @@ test_records = [
|
||||
"transfer_qty": 40.0,
|
||||
"uom": "_Test UOM",
|
||||
"s_warehouse": "_Test Warehouse - _TC",
|
||||
"expense_account": "Stock Adjustment - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC"
|
||||
},
|
||||
],
|
||||
[
|
||||
@@ -791,7 +856,6 @@ test_records = [
|
||||
"posting_time": "17:14:24",
|
||||
"purpose": "Material Transfer",
|
||||
"fiscal_year": "_Test Fiscal Year 2013",
|
||||
"expense_adjustment_account": "Stock Adjustment - _TC"
|
||||
},
|
||||
{
|
||||
"conversion_factor": 1.0,
|
||||
@@ -805,6 +869,46 @@ test_records = [
|
||||
"uom": "_Test UOM",
|
||||
"s_warehouse": "_Test Warehouse - _TC",
|
||||
"t_warehouse": "_Test Warehouse 1 - _TC",
|
||||
"expense_account": "Stock Adjustment - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC"
|
||||
}
|
||||
]
|
||||
],
|
||||
[
|
||||
{
|
||||
"company": "_Test Company",
|
||||
"doctype": "Stock Entry",
|
||||
"posting_date": "2013-01-25",
|
||||
"posting_time": "17:14:24",
|
||||
"purpose": "Manufacture/Repack",
|
||||
"fiscal_year": "_Test Fiscal Year 2013",
|
||||
},
|
||||
{
|
||||
"conversion_factor": 1.0,
|
||||
"doctype": "Stock Entry Detail",
|
||||
"item_code": "_Test Item",
|
||||
"parentfield": "mtn_details",
|
||||
"incoming_rate": 100,
|
||||
"qty": 50.0,
|
||||
"stock_uom": "_Test UOM",
|
||||
"transfer_qty": 50.0,
|
||||
"uom": "_Test UOM",
|
||||
"s_warehouse": "_Test Warehouse - _TC",
|
||||
"expense_account": "Stock Adjustment - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC"
|
||||
},
|
||||
{
|
||||
"conversion_factor": 1.0,
|
||||
"doctype": "Stock Entry Detail",
|
||||
"item_code": "_Test Item Home Desktop 100",
|
||||
"parentfield": "mtn_details",
|
||||
"incoming_rate": 5000,
|
||||
"qty": 1,
|
||||
"stock_uom": "_Test UOM",
|
||||
"transfer_qty": 1,
|
||||
"uom": "_Test UOM",
|
||||
"t_warehouse": "_Test Warehouse - _TC",
|
||||
"expense_account": "Stock Adjustment - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC"
|
||||
},
|
||||
],
|
||||
]
|
||||
@@ -2,7 +2,7 @@
|
||||
{
|
||||
"creation": "2013-03-29 18:22:12",
|
||||
"docstatus": 0,
|
||||
"modified": "2013-07-10 14:54:23",
|
||||
"modified": "2013-08-28 19:25:38",
|
||||
"modified_by": "Administrator",
|
||||
"owner": "Administrator"
|
||||
},
|
||||
@@ -144,6 +144,27 @@
|
||||
"print_hide": 1,
|
||||
"read_only": 0
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:sys_defaults.auto_accounting_for_stock",
|
||||
"doctype": "DocField",
|
||||
"fieldname": "expense_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Difference Account",
|
||||
"options": "Account",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:sys_defaults.auto_accounting_for_stock",
|
||||
"doctype": "DocField",
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"label": "Cost Center",
|
||||
"options": "Cost Center",
|
||||
"print_hide": 1,
|
||||
"read_only": 0,
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "actual_qty",
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Control (to be deprecated) for updating stock entries.
|
||||
@@ -1 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
@@ -1,22 +0,0 @@
|
||||
[
|
||||
{
|
||||
"creation": "2013-01-10 16:34:30",
|
||||
"docstatus": 0,
|
||||
"modified": "2013-07-10 14:54:23",
|
||||
"modified_by": "Administrator",
|
||||
"owner": "Administrator"
|
||||
},
|
||||
{
|
||||
"doctype": "DocType",
|
||||
"hide_toolbar": 1,
|
||||
"in_create": 1,
|
||||
"issingle": 1,
|
||||
"module": "Stock",
|
||||
"name": "__common__",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocType",
|
||||
"name": "Stock Ledger"
|
||||
}
|
||||
]
|
||||
@@ -34,25 +34,35 @@ class DocType(DocListController):
|
||||
self.validate_item()
|
||||
validate_warehouse_user(self.doc.warehouse)
|
||||
self.validate_warehouse_company()
|
||||
self.actual_amt_check()
|
||||
self.check_stock_frozen_date()
|
||||
self.scrub_posting_time()
|
||||
|
||||
from accounts.utils import validate_fiscal_year
|
||||
validate_fiscal_year(self.doc.posting_date, self.doc.fiscal_year, self.meta.get_label("posting_date"))
|
||||
|
||||
def on_submit(self):
|
||||
self.check_stock_frozen_date()
|
||||
self.actual_amt_check()
|
||||
self.validate_serial_no()
|
||||
|
||||
#check for item quantity available in stock
|
||||
def actual_amt_check(self):
|
||||
if self.doc.batch_no:
|
||||
batch_bal = flt(webnotes.conn.sql("select sum(actual_qty) from `tabStock Ledger Entry` where warehouse = '%s' and item_code = '%s' and batch_no = '%s'"%(self.doc.warehouse,self.doc.item_code,self.doc.batch_no))[0][0])
|
||||
self.doc.fields.update({'batch_bal': batch_bal})
|
||||
batch_bal_after_transaction = flt(webnotes.conn.sql("""select sum(actual_qty)
|
||||
from `tabStock Ledger Entry`
|
||||
where warehouse=%s and item_code=%s and batch_no=%s""",
|
||||
(self.doc.warehouse, self.doc.item_code, self.doc.batch_no))[0][0])
|
||||
|
||||
if batch_bal_after_transaction < 0:
|
||||
self.doc.fields.update({
|
||||
'batch_bal': batch_bal_after_transaction - self.doc.actual_qty
|
||||
})
|
||||
|
||||
webnotes.throw("""Not enough quantity (requested: %(actual_qty)s, \
|
||||
current: %(batch_bal)s in Batch <b>%(batch_no)s</b> for Item \
|
||||
<b>%(item_code)s</b> at Warehouse <b>%(warehouse)s</b> \
|
||||
as on %(posting_date)s %(posting_time)s""" % self.doc.fields)
|
||||
|
||||
if (batch_bal + self.doc.actual_qty) < 0:
|
||||
msgprint("""Not enough quantity (requested: %(actual_qty)s, current: %(batch_bal)s in Batch
|
||||
<b>%(batch_no)s</b> for Item <b>%(item_code)s</b> at Warehouse <b>%(warehouse)s</b>
|
||||
as on %(posting_date)s %(posting_time)s""" % self.doc.fields, raise_exception = 1)
|
||||
|
||||
self.doc.fields.pop('batch_bal')
|
||||
sself.doc.fields.pop('batch_bal')
|
||||
|
||||
def validate_warehouse_company(self):
|
||||
warehouse_company = webnotes.conn.get_value("Warehouse", self.doc.warehouse, "company")
|
||||
@@ -71,10 +81,7 @@ class DocType(DocListController):
|
||||
msgprint("Warehouse: '%s' does not exist in the system. Please check." % self.doc.fields.get(k), raise_exception = 1)
|
||||
|
||||
def validate_item(self):
|
||||
item_det = webnotes.conn.sql("""select name, has_batch_no, docstatus,
|
||||
is_stock_item, has_serial_no, serial_no_series, stock_uom
|
||||
from tabItem where name=%s""",
|
||||
self.doc.item_code, as_dict=True)[0]
|
||||
item_det = self.get_item_details()
|
||||
|
||||
if item_det.is_stock_item != 'Yes':
|
||||
webnotes.throw("""Item: "%s" is not a Stock Item.""" % self.doc.item_code)
|
||||
@@ -91,10 +98,16 @@ class DocType(DocListController):
|
||||
|
||||
if not self.doc.stock_uom:
|
||||
self.doc.stock_uom = item_det.stock_uom
|
||||
|
||||
self.validate_serial_no(item_det)
|
||||
|
||||
def get_item_details(self):
|
||||
return webnotes.conn.sql("""select name, has_batch_no, docstatus,
|
||||
is_stock_item, has_serial_no, serial_no_series
|
||||
from tabItem where name=%s""",
|
||||
self.doc.item_code, as_dict=True)[0]
|
||||
|
||||
def validate_serial_no(self, item_det):
|
||||
def validate_serial_no(self):
|
||||
item_det = self.get_item_details()
|
||||
|
||||
if item_det.has_serial_no=="No":
|
||||
if self.doc.serial_no:
|
||||
webnotes.throw(_("Serial Number should be blank for Non Serialized Item" + ": " + self.doc.item),
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{
|
||||
"creation": "2013-01-29 19:25:42",
|
||||
"docstatus": 0,
|
||||
"modified": "2013-07-25 16:39:10",
|
||||
"modified": "2013-08-23 12:23:18",
|
||||
"modified_by": "Administrator",
|
||||
"owner": "Administrator"
|
||||
},
|
||||
@@ -224,6 +224,14 @@
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "stock_value_difference",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Stock Value Difference",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "stock_queue",
|
||||
@@ -276,15 +284,10 @@
|
||||
"doctype": "DocField",
|
||||
"fieldname": "is_cancelled",
|
||||
"fieldtype": "Select",
|
||||
"in_filter": 1,
|
||||
"hidden": 1,
|
||||
"label": "Is Cancelled",
|
||||
"oldfieldname": "is_cancelled",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "\nYes\nNo",
|
||||
"print_width": "100px",
|
||||
"read_only": 1,
|
||||
"search_index": 0,
|
||||
"width": "100px"
|
||||
"options": "\nNo\nYes",
|
||||
"report_hide": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
|
||||
@@ -12,7 +12,7 @@ erpnext.stock.StockReconciliation = erpnext.stock.StockController.extend({
|
||||
set_default_expense_account: function() {
|
||||
var me = this;
|
||||
|
||||
if (sys_defaults.auto_inventory_accounting && !this.frm.doc.expense_account) {
|
||||
if (sys_defaults.auto_accounting_for_stock && !this.frm.doc.expense_account) {
|
||||
return this.frm.call({
|
||||
method: "accounts.utils.get_company_default",
|
||||
args: {
|
||||
@@ -28,8 +28,9 @@ erpnext.stock.StockReconciliation = erpnext.stock.StockController.extend({
|
||||
|
||||
setup: function() {
|
||||
var me = this;
|
||||
if (sys_defaults.auto_inventory_accounting) {
|
||||
if (sys_defaults.auto_accounting_for_stock) {
|
||||
this.frm.add_fetch("company", "stock_adjustment_account", "expense_account");
|
||||
this.frm.add_fetch("company", "cost_center", "cost_center");
|
||||
|
||||
this.frm.fields_dict["expense_account"].get_query = function() {
|
||||
return {
|
||||
|
||||
@@ -9,6 +9,7 @@ from webnotes import msgprint, _
|
||||
from webnotes.utils import cstr, flt, cint
|
||||
from stock.stock_ledger import update_entries_after
|
||||
from controllers.stock_controller import StockController
|
||||
from stock.utils import update_bin
|
||||
|
||||
class DocType(StockController):
|
||||
def setup(self):
|
||||
@@ -17,14 +18,14 @@ class DocType(StockController):
|
||||
|
||||
def validate(self):
|
||||
self.validate_data()
|
||||
self.validate_expense_account()
|
||||
|
||||
def on_submit(self):
|
||||
self.insert_stock_ledger_entries()
|
||||
self.set_stock_value_difference()
|
||||
self.make_gl_entries()
|
||||
|
||||
def on_cancel(self):
|
||||
self.delete_stock_ledger_entries()
|
||||
self.delete_and_repost_sle()
|
||||
self.make_cancel_gl_entries()
|
||||
|
||||
def validate_data(self):
|
||||
@@ -56,7 +57,6 @@ class DocType(StockController):
|
||||
if len(rows) > 100:
|
||||
msgprint(_("""Sorry! We can only allow upto 100 rows for Stock Reconciliation."""),
|
||||
raise_exception=True)
|
||||
|
||||
for row_num, row in enumerate(rows):
|
||||
# find duplicates
|
||||
if [row[0], row[1]] in item_warehouse_combinations:
|
||||
@@ -88,7 +88,7 @@ class DocType(StockController):
|
||||
msgprint(msg)
|
||||
|
||||
raise webnotes.ValidationError
|
||||
|
||||
|
||||
def validate_item(self, item_code, row_num):
|
||||
from stock.utils import validate_end_of_life, validate_is_stock_item, \
|
||||
validate_cancelled_item
|
||||
@@ -244,33 +244,26 @@ class DocType(StockController):
|
||||
"voucher_no": self.doc.name,
|
||||
"company": self.doc.company,
|
||||
"stock_uom": webnotes.conn.get_value("Item", row.item_code, "stock_uom"),
|
||||
"is_cancelled": "No",
|
||||
"voucher_detail_no": row.voucher_detail_no,
|
||||
"fiscal_year": self.doc.fiscal_year,
|
||||
})
|
||||
args.update(opts)
|
||||
# create stock ledger entry
|
||||
sle_wrapper = webnotes.bean([args])
|
||||
sle_wrapper.ignore_permissions = 1
|
||||
sle_wrapper.insert()
|
||||
|
||||
# update bin
|
||||
webnotes.get_obj('Warehouse', row.warehouse).update_bin(args)
|
||||
|
||||
self.make_sl_entries([args])
|
||||
|
||||
# append to entries
|
||||
self.entries.append(args)
|
||||
|
||||
def delete_stock_ledger_entries(self):
|
||||
""" Delete Stock Ledger Entries related to this Stock Reconciliation
|
||||
def delete_and_repost_sle(self):
|
||||
""" Delete Stock Ledger Entries related to this voucher
|
||||
and repost future Stock Ledger Entries"""
|
||||
|
||||
existing_entries = webnotes.conn.sql("""select item_code, warehouse
|
||||
from `tabStock Ledger Entry` where voucher_type='Stock Reconciliation'
|
||||
and voucher_no=%s""", self.doc.name, as_dict=1)
|
||||
|
||||
existing_entries = webnotes.conn.sql("""select distinct item_code, warehouse
|
||||
from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s""",
|
||||
(self.doc.doctype, self.doc.name), as_dict=1)
|
||||
|
||||
# delete entries
|
||||
webnotes.conn.sql("""delete from `tabStock Ledger Entry`
|
||||
where voucher_type='Stock Reconciliation' and voucher_no=%s""", self.doc.name)
|
||||
where voucher_type=%s and voucher_no=%s""", (self.doc.doctype, self.doc.name))
|
||||
|
||||
# repost future entries for selected item_code, warehouse
|
||||
for entries in existing_entries:
|
||||
@@ -281,36 +274,27 @@ class DocType(StockController):
|
||||
"posting_time": self.doc.posting_time
|
||||
})
|
||||
|
||||
def set_stock_value_difference(self):
|
||||
"""stock_value_difference is the increment in the stock value"""
|
||||
from stock.utils import get_buying_amount
|
||||
def get_gl_entries_for_stock(self, warehouse_account=None):
|
||||
if not self.doc.cost_center:
|
||||
msgprint(_("Please enter Cost Center"), raise_exception=1)
|
||||
|
||||
return super(DocType, self).get_gl_entries_for_stock(warehouse_account,
|
||||
self.doc.expense_account, self.doc.cost_center)
|
||||
|
||||
item_list = [d.item_code for d in self.entries]
|
||||
warehouse_list = [d.warehouse for d in self.entries]
|
||||
if not (item_list and warehouse_list):
|
||||
webnotes.throw(_("Invalid Item or Warehouse Data"))
|
||||
|
||||
stock_ledger_entries = self.get_stock_ledger_entries(item_list, warehouse_list)
|
||||
|
||||
self.doc.stock_value_difference = 0.0
|
||||
for d in self.entries:
|
||||
self.doc.stock_value_difference -= get_buying_amount(self.doc.doctype, self.doc.name,
|
||||
d.voucher_detail_no, stock_ledger_entries.get((d.item_code, d.warehouse), []))
|
||||
webnotes.conn.set(self.doc, "stock_value_difference", self.doc.stock_value_difference)
|
||||
|
||||
def make_gl_entries(self):
|
||||
if not cint(webnotes.defaults.get_global_default("auto_inventory_accounting")):
|
||||
|
||||
def validate_expense_account(self):
|
||||
if not cint(webnotes.defaults.get_global_default("auto_accounting_for_stock")):
|
||||
return
|
||||
|
||||
|
||||
if not self.doc.expense_account:
|
||||
msgprint(_("Please enter Expense Account"), raise_exception=1)
|
||||
|
||||
from accounts.general_ledger import make_gl_entries
|
||||
|
||||
gl_entries = self.get_gl_entries_for_stock(self.doc.expense_account,
|
||||
self.doc.stock_value_difference)
|
||||
if gl_entries:
|
||||
make_gl_entries(gl_entries, cancel=self.doc.docstatus == 2)
|
||||
elif not webnotes.conn.sql("""select * from `tabStock Ledger Entry`"""):
|
||||
if webnotes.conn.get_value("Account", self.doc.expense_account,
|
||||
"is_pl_account") == "Yes":
|
||||
msgprint(_("""Expense Account can not be a PL Account, as this stock \
|
||||
reconciliation is an opening entry. \
|
||||
Please select 'Temporary Account (Liabilities)' or relevant account"""),
|
||||
raise_exception=1)
|
||||
|
||||
@webnotes.whitelist()
|
||||
def upload():
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{
|
||||
"creation": "2013-03-28 10:35:31",
|
||||
"docstatus": 0,
|
||||
"modified": "2013-07-22 15:22:44",
|
||||
"modified": "2013-09-24 15:35:12",
|
||||
"modified_by": "Administrator",
|
||||
"owner": "Administrator"
|
||||
},
|
||||
@@ -102,13 +102,20 @@
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:sys_defaults.auto_inventory_accounting",
|
||||
"depends_on": "eval:sys_defaults.auto_accounting_for_stock",
|
||||
"doctype": "DocField",
|
||||
"fieldname": "expense_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Expense Account",
|
||||
"label": "Difference Account",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"label": "Cost Center",
|
||||
"options": "Cost Center"
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "col1",
|
||||
@@ -148,15 +155,6 @@
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "stock_value_difference",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Stock Value Difference",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"doctype": "DocPerm"
|
||||
}
|
||||
|
||||
@@ -8,11 +8,12 @@ from __future__ import unicode_literals
|
||||
import webnotes, unittest
|
||||
from webnotes.utils import flt
|
||||
import json
|
||||
from accounts.utils import get_fiscal_year
|
||||
from accounts.utils import get_fiscal_year, get_stock_and_account_difference, get_balance_on
|
||||
|
||||
|
||||
class TestStockReconciliation(unittest.TestCase):
|
||||
def test_reco_for_fifo(self):
|
||||
webnotes.defaults.set_global_default("auto_inventory_accounting", 0)
|
||||
webnotes.defaults.set_global_default("auto_accounting_for_stock", 0)
|
||||
# [[qty, valuation_rate, posting_date,
|
||||
# posting_time, expected_stock_value, bin_qty, bin_valuation]]
|
||||
input_data = [
|
||||
@@ -56,7 +57,7 @@ class TestStockReconciliation(unittest.TestCase):
|
||||
|
||||
|
||||
def test_reco_for_moving_average(self):
|
||||
webnotes.defaults.set_global_default("auto_inventory_accounting", 0)
|
||||
webnotes.defaults.set_global_default("auto_accounting_for_stock", 0)
|
||||
# [[qty, valuation_rate, posting_date,
|
||||
# posting_time, expected_stock_value, bin_qty, bin_valuation]]
|
||||
input_data = [
|
||||
@@ -102,42 +103,40 @@ class TestStockReconciliation(unittest.TestCase):
|
||||
self.assertFalse(gl_entries)
|
||||
|
||||
def test_reco_fifo_gl_entries(self):
|
||||
webnotes.defaults.set_global_default("auto_inventory_accounting", 1)
|
||||
webnotes.defaults.set_global_default("auto_accounting_for_stock", 1)
|
||||
|
||||
# [[qty, valuation_rate, posting_date,
|
||||
# posting_time, stock_in_hand_debit]]
|
||||
# [[qty, valuation_rate, posting_date, posting_time, stock_in_hand_debit]]
|
||||
input_data = [
|
||||
[50, 1000, "2012-12-26", "12:00", 38000],
|
||||
[5, 1000, "2012-12-26", "12:00", -7000],
|
||||
[15, 1000, "2012-12-26", "12:00", 3000],
|
||||
[25, 900, "2012-12-26", "12:00", 10500],
|
||||
[20, 500, "2012-12-26", "12:00", -2000],
|
||||
["", 1000, "2012-12-26", "12:05", 3000],
|
||||
[20, "", "2012-12-26", "12:05", 4000],
|
||||
[10, 2000, "2012-12-26", "12:10", 8000],
|
||||
[0, "", "2012-12-26", "12:10", -12000],
|
||||
[50, 1000, "2013-01-01", "12:00", 50000],
|
||||
[5, 1000, "2013-01-01", "12:00", 5000],
|
||||
[1, 1000, "2012-12-01", "00:00", 1000],
|
||||
|
||||
[50, 1000, "2012-12-26", "12:00"],
|
||||
[5, 1000, "2012-12-26", "12:00"],
|
||||
[15, 1000, "2012-12-26", "12:00"],
|
||||
[25, 900, "2012-12-26", "12:00"],
|
||||
[20, 500, "2012-12-26", "12:00"],
|
||||
["", 1000, "2012-12-26", "12:05"],
|
||||
[20, "", "2012-12-26", "12:05"],
|
||||
[10, 2000, "2012-12-26", "12:10"],
|
||||
[0, "", "2012-12-26", "12:10"],
|
||||
[50, 1000, "2013-01-01", "12:00"],
|
||||
[5, 1000, "2013-01-01", "12:00"],
|
||||
[1, 1000, "2012-12-01", "00:00"],
|
||||
]
|
||||
|
||||
for d in input_data:
|
||||
self.cleanup_data()
|
||||
self.insert_existing_sle("FIFO")
|
||||
self.assertFalse(get_stock_and_account_difference(["_Test Account Stock In Hand - _TC"]))
|
||||
stock_reco = self.submit_stock_reconciliation(d[0], d[1], d[2], d[3])
|
||||
|
||||
# check gl_entries
|
||||
self.check_gl_entries(stock_reco.doc.name, d[4])
|
||||
|
||||
# cancel
|
||||
self.assertFalse(get_stock_and_account_difference(["_Test Account Stock In Hand - _TC"]))
|
||||
|
||||
stock_reco.cancel()
|
||||
self.check_gl_entries(stock_reco.doc.name, -d[4], True)
|
||||
self.assertFalse(get_stock_and_account_difference(["_Test Account Stock In Hand - _TC"]))
|
||||
|
||||
webnotes.defaults.set_global_default("auto_inventory_accounting", 0)
|
||||
webnotes.defaults.set_global_default("auto_accounting_for_stock", 0)
|
||||
|
||||
def test_reco_moving_average_gl_entries(self):
|
||||
webnotes.defaults.set_global_default("auto_inventory_accounting", 1)
|
||||
webnotes.defaults.set_global_default("auto_accounting_for_stock", 1)
|
||||
|
||||
# [[qty, valuation_rate, posting_date,
|
||||
# posting_time, stock_in_hand_debit]]
|
||||
@@ -161,20 +160,19 @@ class TestStockReconciliation(unittest.TestCase):
|
||||
self.cleanup_data()
|
||||
self.insert_existing_sle("Moving Average")
|
||||
stock_reco = self.submit_stock_reconciliation(d[0], d[1], d[2], d[3])
|
||||
|
||||
# check gl_entries
|
||||
self.check_gl_entries(stock_reco.doc.name, d[4])
|
||||
self.assertFalse(get_stock_and_account_difference(["_Test Warehouse - _TC"]))
|
||||
|
||||
# cancel
|
||||
stock_reco.cancel()
|
||||
self.check_gl_entries(stock_reco.doc.name, -d[4], True)
|
||||
self.assertFalse(get_stock_and_account_difference(["_Test Warehouse - _TC"]))
|
||||
|
||||
webnotes.defaults.set_global_default("auto_inventory_accounting", 0)
|
||||
webnotes.defaults.set_global_default("auto_accounting_for_stock", 0)
|
||||
|
||||
|
||||
def cleanup_data(self):
|
||||
webnotes.conn.sql("delete from `tabStock Ledger Entry`")
|
||||
webnotes.conn.sql("delete from tabBin")
|
||||
webnotes.conn.sql("delete from `tabGL Entry`")
|
||||
|
||||
def submit_stock_reconciliation(self, qty, rate, posting_date, posting_time):
|
||||
stock_reco = webnotes.bean([{
|
||||
@@ -184,6 +182,7 @@ class TestStockReconciliation(unittest.TestCase):
|
||||
"fiscal_year": get_fiscal_year(posting_date)[0],
|
||||
"company": "_Test Company",
|
||||
"expense_account": "Stock Adjustment - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"reconciliation_json": json.dumps([
|
||||
["Item Code", "Warehouse", "Quantity", "Valuation Rate"],
|
||||
["_Test Item", "_Test Warehouse - _TC", qty, rate]
|
||||
@@ -193,82 +192,82 @@ class TestStockReconciliation(unittest.TestCase):
|
||||
stock_reco.submit()
|
||||
return stock_reco
|
||||
|
||||
def check_gl_entries(self, voucher_no, stock_value_diff, cancel=None):
|
||||
stock_in_hand_account = webnotes.conn.get_value("Company", "_Test Company",
|
||||
"stock_in_hand_account")
|
||||
debit_amount = stock_value_diff > 0 and stock_value_diff or 0.0
|
||||
credit_amount = stock_value_diff < 0 and abs(stock_value_diff) or 0.0
|
||||
|
||||
expected_gl_entries = sorted([
|
||||
[stock_in_hand_account, debit_amount, credit_amount],
|
||||
["Stock Adjustment - _TC", credit_amount, debit_amount]
|
||||
])
|
||||
if cancel:
|
||||
expected_gl_entries = sorted([
|
||||
[stock_in_hand_account, debit_amount, credit_amount],
|
||||
["Stock Adjustment - _TC", credit_amount, debit_amount],
|
||||
[stock_in_hand_account, credit_amount, debit_amount],
|
||||
["Stock Adjustment - _TC", debit_amount, credit_amount]
|
||||
])
|
||||
|
||||
gl_entries = webnotes.conn.sql("""select account, debit, credit
|
||||
from `tabGL Entry` where voucher_type='Stock Reconciliation' and voucher_no=%s
|
||||
order by account asc, debit asc""", voucher_no, as_dict=1)
|
||||
self.assertTrue(gl_entries)
|
||||
|
||||
for i, gle in enumerate(gl_entries):
|
||||
self.assertEquals(expected_gl_entries[i][0], gle.account)
|
||||
self.assertEquals(expected_gl_entries[i][1], gle.debit)
|
||||
self.assertEquals(expected_gl_entries[i][2], gle.credit)
|
||||
|
||||
def insert_existing_sle(self, valuation_method):
|
||||
webnotes.conn.set_value("Item", "_Test Item", "valuation_method", valuation_method)
|
||||
webnotes.conn.set_default("allow_negative_stock", 1)
|
||||
|
||||
existing_ledgers = [
|
||||
stock_entry = [
|
||||
{
|
||||
"doctype": "Stock Ledger Entry", "__islocal": 1,
|
||||
"voucher_type": "Stock Entry", "voucher_no": "TEST",
|
||||
"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC",
|
||||
"posting_date": "2012-12-12", "posting_time": "01:00",
|
||||
"actual_qty": 20, "incoming_rate": 1000, "company": "_Test Company",
|
||||
"fiscal_year": "_Test Fiscal Year 2012",
|
||||
},
|
||||
"company": "_Test Company",
|
||||
"doctype": "Stock Entry",
|
||||
"posting_date": "2012-12-12",
|
||||
"posting_time": "01:00",
|
||||
"purpose": "Material Receipt",
|
||||
"fiscal_year": "_Test Fiscal Year 2012",
|
||||
},
|
||||
{
|
||||
"doctype": "Stock Ledger Entry", "__islocal": 1,
|
||||
"voucher_type": "Stock Entry", "voucher_no": "TEST",
|
||||
"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC",
|
||||
"posting_date": "2012-12-15", "posting_time": "02:00",
|
||||
"actual_qty": 10, "incoming_rate": 700, "company": "_Test Company",
|
||||
"fiscal_year": "_Test Fiscal Year 2012",
|
||||
},
|
||||
{
|
||||
"doctype": "Stock Ledger Entry", "__islocal": 1,
|
||||
"voucher_type": "Stock Entry", "voucher_no": "TEST",
|
||||
"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC",
|
||||
"posting_date": "2012-12-25", "posting_time": "03:00",
|
||||
"actual_qty": -15, "company": "_Test Company",
|
||||
"fiscal_year": "_Test Fiscal Year 2012",
|
||||
},
|
||||
{
|
||||
"doctype": "Stock Ledger Entry", "__islocal": 1,
|
||||
"voucher_type": "Stock Entry", "voucher_no": "TEST",
|
||||
"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC",
|
||||
"posting_date": "2012-12-31", "posting_time": "08:00",
|
||||
"actual_qty": -20, "company": "_Test Company",
|
||||
"fiscal_year": "_Test Fiscal Year 2012",
|
||||
},
|
||||
{
|
||||
"doctype": "Stock Ledger Entry", "__islocal": 1,
|
||||
"voucher_type": "Stock Entry", "voucher_no": "TEST",
|
||||
"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC",
|
||||
"posting_date": "2013-01-05", "posting_time": "07:00",
|
||||
"actual_qty": 15, "incoming_rate": 1200, "company": "_Test Company",
|
||||
"fiscal_year": "_Test Fiscal Year 2013",
|
||||
},
|
||||
"conversion_factor": 1.0,
|
||||
"doctype": "Stock Entry Detail",
|
||||
"item_code": "_Test Item",
|
||||
"parentfield": "mtn_details",
|
||||
"incoming_rate": 1000,
|
||||
"qty": 20.0,
|
||||
"stock_uom": "_Test UOM",
|
||||
"transfer_qty": 20.0,
|
||||
"uom": "_Test UOM",
|
||||
"t_warehouse": "_Test Warehouse - _TC",
|
||||
"expense_account": "Stock Adjustment - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC"
|
||||
},
|
||||
]
|
||||
|
||||
pr = webnotes.bean(copy=stock_entry)
|
||||
pr.insert()
|
||||
pr.submit()
|
||||
|
||||
pr1 = webnotes.bean(copy=stock_entry)
|
||||
pr1.doc.posting_date = "2012-12-15"
|
||||
pr1.doc.posting_time = "02:00"
|
||||
pr1.doclist[1].qty = 10
|
||||
pr1.doclist[1].transfer_qty = 10
|
||||
pr1.doclist[1].incoming_rate = 700
|
||||
pr1.insert()
|
||||
pr1.submit()
|
||||
|
||||
pr2 = webnotes.bean(copy=stock_entry)
|
||||
pr2.doc.posting_date = "2012-12-25"
|
||||
pr2.doc.posting_time = "03:00"
|
||||
pr2.doc.purpose = "Material Issue"
|
||||
pr2.doclist[1].s_warehouse = "_Test Warehouse - _TC"
|
||||
pr2.doclist[1].t_warehouse = None
|
||||
pr2.doclist[1].qty = 15
|
||||
pr2.doclist[1].transfer_qty = 15
|
||||
pr2.doclist[1].incoming_rate = 0
|
||||
pr2.insert()
|
||||
pr2.submit()
|
||||
|
||||
pr3 = webnotes.bean(copy=stock_entry)
|
||||
pr3.doc.posting_date = "2012-12-31"
|
||||
pr3.doc.posting_time = "08:00"
|
||||
pr3.doc.purpose = "Material Issue"
|
||||
pr3.doclist[1].s_warehouse = "_Test Warehouse - _TC"
|
||||
pr3.doclist[1].t_warehouse = None
|
||||
pr3.doclist[1].qty = 20
|
||||
pr3.doclist[1].transfer_qty = 20
|
||||
pr3.doclist[1].incoming_rate = 0
|
||||
pr3.insert()
|
||||
pr3.submit()
|
||||
|
||||
|
||||
pr4 = webnotes.bean(copy=stock_entry)
|
||||
pr4.doc.posting_date = "2013-01-05"
|
||||
pr4.doc.fiscal_year = "_Test Fiscal Year 2013"
|
||||
pr4.doc.posting_time = "07:00"
|
||||
pr4.doclist[1].qty = 15
|
||||
pr4.doclist[1].transfer_qty = 15
|
||||
pr4.doclist[1].incoming_rate = 1200
|
||||
pr4.insert()
|
||||
pr4.submit()
|
||||
|
||||
webnotes.get_obj("Stock Ledger").update_stock(existing_ledgers)
|
||||
|
||||
|
||||
test_dependencies = ["Item", "Warehouse"]
|
||||
@@ -5,16 +5,19 @@ test_records = [
|
||||
[{
|
||||
"doctype": "Warehouse",
|
||||
"warehouse_name": "_Test Warehouse",
|
||||
"company": "_Test Company"
|
||||
"company": "_Test Company",
|
||||
"create_account_under": "Stock Assets - _TC"
|
||||
}],
|
||||
[{
|
||||
"doctype": "Warehouse",
|
||||
"warehouse_name": "_Test Warehouse 1",
|
||||
"company": "_Test Company"
|
||||
"company": "_Test Company",
|
||||
"create_account_under": "Fixed Assets - _TC"
|
||||
}],
|
||||
[{
|
||||
"doctype": "Warehouse",
|
||||
"warehouse_name": "_Test Warehouse 2",
|
||||
"create_account_under": "Stock Assets - _TC",
|
||||
"company": "_Test Company 1"
|
||||
}, {
|
||||
"doctype": "Warehouse User",
|
||||
|
||||
@@ -15,4 +15,14 @@ cur_frm.cscript.merge = function(doc, cdt, cdn) {
|
||||
if (check) {
|
||||
return $c_obj(make_doclist(cdt, cdn), 'merge_warehouses', '', '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cur_frm.set_query("create_account_under", function() {
|
||||
return {
|
||||
filters: {
|
||||
"company": cur_frm.doc.company,
|
||||
"debit_or_credit": "Debit",
|
||||
'group_or_ledger': "Group"
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
from __future__ import unicode_literals
|
||||
import webnotes
|
||||
|
||||
from webnotes.utils import flt, validate_email_add
|
||||
from webnotes.utils import cint, flt, validate_email_add
|
||||
from webnotes.model.code import get_obj
|
||||
from webnotes import msgprint
|
||||
from webnotes import msgprint, _
|
||||
|
||||
|
||||
class DocType:
|
||||
@@ -18,39 +18,56 @@ class DocType:
|
||||
suffix = " - " + webnotes.conn.get_value("Company", self.doc.company, "abbr")
|
||||
if not self.doc.warehouse_name.endswith(suffix):
|
||||
self.doc.name = self.doc.warehouse_name + suffix
|
||||
|
||||
def get_bin(self, item_code, warehouse=None):
|
||||
warehouse = warehouse or self.doc.name
|
||||
bin = webnotes.conn.sql("select name from tabBin where item_code = %s and \
|
||||
warehouse = %s", (item_code, warehouse))
|
||||
bin = bin and bin[0][0] or ''
|
||||
if not bin:
|
||||
bin_wrapper = webnotes.bean([{
|
||||
"doctype": "Bin",
|
||||
"item_code": item_code,
|
||||
"warehouse": warehouse,
|
||||
}])
|
||||
bin_wrapper.ignore_permissions = 1
|
||||
bin_wrapper.insert()
|
||||
|
||||
bin_obj = bin_wrapper.make_controller()
|
||||
else:
|
||||
bin_obj = get_obj('Bin', bin)
|
||||
return bin_obj
|
||||
|
||||
def update_bin(self, args):
|
||||
is_stock_item = webnotes.conn.get_value('Item', args.get("item_code"), 'is_stock_item')
|
||||
if is_stock_item == 'Yes':
|
||||
bin = self.get_bin(args.get("item_code"))
|
||||
bin.update_stock(args)
|
||||
return bin
|
||||
else:
|
||||
msgprint("[Stock Update] Ignored %s since it is not a stock item"
|
||||
% args.get("item_code"))
|
||||
|
||||
def validate(self):
|
||||
if self.doc.email_id and not validate_email_add(self.doc.email_id):
|
||||
msgprint("Please enter valid Email Id", raise_exception=1)
|
||||
|
||||
def on_update(self):
|
||||
self.create_account_head()
|
||||
|
||||
def create_account_head(self):
|
||||
if cint(webnotes.defaults.get_global_default("auto_accounting_for_stock")):
|
||||
if not webnotes.conn.get_value("Account", {"account_type": "Warehouse",
|
||||
"master_name": self.doc.name}) and not webnotes.conn.get_value("Account",
|
||||
{"account_name": self.doc.warehouse_name}):
|
||||
if self.doc.__islocal or not webnotes.conn.get_value("Stock Ledger Entry",
|
||||
{"warehouse": self.doc.name}):
|
||||
self.validate_parent_account()
|
||||
ac_bean = webnotes.bean({
|
||||
"doctype": "Account",
|
||||
'account_name': self.doc.warehouse_name,
|
||||
'parent_account': self.doc.create_account_under,
|
||||
'group_or_ledger':'Ledger',
|
||||
'company':self.doc.company,
|
||||
"account_type": "Warehouse",
|
||||
"master_name": self.doc.name,
|
||||
"freeze_account": "No"
|
||||
})
|
||||
ac_bean.ignore_permissions = True
|
||||
ac_bean.insert()
|
||||
|
||||
msgprint(_("Account Head") + ": " + ac_bean.doc.name + _(" created"))
|
||||
|
||||
def validate_parent_account(self):
|
||||
if not self.doc.create_account_under:
|
||||
parent_account = webnotes.conn.get_value("Account",
|
||||
{"account_name": "Stock Assets", "company": self.doc.company})
|
||||
if parent_account:
|
||||
self.doc.create_account_under = parent_account
|
||||
else:
|
||||
webnotes.throw(_("Please enter account group under which account \
|
||||
for warehouse ") + self.doc.name +_(" will be created"))
|
||||
|
||||
def on_rename(self, new, old, merge=False):
|
||||
webnotes.conn.set_value("Account", {"account_type": "Warehouse", "master_name": old},
|
||||
"master_name", new)
|
||||
|
||||
if merge:
|
||||
from stock.stock_ledger import update_entries_after
|
||||
for item_code in webnotes.conn.sql("""select item_code from `tabBin`
|
||||
where warehouse=%s""", new):
|
||||
update_entries_after({"item_code": item_code, "warehouse": new})
|
||||
|
||||
def merge_warehouses(self):
|
||||
webnotes.conn.auto_commit_on_many_writes = 1
|
||||
@@ -65,6 +82,15 @@ class DocType:
|
||||
link_fields = rename_doc.get_link_fields('Warehouse')
|
||||
rename_doc.update_link_field_values(link_fields, self.doc.name, self.doc.merge_with)
|
||||
|
||||
account_link_fields = rename_doc.get_link_fields('Account')
|
||||
old_warehouse_account = webnotes.conn.get_value("Account", {"master_name": self.doc.name})
|
||||
new_warehouse_account = webnotes.conn.get_value("Account",
|
||||
{"master_name": self.doc.merge_with})
|
||||
rename_doc.update_link_field_values(account_link_fields, old_warehouse_account,
|
||||
new_warehouse_account)
|
||||
|
||||
webnotes.conn.delete_doc("Account", old_warehouse_account)
|
||||
|
||||
for item_code in items:
|
||||
self.repost(item_code[0], self.doc.merge_with)
|
||||
|
||||
@@ -75,9 +101,10 @@ class DocType:
|
||||
|
||||
|
||||
def repost(self, item_code, warehouse=None):
|
||||
from stock.utils import get_bin
|
||||
self.repost_actual_qty(item_code, warehouse)
|
||||
|
||||
bin = self.get_bin(item_code, warehouse)
|
||||
bin = get_bin(item_code, warehouse)
|
||||
self.repost_reserved_qty(bin)
|
||||
self.repost_indented_qty(bin)
|
||||
self.repost_ordered_qty(bin)
|
||||
@@ -172,17 +199,15 @@ class DocType:
|
||||
else:
|
||||
webnotes.conn.sql("delete from `tabBin` where name = %s", d['name'])
|
||||
|
||||
warehouse_account = webnotes.conn.get_value("Account",
|
||||
{"account_type": "Warehosue", "master_name": self.doc.name})
|
||||
if warehouse_account:
|
||||
webnotes.delete_doc("Account", warehouse_account)
|
||||
|
||||
# delete cancelled sle
|
||||
if webnotes.conn.sql("""select name from `tabStock Ledger Entry`
|
||||
where warehouse = %s and ifnull('is_cancelled', '') = 'No'""", self.doc.name):
|
||||
if webnotes.conn.sql("""select name from `tabStock Ledger Entry` where warehouse = %s""", self.doc.name):
|
||||
msgprint("""Warehosue can not be deleted as stock ledger entry
|
||||
exists for this warehouse.""", raise_exception=1)
|
||||
else:
|
||||
webnotes.conn.sql("delete from `tabStock Ledger Entry` where warehouse = %s", self.doc.name)
|
||||
|
||||
def on_rename(self, newdn, olddn, merge=False):
|
||||
if merge:
|
||||
from stock.stock_ledger import update_entries_after
|
||||
for item_code in webnotes.conn.sql("""select item_code from `tabBin`
|
||||
where warehouse=%s""", newdn):
|
||||
update_entries_after({"item_code": item_code, "warehouse": newdn})
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{
|
||||
"creation": "2013-03-07 18:50:32",
|
||||
"docstatus": 0,
|
||||
"modified": "2013-07-23 12:01:16",
|
||||
"modified": "2013-09-16 10:45:49",
|
||||
"modified_by": "Administrator",
|
||||
"owner": "Administrator"
|
||||
},
|
||||
@@ -20,8 +20,7 @@
|
||||
"name": "__common__",
|
||||
"parent": "Warehouse",
|
||||
"parentfield": "fields",
|
||||
"parenttype": "DocType",
|
||||
"read_only": 0
|
||||
"parenttype": "DocType"
|
||||
},
|
||||
{
|
||||
"doctype": "DocPerm",
|
||||
@@ -43,7 +42,8 @@
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Warehouse Detail",
|
||||
"oldfieldtype": "Section Break",
|
||||
"permlevel": 0
|
||||
"permlevel": 0,
|
||||
"read_only": 0
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
@@ -53,6 +53,7 @@
|
||||
"oldfieldname": "warehouse_name",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@@ -65,14 +66,26 @@
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Company",
|
||||
"permlevel": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:sys_defaults.auto_accounting_for_stock",
|
||||
"description": "Account for the warehouse (Perpetual Inventory) will be created under this Account.",
|
||||
"doctype": "DocField",
|
||||
"fieldname": "create_account_under",
|
||||
"fieldtype": "Link",
|
||||
"label": "Create Account Under",
|
||||
"options": "Account",
|
||||
"permlevel": 0
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Section Break",
|
||||
"permlevel": 0
|
||||
"permlevel": 0,
|
||||
"read_only": 0
|
||||
},
|
||||
{
|
||||
"description": "If set, data entry is only allowed for specified users. Else, entry is allowed for all users with requisite permissions.",
|
||||
@@ -81,7 +94,8 @@
|
||||
"fieldtype": "Table",
|
||||
"label": "Warehouse Users",
|
||||
"options": "Warehouse User",
|
||||
"permlevel": 0
|
||||
"permlevel": 0,
|
||||
"read_only": 0
|
||||
},
|
||||
{
|
||||
"description": "For Reference Only.",
|
||||
@@ -89,7 +103,8 @@
|
||||
"fieldname": "warehouse_contact_info",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Warehouse Contact Info",
|
||||
"permlevel": 0
|
||||
"permlevel": 0,
|
||||
"read_only": 0
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
@@ -100,7 +115,8 @@
|
||||
"oldfieldname": "email_id",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0
|
||||
"print_hide": 0,
|
||||
"read_only": 0
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
@@ -110,7 +126,8 @@
|
||||
"oldfieldname": "phone_no",
|
||||
"oldfieldtype": "Int",
|
||||
"options": "Phone",
|
||||
"permlevel": 0
|
||||
"permlevel": 0,
|
||||
"read_only": 0
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
@@ -120,14 +137,16 @@
|
||||
"oldfieldname": "mobile_no",
|
||||
"oldfieldtype": "Int",
|
||||
"options": "Phone",
|
||||
"permlevel": 0
|
||||
"permlevel": 0,
|
||||
"read_only": 0
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "column_break0",
|
||||
"fieldtype": "Column Break",
|
||||
"oldfieldtype": "Column Break",
|
||||
"permlevel": 0
|
||||
"permlevel": 0,
|
||||
"read_only": 0
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
@@ -136,7 +155,8 @@
|
||||
"label": "Address Line 1",
|
||||
"oldfieldname": "address_line_1",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0
|
||||
"permlevel": 0,
|
||||
"read_only": 0
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
@@ -145,7 +165,8 @@
|
||||
"label": "Address Line 2",
|
||||
"oldfieldname": "address_line_2",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0
|
||||
"permlevel": 0,
|
||||
"read_only": 0
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
@@ -156,6 +177,7 @@
|
||||
"oldfieldname": "city",
|
||||
"oldfieldtype": "Data",
|
||||
"permlevel": 0,
|
||||
"read_only": 0,
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
@@ -166,7 +188,8 @@
|
||||
"oldfieldname": "state",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "Suggest",
|
||||
"permlevel": 0
|
||||
"permlevel": 0,
|
||||
"read_only": 0
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
@@ -175,7 +198,8 @@
|
||||
"label": "PIN",
|
||||
"oldfieldname": "pin",
|
||||
"oldfieldtype": "Int",
|
||||
"permlevel": 0
|
||||
"permlevel": 0,
|
||||
"read_only": 0
|
||||
},
|
||||
{
|
||||
"description": "This feature is for merging duplicate warehouses. It will replace all the links of this warehouse by \"Merge Into\" warehouse. After merging you can delete this warehouse, as stock level for this warehouse will be zero.",
|
||||
@@ -183,7 +207,8 @@
|
||||
"fieldname": "merge_warehouses_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Merge Warehouses",
|
||||
"permlevel": 2
|
||||
"permlevel": 2,
|
||||
"read_only": 0
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
@@ -191,14 +216,16 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Merge Into",
|
||||
"options": "Warehouse",
|
||||
"permlevel": 2
|
||||
"permlevel": 2,
|
||||
"read_only": 0
|
||||
},
|
||||
{
|
||||
"doctype": "DocField",
|
||||
"fieldname": "merge",
|
||||
"fieldtype": "Button",
|
||||
"label": "Merge",
|
||||
"permlevel": 2
|
||||
"permlevel": 2,
|
||||
"read_only": 0
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
|
||||
@@ -201,9 +201,9 @@ wn.module_page["Stock"] = [
|
||||
doctype: "Serial No"
|
||||
},
|
||||
{
|
||||
"label":wn._("Item-Wise Price List"),
|
||||
route: "query-report/Item-Wise Price List",
|
||||
doctype: "Item"
|
||||
"label":wn._("Item-wise Price List Rate"),
|
||||
route: "Report/Price List/Item-Wise Price List",
|
||||
doctype: "Price List"
|
||||
},
|
||||
{
|
||||
"label":wn._("Purchase In Transit"),
|
||||
|
||||
@@ -52,7 +52,7 @@ def get_stock_ledger_entries(filters):
|
||||
return webnotes.conn.sql("""select item_code, batch_no, warehouse,
|
||||
posting_date, actual_qty
|
||||
from `tabStock Ledger Entry`
|
||||
where ifnull(is_cancelled, 'No') = 'No' %s order by item_code, warehouse""" %
|
||||
where docstatus < 2 %s order by item_code, warehouse""" %
|
||||
conditions, as_dict=1)
|
||||
|
||||
def get_item_warehouse_batch_map(filters):
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
[
|
||||
{
|
||||
"creation": "2013-02-22 18:01:55",
|
||||
"docstatus": 0,
|
||||
"modified": "2013-09-10 15:50:26",
|
||||
"modified_by": "Administrator",
|
||||
"owner": "Administrator"
|
||||
},
|
||||
{
|
||||
"doctype": "Report",
|
||||
"is_standard": "Yes",
|
||||
"name": "__common__",
|
||||
"query": "select\n item.name as \"ID:Link/Item:120\", \n item.item_name as \"Item Name::120\", \n item_price.parent as \"Price List::80\",\n price_list.currency as \"Currency::40\", \n item_price.ref_rate as \"Rate:Float:80\",\n item.description as \"Description::160\",\n item.item_group as \"Item Group:Link/Item Group:100\",\n item.brand as \"Brand::100\"\nfrom `tabItem` item, `tabItem Price` item_price, `tabPrice List` price_list\nwhere\n item_price.item_code = item.name and\n item_price.parent = price_list.name",
|
||||
"ref_doctype": "Item",
|
||||
"report_name": "Item-Wise Price List",
|
||||
"report_type": "Query Report"
|
||||
},
|
||||
{
|
||||
"doctype": "Report",
|
||||
"name": "Item-Wise Price List"
|
||||
}
|
||||
]
|
||||
@@ -2,14 +2,14 @@
|
||||
{
|
||||
"creation": "2013-01-14 15:26:21",
|
||||
"docstatus": 0,
|
||||
"modified": "2013-02-22 15:53:01",
|
||||
"modified": "2013-08-20 11:53:43",
|
||||
"modified_by": "Administrator",
|
||||
"owner": "Administrator"
|
||||
},
|
||||
{
|
||||
"doctype": "Report",
|
||||
"is_standard": "Yes",
|
||||
"json": "{\"filters\":[[\"Stock Ledger Entry\",\"is_cancelled\",\"=\",\"No\"]],\"columns\":[[\"item_code\",\"Stock Ledger Entry\"],[\"warehouse\",\"Stock Ledger Entry\"],[\"posting_date\",\"Stock Ledger Entry\"],[\"posting_time\",\"Stock Ledger Entry\"],[\"actual_qty\",\"Stock Ledger Entry\"],[\"qty_after_transaction\",\"Stock Ledger Entry\"],[\"voucher_type\",\"Stock Ledger Entry\"],[\"voucher_no\",\"Stock Ledger Entry\"]],\"sort_by\":\"Stock Ledger Entry.posting_date\",\"sort_order\":\"desc\",\"sort_by_next\":\"Stock Ledger Entry.posting_time\",\"sort_order_next\":\"desc\"}",
|
||||
"json": "{\"filters\":[],\"columns\":[[\"item_code\",\"Stock Ledger Entry\"],[\"warehouse\",\"Stock Ledger Entry\"],[\"posting_date\",\"Stock Ledger Entry\"],[\"posting_time\",\"Stock Ledger Entry\"],[\"actual_qty\",\"Stock Ledger Entry\"],[\"qty_after_transaction\",\"Stock Ledger Entry\"],[\"voucher_type\",\"Stock Ledger Entry\"],[\"voucher_no\",\"Stock Ledger Entry\"]],\"sort_by\":\"Stock Ledger Entry.posting_date\",\"sort_order\":\"desc\",\"sort_by_next\":\"Stock Ledger Entry.posting_time\",\"sort_order_next\":\"desc\"}",
|
||||
"name": "__common__",
|
||||
"ref_doctype": "Stock Ledger Entry",
|
||||
"report_name": "Stock Ledger",
|
||||
|
||||
@@ -52,7 +52,7 @@ def get_stock_ledger_entries(filters):
|
||||
return webnotes.conn.sql("""select item_code, warehouse,
|
||||
posting_date, actual_qty, company
|
||||
from `tabStock Ledger Entry`
|
||||
where ifnull(is_cancelled, 'No') = 'No' %s order by item_code, warehouse""" %
|
||||
where docstatus < 2 %s order by item_code, warehouse""" %
|
||||
conditions, as_dict=1)
|
||||
|
||||
def get_item_warehouse_map(filters):
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import webnotes
|
||||
from webnotes import msgprint
|
||||
from webnotes.utils import cint, flt, cstr
|
||||
from webnotes.utils import cint, flt, cstr, now
|
||||
from stock.utils import get_valuation_method
|
||||
import json
|
||||
|
||||
@@ -11,8 +11,51 @@ import json
|
||||
class NegativeStockError(webnotes.ValidationError): pass
|
||||
|
||||
_exceptions = webnotes.local('stockledger_exceptions')
|
||||
|
||||
# _exceptions = []
|
||||
|
||||
def make_sl_entries(sl_entries, is_amended=None):
|
||||
from stock.utils import update_bin
|
||||
|
||||
cancel = True if sl_entries[0].get("is_cancelled") == "Yes" else False
|
||||
if cancel:
|
||||
set_as_cancel(sl_entries[0].get('voucher_no'), sl_entries[0].get('voucher_type'))
|
||||
|
||||
for sle in sl_entries:
|
||||
sle_id = None
|
||||
if sle.get('is_cancelled') == 'Yes':
|
||||
sle['actual_qty'] = -flt(sle['actual_qty'])
|
||||
|
||||
if sle.get("actual_qty"):
|
||||
sle_id = make_entry(sle)
|
||||
|
||||
args = sle.copy()
|
||||
args.update({
|
||||
"sle_id": sle_id,
|
||||
"is_amended": is_amended
|
||||
})
|
||||
update_bin(args)
|
||||
|
||||
if cancel:
|
||||
delete_cancelled_entry(sl_entries[0].get('voucher_type'), sl_entries[0].get('voucher_no'))
|
||||
|
||||
def set_as_cancel(voucher_type, voucher_no):
|
||||
webnotes.conn.sql("""update `tabStock Ledger Entry` set is_cancelled='Yes',
|
||||
modified=%s, modified_by=%s
|
||||
where voucher_no=%s and voucher_type=%s""",
|
||||
(now(), webnotes.session.user, voucher_type, voucher_no))
|
||||
|
||||
def make_entry(args):
|
||||
args.update({"doctype": "Stock Ledger Entry"})
|
||||
sle = webnotes.bean([args])
|
||||
sle.ignore_permissions = 1
|
||||
sle.insert()
|
||||
sle.submit()
|
||||
return sle.doc.name
|
||||
|
||||
def delete_cancelled_entry(voucher_type, voucher_no):
|
||||
webnotes.conn.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):
|
||||
"""
|
||||
update valution rate and qty after transaction
|
||||
@@ -33,13 +76,15 @@ def update_entries_after(args, verbose=1):
|
||||
qty_after_transaction = flt(previous_sle.get("qty_after_transaction"))
|
||||
valuation_rate = flt(previous_sle.get("valuation_rate"))
|
||||
stock_queue = json.loads(previous_sle.get("stock_queue") or "[]")
|
||||
stock_value = 0.0
|
||||
|
||||
stock_value = flt(previous_sle.get("stock_value"))
|
||||
prev_stock_value = flt(previous_sle.get("stock_value"))
|
||||
|
||||
entries_to_fix = get_sle_after_datetime(previous_sle or \
|
||||
{"item_code": args["item_code"], "warehouse": args["warehouse"]}, for_update=True)
|
||||
|
||||
|
||||
valuation_method = get_valuation_method(args["item_code"])
|
||||
|
||||
stock_value_difference = 0.0
|
||||
|
||||
for sle in entries_to_fix:
|
||||
if sle.serial_no or not cint(webnotes.conn.get_default("allow_negative_stock")):
|
||||
# validate negative stock for serialized items, fifo valuation
|
||||
@@ -47,7 +92,7 @@ def update_entries_after(args, verbose=1):
|
||||
if not validate_negative_stock(qty_after_transaction, sle):
|
||||
qty_after_transaction += flt(sle.actual_qty)
|
||||
continue
|
||||
|
||||
|
||||
if sle.serial_no:
|
||||
valuation_rate = get_serialized_values(qty_after_transaction, sle, valuation_rate)
|
||||
elif valuation_method == "Moving Average":
|
||||
@@ -65,13 +110,16 @@ def update_entries_after(args, verbose=1):
|
||||
(qty_after_transaction * valuation_rate) or 0
|
||||
else:
|
||||
stock_value = sum((flt(batch[0]) * flt(batch[1]) for batch in stock_queue))
|
||||
# print sle.posting_date, sle.actual_qty, sle.incoming_rate, stock_queue, stock_value
|
||||
|
||||
stock_value_difference = stock_value - prev_stock_value
|
||||
prev_stock_value = stock_value
|
||||
|
||||
# update current sle
|
||||
webnotes.conn.sql("""update `tabStock Ledger Entry`
|
||||
set qty_after_transaction=%s, valuation_rate=%s, stock_queue=%s,
|
||||
stock_value=%s where name=%s""",
|
||||
stock_value=%s, stock_value_difference=%s where name=%s""",
|
||||
(qty_after_transaction, valuation_rate,
|
||||
json.dumps(stock_queue), stock_value, sle.name))
|
||||
json.dumps(stock_queue), stock_value, stock_value_difference, sle.name))
|
||||
|
||||
if _exceptions:
|
||||
_raise_exceptions(args, verbose)
|
||||
@@ -126,7 +174,7 @@ def get_stock_ledger_entries(args, conditions=None, order="desc", limit=None, fo
|
||||
return webnotes.conn.sql("""select * from `tabStock Ledger Entry`
|
||||
where item_code = %%(item_code)s
|
||||
and warehouse = %%(warehouse)s
|
||||
and ifnull(is_cancelled, 'No') = 'No'
|
||||
and ifnull(is_cancelled, 'No')='No'
|
||||
%(conditions)s
|
||||
order by timestamp(posting_date, posting_time) %(order)s, name %(order)s
|
||||
%(limit)s %(for_update)s""" % {
|
||||
|
||||
@@ -9,6 +9,59 @@ from webnotes.defaults import get_global_default
|
||||
from webnotes.utils.email_lib import sendmail
|
||||
|
||||
class UserNotAllowedForWarehouse(webnotes.ValidationError): pass
|
||||
|
||||
def get_stock_balance_on(warehouse, posting_date=None):
|
||||
if not posting_date: posting_date = nowdate()
|
||||
|
||||
stock_ledger_entries = webnotes.conn.sql("""
|
||||
SELECT
|
||||
item_code, stock_value
|
||||
FROM
|
||||
`tabStock Ledger Entry`
|
||||
WHERE
|
||||
warehouse=%s AND posting_date <= %s
|
||||
ORDER BY timestamp(posting_date, posting_time) DESC, name DESC
|
||||
""", (warehouse, posting_date), as_dict=1)
|
||||
|
||||
sle_map = {}
|
||||
for sle in stock_ledger_entries:
|
||||
sle_map.setdefault(sle.item_code, flt(sle.stock_value))
|
||||
|
||||
return sum(sle_map.values())
|
||||
|
||||
def get_latest_stock_balance():
|
||||
bin_map = {}
|
||||
for d in webnotes.conn.sql("""SELECT item_code, warehouse, stock_value as stock_value
|
||||
FROM tabBin""", as_dict=1):
|
||||
bin_map.setdefault(d.warehouse, {}).setdefault(d.item_code, flt(d.stock_value))
|
||||
|
||||
return bin_map
|
||||
|
||||
def get_bin(item_code, warehouse):
|
||||
bin = webnotes.conn.get_value("Bin", {"item_code": item_code, "warehouse": warehouse})
|
||||
if not bin:
|
||||
bin_wrapper = webnotes.bean([{
|
||||
"doctype": "Bin",
|
||||
"item_code": item_code,
|
||||
"warehouse": warehouse,
|
||||
}])
|
||||
bin_wrapper.ignore_permissions = 1
|
||||
bin_wrapper.insert()
|
||||
bin_obj = bin_wrapper.make_controller()
|
||||
else:
|
||||
from webnotes.model.code import get_obj
|
||||
bin_obj = get_obj('Bin', bin)
|
||||
return bin_obj
|
||||
|
||||
def update_bin(args):
|
||||
is_stock_item = webnotes.conn.get_value('Item', args.get("item_code"), 'is_stock_item')
|
||||
if is_stock_item == 'Yes':
|
||||
bin = get_bin(args.get("item_code"), args.get("warehouse"))
|
||||
bin.update_stock(args)
|
||||
return bin
|
||||
else:
|
||||
msgprint("[Stock Update] Ignored %s since it is not a stock item"
|
||||
% args.get("item_code"))
|
||||
|
||||
def validate_end_of_life(item_code, end_of_life=None, verbose=1):
|
||||
if not end_of_life:
|
||||
@@ -184,7 +237,6 @@ def get_buying_amount(voucher_type, voucher_no, item_row, stock_ledger_entries):
|
||||
sle.voucher_detail_no == item_row:
|
||||
previous_stock_value = len(stock_ledger_entries) > i+1 and \
|
||||
flt(stock_ledger_entries[i+1].stock_value) or 0.0
|
||||
|
||||
buying_amount = previous_stock_value - flt(sle.stock_value)
|
||||
|
||||
return buying_amount
|
||||
@@ -335,3 +387,12 @@ def notify_errors(exceptions_list):
|
||||
|
||||
from webnotes.profile import get_system_managers
|
||||
sendmail(get_system_managers(), subject=subject, msg=msg)
|
||||
|
||||
|
||||
def repost():
|
||||
"""
|
||||
Repost everything!
|
||||
"""
|
||||
from webnotes.model.code import get_obj
|
||||
for wh in webnotes.conn.sql("select name from tabWarehouse"):
|
||||
get_obj('Warehouse', wh[0]).repost_stock()
|
||||
|
||||
Reference in New Issue
Block a user