Merge remote-tracking branch 'frappe/v4.x.x' into develop

Conflicts:
	.travis.yml
	erpnext/__version__.py
	erpnext/controllers/queries.py
	erpnext/hooks.py
	erpnext/selling/sales_common.js
	erpnext/stock/doctype/bin/bin.py
	erpnext/stock/doctype/stock_entry/stock_entry.js
	erpnext/stock/doctype/stock_entry/stock_entry.py
	erpnext/stock/stock_ledger.py
	setup.py
This commit is contained in:
Anand Doshi
2015-04-06 12:59:34 +05:30
14 changed files with 105 additions and 121 deletions

View File

@@ -6,12 +6,12 @@
"doctype": "Report", "doctype": "Report",
"idx": 1, "idx": 1,
"is_standard": "Yes", "is_standard": "Yes",
"modified": "2014-06-03 07:18:17.244501", "modified": "2015-03-26 11:00:48.720037",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Order Items To Be Billed", "name": "Purchase Order Items To Be Billed",
"owner": "Administrator", "owner": "Administrator",
"query": "select \n `tabPurchase Order`.`name` as \"Purchase Order:Link/Purchase Order:120\",\n `tabPurchase Order`.`transaction_date` as \"Date:Date:100\",\n\t`tabPurchase Order`.`supplier` as \"Supplier:Link/Supplier:120\",\n\t`tabPurchase Order Item`.`project_name` as \"Project\",\n\t`tabPurchase Order Item`.item_code as \"Item Code:Link/Item:120\",\n\t`tabPurchase Order Item`.base_amount as \"Amount:Currency:100\",\n\t`tabPurchase Order Item`.billed_amt as \"Billed Amount:Currency:100\", \n\t(`tabPurchase Order Item`.base_amount - ifnull(`tabPurchase Order Item`.billed_amt, 0)) as \"Amount to Bill:Currency:100\",\n\t`tabPurchase Order Item`.item_name as \"Item Name::150\",\n\t`tabPurchase Order Item`.description as \"Description::200\"\nfrom\n\t`tabPurchase Order`, `tabPurchase Order Item`\nwhere\n\t`tabPurchase Order Item`.`parent` = `tabPurchase Order`.`name`\n\tand `tabPurchase Order`.docstatus = 1\n\tand `tabPurchase Order`.status != \"Stopped\"\n\tand ifnull(`tabPurchase Order Item`.billed_amt, 0) < ifnull(`tabPurchase Order Item`.base_amount, 0)\norder by `tabPurchase Order`.transaction_date asc", "query": "select \n `tabPurchase Order`.`name` as \"Purchase Order:Link/Purchase Order:120\",\n `tabPurchase Order`.`transaction_date` as \"Date:Date:100\",\n\t`tabPurchase Order`.`supplier` as \"Supplier:Link/Supplier:120\",\n\t`tabPurchase Order Item`.`project_name` as \"Project\",\n\t`tabPurchase Order Item`.item_code as \"Item Code:Link/Item:120\",\n\t`tabPurchase Order Item`.base_amount as \"Amount:Currency:100\",\n\t(`tabPurchase Order Item`.billed_amt * ifnull(`tabPurchase Order`.conversion_rate, 1)) as \"Billed Amount:Currency:100\", \n\t(`tabPurchase Order Item`.base_amount - (ifnull(`tabPurchase Order Item`.billed_amt, 0) * ifnull(`tabPurchase Order`.conversion_rate, 1))) as \"Amount to Bill:Currency:100\",\n\t`tabPurchase Order Item`.item_name as \"Item Name::150\",\n\t`tabPurchase Order Item`.description as \"Description::200\"\nfrom\n\t`tabPurchase Order`, `tabPurchase Order Item`\nwhere\n\t`tabPurchase Order Item`.`parent` = `tabPurchase Order`.`name`\n\tand `tabPurchase Order`.docstatus = 1\n\tand `tabPurchase Order`.status != \"Stopped\"\n\tand (ifnull(`tabPurchase Order Item`.billed_amt, 0) * ifnull(`tabPurchase Order`.conversion_rate, 1)) < ifnull(`tabPurchase Order Item`.base_amount, 0)\norder by `tabPurchase Order`.transaction_date asc",
"ref_doctype": "Purchase Invoice", "ref_doctype": "Purchase Invoice",
"report_name": "Purchase Order Items To Be Billed", "report_name": "Purchase Order Items To Be Billed",
"report_type": "Query Report" "report_type": "Query Report"

View File

@@ -5,6 +5,7 @@ from __future__ import unicode_literals
import frappe import frappe
from frappe.desk.reportview import get_match_cond from frappe.desk.reportview import get_match_cond
from frappe.model.db_query import DatabaseQuery from frappe.model.db_query import DatabaseQuery
from frappe.utils import nowdate
def get_filters_cond(doctype, filters, conditions): def get_filters_cond(doctype, filters, conditions):
if filters: if filters:
@@ -154,8 +155,6 @@ def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
return tax_accounts return tax_accounts
def item_query(doctype, txt, searchfield, start, page_len, filters): def item_query(doctype, txt, searchfield, start, page_len, filters):
from frappe.utils import nowdate
conditions = [] conditions = []
return frappe.db.sql("""select tabItem.name, return frappe.db.sql("""select tabItem.name,
@@ -230,38 +229,45 @@ def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len,
}, { "start": start, "page_len": page_len, "txt": ("%%%s%%" % txt) }) }, { "start": start, "page_len": page_len, "txt": ("%%%s%%" % txt) })
def get_batch_no(doctype, txt, searchfield, start, page_len, filters): def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
from erpnext.controllers.queries import get_match_cond if not filters.get("posting_date"):
filters["posting_date"] = nowdate()
if filters.has_key('warehouse'): batch_nos = None
return frappe.db.sql("""select batch_no, round(sum(actual_qty),2), stock_uom, expiry_date from `tabStock Ledger Entry` sle args = {
INNER JOIN `tabBatch` 'item_code': filters.get("item_code"),
on sle.batch_no = `tabBatch`.batch_id 'warehouse': filters.get("warehouse"),
where item_code = '%(item_code)s' 'posting_date': filters.get('posting_date'),
and warehouse = '%(warehouse)s' 'txt': "%{0}%".format(txt),
and batch_no like '%(txt)s' "start": start,
and exists(select * from `tabBatch` "page_len": page_len
where name = sle.batch_no }
and (ifnull(expiry_date, '')='' or expiry_date >= '%(posting_date)s')
and docstatus != 2) if args.get('warehouse'):
%(mcond)s batch_nos = frappe.db.sql("""select sle.batch_no, round(sum(sle.actual_qty),2), sle.stock_uom, batch.expiry_date
group by batch_no having sum(actual_qty) > 0 from `tabStock Ledger Entry` sle
order by expiry_date,batch_no desc INNER JOIN `tabBatch` batch on sle.batch_no = batch.name
limit %(start)s, %(page_len)s """ % {'item_code': filters['item_code'], where
'warehouse': filters['warehouse'], 'posting_date': filters['posting_date'], sle.item_code = %(item_code)s
'txt': "%%%s%%" % txt, 'mcond':get_match_cond(doctype), and sle.warehouse = %(warehouse)s
'start': start, 'page_len': page_len}) and sle.batch_no like %(txt)s
and batch.docstatus < 2
and (ifnull(batch.expiry_date, '')='' or batch.expiry_date >= %(posting_date)s)
{match_conditions}
group by batch_no having sum(sle.actual_qty) > 0
order by batch.expiry_date, sle.batch_no desc
limit %(start)s, %(page_len)s""".format(match_conditions=get_match_cond(doctype)), args)
if batch_nos:
return batch_nos
else: else:
return frappe.db.sql("""select name from tabBatch return frappe.db.sql("""select name, expiry_date from `tabBatch`
where docstatus != 2 where item = %(item_code)s
and item = '%(item_code)s' and name like %(txt)s
and (ifnull(expiry_date, '')='' or expiry_date >= '%(posting_date)s') and docstatus < 2
and name like '%(txt)s' and (ifnull(batch.expiry_date, '')='' or batch.expiry_date >= %(posting_date)s)
%(mcond)s {match_conditions}
order by name desc order by expiry_date, name desc
limit %(start)s, %(page_len)s""" % {'item_code': filters['item_code'], limit %(start)s, %(page_len)s""".format(match_conditions=get_match_cond(doctype)), args)
'posting_date': filters['posting_date'], 'txt': "%%%s%%" % txt,
'mcond':get_match_cond(doctype),'start': start,
'page_len': page_len})
def get_account_list(doctype, txt, searchfield, start, page_len, filters): def get_account_list(doctype, txt, searchfield, start, page_len, filters):
filter_list = [] filter_list = []

View File

@@ -197,9 +197,10 @@ class StockController(AccountsController):
sl_dict.update(args) sl_dict.update(args)
return sl_dict return sl_dict
def make_sl_entries(self, sl_entries, is_amended=None, allow_negative_stock=False): def make_sl_entries(self, sl_entries, is_amended=None, allow_negative_stock=False,
via_landed_cost_voucher=False):
from erpnext.stock.stock_ledger import make_sl_entries from erpnext.stock.stock_ledger import make_sl_entries
make_sl_entries(sl_entries, is_amended, allow_negative_stock) make_sl_entries(sl_entries, is_amended, allow_negative_stock, via_landed_cost_voucher)
def make_gl_entries_on_cancel(self): def make_gl_entries_on_cancel(self):
if frappe.db.sql("""select name from `tabGL Entry` where voucher_type=%s if frappe.db.sql("""select name from `tabGL Entry` where voucher_type=%s

View File

@@ -282,7 +282,8 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({
batch_no: function(doc, cdt, cdn) { batch_no: function(doc, cdt, cdn) {
var me = this; var me = this;
var item = frappe.get_doc(cdt, cdn); var item = frappe.get_doc(cdt, cdn);
if(item.item_code) {
if(item.warehouse && item.item_code && item.batch_no) {
return this.frm.call({ return this.frm.call({
method: "erpnext.stock.get_item_details.get_batch_qty", method: "erpnext.stock.get_item_details.get_batch_qty",
child: item, child: item,

View File

@@ -23,7 +23,7 @@ class Bin(Document):
if (not getattr(self, f, None)) or (not self.get(f)): if (not getattr(self, f, None)) or (not self.get(f)):
self.set(f, 0.0) self.set(f, 0.0)
def update_stock(self, args, allow_negative_stock=False): def update_stock(self, args, allow_negative_stock=False, via_landed_cost_voucher=False):
self.update_qty(args) self.update_qty(args)
if args.get("actual_qty") or args.get("voucher_type") == "Stock Reconciliation": if args.get("actual_qty") or args.get("voucher_type") == "Stock Reconciliation":
@@ -39,7 +39,7 @@ class Bin(Document):
"posting_date": args.get("posting_date"), "posting_date": args.get("posting_date"),
"posting_time": args.get("posting_time"), "posting_time": args.get("posting_time"),
"voucher_no": args.get("voucher_no") "voucher_no": args.get("voucher_no")
}, allow_negative_stock=allow_negative_stock) }, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher)
def update_qty(self, args): def update_qty(self, args):
# update the stock values (for current quantities) # update the stock values (for current quantities)

View File

@@ -6,6 +6,7 @@ import frappe
from frappe import _ from frappe import _
from frappe.utils import flt from frappe.utils import flt
from frappe.model.document import Document from frappe.model.document import Document
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
class LandedCostVoucher(Document): class LandedCostVoucher(Document):
def get_items_from_purchase_receipts(self): def get_items_from_purchase_receipts(self):
@@ -93,12 +94,24 @@ class LandedCostVoucher(Document):
# as those fields are allowed to edit after submit # as those fields are allowed to edit after submit
pr.save() pr.save()
# update latest valuation rate in serial no
self.update_rate_in_serial_no(pr)
# update stock & gl entries for cancelled state of PR # update stock & gl entries for cancelled state of PR
pr.docstatus = 2 pr.docstatus = 2
pr.update_stock_ledger(allow_negative_stock=True) pr.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True)
pr.make_gl_entries_on_cancel() pr.make_gl_entries_on_cancel()
# update stock & gl entries for submit state of PR # update stock & gl entries for submit state of PR
pr.docstatus = 1 pr.docstatus = 1
pr.update_stock_ledger() pr.update_stock_ledger(via_landed_cost_voucher=True)
pr.make_gl_entries() pr.make_gl_entries()
def update_rate_in_serial_no(self, purchase_receipt):
for item in purchase_receipt.get("items"):
if item.serial_no:
serial_nos = get_serial_nos(item.serial_no)
if serial_nos:
frappe.db.sql("update `tabSerial No` set purchase_rate=%s where name in ({0})"
.format(", ".join(["%s"]*len(serial_nos))), tuple([item.valuation_rate] + serial_nos))

View File

@@ -183,6 +183,7 @@ def set_missing_values(source, target_doc):
def update_item(obj, target, source_parent): def update_item(obj, target, source_parent):
target.conversion_factor = 1 target.conversion_factor = 1
target.qty = flt(obj.qty) - flt(obj.ordered_qty) target.qty = flt(obj.qty) - flt(obj.ordered_qty)
target.stock_qty = target.qty
@frappe.whitelist() @frappe.whitelist()
def make_purchase_order(source_name, target_doc=None): def make_purchase_order(source_name, target_doc=None):

View File

@@ -126,7 +126,7 @@ class PurchaseReceipt(BuyingController):
if not d.prevdoc_docname: if not d.prevdoc_docname:
frappe.throw(_("Purchase Order number required for Item {0}").format(d.item_code)) frappe.throw(_("Purchase Order number required for Item {0}").format(d.item_code))
def update_stock_ledger(self, allow_negative_stock=False): def update_stock_ledger(self, allow_negative_stock=False, via_landed_cost_voucher=False):
sl_entries = [] sl_entries = []
stock_items = self.get_stock_items() stock_items = self.get_stock_items()
@@ -151,7 +151,8 @@ class PurchaseReceipt(BuyingController):
})) }))
self.bk_flush_supp_wh(sl_entries) self.bk_flush_supp_wh(sl_entries)
self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock) self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock,
via_landed_cost_voucher=via_landed_cost_voucher)
def update_ordered_qty(self): def update_ordered_qty(self):
po_map = {} po_map = {}

View File

@@ -132,7 +132,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
var me = this; var me = this;
if(!this.frm.doc.fg_completed_qty || !this.frm.doc.bom_no) if(!this.frm.doc.fg_completed_qty || !this.frm.doc.bom_no)
frappe.throw(__("BOM and Manufacturing Quantity are required")); frappe.throw(__("BOM and Manufacturing Quantity are required"));
if(this.frm.doc.production_order || this.frm.doc.bom_no) { if(this.frm.doc.production_order || this.frm.doc.bom_no) {
// if production order / bom is mentioned, get items // if production order / bom is mentioned, get items
return this.frm.call({ return this.frm.call({
@@ -393,18 +393,21 @@ cur_frm.cscript.purpose = function(doc, cdt, cdn) {
// Overloaded query for link batch_no // Overloaded query for link batch_no
cur_frm.fields_dict['items'].grid.get_field('batch_no').get_query = function(doc, cdt, cdn) { cur_frm.fields_dict['items'].grid.get_field('batch_no').get_query = function(doc, cdt, cdn) {
var d = locals[cdt][cdn]; var item = locals[cdt][cdn];
if(d.item_code) { if(!item.item_code) {
return{ frappe.throw(__("Please enter Item Code to get batch no"));
query: "erpnext.stock.doctype.stock_entry.stock_entry.get_batch_no",
filters:{
'item_code' : d.item_code,
's_warehouse' : d.s_warehouse,
'posting_date' : doc.posting_date
}
}
} else { } else {
msgprint(__("Please enter Item Code to get batch no")); var filters = {
'item_code': item.item_code,
'posting_date': me.frm.doc.posting_date,
}
if(item.s_warehouse) filters["warehouse"] = item.s_warehouse
return {
query : "erpnext.controllers.queries.get_batch_no",
filters: filters
}
} }
} }

View File

@@ -289,8 +289,8 @@ class StockEntry(StockController):
incoming_rate = flt(self.get_incoming_rate(args), self.precision("incoming_rate", d)) incoming_rate = flt(self.get_incoming_rate(args), self.precision("incoming_rate", d))
if incoming_rate > 0: if incoming_rate > 0:
d.incoming_rate = incoming_rate d.incoming_rate = incoming_rate
d.amount = flt(d.transfer_qty) * flt(d.incoming_rate) d.amount = flt(flt(d.transfer_qty) * flt(d.incoming_rate), d.precision("amount"))
if not d.t_warehouse: if not d.t_warehouse:
raw_material_cost += flt(d.amount) raw_material_cost += flt(d.amount)
@@ -386,7 +386,7 @@ class StockEntry(StockController):
def validate_return_reference_doc(self): def validate_return_reference_doc(self):
"""validate item with reference doc""" """validate item with reference doc"""
ref = get_return_doc_and_details(self) ref = get_return_doc_and_details(self)
if ref.doc: if ref.doc:
# validate docstatus # validate docstatus
if ref.doc.docstatus != 1: if ref.doc.docstatus != 1:
@@ -400,7 +400,7 @@ class StockEntry(StockController):
# posting date check # posting date check
ref_posting_datetime = "%s %s" % (ref.doc.posting_date, ref.doc.posting_time or "00:00:00") ref_posting_datetime = "%s %s" % (ref.doc.posting_date, ref.doc.posting_time or "00:00:00")
this_posting_datetime = "%s %s" % (self.posting_date, self.posting_time) this_posting_datetime = "%s %s" % (self.posting_date, self.posting_time)
if get_datetime(ref_posting_datetime) < get_datetime(ref_posting_datetime): if get_datetime(ref_posting_datetime) < get_datetime(ref_posting_datetime):
from frappe.utils.dateutils import datetime_in_user_format from frappe.utils.dateutils import datetime_in_user_format
frappe.throw(_("Posting timestamp must be after {0}") frappe.throw(_("Posting timestamp must be after {0}")
@@ -784,51 +784,6 @@ def query_return_item(doctype, txt, searchfield, start, page_len, filters):
return result[start:start+page_len] return result[start:start+page_len]
def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
if not filters.get("posting_date"):
filters["posting_date"] = nowdate()
batch_nos = None
args = {
'item_code': filters.get("item_code"),
's_warehouse': filters.get('s_warehouse'),
'posting_date': filters.get('posting_date'),
'txt': "%%%s%%" % txt,
'mcond':get_match_cond(doctype),
"start": start,
"page_len": page_len
}
if filters.get("s_warehouse"):
batch_nos = frappe.db.sql("""select batch_no
from `tabStock Ledger Entry` sle
where item_code = '%(item_code)s'
and warehouse = '%(s_warehouse)s'
and batch_no like '%(txt)s'
and exists(select * from `tabBatch`
where name = sle.batch_no
and (ifnull(expiry_date, '2099-12-31') >= %(posting_date)s
or expiry_date = '')
and docstatus != 2)
%(mcond)s
group by batch_no having sum(actual_qty) > 0
order by batch_no desc
limit %(start)s, %(page_len)s """
% args)
if batch_nos:
return batch_nos
else:
return frappe.db.sql("""select name from `tabBatch`
where item = '%(item_code)s'
and docstatus < 2
and (ifnull(expiry_date, '2099-12-31') >= %(posting_date)s
or expiry_date = '' or expiry_date = "0000-00-00")
%(mcond)s
order by name desc
limit %(start)s, %(page_len)s
""" % args)
def get_stock_items_for_return(ref_doc, parentfields): def get_stock_items_for_return(ref_doc, parentfields):
"""return item codes filtered from doc, which are stock items""" """return item codes filtered from doc, which are stock items"""
if isinstance(parentfields, basestring): if isinstance(parentfields, basestring):

View File

@@ -27,8 +27,9 @@ class StockLedgerEntry(Document):
self.check_stock_frozen_date() self.check_stock_frozen_date()
self.actual_amt_check() self.actual_amt_check()
from erpnext.stock.doctype.serial_no.serial_no import process_serial_no if not self.get("via_landed_cost_voucher"):
process_serial_no(self) from erpnext.stock.doctype.serial_no.serial_no import process_serial_no
process_serial_no(self)
#check for item quantity available in stock #check for item quantity available in stock
def actual_amt_check(self): def actual_amt_check(self):

View File

@@ -305,13 +305,13 @@ def get_serial_nos_by_fifo(args, item_doc):
})) }))
def get_actual_batch_qty(batch_no,warehouse,item_code): def get_actual_batch_qty(batch_no,warehouse,item_code):
actual_batch_qty = 0 actual_batch_qty = 0
if batch_no: if batch_no:
actual_batch_qty = flt(frappe.db.sql("""select sum(actual_qty) actual_batch_qty = flt(frappe.db.sql("""select sum(actual_qty)
from `tabStock Ledger Entry` from `tabStock Ledger Entry`
where warehouse=%s and item_code=%s and batch_no=%s""", where warehouse=%s and item_code=%s and batch_no=%s""",
(warehouse, item_code, batch_no))[0][0]) (warehouse, item_code, batch_no))[0][0])
return actual_batch_qty return actual_batch_qty
@frappe.whitelist() @frappe.whitelist()
def get_conversion_factor(item_code, uom): def get_conversion_factor(item_code, uom):

View File

@@ -14,7 +14,7 @@ class NegativeStockError(frappe.ValidationError): pass
_exceptions = frappe.local('stockledger_exceptions') _exceptions = frappe.local('stockledger_exceptions')
# _exceptions = [] # _exceptions = []
def make_sl_entries(sl_entries, is_amended=None, allow_negative_stock=False): def make_sl_entries(sl_entries, is_amended=None, allow_negative_stock=False, via_landed_cost_voucher=False):
if sl_entries: if sl_entries:
from erpnext.stock.utils import update_bin from erpnext.stock.utils import update_bin
@@ -28,14 +28,14 @@ def make_sl_entries(sl_entries, is_amended=None, allow_negative_stock=False):
sle['actual_qty'] = -flt(sle['actual_qty']) sle['actual_qty'] = -flt(sle['actual_qty'])
if sle.get("actual_qty") or sle.get("voucher_type")=="Stock Reconciliation": if sle.get("actual_qty") or sle.get("voucher_type")=="Stock Reconciliation":
sle_id = make_entry(sle, allow_negative_stock) sle_id = make_entry(sle, allow_negative_stock, via_landed_cost_voucher)
args = sle.copy() args = sle.copy()
args.update({ args.update({
"sle_id": sle_id, "sle_id": sle_id,
"is_amended": is_amended "is_amended": is_amended
}) })
update_bin(args, allow_negative_stock) update_bin(args, allow_negative_stock, via_landed_cost_voucher)
if cancel: if cancel:
delete_cancelled_entry(sl_entries[0].get('voucher_type'), sl_entries[0].get('voucher_no')) delete_cancelled_entry(sl_entries[0].get('voucher_type'), sl_entries[0].get('voucher_no'))
@@ -46,11 +46,12 @@ def set_as_cancel(voucher_type, voucher_no):
where voucher_no=%s and voucher_type=%s""", where voucher_no=%s and voucher_type=%s""",
(now(), frappe.session.user, voucher_type, voucher_no)) (now(), frappe.session.user, voucher_type, voucher_no))
def make_entry(args, allow_negative_stock=False): def make_entry(args, allow_negative_stock=False, via_landed_cost_voucher=False):
args.update({"doctype": "Stock Ledger Entry"}) args.update({"doctype": "Stock Ledger Entry"})
sle = frappe.get_doc(args) sle = frappe.get_doc(args)
sle.flags.ignore_permissions = 1 sle.flags.ignore_permissions = 1
sle.allow_negative_stock=allow_negative_stock sle.allow_negative_stock=allow_negative_stock
sle.via_landed_cost_voucher = via_landed_cost_voucher
sle.insert() sle.insert()
sle.submit() sle.submit()
return sle.name return sle.name
@@ -73,13 +74,14 @@ class update_entries_after(object):
"posting_time": "12:00" "posting_time": "12:00"
} }
""" """
def __init__(self, args, allow_zero_rate=False, allow_negative_stock=None, verbose=1): def __init__(self, args, allow_zero_rate=False, allow_negative_stock=None, via_landed_cost_voucher=False, verbose=1):
from frappe.model.meta import get_field_precision from frappe.model.meta import get_field_precision
self.exceptions = [] self.exceptions = []
self.verbose = verbose self.verbose = verbose
self.allow_zero_rate = allow_zero_rate self.allow_zero_rate = allow_zero_rate
self.allow_negative_stock = allow_negative_stock self.allow_negative_stock = allow_negative_stock
self.via_landed_cost_voucher = via_landed_cost_voucher
if not self.allow_negative_stock: if not self.allow_negative_stock:
self.allow_negative_stock = cint(frappe.db.get_single_value("Stock Settings", self.allow_negative_stock = cint(frappe.db.get_single_value("Stock Settings",
"allow_negative_stock")) "allow_negative_stock"))
@@ -141,7 +143,7 @@ class update_entries_after(object):
bin_doc.save(ignore_permissions=True) bin_doc.save(ignore_permissions=True)
def process_sle(self, sle): def process_sle(self, sle):
if sle.serial_no or not cint(self.allow_negative_stock): if (sle.serial_no and not self.via_landed_cost_voucher) or not cint(self.allow_negative_stock):
# validate negative stock for serialized items, fifo valuation # validate negative stock for serialized items, fifo valuation
# or when negative stock is not allowed for moving average # or when negative stock is not allowed for moving average
if not self.validate_negative_stock(sle): if not self.validate_negative_stock(sle):

View File

@@ -79,11 +79,11 @@ def get_bin(item_code, warehouse):
bin_obj.flags.ignore_permissions = True bin_obj.flags.ignore_permissions = True
return bin_obj return bin_obj
def update_bin(args, allow_negative_stock=False): def update_bin(args, allow_negative_stock=False, via_landed_cost_voucher=False):
is_stock_item = frappe.db.get_value('Item', args.get("item_code"), 'is_stock_item') is_stock_item = frappe.db.get_value('Item', args.get("item_code"), 'is_stock_item')
if is_stock_item == 'Yes': if is_stock_item == 'Yes':
bin = get_bin(args.get("item_code"), args.get("warehouse")) bin = get_bin(args.get("item_code"), args.get("warehouse"))
bin.update_stock(args, allow_negative_stock) bin.update_stock(args, allow_negative_stock, via_landed_cost_voucher)
return bin return bin
else: else:
frappe.msgprint(_("Item {0} ignored since it is not a stock item").format(args.get("item_code"))) frappe.msgprint(_("Item {0} ignored since it is not a stock item").format(args.get("item_code")))