style: format code with black
diff --git a/erpnext/stock/__init__.py b/erpnext/stock/__init__.py
index e8b2804..45bf012 100644
--- a/erpnext/stock/__init__.py
+++ b/erpnext/stock/__init__.py
@@ -2,17 +2,24 @@
from frappe import _
install_docs = [
- {"doctype":"Role", "role_name":"Stock Manager", "name":"Stock Manager"},
- {"doctype":"Role", "role_name":"Item Manager", "name":"Item Manager"},
- {"doctype":"Role", "role_name":"Stock User", "name":"Stock User"},
- {"doctype":"Role", "role_name":"Quality Manager", "name":"Quality Manager"},
- {"doctype":"Item Group", "item_group_name":"All Item Groups", "is_group": 1},
- {"doctype":"Item Group", "item_group_name":"Default",
- "parent_item_group":"All Item Groups", "is_group": 0},
+ {"doctype": "Role", "role_name": "Stock Manager", "name": "Stock Manager"},
+ {"doctype": "Role", "role_name": "Item Manager", "name": "Item Manager"},
+ {"doctype": "Role", "role_name": "Stock User", "name": "Stock User"},
+ {"doctype": "Role", "role_name": "Quality Manager", "name": "Quality Manager"},
+ {"doctype": "Item Group", "item_group_name": "All Item Groups", "is_group": 1},
+ {
+ "doctype": "Item Group",
+ "item_group_name": "Default",
+ "parent_item_group": "All Item Groups",
+ "is_group": 0,
+ },
]
+
def get_warehouse_account_map(company=None):
- company_warehouse_account_map = company and frappe.flags.setdefault('warehouse_account_map', {}).get(company)
+ company_warehouse_account_map = company and frappe.flags.setdefault(
+ "warehouse_account_map", {}
+ ).get(company)
warehouse_account_map = frappe.flags.warehouse_account_map
if not warehouse_account_map or not company_warehouse_account_map or frappe.flags.in_test:
@@ -20,18 +27,20 @@
filters = {}
if company:
- filters['company'] = company
- frappe.flags.setdefault('warehouse_account_map', {}).setdefault(company, {})
+ filters["company"] = company
+ frappe.flags.setdefault("warehouse_account_map", {}).setdefault(company, {})
- for d in frappe.get_all('Warehouse',
- fields = ["name", "account", "parent_warehouse", "company", "is_group"],
- filters = filters,
- order_by="lft, rgt"):
+ for d in frappe.get_all(
+ "Warehouse",
+ fields=["name", "account", "parent_warehouse", "company", "is_group"],
+ filters=filters,
+ order_by="lft, rgt",
+ ):
if not d.account:
d.account = get_warehouse_account(d, warehouse_account)
if d.account:
- d.account_currency = frappe.db.get_value('Account', d.account, 'account_currency', cache=True)
+ d.account_currency = frappe.db.get_value("Account", d.account, "account_currency", cache=True)
warehouse_account.setdefault(d.name, d)
if company:
frappe.flags.warehouse_account_map[company] = warehouse_account
@@ -40,6 +49,7 @@
return frappe.flags.warehouse_account_map.get(company) or frappe.flags.warehouse_account_map
+
def get_warehouse_account(warehouse, warehouse_account=None):
account = warehouse.account
if not account and warehouse.parent_warehouse:
@@ -48,15 +58,20 @@
account = warehouse_account.get(warehouse.parent_warehouse).account
else:
from frappe.utils.nestedset import rebuild_tree
+
rebuild_tree("Warehouse", "parent_warehouse")
else:
- account = frappe.db.sql("""
+ account = frappe.db.sql(
+ """
select
account from `tabWarehouse`
where
lft <= %s and rgt >= %s and company = %s
and account is not null and ifnull(account, '') !=''
- order by lft desc limit 1""", (warehouse.lft, warehouse.rgt, warehouse.company), as_list=1)
+ order by lft desc limit 1""",
+ (warehouse.lft, warehouse.rgt, warehouse.company),
+ as_list=1,
+ )
account = account[0][0] if account else None
@@ -64,13 +79,18 @@
account = get_company_default_inventory_account(warehouse.company)
if not account and warehouse.company:
- account = frappe.db.get_value('Account',
- {'account_type': 'Stock', 'is_group': 0, 'company': warehouse.company}, 'name')
+ account = frappe.db.get_value(
+ "Account", {"account_type": "Stock", "is_group": 0, "company": warehouse.company}, "name"
+ )
if not account and warehouse.company and not warehouse.is_group:
- frappe.throw(_("Please set Account in Warehouse {0} or Default Inventory Account in Company {1}")
- .format(warehouse.name, warehouse.company))
+ frappe.throw(
+ _("Please set Account in Warehouse {0} or Default Inventory Account in Company {1}").format(
+ warehouse.name, warehouse.company
+ )
+ )
return account
+
def get_company_default_inventory_account(company):
- return frappe.get_cached_value('Company', company, 'default_inventory_account')
+ return frappe.get_cached_value("Company", company, "default_inventory_account")
diff --git a/erpnext/stock/dashboard/item_dashboard.py b/erpnext/stock/dashboard/item_dashboard.py
index 75aa1d3..8fbb56c 100644
--- a/erpnext/stock/dashboard/item_dashboard.py
+++ b/erpnext/stock/dashboard/item_dashboard.py
@@ -4,55 +4,72 @@
@frappe.whitelist()
-def get_data(item_code=None, warehouse=None, item_group=None,
- start=0, sort_by='actual_qty', sort_order='desc'):
- '''Return data to render the item dashboard'''
+def get_data(
+ item_code=None, warehouse=None, item_group=None, start=0, sort_by="actual_qty", sort_order="desc"
+):
+ """Return data to render the item dashboard"""
filters = []
if item_code:
- filters.append(['item_code', '=', item_code])
+ filters.append(["item_code", "=", item_code])
if warehouse:
- filters.append(['warehouse', '=', warehouse])
+ filters.append(["warehouse", "=", warehouse])
if item_group:
lft, rgt = frappe.db.get_value("Item Group", item_group, ["lft", "rgt"])
- items = frappe.db.sql_list("""
+ items = frappe.db.sql_list(
+ """
select i.name from `tabItem` i
where exists(select name from `tabItem Group`
where name=i.item_group and lft >=%s and rgt<=%s)
- """, (lft, rgt))
- filters.append(['item_code', 'in', items])
+ """,
+ (lft, rgt),
+ )
+ filters.append(["item_code", "in", items])
try:
# check if user has any restrictions based on user permissions on warehouse
- if DatabaseQuery('Warehouse', user=frappe.session.user).build_match_conditions():
- filters.append(['warehouse', 'in', [w.name for w in frappe.get_list('Warehouse')]])
+ if DatabaseQuery("Warehouse", user=frappe.session.user).build_match_conditions():
+ filters.append(["warehouse", "in", [w.name for w in frappe.get_list("Warehouse")]])
except frappe.PermissionError:
# user does not have access on warehouse
return []
- items = frappe.db.get_all('Bin', fields=['item_code', 'warehouse', 'projected_qty',
- 'reserved_qty', 'reserved_qty_for_production', 'reserved_qty_for_sub_contract', 'actual_qty', 'valuation_rate'],
+ items = frappe.db.get_all(
+ "Bin",
+ fields=[
+ "item_code",
+ "warehouse",
+ "projected_qty",
+ "reserved_qty",
+ "reserved_qty_for_production",
+ "reserved_qty_for_sub_contract",
+ "actual_qty",
+ "valuation_rate",
+ ],
or_filters={
- 'projected_qty': ['!=', 0],
- 'reserved_qty': ['!=', 0],
- 'reserved_qty_for_production': ['!=', 0],
- 'reserved_qty_for_sub_contract': ['!=', 0],
- 'actual_qty': ['!=', 0],
+ "projected_qty": ["!=", 0],
+ "reserved_qty": ["!=", 0],
+ "reserved_qty_for_production": ["!=", 0],
+ "reserved_qty_for_sub_contract": ["!=", 0],
+ "actual_qty": ["!=", 0],
},
filters=filters,
- order_by=sort_by + ' ' + sort_order,
+ order_by=sort_by + " " + sort_order,
limit_start=start,
- limit_page_length=21)
+ limit_page_length=21,
+ )
precision = cint(frappe.db.get_single_value("System Settings", "float_precision"))
for item in items:
- item.update({
- 'item_name': frappe.get_cached_value("Item", item.item_code, 'item_name'),
- 'disable_quick_entry': frappe.get_cached_value( "Item", item.item_code, 'has_batch_no')
- or frappe.get_cached_value( "Item", item.item_code, 'has_serial_no'),
- 'projected_qty': flt(item.projected_qty, precision),
- 'reserved_qty': flt(item.reserved_qty, precision),
- 'reserved_qty_for_production': flt(item.reserved_qty_for_production, precision),
- 'reserved_qty_for_sub_contract': flt(item.reserved_qty_for_sub_contract, precision),
- 'actual_qty': flt(item.actual_qty, precision),
- })
+ item.update(
+ {
+ "item_name": frappe.get_cached_value("Item", item.item_code, "item_name"),
+ "disable_quick_entry": frappe.get_cached_value("Item", item.item_code, "has_batch_no")
+ or frappe.get_cached_value("Item", item.item_code, "has_serial_no"),
+ "projected_qty": flt(item.projected_qty, precision),
+ "reserved_qty": flt(item.reserved_qty, precision),
+ "reserved_qty_for_production": flt(item.reserved_qty_for_production, precision),
+ "reserved_qty_for_sub_contract": flt(item.reserved_qty_for_sub_contract, precision),
+ "actual_qty": flt(item.actual_qty, precision),
+ }
+ )
return items
diff --git a/erpnext/stock/dashboard/warehouse_capacity_dashboard.py b/erpnext/stock/dashboard/warehouse_capacity_dashboard.py
index c0666cf..24e0ef1 100644
--- a/erpnext/stock/dashboard/warehouse_capacity_dashboard.py
+++ b/erpnext/stock/dashboard/warehouse_capacity_dashboard.py
@@ -6,8 +6,15 @@
@frappe.whitelist()
-def get_data(item_code=None, warehouse=None, parent_warehouse=None,
- company=None, start=0, sort_by="stock_capacity", sort_order="desc"):
+def get_data(
+ item_code=None,
+ warehouse=None,
+ parent_warehouse=None,
+ company=None,
+ start=0,
+ sort_by="stock_capacity",
+ sort_order="desc",
+):
"""Return data to render the warehouse capacity dashboard."""
filters = get_filters(item_code, warehouse, parent_warehouse, company)
@@ -18,51 +25,59 @@
capacity_data = get_warehouse_capacity_data(filters, start)
asc_desc = -1 if sort_order == "desc" else 1
- capacity_data = sorted(capacity_data, key = lambda i: (i[sort_by] * asc_desc))
+ capacity_data = sorted(capacity_data, key=lambda i: (i[sort_by] * asc_desc))
return capacity_data
-def get_filters(item_code=None, warehouse=None, parent_warehouse=None,
- company=None):
- filters = [['disable', '=', 0]]
+
+def get_filters(item_code=None, warehouse=None, parent_warehouse=None, company=None):
+ filters = [["disable", "=", 0]]
if item_code:
- filters.append(['item_code', '=', item_code])
+ filters.append(["item_code", "=", item_code])
if warehouse:
- filters.append(['warehouse', '=', warehouse])
+ filters.append(["warehouse", "=", warehouse])
if company:
- filters.append(['company', '=', company])
+ filters.append(["company", "=", company])
if parent_warehouse:
lft, rgt = frappe.db.get_value("Warehouse", parent_warehouse, ["lft", "rgt"])
- warehouses = frappe.db.sql_list("""
+ warehouses = frappe.db.sql_list(
+ """
select name from `tabWarehouse`
where lft >=%s and rgt<=%s
- """, (lft, rgt))
- filters.append(['warehouse', 'in', warehouses])
+ """,
+ (lft, rgt),
+ )
+ filters.append(["warehouse", "in", warehouses])
return filters
+
def get_warehouse_filter_based_on_permissions(filters):
try:
# check if user has any restrictions based on user permissions on warehouse
- if DatabaseQuery('Warehouse', user=frappe.session.user).build_match_conditions():
- filters.append(['warehouse', 'in', [w.name for w in frappe.get_list('Warehouse')]])
+ if DatabaseQuery("Warehouse", user=frappe.session.user).build_match_conditions():
+ filters.append(["warehouse", "in", [w.name for w in frappe.get_list("Warehouse")]])
return False, filters
except frappe.PermissionError:
# user does not have access on warehouse
return True, []
+
def get_warehouse_capacity_data(filters, start):
- capacity_data = frappe.db.get_all('Putaway Rule',
- fields=['item_code', 'warehouse','stock_capacity', 'company'],
+ capacity_data = frappe.db.get_all(
+ "Putaway Rule",
+ fields=["item_code", "warehouse", "stock_capacity", "company"],
filters=filters,
limit_start=start,
- limit_page_length='11'
+ limit_page_length="11",
)
for entry in capacity_data:
balance_qty = get_stock_balance(entry.item_code, entry.warehouse, nowdate()) or 0
- entry.update({
- 'actual_qty': balance_qty,
- 'percent_occupied': flt((flt(balance_qty) / flt(entry.stock_capacity)) * 100, 0)
- })
+ entry.update(
+ {
+ "actual_qty": balance_qty,
+ "percent_occupied": flt((flt(balance_qty) / flt(entry.stock_capacity)) * 100, 0),
+ }
+ )
return capacity_data
diff --git a/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.py b/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.py
index d835420..dbf6cf0 100644
--- a/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.py
+++ b/erpnext/stock/dashboard_chart_source/warehouse_wise_stock_value/warehouse_wise_stock_value.py
@@ -11,27 +11,38 @@
@frappe.whitelist()
@cache_source
-def get(chart_name = None, chart = None, no_cache = None, filters = None, from_date = None,
- to_date = None, timespan = None, time_interval = None, heatmap_year = None):
+def get(
+ chart_name=None,
+ chart=None,
+ no_cache=None,
+ filters=None,
+ from_date=None,
+ to_date=None,
+ timespan=None,
+ time_interval=None,
+ heatmap_year=None,
+):
labels, datapoints = [], []
filters = frappe.parse_json(filters)
- warehouse_filters = [['is_group', '=', 0]]
+ warehouse_filters = [["is_group", "=", 0]]
if filters and filters.get("company"):
- warehouse_filters.append(['company', '=', filters.get("company")])
+ warehouse_filters.append(["company", "=", filters.get("company")])
- warehouses = frappe.get_list("Warehouse", fields=['name'], filters=warehouse_filters, order_by='name')
+ warehouses = frappe.get_list(
+ "Warehouse", fields=["name"], filters=warehouse_filters, order_by="name"
+ )
for wh in warehouses:
balance = get_stock_value_from_bin(warehouse=wh.name)
wh["balance"] = balance[0][0]
- warehouses = [x for x in warehouses if not (x.get('balance') == None)]
+ warehouses = [x for x in warehouses if not (x.get("balance") == None)]
if not warehouses:
return []
- sorted_warehouse_map = sorted(warehouses, key = lambda i: i['balance'], reverse=True)
+ sorted_warehouse_map = sorted(warehouses, key=lambda i: i["balance"], reverse=True)
if len(sorted_warehouse_map) > 10:
sorted_warehouse_map = sorted_warehouse_map[:10]
@@ -40,11 +51,8 @@
labels.append(_(warehouse.get("name")))
datapoints.append(warehouse.get("balance"))
- return{
+ return {
"labels": labels,
- "datasets": [{
- "name": _("Stock Value"),
- "values": datapoints
- }],
- "type": "bar"
+ "datasets": [{"name": _("Stock Value"), "values": datapoints}],
+ "type": "bar",
}
diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py
index c9b4c14..aac6cd3 100644
--- a/erpnext/stock/doctype/batch/batch.py
+++ b/erpnext/stock/doctype/batch/batch.py
@@ -23,7 +23,7 @@
temp = None
while not temp:
temp = frappe.generate_hash()[:7].upper()
- if frappe.db.exists('Batch', temp):
+ if frappe.db.exists("Batch", temp):
temp = None
return temp
@@ -34,7 +34,7 @@
Verify if the Batch is to be named using a naming series
:return: bool
"""
- use_naming_series = cint(frappe.db.get_single_value('Stock Settings', 'use_naming_series'))
+ use_naming_series = cint(frappe.db.get_single_value("Stock Settings", "use_naming_series"))
return bool(use_naming_series)
@@ -46,9 +46,9 @@
is set to use naming series.
:return: The naming series.
"""
- naming_series_prefix = frappe.db.get_single_value('Stock Settings', 'naming_series_prefix')
+ naming_series_prefix = frappe.db.get_single_value("Stock Settings", "naming_series_prefix")
if not naming_series_prefix:
- naming_series_prefix = 'BATCH-'
+ naming_series_prefix = "BATCH-"
return naming_series_prefix
@@ -62,9 +62,9 @@
:return: The derived key. If no prefix is given, an empty string is returned
"""
if not str(prefix):
- return ''
+ return ""
else:
- return prefix.upper() + '.#####'
+ return prefix.upper() + ".#####"
def get_batch_naming_series():
@@ -74,7 +74,7 @@
Naming series key is in the format [prefix].[#####]
:return: The naming series or empty string if not available
"""
- series = ''
+ series = ""
if batch_uses_naming_series():
prefix = _get_batch_prefix()
key = _make_naming_series_key(prefix)
@@ -87,8 +87,9 @@
def autoname(self):
"""Generate random ID for batch if not specified"""
if not self.batch_id:
- create_new_batch, batch_number_series = frappe.db.get_value('Item', self.item,
- ['create_new_batch', 'batch_number_series'])
+ create_new_batch, batch_number_series = frappe.db.get_value(
+ "Item", self.item, ["create_new_batch", "batch_number_series"]
+ )
if create_new_batch:
if batch_number_series:
@@ -98,12 +99,12 @@
else:
self.batch_id = get_name_from_hash()
else:
- frappe.throw(_('Batch ID is mandatory'), frappe.MandatoryError)
+ frappe.throw(_("Batch ID is mandatory"), frappe.MandatoryError)
self.name = self.batch_id
def onload(self):
- self.image = frappe.db.get_value('Item', self.item, 'image')
+ self.image = frappe.db.get_value("Item", self.item, "image")
def after_delete(self):
revert_series_if_last(get_batch_naming_series(), self.name)
@@ -123,16 +124,21 @@
self.use_batchwise_valuation = 1
def before_save(self):
- has_expiry_date, shelf_life_in_days = frappe.db.get_value('Item', self.item, ['has_expiry_date', 'shelf_life_in_days'])
+ has_expiry_date, shelf_life_in_days = frappe.db.get_value(
+ "Item", self.item, ["has_expiry_date", "shelf_life_in_days"]
+ )
if not self.expiry_date and has_expiry_date and shelf_life_in_days:
self.expiry_date = add_days(self.manufacturing_date, shelf_life_in_days)
if has_expiry_date and not self.expiry_date:
- frappe.throw(msg=_("Please set {0} for Batched Item {1}, which is used to set {2} on Submit.") \
- .format(frappe.bold("Shelf Life in Days"),
+ frappe.throw(
+ msg=_("Please set {0} for Batched Item {1}, which is used to set {2} on Submit.").format(
+ frappe.bold("Shelf Life in Days"),
get_link_to_form("Item", self.item),
- frappe.bold("Batch Expiry Date")),
- title=_("Expiry Date Mandatory"))
+ frappe.bold("Batch Expiry Date"),
+ ),
+ title=_("Expiry Date Mandatory"),
+ )
def get_name_from_naming_series(self):
"""
@@ -149,9 +155,11 @@
@frappe.whitelist()
-def get_batch_qty(batch_no=None, warehouse=None, item_code=None, posting_date=None, posting_time=None):
+def get_batch_qty(
+ batch_no=None, warehouse=None, item_code=None, posting_date=None, posting_time=None
+):
"""Returns batch actual qty if warehouse is passed,
- or returns dict of qty by warehouse if warehouse is None
+ or returns dict of qty by warehouse if warehouse is None
The user must pass either batch_no or batch_no + warehouse or item_code + warehouse
@@ -163,25 +171,41 @@
if batch_no and warehouse:
cond = ""
if posting_date and posting_time:
- cond = " and timestamp(posting_date, posting_time) <= timestamp('{0}', '{1}')".format(posting_date,
- posting_time)
+ cond = " and timestamp(posting_date, posting_time) <= timestamp('{0}', '{1}')".format(
+ posting_date, posting_time
+ )
- out = float(frappe.db.sql("""select sum(actual_qty)
+ out = float(
+ frappe.db.sql(
+ """select sum(actual_qty)
from `tabStock Ledger Entry`
- where is_cancelled = 0 and warehouse=%s and batch_no=%s {0}""".format(cond),
- (warehouse, batch_no))[0][0] or 0)
+ where is_cancelled = 0 and warehouse=%s and batch_no=%s {0}""".format(
+ cond
+ ),
+ (warehouse, batch_no),
+ )[0][0]
+ or 0
+ )
if batch_no and not warehouse:
- out = frappe.db.sql('''select warehouse, sum(actual_qty) as qty
+ out = frappe.db.sql(
+ """select warehouse, sum(actual_qty) as qty
from `tabStock Ledger Entry`
where is_cancelled = 0 and batch_no=%s
- group by warehouse''', batch_no, as_dict=1)
+ group by warehouse""",
+ batch_no,
+ as_dict=1,
+ )
if not batch_no and item_code and warehouse:
- out = frappe.db.sql('''select batch_no, sum(actual_qty) as qty
+ out = frappe.db.sql(
+ """select batch_no, sum(actual_qty) as qty
from `tabStock Ledger Entry`
where is_cancelled = 0 and item_code = %s and warehouse=%s
- group by batch_no''', (item_code, warehouse), as_dict=1)
+ group by batch_no""",
+ (item_code, warehouse),
+ as_dict=1,
+ )
return out
@@ -190,7 +214,9 @@
def get_batches_by_oldest(item_code, warehouse):
"""Returns the oldest batch and qty for the given item_code and warehouse"""
batches = get_batch_qty(item_code=item_code, warehouse=warehouse)
- batches_dates = [[batch, frappe.get_value('Batch', batch.batch_no, 'expiry_date')] for batch in batches]
+ batches_dates = [
+ [batch, frappe.get_value("Batch", batch.batch_no, "expiry_date")] for batch in batches
+ ]
batches_dates.sort(key=lambda tup: tup[1])
return batches_dates
@@ -198,33 +224,25 @@
@frappe.whitelist()
def split_batch(batch_no, item_code, warehouse, qty, new_batch_id=None):
"""Split the batch into a new batch"""
- batch = frappe.get_doc(dict(doctype='Batch', item=item_code, batch_id=new_batch_id)).insert()
+ batch = frappe.get_doc(dict(doctype="Batch", item=item_code, batch_id=new_batch_id)).insert()
- company = frappe.db.get_value('Stock Ledger Entry', dict(
- item_code=item_code,
- batch_no=batch_no,
- warehouse=warehouse
- ), ['company'])
+ company = frappe.db.get_value(
+ "Stock Ledger Entry",
+ dict(item_code=item_code, batch_no=batch_no, warehouse=warehouse),
+ ["company"],
+ )
- stock_entry = frappe.get_doc(dict(
- doctype='Stock Entry',
- purpose='Repack',
- company=company,
- items=[
- dict(
- item_code=item_code,
- qty=float(qty or 0),
- s_warehouse=warehouse,
- batch_no=batch_no
- ),
- dict(
- item_code=item_code,
- qty=float(qty or 0),
- t_warehouse=warehouse,
- batch_no=batch.name
- ),
- ]
- ))
+ stock_entry = frappe.get_doc(
+ dict(
+ doctype="Stock Entry",
+ purpose="Repack",
+ company=company,
+ items=[
+ dict(item_code=item_code, qty=float(qty or 0), s_warehouse=warehouse, batch_no=batch_no),
+ dict(item_code=item_code, qty=float(qty or 0), t_warehouse=warehouse, batch_no=batch.name),
+ ],
+ )
+ )
stock_entry.set_stock_entry_type()
stock_entry.insert()
stock_entry.submit()
@@ -235,15 +253,20 @@
def set_batch_nos(doc, warehouse_field, throw=False, child_table="items"):
"""Automatically select `batch_no` for outgoing items in item table"""
for d in doc.get(child_table):
- qty = d.get('stock_qty') or d.get('transfer_qty') or d.get('qty') or 0
+ qty = d.get("stock_qty") or d.get("transfer_qty") or d.get("qty") or 0
warehouse = d.get(warehouse_field, None)
- if warehouse and qty > 0 and frappe.db.get_value('Item', d.item_code, 'has_batch_no'):
+ if warehouse and qty > 0 and frappe.db.get_value("Item", d.item_code, "has_batch_no"):
if not d.batch_no:
d.batch_no = get_batch_no(d.item_code, warehouse, qty, throw, d.serial_no)
else:
batch_qty = get_batch_qty(batch_no=d.batch_no, warehouse=warehouse)
if flt(batch_qty, d.precision("qty")) < flt(qty, d.precision("qty")):
- frappe.throw(_("Row #{0}: The batch {1} has only {2} qty. Please select another batch which has {3} qty available or split the row into multiple rows, to deliver/issue from multiple batches").format(d.idx, d.batch_no, batch_qty, qty))
+ frappe.throw(
+ _(
+ "Row #{0}: The batch {1} has only {2} qty. Please select another batch which has {3} qty available or split the row into multiple rows, to deliver/issue from multiple batches"
+ ).format(d.idx, d.batch_no, batch_qty, qty)
+ )
+
@frappe.whitelist()
def get_batch_no(item_code, warehouse, qty=1, throw=False, serial_no=None):
@@ -264,7 +287,11 @@
break
if not batch_no:
- frappe.msgprint(_('Please select a Batch for Item {0}. Unable to find a single batch that fulfills this requirement').format(frappe.bold(item_code)))
+ frappe.msgprint(
+ _(
+ "Please select a Batch for Item {0}. Unable to find a single batch that fulfills this requirement"
+ ).format(frappe.bold(item_code))
+ )
if throw:
raise UnableToSelectBatchError
@@ -273,16 +300,14 @@
def get_batches(item_code, warehouse, qty=1, throw=False, serial_no=None):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
- cond = ''
- if serial_no and frappe.get_cached_value('Item', item_code, 'has_batch_no'):
+
+ cond = ""
+ if serial_no and frappe.get_cached_value("Item", item_code, "has_batch_no"):
serial_nos = get_serial_nos(serial_no)
- batch = frappe.get_all("Serial No",
- fields = ["distinct batch_no"],
- filters= {
- "item_code": item_code,
- "warehouse": warehouse,
- "name": ("in", serial_nos)
- }
+ batch = frappe.get_all(
+ "Serial No",
+ fields=["distinct batch_no"],
+ filters={"item_code": item_code, "warehouse": warehouse, "name": ("in", serial_nos)},
)
if not batch:
@@ -291,9 +316,10 @@
if batch and len(batch) > 1:
return []
- cond = " and `tabBatch`.name = %s" %(frappe.db.escape(batch[0].batch_no))
+ cond = " and `tabBatch`.name = %s" % (frappe.db.escape(batch[0].batch_no))
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
select batch_id, sum(`tabStock Ledger Entry`.actual_qty) as qty
from `tabBatch`
join `tabStock Ledger Entry` ignore index (item_code, warehouse)
@@ -303,24 +329,34 @@
and (`tabBatch`.expiry_date >= CURDATE() or `tabBatch`.expiry_date IS NULL) {0}
group by batch_id
order by `tabBatch`.expiry_date ASC, `tabBatch`.creation ASC
- """.format(cond), (item_code, warehouse), as_dict=True)
+ """.format(
+ cond
+ ),
+ (item_code, warehouse),
+ as_dict=True,
+ )
+
def validate_serial_no_with_batch(serial_nos, item_code):
if frappe.get_cached_value("Serial No", serial_nos[0], "item_code") != item_code:
- frappe.throw(_("The serial no {0} does not belong to item {1}")
- .format(get_link_to_form("Serial No", serial_nos[0]), get_link_to_form("Item", item_code)))
+ frappe.throw(
+ _("The serial no {0} does not belong to item {1}").format(
+ get_link_to_form("Serial No", serial_nos[0]), get_link_to_form("Item", item_code)
+ )
+ )
- serial_no_link = ','.join(get_link_to_form("Serial No", sn) for sn in serial_nos)
+ serial_no_link = ",".join(get_link_to_form("Serial No", sn) for sn in serial_nos)
message = "Serial Nos" if len(serial_nos) > 1 else "Serial No"
- frappe.throw(_("There is no batch found against the {0}: {1}")
- .format(message, serial_no_link))
+ frappe.throw(_("There is no batch found against the {0}: {1}").format(message, serial_no_link))
+
def make_batch(args):
if frappe.db.get_value("Item", args.item, "has_batch_no"):
args.doctype = "Batch"
frappe.get_doc(args).insert().name
+
@frappe.whitelist()
def get_pos_reserved_batch_qty(filters):
import json
@@ -332,16 +368,22 @@
item = frappe.qb.DocType("POS Invoice Item").as_("item")
sum_qty = frappe.query_builder.functions.Sum(item.qty).as_("qty")
- reserved_batch_qty = frappe.qb.from_(p).from_(item).select(sum_qty).where(
- (p.name == item.parent) &
- (p.consolidated_invoice.isnull()) &
- (p.status != "Consolidated") &
- (p.docstatus == 1) &
- (item.docstatus == 1) &
- (item.item_code == filters.get('item_code')) &
- (item.warehouse == filters.get('warehouse')) &
- (item.batch_no == filters.get('batch_no'))
- ).run()
+ reserved_batch_qty = (
+ frappe.qb.from_(p)
+ .from_(item)
+ .select(sum_qty)
+ .where(
+ (p.name == item.parent)
+ & (p.consolidated_invoice.isnull())
+ & (p.status != "Consolidated")
+ & (p.docstatus == 1)
+ & (item.docstatus == 1)
+ & (item.item_code == filters.get("item_code"))
+ & (item.warehouse == filters.get("warehouse"))
+ & (item.batch_no == filters.get("batch_no"))
+ )
+ .run()
+ )
flt_reserved_batch_qty = flt(reserved_batch_qty[0][0])
return flt_reserved_batch_qty
diff --git a/erpnext/stock/doctype/batch/batch_dashboard.py b/erpnext/stock/doctype/batch/batch_dashboard.py
index 725365b..84b64f3 100644
--- a/erpnext/stock/doctype/batch/batch_dashboard.py
+++ b/erpnext/stock/doctype/batch/batch_dashboard.py
@@ -3,23 +3,11 @@
def get_data():
return {
- 'fieldname': 'batch_no',
- 'transactions': [
- {
- 'label': _('Buy'),
- 'items': ['Purchase Invoice', 'Purchase Receipt']
- },
- {
- 'label': _('Sell'),
- 'items': ['Sales Invoice', 'Delivery Note']
- },
- {
- 'label': _('Move'),
- 'items': ['Stock Entry']
- },
- {
- 'label': _('Quality'),
- 'items': ['Quality Inspection']
- }
- ]
+ "fieldname": "batch_no",
+ "transactions": [
+ {"label": _("Buy"), "items": ["Purchase Invoice", "Purchase Receipt"]},
+ {"label": _("Sell"), "items": ["Sales Invoice", "Delivery Note"]},
+ {"label": _("Move"), "items": ["Stock Entry"]},
+ {"label": _("Quality"), "items": ["Quality Inspection"]},
+ ],
}
diff --git a/erpnext/stock/doctype/batch/test_batch.py b/erpnext/stock/doctype/batch/test_batch.py
index 5763753..c76da62 100644
--- a/erpnext/stock/doctype/batch/test_batch.py
+++ b/erpnext/stock/doctype/batch/test_batch.py
@@ -21,134 +21,127 @@
class TestBatch(FrappeTestCase):
def test_item_has_batch_enabled(self):
- self.assertRaises(ValidationError, frappe.get_doc({
- "doctype": "Batch",
- "name": "_test Batch",
- "item": "_Test Item"
- }).save)
+ self.assertRaises(
+ ValidationError,
+ frappe.get_doc({"doctype": "Batch", "name": "_test Batch", "item": "_Test Item"}).save,
+ )
@classmethod
def make_batch_item(cls, item_name):
from erpnext.stock.doctype.item.test_item import make_item
+
if not frappe.db.exists(item_name):
- return make_item(item_name, dict(has_batch_no = 1, create_new_batch = 1, is_stock_item=1))
+ return make_item(item_name, dict(has_batch_no=1, create_new_batch=1, is_stock_item=1))
- def test_purchase_receipt(self, batch_qty = 100):
- '''Test automated batch creation from Purchase Receipt'''
- self.make_batch_item('ITEM-BATCH-1')
+ def test_purchase_receipt(self, batch_qty=100):
+ """Test automated batch creation from Purchase Receipt"""
+ self.make_batch_item("ITEM-BATCH-1")
- receipt = frappe.get_doc(dict(
- doctype='Purchase Receipt',
- supplier='_Test Supplier',
- company='_Test Company',
- items=[
- dict(
- item_code='ITEM-BATCH-1',
- qty=batch_qty,
- rate=10,
- warehouse= 'Stores - _TC'
- )
- ]
- )).insert()
+ receipt = frappe.get_doc(
+ dict(
+ doctype="Purchase Receipt",
+ supplier="_Test Supplier",
+ company="_Test Company",
+ items=[dict(item_code="ITEM-BATCH-1", qty=batch_qty, rate=10, warehouse="Stores - _TC")],
+ )
+ ).insert()
receipt.submit()
self.assertTrue(receipt.items[0].batch_no)
- self.assertEqual(get_batch_qty(receipt.items[0].batch_no,
- receipt.items[0].warehouse), batch_qty)
+ self.assertEqual(get_batch_qty(receipt.items[0].batch_no, receipt.items[0].warehouse), batch_qty)
return receipt
def test_stock_entry_incoming(self):
- '''Test batch creation via Stock Entry (Work Order)'''
+ """Test batch creation via Stock Entry (Work Order)"""
- self.make_batch_item('ITEM-BATCH-1')
+ self.make_batch_item("ITEM-BATCH-1")
- stock_entry = frappe.get_doc(dict(
- doctype = 'Stock Entry',
- purpose = 'Material Receipt',
- company = '_Test Company',
- items = [
- dict(
- item_code = 'ITEM-BATCH-1',
- qty = 90,
- t_warehouse = '_Test Warehouse - _TC',
- cost_center = 'Main - _TC',
- rate = 10
- )
- ]
- ))
+ stock_entry = frappe.get_doc(
+ dict(
+ doctype="Stock Entry",
+ purpose="Material Receipt",
+ company="_Test Company",
+ items=[
+ dict(
+ item_code="ITEM-BATCH-1",
+ qty=90,
+ t_warehouse="_Test Warehouse - _TC",
+ cost_center="Main - _TC",
+ rate=10,
+ )
+ ],
+ )
+ )
stock_entry.set_stock_entry_type()
stock_entry.insert()
stock_entry.submit()
self.assertTrue(stock_entry.items[0].batch_no)
- self.assertEqual(get_batch_qty(stock_entry.items[0].batch_no, stock_entry.items[0].t_warehouse), 90)
+ self.assertEqual(
+ get_batch_qty(stock_entry.items[0].batch_no, stock_entry.items[0].t_warehouse), 90
+ )
def test_delivery_note(self):
- '''Test automatic batch selection for outgoing items'''
+ """Test automatic batch selection for outgoing items"""
batch_qty = 15
receipt = self.test_purchase_receipt(batch_qty)
- item_code = 'ITEM-BATCH-1'
+ item_code = "ITEM-BATCH-1"
- delivery_note = frappe.get_doc(dict(
- doctype='Delivery Note',
- customer='_Test Customer',
- company=receipt.company,
- items=[
- dict(
- item_code=item_code,
- qty=batch_qty,
- rate=10,
- warehouse=receipt.items[0].warehouse
- )
- ]
- )).insert()
+ delivery_note = frappe.get_doc(
+ dict(
+ doctype="Delivery Note",
+ customer="_Test Customer",
+ company=receipt.company,
+ items=[
+ dict(item_code=item_code, qty=batch_qty, rate=10, warehouse=receipt.items[0].warehouse)
+ ],
+ )
+ ).insert()
delivery_note.submit()
# shipped from FEFO batch
self.assertEqual(
- delivery_note.items[0].batch_no,
- get_batch_no(item_code, receipt.items[0].warehouse, batch_qty)
+ delivery_note.items[0].batch_no, get_batch_no(item_code, receipt.items[0].warehouse, batch_qty)
)
def test_delivery_note_fail(self):
- '''Test automatic batch selection for outgoing items'''
+ """Test automatic batch selection for outgoing items"""
receipt = self.test_purchase_receipt(100)
- delivery_note = frappe.get_doc(dict(
- doctype = 'Delivery Note',
- customer = '_Test Customer',
- company = receipt.company,
- items = [
- dict(
- item_code = 'ITEM-BATCH-1',
- qty = 5000,
- rate = 10,
- warehouse = receipt.items[0].warehouse
- )
- ]
- ))
+ delivery_note = frappe.get_doc(
+ dict(
+ doctype="Delivery Note",
+ customer="_Test Customer",
+ company=receipt.company,
+ items=[
+ dict(item_code="ITEM-BATCH-1", qty=5000, rate=10, warehouse=receipt.items[0].warehouse)
+ ],
+ )
+ )
self.assertRaises(UnableToSelectBatchError, delivery_note.insert)
def test_stock_entry_outgoing(self):
- '''Test automatic batch selection for outgoing stock entry'''
+ """Test automatic batch selection for outgoing stock entry"""
batch_qty = 16
receipt = self.test_purchase_receipt(batch_qty)
- item_code = 'ITEM-BATCH-1'
+ item_code = "ITEM-BATCH-1"
- stock_entry = frappe.get_doc(dict(
- doctype='Stock Entry',
- purpose='Material Issue',
- company=receipt.company,
- items=[
- dict(
- item_code=item_code,
- qty=batch_qty,
- s_warehouse=receipt.items[0].warehouse,
- )
- ]
- ))
+ stock_entry = frappe.get_doc(
+ dict(
+ doctype="Stock Entry",
+ purpose="Material Issue",
+ company=receipt.company,
+ items=[
+ dict(
+ item_code=item_code,
+ qty=batch_qty,
+ s_warehouse=receipt.items[0].warehouse,
+ )
+ ],
+ )
+ )
stock_entry.set_stock_entry_type()
stock_entry.insert()
@@ -156,35 +149,38 @@
# assert same batch is selected
self.assertEqual(
- stock_entry.items[0].batch_no,
- get_batch_no(item_code, receipt.items[0].warehouse, batch_qty)
+ stock_entry.items[0].batch_no, get_batch_no(item_code, receipt.items[0].warehouse, batch_qty)
)
def test_batch_split(self):
- '''Test batch splitting'''
+ """Test batch splitting"""
receipt = self.test_purchase_receipt()
from erpnext.stock.doctype.batch.batch import split_batch
- new_batch = split_batch(receipt.items[0].batch_no, 'ITEM-BATCH-1', receipt.items[0].warehouse, 22)
+ new_batch = split_batch(
+ receipt.items[0].batch_no, "ITEM-BATCH-1", receipt.items[0].warehouse, 22
+ )
self.assertEqual(get_batch_qty(receipt.items[0].batch_no, receipt.items[0].warehouse), 78)
self.assertEqual(get_batch_qty(new_batch, receipt.items[0].warehouse), 22)
def test_get_batch_qty(self):
- '''Test getting batch quantities by batch_numbers, item_code or warehouse'''
- self.make_batch_item('ITEM-BATCH-2')
- self.make_new_batch_and_entry('ITEM-BATCH-2', 'batch a', '_Test Warehouse - _TC')
- self.make_new_batch_and_entry('ITEM-BATCH-2', 'batch b', '_Test Warehouse - _TC')
+ """Test getting batch quantities by batch_numbers, item_code or warehouse"""
+ self.make_batch_item("ITEM-BATCH-2")
+ self.make_new_batch_and_entry("ITEM-BATCH-2", "batch a", "_Test Warehouse - _TC")
+ self.make_new_batch_and_entry("ITEM-BATCH-2", "batch b", "_Test Warehouse - _TC")
- self.assertEqual(get_batch_qty(item_code = 'ITEM-BATCH-2', warehouse = '_Test Warehouse - _TC'),
- [{'batch_no': u'batch a', 'qty': 90.0}, {'batch_no': u'batch b', 'qty': 90.0}])
+ self.assertEqual(
+ get_batch_qty(item_code="ITEM-BATCH-2", warehouse="_Test Warehouse - _TC"),
+ [{"batch_no": "batch a", "qty": 90.0}, {"batch_no": "batch b", "qty": 90.0}],
+ )
- self.assertEqual(get_batch_qty('batch a', '_Test Warehouse - _TC'), 90)
+ self.assertEqual(get_batch_qty("batch a", "_Test Warehouse - _TC"), 90)
def test_total_batch_qty(self):
- self.make_batch_item('ITEM-BATCH-3')
+ self.make_batch_item("ITEM-BATCH-3")
existing_batch_qty = flt(frappe.db.get_value("Batch", "B100", "batch_qty"))
- stock_entry = self.make_new_batch_and_entry('ITEM-BATCH-3', 'B100', '_Test Warehouse - _TC')
+ stock_entry = self.make_new_batch_and_entry("ITEM-BATCH-3", "B100", "_Test Warehouse - _TC")
current_batch_qty = flt(frappe.db.get_value("Batch", "B100", "batch_qty"))
self.assertEqual(current_batch_qty, existing_batch_qty + 90)
@@ -195,32 +191,32 @@
@classmethod
def make_new_batch_and_entry(cls, item_name, batch_name, warehouse):
- '''Make a new stock entry for given target warehouse and batch name of item'''
+ """Make a new stock entry for given target warehouse and batch name of item"""
if not frappe.db.exists("Batch", batch_name):
- batch = frappe.get_doc(dict(
- doctype = 'Batch',
- item = item_name,
- batch_id = batch_name
- )).insert(ignore_permissions=True)
+ batch = frappe.get_doc(dict(doctype="Batch", item=item_name, batch_id=batch_name)).insert(
+ ignore_permissions=True
+ )
batch.save()
- stock_entry = frappe.get_doc(dict(
- doctype = 'Stock Entry',
- purpose = 'Material Receipt',
- company = '_Test Company',
- items = [
- dict(
- item_code = item_name,
- qty = 90,
- t_warehouse = warehouse,
- cost_center = 'Main - _TC',
- rate = 10,
- batch_no = batch_name,
- allow_zero_valuation_rate = 1
- )
- ]
- ))
+ stock_entry = frappe.get_doc(
+ dict(
+ doctype="Stock Entry",
+ purpose="Material Receipt",
+ company="_Test Company",
+ items=[
+ dict(
+ item_code=item_name,
+ qty=90,
+ t_warehouse=warehouse,
+ cost_center="Main - _TC",
+ rate=10,
+ batch_no=batch_name,
+ allow_zero_valuation_rate=1,
+ )
+ ],
+ )
+ )
stock_entry.set_stock_entry_type()
stock_entry.insert()
@@ -229,28 +225,28 @@
return stock_entry
def test_batch_name_with_naming_series(self):
- stock_settings = frappe.get_single('Stock Settings')
+ stock_settings = frappe.get_single("Stock Settings")
use_naming_series = cint(stock_settings.use_naming_series)
if not use_naming_series:
- frappe.set_value('Stock Settings', 'Stock Settings', 'use_naming_series', 1)
+ frappe.set_value("Stock Settings", "Stock Settings", "use_naming_series", 1)
- batch = self.make_new_batch('_Test Stock Item For Batch Test1')
+ batch = self.make_new_batch("_Test Stock Item For Batch Test1")
batch_name = batch.name
- self.assertTrue(batch_name.startswith('BATCH-'))
+ self.assertTrue(batch_name.startswith("BATCH-"))
batch.delete()
- batch = self.make_new_batch('_Test Stock Item For Batch Test2')
+ batch = self.make_new_batch("_Test Stock Item For Batch Test2")
self.assertEqual(batch_name, batch.name)
# reset Stock Settings
if not use_naming_series:
- frappe.set_value('Stock Settings', 'Stock Settings', 'use_naming_series', 0)
+ frappe.set_value("Stock Settings", "Stock Settings", "use_naming_series", 0)
def make_new_batch(self, item_name, batch_id=None, do_not_insert=0):
- batch = frappe.new_doc('Batch')
+ batch = frappe.new_doc("Batch")
item = self.make_batch_item(item_name)
batch.item = item.name
@@ -263,53 +259,56 @@
return batch
def test_batch_wise_item_price(self):
- if not frappe.db.get_value('Item', '_Test Batch Price Item'):
- frappe.get_doc({
- 'doctype': 'Item',
- 'is_stock_item': 1,
- 'item_code': '_Test Batch Price Item',
- 'item_group': 'Products',
- 'has_batch_no': 1,
- 'create_new_batch': 1
- }).insert(ignore_permissions=True)
+ if not frappe.db.get_value("Item", "_Test Batch Price Item"):
+ frappe.get_doc(
+ {
+ "doctype": "Item",
+ "is_stock_item": 1,
+ "item_code": "_Test Batch Price Item",
+ "item_group": "Products",
+ "has_batch_no": 1,
+ "create_new_batch": 1,
+ }
+ ).insert(ignore_permissions=True)
- batch1 = create_batch('_Test Batch Price Item', 200, 1)
- batch2 = create_batch('_Test Batch Price Item', 300, 1)
- batch3 = create_batch('_Test Batch Price Item', 400, 0)
+ batch1 = create_batch("_Test Batch Price Item", 200, 1)
+ batch2 = create_batch("_Test Batch Price Item", 300, 1)
+ batch3 = create_batch("_Test Batch Price Item", 400, 0)
company = "_Test Company with perpetual inventory"
- currency = frappe.get_cached_value("Company", company, "default_currency")
+ currency = frappe.get_cached_value("Company", company, "default_currency")
- args = frappe._dict({
- "item_code": "_Test Batch Price Item",
- "company": company,
- "price_list": "_Test Price List",
- "currency": currency,
- "doctype": "Sales Invoice",
- "conversion_rate": 1,
- "price_list_currency": "_Test Currency",
- "plc_conversion_rate": 1,
- "customer": "_Test Customer",
- "name": None
- })
+ args = frappe._dict(
+ {
+ "item_code": "_Test Batch Price Item",
+ "company": company,
+ "price_list": "_Test Price List",
+ "currency": currency,
+ "doctype": "Sales Invoice",
+ "conversion_rate": 1,
+ "price_list_currency": "_Test Currency",
+ "plc_conversion_rate": 1,
+ "customer": "_Test Customer",
+ "name": None,
+ }
+ )
- #test price for batch1
- args.update({'batch_no': batch1})
+ # test price for batch1
+ args.update({"batch_no": batch1})
details = get_item_details(args)
- self.assertEqual(details.get('price_list_rate'), 200)
+ self.assertEqual(details.get("price_list_rate"), 200)
- #test price for batch2
- args.update({'batch_no': batch2})
+ # test price for batch2
+ args.update({"batch_no": batch2})
details = get_item_details(args)
- self.assertEqual(details.get('price_list_rate'), 300)
+ self.assertEqual(details.get("price_list_rate"), 300)
- #test price for batch3
- args.update({'batch_no': batch3})
+ # test price for batch3
+ args.update({"batch_no": batch3})
details = get_item_details(args)
- self.assertEqual(details.get('price_list_rate'), 400)
+ self.assertEqual(details.get("price_list_rate"), 400)
-
- def test_basic_batch_wise_valuation(self, batch_qty = 100):
+ def test_basic_batch_wise_valuation(self, batch_qty=100):
item_code = "_TestBatchWiseVal"
warehouse = "_Test Warehouse - _TC"
self.make_batch_item(item_code)
@@ -358,7 +357,9 @@
self.make_batch_item(item_code)
def assertValuation(expected):
- actual = get_valuation_rate(item_code, warehouse, "voucher_type", "voucher_no", batch_no=batch_no)
+ actual = get_valuation_rate(
+ item_code, warehouse, "voucher_type", "voucher_no", batch_no=batch_no
+ )
self.assertAlmostEqual(actual, expected)
se = make_stock_entry(item_code=item_code, qty=100, rate=10, target=warehouse)
@@ -381,13 +382,14 @@
assertValuation(15)
# reset rate with stock reconiliation
- create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=10, rate=25, batch_no=batch_no)
+ create_stock_reconciliation(
+ item_code=item_code, warehouse=warehouse, qty=10, rate=25, batch_no=batch_no
+ )
assertValuation(25)
make_stock_entry(item_code=item_code, qty=20, rate=20, target=warehouse, batch_no=batch_no)
assertValuation((20 * 20 + 10 * 25) / (10 + 20))
-
def test_update_batch_properties(self):
item_code = "_TestBatchWiseVal"
self.make_batch_item(item_code)
@@ -406,13 +408,17 @@
self.assertEqual(getdate(batch.expiry_date), getdate(expiry_date))
-
def create_batch(item_code, rate, create_item_price_for_batch):
- pi = make_purchase_invoice(company="_Test Company",
- warehouse= "Stores - _TC", cost_center = "Main - _TC", update_stock=1,
- expense_account ="_Test Account Cost for Goods Sold - _TC", item_code=item_code)
+ pi = make_purchase_invoice(
+ company="_Test Company",
+ warehouse="Stores - _TC",
+ cost_center="Main - _TC",
+ update_stock=1,
+ expense_account="_Test Account Cost for Goods Sold - _TC",
+ item_code=item_code,
+ )
- batch = frappe.db.get_value('Batch', {'item': item_code, 'reference_name': pi.name})
+ batch = frappe.db.get_value("Batch", {"item": item_code, "reference_name": pi.name})
if not create_item_price_for_batch:
create_price_list_for_batch(item_code, None, rate)
@@ -421,14 +427,18 @@
return batch
+
def create_price_list_for_batch(item_code, batch, rate):
- frappe.get_doc({
- 'doctype': 'Item Price',
- 'item_code': '_Test Batch Price Item',
- 'price_list': '_Test Price List',
- 'batch_no': batch,
- 'price_list_rate': rate
- }).insert()
+ frappe.get_doc(
+ {
+ "doctype": "Item Price",
+ "item_code": "_Test Batch Price Item",
+ "price_list": "_Test Price List",
+ "batch_no": batch,
+ "price_list_rate": rate,
+ }
+ ).insert()
+
def make_new_batch(**args):
args = frappe._dict(args)
@@ -436,10 +446,12 @@
if frappe.db.exists("Batch", args.batch_id):
batch = frappe.get_doc("Batch", args.batch_id)
else:
- batch = frappe.get_doc({
- "doctype": "Batch",
- "batch_id": args.batch_id,
- "item": args.item_code,
- }).insert()
+ batch = frappe.get_doc(
+ {
+ "doctype": "Batch",
+ "batch_id": args.batch_id,
+ "item": args.item_code,
+ }
+ ).insert()
return batch
diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py
index 3bc15a8..6cb9f7e 100644
--- a/erpnext/stock/doctype/bin/bin.py
+++ b/erpnext/stock/doctype/bin/bin.py
@@ -12,93 +12,109 @@
class Bin(Document):
def before_save(self):
if self.get("__islocal") or not self.stock_uom:
- self.stock_uom = frappe.get_cached_value('Item', self.item_code, 'stock_uom')
+ self.stock_uom = frappe.get_cached_value("Item", self.item_code, "stock_uom")
self.set_projected_qty()
def set_projected_qty(self):
- self.projected_qty = (flt(self.actual_qty) + flt(self.ordered_qty)
- + flt(self.indented_qty) + flt(self.planned_qty) - flt(self.reserved_qty)
- - flt(self.reserved_qty_for_production) - flt(self.reserved_qty_for_sub_contract))
+ self.projected_qty = (
+ flt(self.actual_qty)
+ + flt(self.ordered_qty)
+ + flt(self.indented_qty)
+ + flt(self.planned_qty)
+ - flt(self.reserved_qty)
+ - flt(self.reserved_qty_for_production)
+ - flt(self.reserved_qty_for_sub_contract)
+ )
def update_reserved_qty_for_production(self):
- '''Update qty reserved for production from Production Item tables
- in open work orders'''
+ """Update qty reserved for production from Production Item tables
+ in open work orders"""
from erpnext.manufacturing.doctype.work_order.work_order import get_reserved_qty_for_production
- self.reserved_qty_for_production = get_reserved_qty_for_production(self.item_code, self.warehouse)
+ self.reserved_qty_for_production = get_reserved_qty_for_production(
+ self.item_code, self.warehouse
+ )
self.set_projected_qty()
- self.db_set('reserved_qty_for_production', flt(self.reserved_qty_for_production))
- self.db_set('projected_qty', self.projected_qty)
+ self.db_set("reserved_qty_for_production", flt(self.reserved_qty_for_production))
+ self.db_set("projected_qty", self.projected_qty)
def update_reserved_qty_for_sub_contracting(self):
- #reserved qty
+ # reserved qty
po = frappe.qb.DocType("Purchase Order")
supplied_item = frappe.qb.DocType("Purchase Order Item Supplied")
reserved_qty_for_sub_contract = (
- frappe.qb
- .from_(po)
- .from_(supplied_item)
- .select(Sum(Coalesce(supplied_item.required_qty, 0)))
- .where(
- (supplied_item.rm_item_code == self.item_code)
- & (po.name == supplied_item.parent)
- & (po.docstatus == 1)
- & (po.is_subcontracted == "Yes")
- & (po.status != "Closed")
- & (po.per_received < 100)
- & (supplied_item.reserve_warehouse == self.warehouse)
- )
- ).run()[0][0] or 0.0
+ frappe.qb.from_(po)
+ .from_(supplied_item)
+ .select(Sum(Coalesce(supplied_item.required_qty, 0)))
+ .where(
+ (supplied_item.rm_item_code == self.item_code)
+ & (po.name == supplied_item.parent)
+ & (po.docstatus == 1)
+ & (po.is_subcontracted == "Yes")
+ & (po.status != "Closed")
+ & (po.per_received < 100)
+ & (supplied_item.reserve_warehouse == self.warehouse)
+ )
+ ).run()[0][0] or 0.0
se = frappe.qb.DocType("Stock Entry")
se_item = frappe.qb.DocType("Stock Entry Detail")
materials_transferred = (
- frappe.qb
- .from_(se)
- .from_(se_item)
- .from_(po)
- .select(Sum(
- Case()
- .when(se.is_return == 1, se_item.transfer_qty * -1)
- .else_(se_item.transfer_qty)
- ))
- .where(
- (se.docstatus == 1)
- & (se.purpose == "Send to Subcontractor")
- & (Coalesce(se.purchase_order, "") != "")
- & ((se_item.item_code == self.item_code)
- | (se_item.original_item == self.item_code))
- & (se.name == se_item.parent)
- & (po.name == se.purchase_order)
- & (po.docstatus == 1)
- & (po.is_subcontracted == "Yes")
- & (po.status != "Closed")
- & (po.per_received < 100)
- )
- ).run()[0][0] or 0.0
+ frappe.qb.from_(se)
+ .from_(se_item)
+ .from_(po)
+ .select(
+ Sum(Case().when(se.is_return == 1, se_item.transfer_qty * -1).else_(se_item.transfer_qty))
+ )
+ .where(
+ (se.docstatus == 1)
+ & (se.purpose == "Send to Subcontractor")
+ & (Coalesce(se.purchase_order, "") != "")
+ & ((se_item.item_code == self.item_code) | (se_item.original_item == self.item_code))
+ & (se.name == se_item.parent)
+ & (po.name == se.purchase_order)
+ & (po.docstatus == 1)
+ & (po.is_subcontracted == "Yes")
+ & (po.status != "Closed")
+ & (po.per_received < 100)
+ )
+ ).run()[0][0] or 0.0
if reserved_qty_for_sub_contract > materials_transferred:
reserved_qty_for_sub_contract = reserved_qty_for_sub_contract - materials_transferred
else:
reserved_qty_for_sub_contract = 0
- self.db_set('reserved_qty_for_sub_contract', reserved_qty_for_sub_contract)
+ self.db_set("reserved_qty_for_sub_contract", reserved_qty_for_sub_contract)
self.set_projected_qty()
- self.db_set('projected_qty', self.projected_qty)
+ self.db_set("projected_qty", self.projected_qty)
+
def on_doctype_update():
frappe.db.add_unique("Bin", ["item_code", "warehouse"], constraint_name="unique_item_warehouse")
def get_bin_details(bin_name):
- return frappe.db.get_value('Bin', bin_name, ['actual_qty', 'ordered_qty',
- 'reserved_qty', 'indented_qty', 'planned_qty', 'reserved_qty_for_production',
- 'reserved_qty_for_sub_contract'], as_dict=1)
+ return frappe.db.get_value(
+ "Bin",
+ bin_name,
+ [
+ "actual_qty",
+ "ordered_qty",
+ "reserved_qty",
+ "indented_qty",
+ "planned_qty",
+ "reserved_qty_for_production",
+ "reserved_qty_for_sub_contract",
+ ],
+ as_dict=1,
+ )
+
def update_qty(bin_name, args):
from erpnext.controllers.stock_controller import future_sle_exists
@@ -109,32 +125,45 @@
# actual qty is not up to date in case of backdated transaction
if future_sle_exists(args):
- actual_qty = frappe.db.get_value("Stock Ledger Entry",
+ actual_qty = (
+ frappe.db.get_value(
+ "Stock Ledger Entry",
filters={
"item_code": args.get("item_code"),
"warehouse": args.get("warehouse"),
- "is_cancelled": 0
+ "is_cancelled": 0,
},
fieldname="qty_after_transaction",
order_by="posting_date desc, posting_time desc, creation desc",
- ) or 0.0
+ )
+ or 0.0
+ )
ordered_qty = flt(bin_details.ordered_qty) + flt(args.get("ordered_qty"))
reserved_qty = flt(bin_details.reserved_qty) + flt(args.get("reserved_qty"))
indented_qty = flt(bin_details.indented_qty) + flt(args.get("indented_qty"))
planned_qty = flt(bin_details.planned_qty) + flt(args.get("planned_qty"))
-
# compute projected qty
- projected_qty = (flt(actual_qty) + flt(ordered_qty)
- + flt(indented_qty) + flt(planned_qty) - flt(reserved_qty)
- - flt(bin_details.reserved_qty_for_production) - flt(bin_details.reserved_qty_for_sub_contract))
+ projected_qty = (
+ flt(actual_qty)
+ + flt(ordered_qty)
+ + flt(indented_qty)
+ + flt(planned_qty)
+ - flt(reserved_qty)
+ - flt(bin_details.reserved_qty_for_production)
+ - flt(bin_details.reserved_qty_for_sub_contract)
+ )
- frappe.db.set_value('Bin', bin_name, {
- 'actual_qty': actual_qty,
- 'ordered_qty': ordered_qty,
- 'reserved_qty': reserved_qty,
- 'indented_qty': indented_qty,
- 'planned_qty': planned_qty,
- 'projected_qty': projected_qty
- })
+ frappe.db.set_value(
+ "Bin",
+ bin_name,
+ {
+ "actual_qty": actual_qty,
+ "ordered_qty": ordered_qty,
+ "reserved_qty": reserved_qty,
+ "indented_qty": indented_qty,
+ "planned_qty": planned_qty,
+ "projected_qty": projected_qty,
+ },
+ )
diff --git a/erpnext/stock/doctype/bin/test_bin.py b/erpnext/stock/doctype/bin/test_bin.py
index ec0d8a8..b79dee8 100644
--- a/erpnext/stock/doctype/bin/test_bin.py
+++ b/erpnext/stock/doctype/bin/test_bin.py
@@ -9,10 +9,8 @@
class TestBin(FrappeTestCase):
-
-
def test_concurrent_inserts(self):
- """ Ensure no duplicates are possible in case of concurrent inserts"""
+ """Ensure no duplicates are possible in case of concurrent inserts"""
item_code = "_TestConcurrentBin"
make_item(item_code)
warehouse = "_Test Warehouse - _TC"
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index 492f90b..69e052b 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -15,72 +15,76 @@
from erpnext.stock.doctype.batch.batch import set_batch_nos
from erpnext.stock.doctype.serial_no.serial_no import get_delivery_note_serial_no
-form_grid_templates = {
- "items": "templates/form_grid/item_grid.html"
-}
+form_grid_templates = {"items": "templates/form_grid/item_grid.html"}
+
class DeliveryNote(SellingController):
def __init__(self, *args, **kwargs):
super(DeliveryNote, self).__init__(*args, **kwargs)
- self.status_updater = [{
- 'source_dt': 'Delivery Note Item',
- 'target_dt': 'Sales Order Item',
- 'join_field': 'so_detail',
- 'target_field': 'delivered_qty',
- 'target_parent_dt': 'Sales Order',
- 'target_parent_field': 'per_delivered',
- 'target_ref_field': 'qty',
- 'source_field': 'qty',
- 'percent_join_field': 'against_sales_order',
- 'status_field': 'delivery_status',
- 'keyword': 'Delivered',
- 'second_source_dt': 'Sales Invoice Item',
- 'second_source_field': 'qty',
- 'second_join_field': 'so_detail',
- 'overflow_type': 'delivery',
- 'second_source_extra_cond': """ and exists(select name from `tabSales Invoice`
- where name=`tabSales Invoice Item`.parent and update_stock = 1)"""
- },
- {
- 'source_dt': 'Delivery Note Item',
- 'target_dt': 'Sales Invoice Item',
- 'join_field': 'si_detail',
- 'target_field': 'delivered_qty',
- 'target_parent_dt': 'Sales Invoice',
- 'target_ref_field': 'qty',
- 'source_field': 'qty',
- 'percent_join_field': 'against_sales_invoice',
- 'overflow_type': 'delivery',
- 'no_allowance': 1
- }]
- if cint(self.is_return):
- self.status_updater.extend([{
- 'source_dt': 'Delivery Note Item',
- 'target_dt': 'Sales Order Item',
- 'join_field': 'so_detail',
- 'target_field': 'returned_qty',
- 'target_parent_dt': 'Sales Order',
- 'source_field': '-1 * qty',
- 'second_source_dt': 'Sales Invoice Item',
- 'second_source_field': '-1 * qty',
- 'second_join_field': 'so_detail',
- 'extra_cond': """ and exists (select name from `tabDelivery Note`
- where name=`tabDelivery Note Item`.parent and is_return=1)""",
- 'second_source_extra_cond': """ and exists (select name from `tabSales Invoice`
- where name=`tabSales Invoice Item`.parent and is_return=1 and update_stock=1)"""
+ self.status_updater = [
+ {
+ "source_dt": "Delivery Note Item",
+ "target_dt": "Sales Order Item",
+ "join_field": "so_detail",
+ "target_field": "delivered_qty",
+ "target_parent_dt": "Sales Order",
+ "target_parent_field": "per_delivered",
+ "target_ref_field": "qty",
+ "source_field": "qty",
+ "percent_join_field": "against_sales_order",
+ "status_field": "delivery_status",
+ "keyword": "Delivered",
+ "second_source_dt": "Sales Invoice Item",
+ "second_source_field": "qty",
+ "second_join_field": "so_detail",
+ "overflow_type": "delivery",
+ "second_source_extra_cond": """ and exists(select name from `tabSales Invoice`
+ where name=`tabSales Invoice Item`.parent and update_stock = 1)""",
},
{
- 'source_dt': 'Delivery Note Item',
- 'target_dt': 'Delivery Note Item',
- 'join_field': 'dn_detail',
- 'target_field': 'returned_qty',
- 'target_parent_dt': 'Delivery Note',
- 'target_parent_field': 'per_returned',
- 'target_ref_field': 'stock_qty',
- 'source_field': '-1 * stock_qty',
- 'percent_join_field_parent': 'return_against'
- }
- ])
+ "source_dt": "Delivery Note Item",
+ "target_dt": "Sales Invoice Item",
+ "join_field": "si_detail",
+ "target_field": "delivered_qty",
+ "target_parent_dt": "Sales Invoice",
+ "target_ref_field": "qty",
+ "source_field": "qty",
+ "percent_join_field": "against_sales_invoice",
+ "overflow_type": "delivery",
+ "no_allowance": 1,
+ },
+ ]
+ if cint(self.is_return):
+ self.status_updater.extend(
+ [
+ {
+ "source_dt": "Delivery Note Item",
+ "target_dt": "Sales Order Item",
+ "join_field": "so_detail",
+ "target_field": "returned_qty",
+ "target_parent_dt": "Sales Order",
+ "source_field": "-1 * qty",
+ "second_source_dt": "Sales Invoice Item",
+ "second_source_field": "-1 * qty",
+ "second_join_field": "so_detail",
+ "extra_cond": """ and exists (select name from `tabDelivery Note`
+ where name=`tabDelivery Note Item`.parent and is_return=1)""",
+ "second_source_extra_cond": """ and exists (select name from `tabSales Invoice`
+ where name=`tabSales Invoice Item`.parent and is_return=1 and update_stock=1)""",
+ },
+ {
+ "source_dt": "Delivery Note Item",
+ "target_dt": "Delivery Note Item",
+ "join_field": "dn_detail",
+ "target_field": "returned_qty",
+ "target_parent_dt": "Delivery Note",
+ "target_parent_field": "per_returned",
+ "target_ref_field": "stock_qty",
+ "source_field": "-1 * stock_qty",
+ "percent_join_field_parent": "return_against",
+ },
+ ]
+ )
def before_print(self, settings=None):
def toggle_print_hide(meta, fieldname):
@@ -93,7 +97,7 @@
item_meta = frappe.get_meta("Delivery Note Item")
print_hide_fields = {
"parent": ["grand_total", "rounded_total", "in_words", "currency", "total", "taxes"],
- "items": ["rate", "amount", "discount_amount", "price_list_rate", "discount_percentage"]
+ "items": ["rate", "amount", "discount_amount", "price_list_rate", "discount_percentage"],
}
for key, fieldname in print_hide_fields.items():
@@ -103,16 +107,19 @@
super(DeliveryNote, self).before_print(settings)
def set_actual_qty(self):
- for d in self.get('items'):
+ for d in self.get("items"):
if d.item_code and d.warehouse:
- actual_qty = frappe.db.sql("""select actual_qty from `tabBin`
- where item_code = %s and warehouse = %s""", (d.item_code, d.warehouse))
+ actual_qty = frappe.db.sql(
+ """select actual_qty from `tabBin`
+ where item_code = %s and warehouse = %s""",
+ (d.item_code, d.warehouse),
+ )
d.actual_qty = actual_qty and flt(actual_qty[0][0]) or 0
def so_required(self):
"""check in manage account if sales order required or not"""
- if frappe.db.get_value("Selling Settings", None, 'so_required') == 'Yes':
- for d in self.get('items'):
+ if frappe.db.get_value("Selling Settings", None, "so_required") == "Yes":
+ for d in self.get("items"):
if not d.against_sales_order:
frappe.throw(_("Sales Order required for Item {0}").format(d.item_code))
@@ -129,71 +136,91 @@
self.validate_with_previous_doc()
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
+
make_packing_list(self)
- if self._action != 'submit' and not self.is_return:
- set_batch_nos(self, 'warehouse', throw=True)
- set_batch_nos(self, 'warehouse', throw=True, child_table="packed_items")
+ if self._action != "submit" and not self.is_return:
+ set_batch_nos(self, "warehouse", throw=True)
+ set_batch_nos(self, "warehouse", throw=True, child_table="packed_items")
self.update_current_stock()
- if not self.installation_status: self.installation_status = 'Not Installed'
+ if not self.installation_status:
+ self.installation_status = "Not Installed"
self.reset_default_field_value("set_warehouse", "items", "warehouse")
def validate_with_previous_doc(self):
- super(DeliveryNote, self).validate_with_previous_doc({
- "Sales Order": {
- "ref_dn_field": "against_sales_order",
- "compare_fields": [["customer", "="], ["company", "="], ["project", "="], ["currency", "="]]
- },
- "Sales Order Item": {
- "ref_dn_field": "so_detail",
- "compare_fields": [["item_code", "="], ["uom", "="], ["conversion_factor", "="]],
- "is_child_table": True,
- "allow_duplicate_prev_row_id": True
- },
- "Sales Invoice": {
- "ref_dn_field": "against_sales_invoice",
- "compare_fields": [["customer", "="], ["company", "="], ["project", "="], ["currency", "="]]
- },
- "Sales Invoice Item": {
- "ref_dn_field": "si_detail",
- "compare_fields": [["item_code", "="], ["uom", "="], ["conversion_factor", "="]],
- "is_child_table": True,
- "allow_duplicate_prev_row_id": True
- },
- })
+ super(DeliveryNote, self).validate_with_previous_doc(
+ {
+ "Sales Order": {
+ "ref_dn_field": "against_sales_order",
+ "compare_fields": [["customer", "="], ["company", "="], ["project", "="], ["currency", "="]],
+ },
+ "Sales Order Item": {
+ "ref_dn_field": "so_detail",
+ "compare_fields": [["item_code", "="], ["uom", "="], ["conversion_factor", "="]],
+ "is_child_table": True,
+ "allow_duplicate_prev_row_id": True,
+ },
+ "Sales Invoice": {
+ "ref_dn_field": "against_sales_invoice",
+ "compare_fields": [["customer", "="], ["company", "="], ["project", "="], ["currency", "="]],
+ },
+ "Sales Invoice Item": {
+ "ref_dn_field": "si_detail",
+ "compare_fields": [["item_code", "="], ["uom", "="], ["conversion_factor", "="]],
+ "is_child_table": True,
+ "allow_duplicate_prev_row_id": True,
+ },
+ }
+ )
- if cint(frappe.db.get_single_value('Selling Settings', 'maintain_same_sales_rate')) \
- and not self.is_return:
- self.validate_rate_with_reference_doc([["Sales Order", "against_sales_order", "so_detail"],
- ["Sales Invoice", "against_sales_invoice", "si_detail"]])
+ if (
+ cint(frappe.db.get_single_value("Selling Settings", "maintain_same_sales_rate"))
+ and not self.is_return
+ ):
+ self.validate_rate_with_reference_doc(
+ [
+ ["Sales Order", "against_sales_order", "so_detail"],
+ ["Sales Invoice", "against_sales_invoice", "si_detail"],
+ ]
+ )
def validate_proj_cust(self):
"""check for does customer belong to same project as entered.."""
if self.project and self.customer:
- res = frappe.db.sql("""select name from `tabProject`
+ res = frappe.db.sql(
+ """select name from `tabProject`
where name = %s and (customer = %s or
- ifnull(customer,'')='')""", (self.project, self.customer))
+ ifnull(customer,'')='')""",
+ (self.project, self.customer),
+ )
if not res:
- frappe.throw(_("Customer {0} does not belong to project {1}").format(self.customer, self.project))
+ frappe.throw(
+ _("Customer {0} does not belong to project {1}").format(self.customer, self.project)
+ )
def validate_warehouse(self):
super(DeliveryNote, self).validate_warehouse()
for d in self.get_item_list():
- if not d['warehouse'] and frappe.db.get_value("Item", d['item_code'], "is_stock_item") == 1:
+ if not d["warehouse"] and frappe.db.get_value("Item", d["item_code"], "is_stock_item") == 1:
frappe.throw(_("Warehouse required for stock Item {0}").format(d["item_code"]))
def update_current_stock(self):
if self.get("_action") and self._action != "update_after_submit":
- for d in self.get('items'):
- d.actual_qty = frappe.db.get_value("Bin", {"item_code": d.item_code,
- "warehouse": d.warehouse}, "actual_qty")
+ for d in self.get("items"):
+ d.actual_qty = frappe.db.get_value(
+ "Bin", {"item_code": d.item_code, "warehouse": d.warehouse}, "actual_qty"
+ )
- for d in self.get('packed_items'):
- bin_qty = frappe.db.get_value("Bin", {"item_code": d.item_code,
- "warehouse": d.warehouse}, ["actual_qty", "projected_qty"], as_dict=True)
+ for d in self.get("packed_items"):
+ bin_qty = frappe.db.get_value(
+ "Bin",
+ {"item_code": d.item_code, "warehouse": d.warehouse},
+ ["actual_qty", "projected_qty"],
+ as_dict=True,
+ )
if bin_qty:
d.actual_qty = flt(bin_qty.actual_qty)
d.projected_qty = flt(bin_qty.projected_qty)
@@ -202,7 +229,9 @@
self.validate_packed_qty()
# Check for Approving Authority
- frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.company, self.base_grand_total, self)
+ frappe.get_doc("Authorization Control").validate_approving_authority(
+ self.doctype, self.company, self.base_grand_total, self
+ )
# update delivered qty in sales order
self.update_prevdoc_status()
@@ -235,16 +264,20 @@
self.make_gl_entries_on_cancel()
self.repost_future_sle_and_gle()
- self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation')
+ self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation")
def check_credit_limit(self):
from erpnext.selling.doctype.customer.customer import check_credit_limit
extra_amount = 0
validate_against_credit_limit = False
- bypass_credit_limit_check_at_sales_order = cint(frappe.db.get_value("Customer Credit Limit",
- filters={'parent': self.customer, 'parenttype': 'Customer', 'company': self.company},
- fieldname="bypass_credit_limit_check"))
+ bypass_credit_limit_check_at_sales_order = cint(
+ frappe.db.get_value(
+ "Customer Credit Limit",
+ filters={"parent": self.customer, "parenttype": "Customer", "company": self.company},
+ fieldname="bypass_credit_limit_check",
+ )
+ )
if bypass_credit_limit_check_at_sales_order:
validate_against_credit_limit = True
@@ -256,48 +289,58 @@
break
if validate_against_credit_limit:
- check_credit_limit(self.customer, self.company,
- bypass_credit_limit_check_at_sales_order, extra_amount)
+ check_credit_limit(
+ self.customer, self.company, bypass_credit_limit_check_at_sales_order, extra_amount
+ )
def validate_packed_qty(self):
"""
- Validate that if packed qty exists, it should be equal to qty
+ Validate that if packed qty exists, it should be equal to qty
"""
- if not any(flt(d.get('packed_qty')) for d in self.get("items")):
+ if not any(flt(d.get("packed_qty")) for d in self.get("items")):
return
has_error = False
for d in self.get("items"):
- if flt(d.get('qty')) != flt(d.get('packed_qty')):
- frappe.msgprint(_("Packed quantity must equal quantity for Item {0} in row {1}").format(d.item_code, d.idx))
+ if flt(d.get("qty")) != flt(d.get("packed_qty")):
+ frappe.msgprint(
+ _("Packed quantity must equal quantity for Item {0} in row {1}").format(d.item_code, d.idx)
+ )
has_error = True
if has_error:
raise frappe.ValidationError
def check_next_docstatus(self):
- submit_rv = frappe.db.sql("""select t1.name
+ submit_rv = frappe.db.sql(
+ """select t1.name
from `tabSales Invoice` t1,`tabSales Invoice Item` t2
where t1.name = t2.parent and t2.delivery_note = %s and t1.docstatus = 1""",
- (self.name))
+ (self.name),
+ )
if submit_rv:
frappe.throw(_("Sales Invoice {0} has already been submitted").format(submit_rv[0][0]))
- submit_in = frappe.db.sql("""select t1.name
+ submit_in = frappe.db.sql(
+ """select t1.name
from `tabInstallation Note` t1, `tabInstallation Note Item` t2
where t1.name = t2.parent and t2.prevdoc_docname = %s and t1.docstatus = 1""",
- (self.name))
+ (self.name),
+ )
if submit_in:
frappe.throw(_("Installation Note {0} has already been submitted").format(submit_in[0][0]))
def cancel_packing_slips(self):
"""
- Cancel submitted packing slips related to this delivery note
+ Cancel submitted packing slips related to this delivery note
"""
- res = frappe.db.sql("""SELECT name FROM `tabPacking Slip` WHERE delivery_note = %s
- AND docstatus = 1""", self.name)
+ res = frappe.db.sql(
+ """SELECT name FROM `tabPacking Slip` WHERE delivery_note = %s
+ AND docstatus = 1""",
+ self.name,
+ )
if res:
for r in res:
- ps = frappe.get_doc('Packing Slip', r[0])
+ ps = frappe.get_doc("Packing Slip", r[0])
ps.cancel()
frappe.msgprint(_("Packing Slip(s) cancelled"))
@@ -310,7 +353,7 @@
updated_delivery_notes = [self.name]
for d in self.get("items"):
if d.si_detail and not d.so_detail:
- d.db_set('billed_amt', d.amount, update_modified=update_modified)
+ d.db_set("billed_amt", d.amount, update_modified=update_modified)
elif d.so_detail:
updated_delivery_notes += update_billed_amount_based_on_so(d.so_detail, update_modified)
@@ -327,11 +370,16 @@
return_invoice.save()
return_invoice.submit()
- credit_note_link = frappe.utils.get_link_to_form('Sales Invoice', return_invoice.name)
+ credit_note_link = frappe.utils.get_link_to_form("Sales Invoice", return_invoice.name)
frappe.msgprint(_("Credit Note {0} has been created automatically").format(credit_note_link))
except Exception:
- frappe.throw(_("Could not create Credit Note automatically, please uncheck 'Issue Credit Note' and submit again"))
+ frappe.throw(
+ _(
+ "Could not create Credit Note automatically, please uncheck 'Issue Credit Note' and submit again"
+ )
+ )
+
def update_billed_amount_based_on_so(so_detail, update_modified=True):
from frappe.query_builder.functions import Sum
@@ -340,25 +388,35 @@
si_item = frappe.qb.DocType("Sales Invoice Item").as_("si_item")
sum_amount = Sum(si_item.amount).as_("amount")
- billed_against_so = frappe.qb.from_(si_item).select(sum_amount).where(
- (si_item.so_detail == so_detail) &
- ((si_item.dn_detail.isnull()) | (si_item.dn_detail == '')) &
- (si_item.docstatus == 1)
- ).run()
+ billed_against_so = (
+ frappe.qb.from_(si_item)
+ .select(sum_amount)
+ .where(
+ (si_item.so_detail == so_detail)
+ & ((si_item.dn_detail.isnull()) | (si_item.dn_detail == ""))
+ & (si_item.docstatus == 1)
+ )
+ .run()
+ )
billed_against_so = billed_against_so and billed_against_so[0][0] or 0
# Get all Delivery Note Item rows against the Sales Order Item row
dn = frappe.qb.DocType("Delivery Note").as_("dn")
dn_item = frappe.qb.DocType("Delivery Note Item").as_("dn_item")
- dn_details = frappe.qb.from_(dn).from_(dn_item).select(dn_item.name, dn_item.amount, dn_item.si_detail, dn_item.parent).where(
- (dn.name == dn_item.parent) &
- (dn_item.so_detail == so_detail) &
- (dn.docstatus == 1) &
- (dn.is_return == 0)
- ).orderby(
- dn.posting_date, dn.posting_time, dn.name
- ).run(as_dict=True)
+ dn_details = (
+ frappe.qb.from_(dn)
+ .from_(dn_item)
+ .select(dn_item.name, dn_item.amount, dn_item.si_detail, dn_item.parent)
+ .where(
+ (dn.name == dn_item.parent)
+ & (dn_item.so_detail == so_detail)
+ & (dn.docstatus == 1)
+ & (dn.is_return == 0)
+ )
+ .orderby(dn.posting_date, dn.posting_time, dn.name)
+ .run(as_dict=True)
+ )
updated_dn = []
for dnd in dn_details:
@@ -370,8 +428,11 @@
billed_against_so -= billed_amt_agianst_dn
else:
# Get billed amount directly against Delivery Note
- billed_amt_agianst_dn = frappe.db.sql("""select sum(amount) from `tabSales Invoice Item`
- where dn_detail=%s and docstatus=1""", dnd.name)
+ billed_amt_agianst_dn = frappe.db.sql(
+ """select sum(amount) from `tabSales Invoice Item`
+ where dn_detail=%s and docstatus=1""",
+ dnd.name,
+ )
billed_amt_agianst_dn = billed_amt_agianst_dn and billed_amt_agianst_dn[0][0] or 0
# Distribute billed amount directly against SO between DNs based on FIFO
@@ -384,50 +445,71 @@
billed_amt_agianst_dn += billed_against_so
billed_against_so = 0
- frappe.db.set_value("Delivery Note Item", dnd.name, "billed_amt", billed_amt_agianst_dn, update_modified=update_modified)
+ frappe.db.set_value(
+ "Delivery Note Item",
+ dnd.name,
+ "billed_amt",
+ billed_amt_agianst_dn,
+ update_modified=update_modified,
+ )
updated_dn.append(dnd.parent)
return updated_dn
+
def get_list_context(context=None):
from erpnext.controllers.website_list_for_contact import get_list_context
+
list_context = get_list_context(context)
- list_context.update({
- 'show_sidebar': True,
- 'show_search': True,
- 'no_breadcrumbs': True,
- 'title': _('Shipments'),
- })
+ list_context.update(
+ {
+ "show_sidebar": True,
+ "show_search": True,
+ "no_breadcrumbs": True,
+ "title": _("Shipments"),
+ }
+ )
return list_context
+
def get_invoiced_qty_map(delivery_note):
"""returns a map: {dn_detail: invoiced_qty}"""
invoiced_qty_map = {}
- for dn_detail, qty in frappe.db.sql("""select dn_detail, qty from `tabSales Invoice Item`
- where delivery_note=%s and docstatus=1""", delivery_note):
- if not invoiced_qty_map.get(dn_detail):
- invoiced_qty_map[dn_detail] = 0
- invoiced_qty_map[dn_detail] += qty
+ for dn_detail, qty in frappe.db.sql(
+ """select dn_detail, qty from `tabSales Invoice Item`
+ where delivery_note=%s and docstatus=1""",
+ delivery_note,
+ ):
+ if not invoiced_qty_map.get(dn_detail):
+ invoiced_qty_map[dn_detail] = 0
+ invoiced_qty_map[dn_detail] += qty
return invoiced_qty_map
+
def get_returned_qty_map(delivery_note):
"""returns a map: {so_detail: returned_qty}"""
- returned_qty_map = frappe._dict(frappe.db.sql("""select dn_item.dn_detail, abs(dn_item.qty) as qty
+ returned_qty_map = frappe._dict(
+ frappe.db.sql(
+ """select dn_item.dn_detail, abs(dn_item.qty) as qty
from `tabDelivery Note Item` dn_item, `tabDelivery Note` dn
where dn.name = dn_item.parent
and dn.docstatus = 1
and dn.is_return = 1
and dn.return_against = %s
- """, delivery_note))
+ """,
+ delivery_note,
+ )
+ )
return returned_qty_map
+
@frappe.whitelist()
def make_sales_invoice(source_name, target_doc=None):
- doc = frappe.get_doc('Delivery Note', source_name)
+ doc = frappe.get_doc("Delivery Note", source_name)
to_make_invoice_qty_map = {}
returned_qty_map = get_returned_qty_map(source_name)
@@ -444,20 +526,21 @@
# set company address
if source.company_address:
- target.update({'company_address': source.company_address})
+ target.update({"company_address": source.company_address})
else:
# set company address
target.update(get_company_address(target.company))
if target.company_address:
- target.update(get_fetch_values("Sales Invoice", 'company_address', target.company_address))
+ target.update(get_fetch_values("Sales Invoice", "company_address", target.company_address))
def update_item(source_doc, target_doc, source_parent):
target_doc.qty = to_make_invoice_qty_map[source_doc.name]
if source_doc.serial_no and source_parent.per_billed > 0 and not source_parent.is_return:
- target_doc.serial_no = get_delivery_note_serial_no(source_doc.item_code,
- target_doc.qty, source_parent.name)
+ target_doc.serial_no = get_delivery_note_serial_no(
+ source_doc.item_code, target_doc.qty, source_parent.name
+ )
def get_pending_qty(item_row):
pending_qty = item_row.qty - invoiced_qty_map.get(item_row.name, 0)
@@ -479,50 +562,52 @@
return pending_qty
- doc = get_mapped_doc("Delivery Note", source_name, {
- "Delivery Note": {
- "doctype": "Sales Invoice",
- "field_map": {
- "is_return": "is_return"
+ doc = get_mapped_doc(
+ "Delivery Note",
+ source_name,
+ {
+ "Delivery Note": {
+ "doctype": "Sales Invoice",
+ "field_map": {"is_return": "is_return"},
+ "validation": {"docstatus": ["=", 1]},
},
- "validation": {
- "docstatus": ["=", 1]
- }
- },
- "Delivery Note Item": {
- "doctype": "Sales Invoice Item",
- "field_map": {
- "name": "dn_detail",
- "parent": "delivery_note",
- "so_detail": "so_detail",
- "against_sales_order": "sales_order",
- "serial_no": "serial_no",
- "cost_center": "cost_center"
+ "Delivery Note Item": {
+ "doctype": "Sales Invoice Item",
+ "field_map": {
+ "name": "dn_detail",
+ "parent": "delivery_note",
+ "so_detail": "so_detail",
+ "against_sales_order": "sales_order",
+ "serial_no": "serial_no",
+ "cost_center": "cost_center",
+ },
+ "postprocess": update_item,
+ "filter": lambda d: get_pending_qty(d) <= 0
+ if not doc.get("is_return")
+ else get_pending_qty(d) > 0,
},
- "postprocess": update_item,
- "filter": lambda d: get_pending_qty(d) <= 0 if not doc.get("is_return") else get_pending_qty(d) > 0
- },
- "Sales Taxes and Charges": {
- "doctype": "Sales Taxes and Charges",
- "add_if_empty": True
- },
- "Sales Team": {
- "doctype": "Sales Team",
- "field_map": {
- "incentives": "incentives"
+ "Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "add_if_empty": True},
+ "Sales Team": {
+ "doctype": "Sales Team",
+ "field_map": {"incentives": "incentives"},
+ "add_if_empty": True,
},
- "add_if_empty": True
- }
- }, target_doc, set_missing_values)
+ },
+ target_doc,
+ set_missing_values,
+ )
- automatically_fetch_payment_terms = cint(frappe.db.get_single_value('Accounts Settings', 'automatically_fetch_payment_terms'))
+ automatically_fetch_payment_terms = cint(
+ frappe.db.get_single_value("Accounts Settings", "automatically_fetch_payment_terms")
+ )
if automatically_fetch_payment_terms:
doc.set_payment_schedule()
- doc.set_onload('ignore_price_list', True)
+ doc.set_onload("ignore_price_list", True)
return doc
+
@frappe.whitelist()
def make_delivery_trip(source_name, target_doc=None):
def update_stop_details(source_doc, target_doc, source_parent):
@@ -538,107 +623,110 @@
delivery_notes = []
- doclist = get_mapped_doc("Delivery Note", source_name, {
- "Delivery Note": {
- "doctype": "Delivery Trip",
- "validation": {
- "docstatus": ["=", 1]
- }
- },
- "Delivery Note Item": {
- "doctype": "Delivery Stop",
- "field_map": {
- "parent": "delivery_note"
+ doclist = get_mapped_doc(
+ "Delivery Note",
+ source_name,
+ {
+ "Delivery Note": {"doctype": "Delivery Trip", "validation": {"docstatus": ["=", 1]}},
+ "Delivery Note Item": {
+ "doctype": "Delivery Stop",
+ "field_map": {"parent": "delivery_note"},
+ "condition": lambda item: item.parent not in delivery_notes,
+ "postprocess": update_stop_details,
},
- "condition": lambda item: item.parent not in delivery_notes,
- "postprocess": update_stop_details
- }
- }, target_doc)
+ },
+ target_doc,
+ )
return doclist
+
@frappe.whitelist()
def make_installation_note(source_name, target_doc=None):
def update_item(obj, target, source_parent):
target.qty = flt(obj.qty) - flt(obj.installed_qty)
target.serial_no = obj.serial_no
- doclist = get_mapped_doc("Delivery Note", source_name, {
- "Delivery Note": {
- "doctype": "Installation Note",
- "validation": {
- "docstatus": ["=", 1]
- }
- },
- "Delivery Note Item": {
- "doctype": "Installation Note Item",
- "field_map": {
- "name": "prevdoc_detail_docname",
- "parent": "prevdoc_docname",
- "parenttype": "prevdoc_doctype",
+ doclist = get_mapped_doc(
+ "Delivery Note",
+ source_name,
+ {
+ "Delivery Note": {"doctype": "Installation Note", "validation": {"docstatus": ["=", 1]}},
+ "Delivery Note Item": {
+ "doctype": "Installation Note Item",
+ "field_map": {
+ "name": "prevdoc_detail_docname",
+ "parent": "prevdoc_docname",
+ "parenttype": "prevdoc_doctype",
+ },
+ "postprocess": update_item,
+ "condition": lambda doc: doc.installed_qty < doc.qty,
},
- "postprocess": update_item,
- "condition": lambda doc: doc.installed_qty < doc.qty
- }
- }, target_doc)
+ },
+ target_doc,
+ )
return doclist
+
@frappe.whitelist()
def make_packing_slip(source_name, target_doc=None):
- doclist = get_mapped_doc("Delivery Note", source_name, {
- "Delivery Note": {
- "doctype": "Packing Slip",
- "field_map": {
- "name": "delivery_note",
- "letter_head": "letter_head"
+ doclist = get_mapped_doc(
+ "Delivery Note",
+ source_name,
+ {
+ "Delivery Note": {
+ "doctype": "Packing Slip",
+ "field_map": {"name": "delivery_note", "letter_head": "letter_head"},
+ "validation": {"docstatus": ["=", 0]},
},
- "validation": {
- "docstatus": ["=", 0]
- }
+ "Delivery Note Item": {
+ "doctype": "Packing Slip Item",
+ "field_map": {
+ "item_code": "item_code",
+ "item_name": "item_name",
+ "description": "description",
+ "qty": "qty",
+ },
+ },
},
-
- "Delivery Note Item": {
- "doctype": "Packing Slip Item",
- "field_map": {
- "item_code": "item_code",
- "item_name": "item_name",
- "description": "description",
- "qty": "qty",
- }
- }
-
- }, target_doc)
+ target_doc,
+ )
return doclist
+
@frappe.whitelist()
def make_shipment(source_name, target_doc=None):
def postprocess(source, target):
- user = frappe.db.get_value("User", frappe.session.user, ['email', 'full_name', 'phone', 'mobile_no'], as_dict=1)
+ user = frappe.db.get_value(
+ "User", frappe.session.user, ["email", "full_name", "phone", "mobile_no"], as_dict=1
+ )
target.pickup_contact_email = user.email
- pickup_contact_display = '{}'.format(user.full_name)
+ pickup_contact_display = "{}".format(user.full_name)
if user:
if user.email:
- pickup_contact_display += '<br>' + user.email
+ pickup_contact_display += "<br>" + user.email
if user.phone:
- pickup_contact_display += '<br>' + user.phone
+ pickup_contact_display += "<br>" + user.phone
if user.mobile_no and not user.phone:
- pickup_contact_display += '<br>' + user.mobile_no
+ pickup_contact_display += "<br>" + user.mobile_no
target.pickup_contact = pickup_contact_display
# As we are using session user details in the pickup_contact then pickup_contact_person will be session user
target.pickup_contact_person = frappe.session.user
- contact = frappe.db.get_value("Contact", source.contact_person, ['email_id', 'phone', 'mobile_no'], as_dict=1)
- delivery_contact_display = '{}'.format(source.contact_display)
+ contact = frappe.db.get_value(
+ "Contact", source.contact_person, ["email_id", "phone", "mobile_no"], as_dict=1
+ )
+ delivery_contact_display = "{}".format(source.contact_display)
if contact:
if contact.email_id:
- delivery_contact_display += '<br>' + contact.email_id
+ delivery_contact_display += "<br>" + contact.email_id
if contact.phone:
- delivery_contact_display += '<br>' + contact.phone
+ delivery_contact_display += "<br>" + contact.phone
if contact.mobile_no and not contact.phone:
- delivery_contact_display += '<br>' + contact.mobile_no
+ delivery_contact_display += "<br>" + contact.mobile_no
target.delivery_contact = delivery_contact_display
if source.shipping_address_name:
@@ -648,38 +736,44 @@
target.delivery_address_name = source.customer_address
target.delivery_address = source.address_display
- doclist = get_mapped_doc("Delivery Note", source_name, {
- "Delivery Note": {
- "doctype": "Shipment",
- "field_map": {
- "grand_total": "value_of_goods",
- "company": "pickup_company",
- "company_address": "pickup_address_name",
- "company_address_display": "pickup_address",
- "customer": "delivery_customer",
- "contact_person": "delivery_contact_name",
- "contact_email": "delivery_contact_email"
+ doclist = get_mapped_doc(
+ "Delivery Note",
+ source_name,
+ {
+ "Delivery Note": {
+ "doctype": "Shipment",
+ "field_map": {
+ "grand_total": "value_of_goods",
+ "company": "pickup_company",
+ "company_address": "pickup_address_name",
+ "company_address_display": "pickup_address",
+ "customer": "delivery_customer",
+ "contact_person": "delivery_contact_name",
+ "contact_email": "delivery_contact_email",
+ },
+ "validation": {"docstatus": ["=", 1]},
},
- "validation": {
- "docstatus": ["=", 1]
- }
+ "Delivery Note Item": {
+ "doctype": "Shipment Delivery Note",
+ "field_map": {
+ "name": "prevdoc_detail_docname",
+ "parent": "prevdoc_docname",
+ "parenttype": "prevdoc_doctype",
+ "base_amount": "grand_total",
+ },
+ },
},
- "Delivery Note Item": {
- "doctype": "Shipment Delivery Note",
- "field_map": {
- "name": "prevdoc_detail_docname",
- "parent": "prevdoc_docname",
- "parenttype": "prevdoc_doctype",
- "base_amount": "grand_total"
- }
- }
- }, target_doc, postprocess)
+ target_doc,
+ postprocess,
+ )
return doclist
+
@frappe.whitelist()
def make_sales_return(source_name, target_doc=None):
from erpnext.controllers.sales_and_purchase_return import make_return_doc
+
return make_return_doc("Delivery Note", source_name, target_doc)
@@ -688,10 +782,12 @@
dn = frappe.get_doc("Delivery Note", docname)
dn.update_status(status)
+
@frappe.whitelist()
def make_inter_company_purchase_receipt(source_name, target_doc=None):
return make_inter_company_transaction("Delivery Note", source_name, target_doc)
+
def make_inter_company_transaction(doctype, source_name, target_doc=None):
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
get_inter_company_details,
@@ -701,16 +797,16 @@
validate_inter_company_transaction,
)
- if doctype == 'Delivery Note':
+ if doctype == "Delivery Note":
source_doc = frappe.get_doc(doctype, source_name)
target_doctype = "Purchase Receipt"
- source_document_warehouse_field = 'target_warehouse'
- target_document_warehouse_field = 'from_warehouse'
+ source_document_warehouse_field = "target_warehouse"
+ target_document_warehouse_field = "from_warehouse"
else:
source_doc = frappe.get_doc(doctype, source_name)
- target_doctype = 'Delivery Note'
- source_document_warehouse_field = 'from_warehouse'
- target_document_warehouse_field = 'target_warehouse'
+ target_doctype = "Delivery Note"
+ source_document_warehouse_field = "from_warehouse"
+ target_document_warehouse_field = "target_warehouse"
validate_inter_company_transaction(source_doc, doctype)
details = get_inter_company_details(source_doc, doctype)
@@ -719,18 +815,18 @@
target.run_method("set_missing_values")
set_purchase_references(target)
- if target.doctype == 'Purchase Receipt':
- master_doctype = 'Purchase Taxes and Charges Template'
+ if target.doctype == "Purchase Receipt":
+ master_doctype = "Purchase Taxes and Charges Template"
else:
- master_doctype = 'Sales Taxes and Charges Template'
+ master_doctype = "Sales Taxes and Charges Template"
- if not target.get('taxes') and target.get('taxes_and_charges'):
- for tax in get_taxes_and_charges(master_doctype, target.get('taxes_and_charges')):
- target.append('taxes', tax)
+ if not target.get("taxes") and target.get("taxes_and_charges"):
+ for tax in get_taxes_and_charges(master_doctype, target.get("taxes_and_charges")):
+ target.append("taxes", tax)
def update_details(source_doc, target_doc, source_parent):
target_doc.inter_company_invoice_reference = source_doc.name
- if target_doc.doctype == 'Purchase Receipt':
+ if target_doc.doctype == "Purchase Receipt":
target_doc.company = details.get("company")
target_doc.supplier = details.get("party")
target_doc.buying_price_list = source_doc.selling_price_list
@@ -738,12 +834,20 @@
target_doc.inter_company_reference = source_doc.name
# Invert the address on target doc creation
- update_address(target_doc, 'supplier_address', 'address_display', source_doc.company_address)
- update_address(target_doc, 'shipping_address', 'shipping_address_display', source_doc.customer_address)
+ update_address(target_doc, "supplier_address", "address_display", source_doc.company_address)
+ update_address(
+ target_doc, "shipping_address", "shipping_address_display", source_doc.customer_address
+ )
- update_taxes(target_doc, party=target_doc.supplier, party_type='Supplier', company=target_doc.company,
- doctype=target_doc.doctype, party_address=target_doc.supplier_address,
- company_address=target_doc.shipping_address)
+ update_taxes(
+ target_doc,
+ party=target_doc.supplier,
+ party_type="Supplier",
+ company=target_doc.company,
+ doctype=target_doc.doctype,
+ party_address=target_doc.supplier_address,
+ company_address=target_doc.shipping_address,
+ )
else:
target_doc.company = details.get("company")
target_doc.customer = details.get("party")
@@ -753,39 +857,52 @@
target_doc.inter_company_reference = source_doc.name
# Invert the address on target doc creation
- update_address(target_doc, 'company_address', 'company_address_display', source_doc.supplier_address)
- update_address(target_doc, 'shipping_address_name', 'shipping_address', source_doc.shipping_address)
- update_address(target_doc, 'customer_address', 'address_display', source_doc.shipping_address)
+ update_address(
+ target_doc, "company_address", "company_address_display", source_doc.supplier_address
+ )
+ update_address(
+ target_doc, "shipping_address_name", "shipping_address", source_doc.shipping_address
+ )
+ update_address(target_doc, "customer_address", "address_display", source_doc.shipping_address)
- update_taxes(target_doc, party=target_doc.customer, party_type='Customer', company=target_doc.company,
- doctype=target_doc.doctype, party_address=target_doc.customer_address,
- company_address=target_doc.company_address, shipping_address_name=target_doc.shipping_address_name)
+ update_taxes(
+ target_doc,
+ party=target_doc.customer,
+ party_type="Customer",
+ company=target_doc.company,
+ doctype=target_doc.doctype,
+ party_address=target_doc.customer_address,
+ company_address=target_doc.company_address,
+ shipping_address_name=target_doc.shipping_address_name,
+ )
- doclist = get_mapped_doc(doctype, source_name, {
- doctype: {
- "doctype": target_doctype,
- "postprocess": update_details,
- "field_no_map": [
- "taxes_and_charges",
- "set_warehouse"
- ]
- },
- doctype +" Item": {
- "doctype": target_doctype + " Item",
- "field_map": {
- source_document_warehouse_field: target_document_warehouse_field,
- 'name': 'delivery_note_item',
- 'batch_no': 'batch_no',
- 'serial_no': 'serial_no'
+ doclist = get_mapped_doc(
+ doctype,
+ source_name,
+ {
+ doctype: {
+ "doctype": target_doctype,
+ "postprocess": update_details,
+ "field_no_map": ["taxes_and_charges", "set_warehouse"],
},
- "field_no_map": [
- "warehouse"
- ]
- }
-
- }, target_doc, set_missing_values)
+ doctype
+ + " Item": {
+ "doctype": target_doctype + " Item",
+ "field_map": {
+ source_document_warehouse_field: target_document_warehouse_field,
+ "name": "delivery_note_item",
+ "batch_no": "batch_no",
+ "serial_no": "serial_no",
+ },
+ "field_no_map": ["warehouse"],
+ },
+ },
+ target_doc,
+ set_missing_values,
+ )
return doclist
+
def on_doctype_update():
frappe.db.add_index("Delivery Note", ["customer", "is_return", "return_against"])
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py b/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py
index ca61a36..fd44e9c 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py
@@ -3,31 +3,19 @@
def get_data():
return {
- 'fieldname': 'delivery_note',
- 'non_standard_fieldnames': {
- 'Stock Entry': 'delivery_note_no',
- 'Quality Inspection': 'reference_name',
- 'Auto Repeat': 'reference_document',
+ "fieldname": "delivery_note",
+ "non_standard_fieldnames": {
+ "Stock Entry": "delivery_note_no",
+ "Quality Inspection": "reference_name",
+ "Auto Repeat": "reference_document",
},
- 'internal_links': {
- 'Sales Order': ['items', 'against_sales_order'],
+ "internal_links": {
+ "Sales Order": ["items", "against_sales_order"],
},
- 'transactions': [
- {
- 'label': _('Related'),
- 'items': ['Sales Invoice', 'Packing Slip', 'Delivery Trip']
- },
- {
- 'label': _('Reference'),
- 'items': ['Sales Order', 'Shipment', 'Quality Inspection']
- },
- {
- 'label': _('Returns'),
- 'items': ['Stock Entry']
- },
- {
- 'label': _('Subscription'),
- 'items': ['Auto Repeat']
- },
- ]
+ "transactions": [
+ {"label": _("Related"), "items": ["Sales Invoice", "Packing Slip", "Delivery Trip"]},
+ {"label": _("Reference"), "items": ["Sales Order", "Shipment", "Quality Inspection"]},
+ {"label": _("Returns"), "items": ["Stock Entry"]},
+ {"label": _("Subscription"), "items": ["Auto Repeat"]},
+ ],
}
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index 82f4e7d..b5a4557 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -2,7 +2,6 @@
# License: GNU General Public License v3. See license.txt
-
import json
import frappe
@@ -54,21 +53,28 @@
self.assertRaises(frappe.ValidationError, frappe.get_doc(si).insert)
def test_delivery_note_no_gl_entry(self):
- company = frappe.db.get_value('Warehouse', '_Test Warehouse - _TC', 'company')
+ company = frappe.db.get_value("Warehouse", "_Test Warehouse - _TC", "company")
make_stock_entry(target="_Test Warehouse - _TC", qty=5, basic_rate=100)
- stock_queue = json.loads(get_previous_sle({
- "item_code": "_Test Item",
- "warehouse": "_Test Warehouse - _TC",
- "posting_date": nowdate(),
- "posting_time": nowtime()
- }).stock_queue or "[]")
+ stock_queue = json.loads(
+ get_previous_sle(
+ {
+ "item_code": "_Test Item",
+ "warehouse": "_Test Warehouse - _TC",
+ "posting_date": nowdate(),
+ "posting_time": nowtime(),
+ }
+ ).stock_queue
+ or "[]"
+ )
dn = create_delivery_note()
- sle = frappe.get_doc("Stock Ledger Entry", {"voucher_type": "Delivery Note", "voucher_no": dn.name})
+ sle = frappe.get_doc(
+ "Stock Ledger Entry", {"voucher_type": "Delivery Note", "voucher_no": dn.name}
+ )
- self.assertEqual(sle.stock_value_difference, flt(-1*stock_queue[0][1], 2))
+ self.assertEqual(sle.stock_value_difference, flt(-1 * stock_queue[0][1], 2))
self.assertFalse(get_gl_entries("Delivery Note", dn.name))
@@ -123,24 +129,43 @@
# set_perpetual_inventory(0, company)
def test_delivery_note_gl_entry_packing_item(self):
- company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
+ company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
make_stock_entry(item_code="_Test Item", target="Stores - TCP1", qty=10, basic_rate=100)
- make_stock_entry(item_code="_Test Item Home Desktop 100",
- target="Stores - TCP1", qty=10, basic_rate=100)
+ make_stock_entry(
+ item_code="_Test Item Home Desktop 100", target="Stores - TCP1", qty=10, basic_rate=100
+ )
- stock_in_hand_account = get_inventory_account('_Test Company with perpetual inventory')
+ stock_in_hand_account = get_inventory_account("_Test Company with perpetual inventory")
prev_bal = get_balance_on(stock_in_hand_account)
- dn = create_delivery_note(item_code="_Test Product Bundle Item", company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', cost_center = 'Main - TCP1', expense_account = "Cost of Goods Sold - TCP1")
+ dn = create_delivery_note(
+ item_code="_Test Product Bundle Item",
+ company="_Test Company with perpetual inventory",
+ warehouse="Stores - TCP1",
+ cost_center="Main - TCP1",
+ expense_account="Cost of Goods Sold - TCP1",
+ )
- stock_value_diff_rm1 = abs(frappe.db.get_value("Stock Ledger Entry",
- {"voucher_type": "Delivery Note", "voucher_no": dn.name, "item_code": "_Test Item"},
- "stock_value_difference"))
+ stock_value_diff_rm1 = abs(
+ frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"voucher_type": "Delivery Note", "voucher_no": dn.name, "item_code": "_Test Item"},
+ "stock_value_difference",
+ )
+ )
- stock_value_diff_rm2 = abs(frappe.db.get_value("Stock Ledger Entry",
- {"voucher_type": "Delivery Note", "voucher_no": dn.name,
- "item_code": "_Test Item Home Desktop 100"}, "stock_value_difference"))
+ stock_value_diff_rm2 = abs(
+ frappe.db.get_value(
+ "Stock Ledger Entry",
+ {
+ "voucher_type": "Delivery Note",
+ "voucher_no": dn.name,
+ "item_code": "_Test Item Home Desktop 100",
+ },
+ "stock_value_difference",
+ )
+ )
stock_value_diff = stock_value_diff_rm1 + stock_value_diff_rm2
@@ -149,7 +174,7 @@
expected_values = {
stock_in_hand_account: [0.0, stock_value_diff],
- "Cost of Goods Sold - TCP1": [stock_value_diff, 0.0]
+ "Cost of Goods Sold - TCP1": [stock_value_diff, 0.0],
}
for i, gle in enumerate(gl_entries):
self.assertEqual([gle.debit, gle.credit], expected_values.get(gle.account))
@@ -166,10 +191,7 @@
dn = create_delivery_note(item_code="_Test Serialized Item With Series", serial_no=serial_no)
- self.check_serial_no_values(serial_no, {
- "warehouse": "",
- "delivery_document_no": dn.name
- })
+ self.check_serial_no_values(serial_no, {"warehouse": "", "delivery_document_no": dn.name})
si = make_sales_invoice(dn.name)
si.insert(ignore_permissions=True)
@@ -177,17 +199,18 @@
dn.cancel()
- self.check_serial_no_values(serial_no, {
- "warehouse": "_Test Warehouse - _TC",
- "delivery_document_no": ""
- })
+ self.check_serial_no_values(
+ serial_no, {"warehouse": "_Test Warehouse - _TC", "delivery_document_no": ""}
+ )
def test_serialized_partial_sales_invoice(self):
se = make_serialized_item()
serial_no = get_serial_nos(se.get("items")[0].serial_no)
- serial_no = '\n'.join(serial_no)
+ serial_no = "\n".join(serial_no)
- dn = create_delivery_note(item_code="_Test Serialized Item With Series", qty=2, serial_no=serial_no)
+ dn = create_delivery_note(
+ item_code="_Test Serialized Item With Series", qty=2, serial_no=serial_no
+ )
si = make_sales_invoice(dn.name)
si.items[0].qty = 1
@@ -200,15 +223,19 @@
def test_serialize_status(self):
from frappe.model.naming import make_autoname
- serial_no = frappe.get_doc({
- "doctype": "Serial No",
- "item_code": "_Test Serialized Item With Series",
- "serial_no": make_autoname("SR", "Serial No")
- })
+
+ serial_no = frappe.get_doc(
+ {
+ "doctype": "Serial No",
+ "item_code": "_Test Serialized Item With Series",
+ "serial_no": make_autoname("SR", "Serial No"),
+ }
+ )
serial_no.save()
- dn = create_delivery_note(item_code="_Test Serialized Item With Series",
- serial_no=serial_no.name, do_not_submit=True)
+ dn = create_delivery_note(
+ item_code="_Test Serialized Item With Series", serial_no=serial_no.name, do_not_submit=True
+ )
self.assertRaises(SerialNoWarehouseError, dn.submit)
@@ -218,26 +245,46 @@
self.assertEqual(cstr(serial_no.get(field)), value)
def test_sales_return_for_non_bundled_items_partial(self):
- company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
+ company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
make_stock_entry(item_code="_Test Item", target="Stores - TCP1", qty=50, basic_rate=100)
actual_qty_0 = get_qty_after_transaction(warehouse="Stores - TCP1")
- dn = create_delivery_note(qty=5, rate=500, warehouse="Stores - TCP1", company=company,
- expense_account="Cost of Goods Sold - TCP1", cost_center="Main - TCP1")
+ dn = create_delivery_note(
+ qty=5,
+ rate=500,
+ warehouse="Stores - TCP1",
+ company=company,
+ expense_account="Cost of Goods Sold - TCP1",
+ cost_center="Main - TCP1",
+ )
actual_qty_1 = get_qty_after_transaction(warehouse="Stores - TCP1")
self.assertEqual(actual_qty_0 - 5, actual_qty_1)
# outgoing_rate
- outgoing_rate = frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Delivery Note",
- "voucher_no": dn.name}, "stock_value_difference") / 5
+ outgoing_rate = (
+ frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"voucher_type": "Delivery Note", "voucher_no": dn.name},
+ "stock_value_difference",
+ )
+ / 5
+ )
# return entry
- dn1 = create_delivery_note(is_return=1, return_against=dn.name, qty=-2, rate=500,
- company=company, warehouse="Stores - TCP1", expense_account="Cost of Goods Sold - TCP1",
- cost_center="Main - TCP1", do_not_submit=1)
+ dn1 = create_delivery_note(
+ is_return=1,
+ return_against=dn.name,
+ qty=-2,
+ rate=500,
+ company=company,
+ warehouse="Stores - TCP1",
+ expense_account="Cost of Goods Sold - TCP1",
+ cost_center="Main - TCP1",
+ do_not_submit=1,
+ )
dn1.items[0].dn_detail = dn.items[0].name
dn1.submit()
@@ -245,15 +292,20 @@
self.assertEqual(actual_qty_1 + 2, actual_qty_2)
- incoming_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry",
+ incoming_rate, stock_value_difference = frappe.db.get_value(
+ "Stock Ledger Entry",
{"voucher_type": "Delivery Note", "voucher_no": dn1.name},
- ["incoming_rate", "stock_value_difference"])
+ ["incoming_rate", "stock_value_difference"],
+ )
self.assertEqual(flt(incoming_rate, 3), abs(flt(outgoing_rate, 3)))
stock_in_hand_account = get_inventory_account(company, dn1.items[0].warehouse)
- gle_warehouse_amount = frappe.db.get_value("GL Entry", {"voucher_type": "Delivery Note",
- "voucher_no": dn1.name, "account": stock_in_hand_account}, "debit")
+ gle_warehouse_amount = frappe.db.get_value(
+ "GL Entry",
+ {"voucher_type": "Delivery Note", "voucher_no": dn1.name, "account": stock_in_hand_account},
+ "debit",
+ )
self.assertEqual(gle_warehouse_amount, stock_value_difference)
@@ -267,6 +319,7 @@
self.assertEqual(dn.per_returned, 40)
from erpnext.controllers.sales_and_purchase_return import make_return_doc
+
return_dn_2 = make_return_doc("Delivery Note", dn.name)
# Check if unreturned amount is mapped in 2nd return
@@ -281,7 +334,7 @@
# DN should be completed on billing all unreturned amount
self.assertEqual(dn.items[0].billed_amt, 1500)
self.assertEqual(dn.per_billed, 100)
- self.assertEqual(dn.status, 'Completed')
+ self.assertEqual(dn.status, "Completed")
si.load_from_db()
si.cancel()
@@ -293,19 +346,35 @@
dn.cancel()
def test_sales_return_for_non_bundled_items_full(self):
- company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
+ company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
- make_item("Box", {'is_stock_item': 1})
+ make_item("Box", {"is_stock_item": 1})
make_stock_entry(item_code="Box", target="Stores - TCP1", qty=10, basic_rate=100)
- dn = create_delivery_note(item_code="Box", qty=5, rate=500, warehouse="Stores - TCP1", company=company,
- expense_account="Cost of Goods Sold - TCP1", cost_center="Main - TCP1")
+ dn = create_delivery_note(
+ item_code="Box",
+ qty=5,
+ rate=500,
+ warehouse="Stores - TCP1",
+ company=company,
+ expense_account="Cost of Goods Sold - TCP1",
+ cost_center="Main - TCP1",
+ )
- #return entry
- dn1 = create_delivery_note(item_code="Box", is_return=1, return_against=dn.name, qty=-5, rate=500,
- company=company, warehouse="Stores - TCP1", expense_account="Cost of Goods Sold - TCP1",
- cost_center="Main - TCP1", do_not_submit=1)
+ # return entry
+ dn1 = create_delivery_note(
+ item_code="Box",
+ is_return=1,
+ return_against=dn.name,
+ qty=-5,
+ rate=500,
+ company=company,
+ warehouse="Stores - TCP1",
+ expense_account="Cost of Goods Sold - TCP1",
+ cost_center="Main - TCP1",
+ do_not_submit=1,
+ )
dn1.items[0].dn_detail = dn.items[0].name
dn1.submit()
@@ -317,93 +386,157 @@
# Check if Original DN updated
self.assertEqual(dn.items[0].returned_qty, 5)
self.assertEqual(dn.per_returned, 100)
- self.assertEqual(dn.status, 'Return Issued')
+ self.assertEqual(dn.status, "Return Issued")
def test_return_single_item_from_bundled_items(self):
- company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
+ company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
- create_stock_reconciliation(item_code="_Test Item",
- warehouse="Stores - TCP1", qty=50, rate=100,
- company=company, expense_account = "Stock Adjustment - TCP1")
- create_stock_reconciliation(item_code="_Test Item Home Desktop 100",
- warehouse="Stores - TCP1", qty=50, rate=100,
- company=company, expense_account = "Stock Adjustment - TCP1")
+ create_stock_reconciliation(
+ item_code="_Test Item",
+ warehouse="Stores - TCP1",
+ qty=50,
+ rate=100,
+ company=company,
+ expense_account="Stock Adjustment - TCP1",
+ )
+ create_stock_reconciliation(
+ item_code="_Test Item Home Desktop 100",
+ warehouse="Stores - TCP1",
+ qty=50,
+ rate=100,
+ company=company,
+ expense_account="Stock Adjustment - TCP1",
+ )
- dn = create_delivery_note(item_code="_Test Product Bundle Item", qty=5, rate=500,
- company=company, warehouse="Stores - TCP1",
- expense_account="Cost of Goods Sold - TCP1", cost_center="Main - TCP1")
+ dn = create_delivery_note(
+ item_code="_Test Product Bundle Item",
+ qty=5,
+ rate=500,
+ company=company,
+ warehouse="Stores - TCP1",
+ expense_account="Cost of Goods Sold - TCP1",
+ cost_center="Main - TCP1",
+ )
# Qty after delivery
actual_qty_1 = get_qty_after_transaction(warehouse="Stores - TCP1")
- self.assertEqual(actual_qty_1, 25)
+ self.assertEqual(actual_qty_1, 25)
# outgoing_rate
- outgoing_rate = frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Delivery Note",
- "voucher_no": dn.name, "item_code": "_Test Item"}, "stock_value_difference") / 25
+ outgoing_rate = (
+ frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"voucher_type": "Delivery Note", "voucher_no": dn.name, "item_code": "_Test Item"},
+ "stock_value_difference",
+ )
+ / 25
+ )
# return 'test item' from packed items
- dn1 = create_delivery_note(is_return=1, return_against=dn.name, qty=-10, rate=500,
- company=company, warehouse="Stores - TCP1",
- expense_account="Cost of Goods Sold - TCP1", cost_center="Main - TCP1")
+ dn1 = create_delivery_note(
+ is_return=1,
+ return_against=dn.name,
+ qty=-10,
+ rate=500,
+ company=company,
+ warehouse="Stores - TCP1",
+ expense_account="Cost of Goods Sold - TCP1",
+ cost_center="Main - TCP1",
+ )
# qty after return
actual_qty_2 = get_qty_after_transaction(warehouse="Stores - TCP1")
self.assertEqual(actual_qty_2, 35)
# Check incoming rate for return entry
- incoming_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry",
+ incoming_rate, stock_value_difference = frappe.db.get_value(
+ "Stock Ledger Entry",
{"voucher_type": "Delivery Note", "voucher_no": dn1.name},
- ["incoming_rate", "stock_value_difference"])
+ ["incoming_rate", "stock_value_difference"],
+ )
self.assertEqual(flt(incoming_rate, 3), abs(flt(outgoing_rate, 3)))
stock_in_hand_account = get_inventory_account(company, dn1.items[0].warehouse)
# Check gl entry for warehouse
- gle_warehouse_amount = frappe.db.get_value("GL Entry", {"voucher_type": "Delivery Note",
- "voucher_no": dn1.name, "account": stock_in_hand_account}, "debit")
+ gle_warehouse_amount = frappe.db.get_value(
+ "GL Entry",
+ {"voucher_type": "Delivery Note", "voucher_no": dn1.name, "account": stock_in_hand_account},
+ "debit",
+ )
self.assertEqual(gle_warehouse_amount, stock_value_difference)
-
def test_return_entire_bundled_items(self):
- company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
+ company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
- create_stock_reconciliation(item_code="_Test Item",
- warehouse="Stores - TCP1", qty=50, rate=100,
- company=company, expense_account = "Stock Adjustment - TCP1")
- create_stock_reconciliation(item_code="_Test Item Home Desktop 100",
- warehouse="Stores - TCP1", qty=50, rate=100,
- company=company, expense_account = "Stock Adjustment - TCP1")
+ create_stock_reconciliation(
+ item_code="_Test Item",
+ warehouse="Stores - TCP1",
+ qty=50,
+ rate=100,
+ company=company,
+ expense_account="Stock Adjustment - TCP1",
+ )
+ create_stock_reconciliation(
+ item_code="_Test Item Home Desktop 100",
+ warehouse="Stores - TCP1",
+ qty=50,
+ rate=100,
+ company=company,
+ expense_account="Stock Adjustment - TCP1",
+ )
actual_qty = get_qty_after_transaction(warehouse="Stores - TCP1")
self.assertEqual(actual_qty, 50)
- dn = create_delivery_note(item_code="_Test Product Bundle Item",
- qty=5, rate=500, company=company, warehouse="Stores - TCP1", expense_account="Cost of Goods Sold - TCP1", cost_center="Main - TCP1")
+ dn = create_delivery_note(
+ item_code="_Test Product Bundle Item",
+ qty=5,
+ rate=500,
+ company=company,
+ warehouse="Stores - TCP1",
+ expense_account="Cost of Goods Sold - TCP1",
+ cost_center="Main - TCP1",
+ )
# qty after return
actual_qty = get_qty_after_transaction(warehouse="Stores - TCP1")
self.assertEqual(actual_qty, 25)
# return bundled item
- dn1 = create_delivery_note(item_code='_Test Product Bundle Item', is_return=1,
- return_against=dn.name, qty=-2, rate=500, company=company, warehouse="Stores - TCP1", expense_account="Cost of Goods Sold - TCP1", cost_center="Main - TCP1")
+ dn1 = create_delivery_note(
+ item_code="_Test Product Bundle Item",
+ is_return=1,
+ return_against=dn.name,
+ qty=-2,
+ rate=500,
+ company=company,
+ warehouse="Stores - TCP1",
+ expense_account="Cost of Goods Sold - TCP1",
+ cost_center="Main - TCP1",
+ )
# qty after return
actual_qty = get_qty_after_transaction(warehouse="Stores - TCP1")
self.assertEqual(actual_qty, 35)
# Check incoming rate for return entry
- incoming_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry",
+ incoming_rate, stock_value_difference = frappe.db.get_value(
+ "Stock Ledger Entry",
{"voucher_type": "Delivery Note", "voucher_no": dn1.name},
- ["incoming_rate", "stock_value_difference"])
+ ["incoming_rate", "stock_value_difference"],
+ )
self.assertEqual(incoming_rate, 100)
- stock_in_hand_account = get_inventory_account('_Test Company', dn1.items[0].warehouse)
+ stock_in_hand_account = get_inventory_account("_Test Company", dn1.items[0].warehouse)
# Check gl entry for warehouse
- gle_warehouse_amount = frappe.db.get_value("GL Entry", {"voucher_type": "Delivery Note",
- "voucher_no": dn1.name, "account": stock_in_hand_account}, "debit")
+ gle_warehouse_amount = frappe.db.get_value(
+ "GL Entry",
+ {"voucher_type": "Delivery Note", "voucher_no": dn1.name, "account": stock_in_hand_account},
+ "debit",
+ )
self.assertEqual(gle_warehouse_amount, 1400)
@@ -411,69 +544,88 @@
se = make_serialized_item()
serial_no = get_serial_nos(se.get("items")[0].serial_no)[0]
- dn = create_delivery_note(item_code="_Test Serialized Item With Series", rate=500, serial_no=serial_no)
+ dn = create_delivery_note(
+ item_code="_Test Serialized Item With Series", rate=500, serial_no=serial_no
+ )
- self.check_serial_no_values(serial_no, {
- "warehouse": "",
- "delivery_document_no": dn.name
- })
+ self.check_serial_no_values(serial_no, {"warehouse": "", "delivery_document_no": dn.name})
# return entry
- dn1 = create_delivery_note(item_code="_Test Serialized Item With Series",
- is_return=1, return_against=dn.name, qty=-1, rate=500, serial_no=serial_no)
+ dn1 = create_delivery_note(
+ item_code="_Test Serialized Item With Series",
+ is_return=1,
+ return_against=dn.name,
+ qty=-1,
+ rate=500,
+ serial_no=serial_no,
+ )
- self.check_serial_no_values(serial_no, {
- "warehouse": "_Test Warehouse - _TC",
- "delivery_document_no": ""
- })
+ self.check_serial_no_values(
+ serial_no, {"warehouse": "_Test Warehouse - _TC", "delivery_document_no": ""}
+ )
dn1.cancel()
- self.check_serial_no_values(serial_no, {
- "warehouse": "",
- "delivery_document_no": dn.name
- })
+ self.check_serial_no_values(serial_no, {"warehouse": "", "delivery_document_no": dn.name})
dn.cancel()
- self.check_serial_no_values(serial_no, {
- "warehouse": "_Test Warehouse - _TC",
- "delivery_document_no": "",
- "purchase_document_no": se.name
- })
+ self.check_serial_no_values(
+ serial_no,
+ {
+ "warehouse": "_Test Warehouse - _TC",
+ "delivery_document_no": "",
+ "purchase_document_no": se.name,
+ },
+ )
def test_delivery_of_bundled_items_to_target_warehouse(self):
from erpnext.selling.doctype.customer.test_customer import create_internal_customer
- company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
+ company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
customer_name = create_internal_customer(
customer_name="_Test Internal Customer 2",
represents_company="_Test Company with perpetual inventory",
- allowed_to_interact_with="_Test Company with perpetual inventory"
+ allowed_to_interact_with="_Test Company with perpetual inventory",
)
set_valuation_method("_Test Item", "FIFO")
set_valuation_method("_Test Item Home Desktop 100", "FIFO")
- target_warehouse = get_warehouse(company=company, abbr="TCP1",
- warehouse_name="_Test Customer Warehouse").name
+ target_warehouse = get_warehouse(
+ company=company, abbr="TCP1", warehouse_name="_Test Customer Warehouse"
+ ).name
for warehouse in ("Stores - TCP1", target_warehouse):
- create_stock_reconciliation(item_code="_Test Item", warehouse=warehouse, company = company,
- expense_account = "Stock Adjustment - TCP1", qty=500, rate=100)
- create_stock_reconciliation(item_code="_Test Item Home Desktop 100", company = company,
- expense_account = "Stock Adjustment - TCP1", warehouse=warehouse, qty=500, rate=100)
+ create_stock_reconciliation(
+ item_code="_Test Item",
+ warehouse=warehouse,
+ company=company,
+ expense_account="Stock Adjustment - TCP1",
+ qty=500,
+ rate=100,
+ )
+ create_stock_reconciliation(
+ item_code="_Test Item Home Desktop 100",
+ company=company,
+ expense_account="Stock Adjustment - TCP1",
+ warehouse=warehouse,
+ qty=500,
+ rate=100,
+ )
dn = create_delivery_note(
item_code="_Test Product Bundle Item",
company="_Test Company with perpetual inventory",
customer=customer_name,
- cost_center = 'Main - TCP1',
- expense_account = "Cost of Goods Sold - TCP1",
+ cost_center="Main - TCP1",
+ expense_account="Cost of Goods Sold - TCP1",
do_not_submit=True,
- qty=5, rate=500,
+ qty=5,
+ rate=500,
warehouse="Stores - TCP1",
- target_warehouse=target_warehouse)
+ target_warehouse=target_warehouse,
+ )
dn.submit()
@@ -485,16 +637,28 @@
self.assertEqual(actual_qty_at_target, 525)
# stock value diff for source warehouse for "_Test Item"
- stock_value_difference = frappe.db.get_value("Stock Ledger Entry",
- {"voucher_type": "Delivery Note", "voucher_no": dn.name,
- "item_code": "_Test Item", "warehouse": "Stores - TCP1"},
- "stock_value_difference")
+ stock_value_difference = frappe.db.get_value(
+ "Stock Ledger Entry",
+ {
+ "voucher_type": "Delivery Note",
+ "voucher_no": dn.name,
+ "item_code": "_Test Item",
+ "warehouse": "Stores - TCP1",
+ },
+ "stock_value_difference",
+ )
# stock value diff for target warehouse
- stock_value_difference1 = frappe.db.get_value("Stock Ledger Entry",
- {"voucher_type": "Delivery Note", "voucher_no": dn.name,
- "item_code": "_Test Item", "warehouse": target_warehouse},
- "stock_value_difference")
+ stock_value_difference1 = frappe.db.get_value(
+ "Stock Ledger Entry",
+ {
+ "voucher_type": "Delivery Note",
+ "voucher_no": dn.name,
+ "item_code": "_Test Item",
+ "warehouse": target_warehouse,
+ },
+ "stock_value_difference",
+ )
self.assertEqual(abs(stock_value_difference), stock_value_difference1)
@@ -502,13 +666,18 @@
gl_entries = get_gl_entries("Delivery Note", dn.name)
self.assertTrue(gl_entries)
- stock_value_difference = abs(frappe.db.sql("""select sum(stock_value_difference)
+ stock_value_difference = abs(
+ frappe.db.sql(
+ """select sum(stock_value_difference)
from `tabStock Ledger Entry` where voucher_type='Delivery Note' and voucher_no=%s
- and warehouse='Stores - TCP1'""", dn.name)[0][0])
+ and warehouse='Stores - TCP1'""",
+ dn.name,
+ )[0][0]
+ )
expected_values = {
"Stock In Hand - TCP1": [0.0, stock_value_difference],
- target_warehouse: [stock_value_difference, 0.0]
+ target_warehouse: [stock_value_difference, 0.0],
}
for i, gle in enumerate(gl_entries):
self.assertEqual([gle.debit, gle.credit], expected_values.get(gle.account))
@@ -521,8 +690,13 @@
make_stock_entry(target="Stores - TCP1", qty=5, basic_rate=100)
- dn = create_delivery_note(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1',
- cost_center = 'Main - TCP1', expense_account = "Cost of Goods Sold - TCP1", do_not_submit=True)
+ dn = create_delivery_note(
+ company="_Test Company with perpetual inventory",
+ warehouse="Stores - TCP1",
+ cost_center="Main - TCP1",
+ expense_account="Cost of Goods Sold - TCP1",
+ do_not_submit=True,
+ )
dn.submit()
@@ -599,6 +773,7 @@
from erpnext.selling.doctype.sales_order.sales_order import (
make_sales_invoice as make_sales_invoice_from_so,
)
+
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
so = make_sales_order()
@@ -672,28 +847,33 @@
def test_delivery_note_with_cost_center(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
- cost_center = "_Test Cost Center for BS Account - TCP1"
- create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company with perpetual inventory")
- company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
+ cost_center = "_Test Cost Center for BS Account - TCP1"
+ create_cost_center(
+ cost_center_name="_Test Cost Center for BS Account",
+ company="_Test Company with perpetual inventory",
+ )
+
+ company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
set_valuation_method("_Test Item", "FIFO")
make_stock_entry(target="Stores - TCP1", qty=5, basic_rate=100)
- stock_in_hand_account = get_inventory_account('_Test Company with perpetual inventory')
- dn = create_delivery_note(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', expense_account = "Cost of Goods Sold - TCP1", cost_center=cost_center)
+ stock_in_hand_account = get_inventory_account("_Test Company with perpetual inventory")
+ dn = create_delivery_note(
+ company="_Test Company with perpetual inventory",
+ warehouse="Stores - TCP1",
+ expense_account="Cost of Goods Sold - TCP1",
+ cost_center=cost_center,
+ )
gl_entries = get_gl_entries("Delivery Note", dn.name)
self.assertTrue(gl_entries)
expected_values = {
- "Cost of Goods Sold - TCP1": {
- "cost_center": cost_center
- },
- stock_in_hand_account: {
- "cost_center": cost_center
- }
+ "Cost of Goods Sold - TCP1": {"cost_center": cost_center},
+ stock_in_hand_account: {"cost_center": cost_center},
}
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
@@ -701,29 +881,30 @@
def test_delivery_note_cost_center_with_balance_sheet_account(self):
cost_center = "Main - TCP1"
- company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
+ company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
set_valuation_method("_Test Item", "FIFO")
make_stock_entry(target="Stores - TCP1", qty=5, basic_rate=100)
- stock_in_hand_account = get_inventory_account('_Test Company with perpetual inventory')
- dn = create_delivery_note(company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', cost_center = 'Main - TCP1', expense_account = "Cost of Goods Sold - TCP1",
- do_not_submit=1)
+ stock_in_hand_account = get_inventory_account("_Test Company with perpetual inventory")
+ dn = create_delivery_note(
+ company="_Test Company with perpetual inventory",
+ warehouse="Stores - TCP1",
+ cost_center="Main - TCP1",
+ expense_account="Cost of Goods Sold - TCP1",
+ do_not_submit=1,
+ )
- dn.get('items')[0].cost_center = None
+ dn.get("items")[0].cost_center = None
dn.submit()
gl_entries = get_gl_entries("Delivery Note", dn.name)
self.assertTrue(gl_entries)
expected_values = {
- "Cost of Goods Sold - TCP1": {
- "cost_center": cost_center
- },
- stock_in_hand_account: {
- "cost_center": cost_center
- }
+ "Cost of Goods Sold - TCP1": {"cost_center": cost_center},
+ stock_in_hand_account: {"cost_center": cost_center},
}
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
@@ -751,15 +932,18 @@
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_invoice
dn = create_delivery_note(qty=8, do_not_submit=True)
- dn.append("items", {
- "item_code": "_Test Item",
- "warehouse": "_Test Warehouse - _TC",
- "qty": 1,
- "rate": 100,
- "conversion_factor": 1.0,
- "expense_account": "Cost of Goods Sold - _TC",
- "cost_center": "_Test Cost Center - _TC"
- })
+ dn.append(
+ "items",
+ {
+ "item_code": "_Test Item",
+ "warehouse": "_Test Warehouse - _TC",
+ "qty": 1,
+ "rate": 100,
+ "conversion_factor": 1.0,
+ "expense_account": "Cost of Goods Sold - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ },
+ )
dn.submit()
si1 = make_sales_invoice(dn.name)
@@ -776,14 +960,21 @@
self.assertEqual(si2.items[0].qty, 2)
self.assertEqual(si2.items[1].qty, 1)
-
def test_delivery_note_bundle_with_batched_item(self):
batched_bundle = make_item("_Test Batched bundle", {"is_stock_item": 0})
- batched_item = make_item("_Test Batched Item",
- {"is_stock_item": 1, "has_batch_no": 1, "create_new_batch": 1, "batch_number_series": "TESTBATCH.#####"}
- )
+ batched_item = make_item(
+ "_Test Batched Item",
+ {
+ "is_stock_item": 1,
+ "has_batch_no": 1,
+ "create_new_batch": 1,
+ "batch_number_series": "TESTBATCH.#####",
+ },
+ )
make_product_bundle(parent=batched_bundle.name, items=[batched_item.name])
- make_stock_entry(item_code=batched_item.name, target="_Test Warehouse - _TC", qty=10, basic_rate=42)
+ make_stock_entry(
+ item_code=batched_item.name, target="_Test Warehouse - _TC", qty=10, basic_rate=42
+ )
try:
dn = create_delivery_note(item_code=batched_bundle.name, qty=1)
@@ -792,7 +983,9 @@
self.fail("Batch numbers not getting added to bundled items in DN.")
raise e
- self.assertTrue("TESTBATCH" in dn.packed_items[0].batch_no, "Batch number not added in packed item")
+ self.assertTrue(
+ "TESTBATCH" in dn.packed_items[0].batch_no, "Batch number not added in packed item"
+ )
def test_payment_terms_are_fetched_when_creating_sales_invoice(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import (
@@ -804,13 +997,13 @@
so = make_sales_order(uom="Nos", do_not_save=1)
create_payment_terms_template()
- so.payment_terms_template = 'Test Receivable Template'
+ so.payment_terms_template = "Test Receivable Template"
so.submit()
dn = create_dn_against_so(so.name, delivered_qty=10)
si = create_sales_invoice(qty=10, do_not_save=1)
- si.items[0].delivery_note= dn.name
+ si.items[0].delivery_note = dn.name
si.items[0].dn_detail = dn.items[0].name
si.items[0].sales_order = so.name
si.items[0].so_detail = so.items[0].name
@@ -823,6 +1016,7 @@
automatically_fetch_payment_terms(enable=0)
+
def create_delivery_note(**args):
dn = frappe.new_doc("Delivery Note")
args = frappe._dict(args)
@@ -836,18 +1030,21 @@
dn.is_return = args.is_return
dn.return_against = args.return_against
- dn.append("items", {
- "item_code": args.item or args.item_code or "_Test Item",
- "warehouse": args.warehouse or "_Test Warehouse - _TC",
- "qty": args.qty or 1,
- "rate": args.rate if args.get("rate") is not None else 100,
- "conversion_factor": 1.0,
- "allow_zero_valuation_rate": args.allow_zero_valuation_rate or 1,
- "expense_account": args.expense_account or "Cost of Goods Sold - _TC",
- "cost_center": args.cost_center or "_Test Cost Center - _TC",
- "serial_no": args.serial_no,
- "target_warehouse": args.target_warehouse
- })
+ dn.append(
+ "items",
+ {
+ "item_code": args.item or args.item_code or "_Test Item",
+ "warehouse": args.warehouse or "_Test Warehouse - _TC",
+ "qty": args.qty or 1,
+ "rate": args.rate if args.get("rate") is not None else 100,
+ "conversion_factor": 1.0,
+ "allow_zero_valuation_rate": args.allow_zero_valuation_rate or 1,
+ "expense_account": args.expense_account or "Cost of Goods Sold - _TC",
+ "cost_center": args.cost_center or "_Test Cost Center - _TC",
+ "serial_no": args.serial_no,
+ "target_warehouse": args.target_warehouse,
+ },
+ )
if not args.do_not_save:
dn.insert()
@@ -855,4 +1052,5 @@
dn.submit()
return dn
+
test_dependencies = ["Product Bundle"]
diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.py b/erpnext/stock/doctype/delivery_trip/delivery_trip.py
index c749b2e..73b250d 100644
--- a/erpnext/stock/doctype/delivery_trip/delivery_trip.py
+++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.py
@@ -17,9 +17,12 @@
super(DeliveryTrip, self).__init__(*args, **kwargs)
# Google Maps returns distances in meters by default
- self.default_distance_uom = frappe.db.get_single_value("Global Defaults", "default_distance_unit") or "Meter"
- self.uom_conversion_factor = frappe.db.get_value("UOM Conversion Factor",
- {"from_uom": "Meter", "to_uom": self.default_distance_uom}, "value")
+ self.default_distance_uom = (
+ frappe.db.get_single_value("Global Defaults", "default_distance_unit") or "Meter"
+ )
+ self.uom_conversion_factor = frappe.db.get_value(
+ "UOM Conversion Factor", {"from_uom": "Meter", "to_uom": self.default_distance_uom}, "value"
+ )
def validate(self):
self.validate_stop_addresses()
@@ -41,11 +44,7 @@
stop.customer_address = get_address_display(frappe.get_doc("Address", stop.address).as_dict())
def update_status(self):
- status = {
- 0: "Draft",
- 1: "Scheduled",
- 2: "Cancelled"
- }[self.docstatus]
+ status = {0: "Draft", 1: "Scheduled", 2: "Cancelled"}[self.docstatus]
if self.docstatus == 1:
visited_stops = [stop.visited for stop in self.delivery_stops]
@@ -63,17 +62,19 @@
are removed.
Args:
- delete (bool, optional): Defaults to `False`. `True` if driver details need to be emptied, else `False`.
+ delete (bool, optional): Defaults to `False`. `True` if driver details need to be emptied, else `False`.
"""
- delivery_notes = list(set(stop.delivery_note for stop in self.delivery_stops if stop.delivery_note))
+ delivery_notes = list(
+ set(stop.delivery_note for stop in self.delivery_stops if stop.delivery_note)
+ )
update_fields = {
"driver": self.driver,
"driver_name": self.driver_name,
"vehicle_no": self.vehicle,
"lr_no": self.name,
- "lr_date": self.departure_time
+ "lr_date": self.departure_time,
}
for delivery_note in delivery_notes:
@@ -97,7 +98,7 @@
on the optimized order, before estimating the arrival times.
Args:
- optimize (bool): True if route needs to be optimized, else False
+ optimize (bool): True if route needs to be optimized, else False
"""
departure_datetime = get_datetime(self.departure_time)
@@ -134,8 +135,9 @@
# Include last leg in the final distance calculation
self.uom = self.default_distance_uom
- total_distance = sum(leg.get("distance", {}).get("value", 0.0)
- for leg in directions.get("legs")) # in meters
+ total_distance = sum(
+ leg.get("distance", {}).get("value", 0.0) for leg in directions.get("legs")
+ ) # in meters
self.total_distance = total_distance * self.uom_conversion_factor
else:
idx += len(route) - 1
@@ -149,10 +151,10 @@
split into sublists at the specified lock position(s).
Args:
- optimize (bool): `True` if route needs to be optimized, else `False`
+ optimize (bool): `True` if route needs to be optimized, else `False`
Returns:
- (list of list of str): List of address routes split at locks, if optimize is `True`
+ (list of list of str): List of address routes split at locks, if optimize is `True`
"""
if not self.driver_address:
frappe.throw(_("Cannot Calculate Arrival Time as Driver Address is Missing."))
@@ -186,8 +188,8 @@
for vehicle routing problems.
Args:
- optimized_order (list of int): The index-based optimized order of the route
- start (int): The index at which to start the rearrangement
+ optimized_order (list of int): The index-based optimized order of the route
+ start (int): The index at which to start the rearrangement
"""
stops_order = []
@@ -200,7 +202,7 @@
self.delivery_stops[old_idx].idx = new_idx
stops_order.append(self.delivery_stops[old_idx])
- self.delivery_stops[start:start + len(stops_order)] = stops_order
+ self.delivery_stops[start : start + len(stops_order)] = stops_order
def get_directions(self, route, optimize):
"""
@@ -212,11 +214,11 @@
but it only works for routes without any waypoints.
Args:
- route (list of str): Route addresses (origin -> waypoint(s), if any -> destination)
- optimize (bool): `True` if route needs to be optimized, else `False`
+ route (list of str): Route addresses (origin -> waypoint(s), if any -> destination)
+ optimize (bool): `True` if route needs to be optimized, else `False`
Returns:
- (dict): Route legs and, if `optimize` is `True`, optimized waypoint order
+ (dict): Route legs and, if `optimize` is `True`, optimized waypoint order
"""
if not frappe.db.get_single_value("Google Settings", "api_key"):
frappe.throw(_("Enter API key in Google Settings."))
@@ -231,8 +233,8 @@
directions_data = {
"origin": route[0],
"destination": route[-1],
- "waypoints": route[1: -1],
- "optimize_waypoints": optimize
+ "waypoints": route[1:-1],
+ "optimize_waypoints": optimize,
}
try:
@@ -243,7 +245,6 @@
return directions[0] if directions else False
-
@frappe.whitelist()
def get_contact_and_address(name):
out = frappe._dict()
@@ -265,7 +266,10 @@
dl.link_doctype="Customer"
AND dl.link_name=%s
AND dl.parenttype = "Contact"
- """, (name), as_dict=1)
+ """,
+ (name),
+ as_dict=1,
+ )
if contact_persons:
for out.contact_person in contact_persons:
@@ -288,7 +292,10 @@
dl.link_doctype="Customer"
AND dl.link_name=%s
AND dl.parenttype = "Address"
- """, (name), as_dict=1)
+ """,
+ (name),
+ as_dict=1,
+ )
if shipping_addresses:
for out.shipping_address in shipping_addresses:
@@ -303,16 +310,18 @@
@frappe.whitelist()
def get_contact_display(contact):
contact_info = frappe.db.get_value(
- "Contact", contact,
- ["first_name", "last_name", "phone", "mobile_no"],
- as_dict=1)
+ "Contact", contact, ["first_name", "last_name", "phone", "mobile_no"], as_dict=1
+ )
- contact_info.html = """ <b>%(first_name)s %(last_name)s</b> <br> %(phone)s <br> %(mobile_no)s""" % {
- "first_name": contact_info.first_name,
- "last_name": contact_info.last_name or "",
- "phone": contact_info.phone or "",
- "mobile_no": contact_info.mobile_no or ""
- }
+ contact_info.html = (
+ """ <b>%(first_name)s %(last_name)s</b> <br> %(phone)s <br> %(mobile_no)s"""
+ % {
+ "first_name": contact_info.first_name,
+ "last_name": contact_info.last_name or "",
+ "phone": contact_info.phone or "",
+ "mobile_no": contact_info.mobile_no or "",
+ }
+ )
return contact_info.html
@@ -322,19 +331,19 @@
Remove HTML breaks in a given address
Args:
- address (str): Address to be sanitized
+ address (str): Address to be sanitized
Returns:
- (str): Sanitized address
+ (str): Sanitized address
"""
if not address:
return
- address = address.split('<br>')
+ address = address.split("<br>")
# Only get the first 3 blocks of the address
- return ', '.join(address[:3])
+ return ", ".join(address[:3])
@frappe.whitelist()
@@ -349,11 +358,15 @@
email_recipients = []
for stop in delivery_trip.delivery_stops:
- contact_info = frappe.db.get_value("Contact", stop.contact, ["first_name", "last_name", "email_id"], as_dict=1)
+ contact_info = frappe.db.get_value(
+ "Contact", stop.contact, ["first_name", "last_name", "email_id"], as_dict=1
+ )
context.update({"items": []})
if stop.delivery_note:
- items = frappe.get_all("Delivery Note Item", filters={"parent": stop.delivery_note, "docstatus": 1}, fields=["*"])
+ items = frappe.get_all(
+ "Delivery Note Item", filters={"parent": stop.delivery_note, "docstatus": 1}, fields=["*"]
+ )
context.update({"items": items})
if contact_info and contact_info.email_id:
@@ -363,10 +376,12 @@
dispatch_template_name = frappe.db.get_single_value("Delivery Settings", "dispatch_template")
dispatch_template = frappe.get_doc("Email Template", dispatch_template_name)
- frappe.sendmail(recipients=contact_info.email_id,
+ frappe.sendmail(
+ recipients=contact_info.email_id,
subject=dispatch_template.subject,
message=frappe.render_template(dispatch_template.response, context),
- attachments=get_attachments(stop))
+ attachments=get_attachments(stop),
+ )
stop.db_set("email_sent_to", contact_info.email_id)
email_recipients.append(contact_info.email_id)
@@ -379,29 +394,37 @@
def get_attachments(delivery_stop):
- if not (frappe.db.get_single_value("Delivery Settings", "send_with_attachment") and delivery_stop.delivery_note):
+ if not (
+ frappe.db.get_single_value("Delivery Settings", "send_with_attachment")
+ and delivery_stop.delivery_note
+ ):
return []
dispatch_attachment = frappe.db.get_single_value("Delivery Settings", "dispatch_attachment")
- attachments = frappe.attach_print("Delivery Note", delivery_stop.delivery_note,
- file_name="Delivery Note", print_format=dispatch_attachment)
+ attachments = frappe.attach_print(
+ "Delivery Note",
+ delivery_stop.delivery_note,
+ file_name="Delivery Note",
+ print_format=dispatch_attachment,
+ )
return [attachments]
+
@frappe.whitelist()
def get_driver_email(driver):
employee = frappe.db.get_value("Driver", driver, "employee")
email = frappe.db.get_value("Employee", employee, "prefered_email")
return {"email": email}
+
@frappe.whitelist()
def make_expense_claim(source_name, target_doc=None):
- doc = get_mapped_doc("Delivery Trip", source_name,
- {"Delivery Trip": {
- "doctype": "Expense Claim",
- "field_map": {
- "name" : "delivery_trip"
- }
- }}, target_doc)
+ doc = get_mapped_doc(
+ "Delivery Trip",
+ source_name,
+ {"Delivery Trip": {"doctype": "Expense Claim", "field_map": {"name": "delivery_trip"}}},
+ target_doc,
+ )
return doc
diff --git a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py b/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py
index dcdff4a..555361a 100644
--- a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py
+++ b/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py
@@ -106,23 +106,21 @@
self.delivery_trip.save()
self.assertEqual(self.delivery_trip.status, "Completed")
+
def create_address(driver):
if not frappe.db.exists("Address", {"address_title": "_Test Address for Driver"}):
- address = frappe.get_doc({
- "doctype": "Address",
- "address_title": "_Test Address for Driver",
- "address_type": "Office",
- "address_line1": "Station Road",
- "city": "_Test City",
- "state": "Test State",
- "country": "India",
- "links":[
- {
- "link_doctype": "Driver",
- "link_name": driver.name
- }
- ]
- }).insert(ignore_permissions=True)
+ address = frappe.get_doc(
+ {
+ "doctype": "Address",
+ "address_title": "_Test Address for Driver",
+ "address_type": "Office",
+ "address_line1": "Station Road",
+ "city": "_Test City",
+ "state": "Test State",
+ "country": "India",
+ "links": [{"link_doctype": "Driver", "link_name": driver.name}],
+ }
+ ).insert(ignore_permissions=True)
frappe.db.set_value("Driver", driver.name, "address", address.name)
@@ -130,49 +128,57 @@
return frappe.get_doc("Address", {"address_title": "_Test Address for Driver"})
+
def create_driver():
if not frappe.db.exists("Driver", {"full_name": "Newton Scmander"}):
- driver = frappe.get_doc({
- "doctype": "Driver",
- "full_name": "Newton Scmander",
- "cell_number": "98343424242",
- "license_number": "B809",
- }).insert(ignore_permissions=True)
+ driver = frappe.get_doc(
+ {
+ "doctype": "Driver",
+ "full_name": "Newton Scmander",
+ "cell_number": "98343424242",
+ "license_number": "B809",
+ }
+ ).insert(ignore_permissions=True)
return driver
return frappe.get_doc("Driver", {"full_name": "Newton Scmander"})
+
def create_delivery_notification():
if not frappe.db.exists("Email Template", "Delivery Notification"):
- dispatch_template = frappe.get_doc({
- 'doctype': 'Email Template',
- 'name': 'Delivery Notification',
- 'response': 'Test Delivery Trip',
- 'subject': 'Test Subject',
- 'owner': frappe.session.user
- })
+ dispatch_template = frappe.get_doc(
+ {
+ "doctype": "Email Template",
+ "name": "Delivery Notification",
+ "response": "Test Delivery Trip",
+ "subject": "Test Subject",
+ "owner": frappe.session.user,
+ }
+ )
dispatch_template.insert()
delivery_settings = frappe.get_single("Delivery Settings")
- delivery_settings.dispatch_template = 'Delivery Notification'
+ delivery_settings.dispatch_template = "Delivery Notification"
delivery_settings.save()
def create_vehicle():
if not frappe.db.exists("Vehicle", "JB 007"):
- vehicle = frappe.get_doc({
- "doctype": "Vehicle",
- "license_plate": "JB 007",
- "make": "Maruti",
- "model": "PCM",
- "last_odometer": 5000,
- "acquisition_date": nowdate(),
- "location": "Mumbai",
- "chassis_no": "1234ABCD",
- "uom": "Litre",
- "vehicle_value": flt(500000)
- })
+ vehicle = frappe.get_doc(
+ {
+ "doctype": "Vehicle",
+ "license_plate": "JB 007",
+ "make": "Maruti",
+ "model": "PCM",
+ "last_odometer": 5000,
+ "acquisition_date": nowdate(),
+ "location": "Mumbai",
+ "chassis_no": "1234ABCD",
+ "uom": "Litre",
+ "vehicle_value": flt(500000),
+ }
+ )
vehicle.insert()
@@ -180,23 +186,27 @@
if not contact:
contact = get_contact_and_address("_Test Customer")
- delivery_trip = frappe.get_doc({
- "doctype": "Delivery Trip",
- "company": erpnext.get_default_company(),
- "departure_time": add_days(now_datetime(), 5),
- "driver": driver.name,
- "driver_address": address.name,
- "vehicle": "JB 007",
- "delivery_stops": [{
- "customer": "_Test Customer",
- "address": contact.shipping_address.parent,
- "contact": contact.contact_person.parent
- },
+ delivery_trip = frappe.get_doc(
{
- "customer": "_Test Customer",
- "address": contact.shipping_address.parent,
- "contact": contact.contact_person.parent
- }]
- }).insert(ignore_permissions=True)
+ "doctype": "Delivery Trip",
+ "company": erpnext.get_default_company(),
+ "departure_time": add_days(now_datetime(), 5),
+ "driver": driver.name,
+ "driver_address": address.name,
+ "vehicle": "JB 007",
+ "delivery_stops": [
+ {
+ "customer": "_Test Customer",
+ "address": contact.shipping_address.parent,
+ "contact": contact.contact_person.parent,
+ },
+ {
+ "customer": "_Test Customer",
+ "address": contact.shipping_address.parent,
+ "contact": contact.contact_person.parent,
+ },
+ ],
+ }
+ ).insert(ignore_permissions=True)
return delivery_trip
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 3abeecd..9d7c22f 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -44,13 +44,15 @@
class InvalidBarcode(frappe.ValidationError):
pass
+
class DataValidationError(frappe.ValidationError):
pass
+
class Item(Document):
def onload(self):
- self.set_onload('stock_exists', self.stock_ledger_created())
- self.set_onload('asset_naming_series', get_asset_naming_series())
+ self.set_onload("stock_exists", self.stock_ledger_created())
+ self.set_onload("asset_naming_series", get_asset_naming_series())
def autoname(self):
if frappe.db.get_default("item_naming_by") == "Naming Series":
@@ -60,6 +62,7 @@
make_variant_item_code(self.variant_of, template_item_name, self)
else:
from frappe.model.naming import set_name_by_naming_series
+
set_name_by_naming_series(self)
self.item_code = self.name
@@ -70,9 +73,8 @@
if not self.description:
self.description = self.item_name
-
def after_insert(self):
- '''set opening stock and item price'''
+ """set opening stock and item price"""
if self.standard_rate:
for default in self.item_defaults or [frappe._dict()]:
self.add_price(default.default_price_list)
@@ -128,8 +130,8 @@
self.update_website_item()
def validate_description(self):
- '''Clean HTML description if set'''
- if cint(frappe.db.get_single_value('Stock Settings', 'clean_description_html')):
+ """Clean HTML description if set"""
+ if cint(frappe.db.get_single_value("Stock Settings", "clean_description_html")):
self.description = clean_html(self.description)
def validate_customer_provided_part(self):
@@ -141,24 +143,27 @@
self.default_material_request_type = "Customer Provided"
def add_price(self, price_list=None):
- '''Add a new price'''
+ """Add a new price"""
if not price_list:
- price_list = (frappe.db.get_single_value('Selling Settings', 'selling_price_list')
- or frappe.db.get_value('Price List', _('Standard Selling')))
+ price_list = frappe.db.get_single_value(
+ "Selling Settings", "selling_price_list"
+ ) or frappe.db.get_value("Price List", _("Standard Selling"))
if price_list:
- item_price = frappe.get_doc({
- "doctype": "Item Price",
- "price_list": price_list,
- "item_code": self.name,
- "uom": self.stock_uom,
- "brand": self.brand,
- "currency": erpnext.get_default_currency(),
- "price_list_rate": self.standard_rate
- })
+ item_price = frappe.get_doc(
+ {
+ "doctype": "Item Price",
+ "price_list": price_list,
+ "item_code": self.name,
+ "uom": self.stock_uom,
+ "brand": self.brand,
+ "currency": erpnext.get_default_currency(),
+ "price_list_rate": self.standard_rate,
+ }
+ )
item_price.insert()
def set_opening_stock(self):
- '''set opening stock'''
+ """set opening stock"""
if not self.is_stock_item or self.has_serial_no or self.has_batch_no:
return
@@ -171,19 +176,30 @@
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
# default warehouse, or Stores
- for default in self.item_defaults or [frappe._dict({'company': frappe.defaults.get_defaults().company})]:
- default_warehouse = (default.default_warehouse
- or frappe.db.get_single_value('Stock Settings', 'default_warehouse'))
+ for default in self.item_defaults or [
+ frappe._dict({"company": frappe.defaults.get_defaults().company})
+ ]:
+ default_warehouse = default.default_warehouse or frappe.db.get_single_value(
+ "Stock Settings", "default_warehouse"
+ )
if default_warehouse:
warehouse_company = frappe.db.get_value("Warehouse", default_warehouse, "company")
if not default_warehouse or warehouse_company != default.company:
- default_warehouse = frappe.db.get_value('Warehouse',
- {'warehouse_name': _('Stores'), 'company': default.company})
+ default_warehouse = frappe.db.get_value(
+ "Warehouse", {"warehouse_name": _("Stores"), "company": default.company}
+ )
if default_warehouse:
- stock_entry = make_stock_entry(item_code=self.name, target=default_warehouse, qty=self.opening_stock,
- rate=self.valuation_rate, company=default.company, posting_date=getdate(), posting_time=nowtime())
+ stock_entry = make_stock_entry(
+ item_code=self.name,
+ target=default_warehouse,
+ qty=self.opening_stock,
+ rate=self.valuation_rate,
+ company=default.company,
+ posting_date=getdate(),
+ posting_time=nowtime(),
+ )
stock_entry.add_comment("Comment", _("Opening Stock"))
@@ -201,14 +217,21 @@
if not self.is_fixed_asset:
asset = frappe.db.get_all("Asset", filters={"item_code": self.name, "docstatus": 1}, limit=1)
if asset:
- frappe.throw(_('"Is Fixed Asset" cannot be unchecked, as Asset record exists against the item'))
+ frappe.throw(
+ _('"Is Fixed Asset" cannot be unchecked, as Asset record exists against the item')
+ )
def validate_retain_sample(self):
- if self.retain_sample and not frappe.db.get_single_value('Stock Settings', 'sample_retention_warehouse'):
+ if self.retain_sample and not frappe.db.get_single_value(
+ "Stock Settings", "sample_retention_warehouse"
+ ):
frappe.throw(_("Please select Sample Retention Warehouse in Stock Settings first"))
if self.retain_sample and not self.has_batch_no:
- frappe.throw(_("{0} Retain Sample is based on batch, please check Has Batch No to retain sample of item").format(
- self.item_code))
+ frappe.throw(
+ _(
+ "{0} Retain Sample is based on batch, please check Has Batch No to retain sample of item"
+ ).format(self.item_code)
+ )
def clear_retain_sample(self):
if not self.has_batch_no:
@@ -228,10 +251,7 @@
uoms_list = [d.uom for d in self.get("uoms")]
if self.stock_uom not in uoms_list:
- self.append("uoms", {
- "uom": self.stock_uom,
- "conversion_factor": 1
- })
+ self.append("uoms", {"uom": self.stock_uom, "conversion_factor": 1})
def update_website_item(self):
"""Update Website Item if change in Item impacts it."""
@@ -239,8 +259,7 @@
if web_item:
changed = {}
- editable_fields = ["item_name", "item_group", "stock_uom", "brand", "description",
- "disabled"]
+ editable_fields = ["item_name", "item_group", "stock_uom", "brand", "description", "disabled"]
doc_before_save = self.get_doc_before_save()
for field in editable_fields:
@@ -258,7 +277,7 @@
web_item_doc.save()
def validate_item_tax_net_rate_range(self):
- for tax in self.get('taxes'):
+ for tax in self.get("taxes"):
if flt(tax.maximum_net_rate) < flt(tax.minimum_net_rate):
frappe.throw(_("Row #{0}: Maximum Net Rate cannot be greater than Minimum Net Rate"))
@@ -273,23 +292,31 @@
if not self.get("reorder_levels"):
for d in template.get("reorder_levels"):
n = {}
- for k in ("warehouse", "warehouse_reorder_level",
- "warehouse_reorder_qty", "material_request_type"):
+ for k in (
+ "warehouse",
+ "warehouse_reorder_level",
+ "warehouse_reorder_qty",
+ "material_request_type",
+ ):
n[k] = d.get(k)
self.append("reorder_levels", n)
def validate_conversion_factor(self):
check_list = []
- for d in self.get('uoms'):
+ for d in self.get("uoms"):
if cstr(d.uom) in check_list:
frappe.throw(
- _("Unit of Measure {0} has been entered more than once in Conversion Factor Table").format(d.uom))
+ _("Unit of Measure {0} has been entered more than once in Conversion Factor Table").format(
+ d.uom
+ )
+ )
else:
check_list.append(cstr(d.uom))
if d.uom and cstr(d.uom) == cstr(self.stock_uom) and flt(d.conversion_factor) != 1:
frappe.throw(
- _("Conversion factor for default Unit of Measure must be 1 in row {0}").format(d.idx))
+ _("Conversion factor for default Unit of Measure must be 1 in row {0}").format(d.idx)
+ )
def validate_item_type(self):
if self.has_serial_no == 1 and self.is_stock_item == 0 and not self.is_fixed_asset:
@@ -302,28 +329,32 @@
for field in ["serial_no_series", "batch_number_series"]:
series = self.get(field)
if series and "#" in series and "." not in series:
- frappe.throw(_("Invalid naming series (. missing) for {0}")
- .format(frappe.bold(self.meta.get_field(field).label)))
+ frappe.throw(
+ _("Invalid naming series (. missing) for {0}").format(
+ frappe.bold(self.meta.get_field(field).label)
+ )
+ )
def check_for_active_boms(self):
if self.default_bom:
bom_item = frappe.db.get_value("BOM", self.default_bom, "item")
if bom_item not in (self.name, self.variant_of):
frappe.throw(
- _("Default BOM ({0}) must be active for this item or its template").format(bom_item))
+ _("Default BOM ({0}) must be active for this item or its template").format(bom_item)
+ )
def fill_customer_code(self):
"""
- Append all the customer codes and insert into "customer_code" field of item table.
- Used to search Item by customer code.
+ Append all the customer codes and insert into "customer_code" field of item table.
+ Used to search Item by customer code.
"""
customer_codes = set(d.ref_code for d in self.get("customer_items", []))
- self.customer_code = ','.join(customer_codes)
+ self.customer_code = ",".join(customer_codes)
def check_item_tax(self):
"""Check whether Tax Rate is not entered twice for same Tax Type"""
check_list = []
- for d in self.get('taxes'):
+ for d in self.get("taxes"):
if d.item_tax_template:
if d.item_tax_template in check_list:
frappe.throw(_("{0} entered twice in Item Tax").format(d.item_tax_template))
@@ -332,24 +363,39 @@
def validate_barcode(self):
from stdnum import ean
+
if len(self.barcodes) > 0:
for item_barcode in self.barcodes:
- options = frappe.get_meta("Item Barcode").get_options("barcode_type").split('\n')
+ options = frappe.get_meta("Item Barcode").get_options("barcode_type").split("\n")
if item_barcode.barcode:
duplicate = frappe.db.sql(
- """select parent from `tabItem Barcode` where barcode = %s and parent != %s""", (item_barcode.barcode, self.name))
+ """select parent from `tabItem Barcode` where barcode = %s and parent != %s""",
+ (item_barcode.barcode, self.name),
+ )
if duplicate:
- frappe.throw(_("Barcode {0} already used in Item {1}").format(
- item_barcode.barcode, duplicate[0][0]))
+ frappe.throw(
+ _("Barcode {0} already used in Item {1}").format(item_barcode.barcode, duplicate[0][0])
+ )
- item_barcode.barcode_type = "" if item_barcode.barcode_type not in options else item_barcode.barcode_type
- if item_barcode.barcode_type and item_barcode.barcode_type.upper() in ('EAN', 'UPC-A', 'EAN-13', 'EAN-8'):
+ item_barcode.barcode_type = (
+ "" if item_barcode.barcode_type not in options else item_barcode.barcode_type
+ )
+ if item_barcode.barcode_type and item_barcode.barcode_type.upper() in (
+ "EAN",
+ "UPC-A",
+ "EAN-13",
+ "EAN-8",
+ ):
if not ean.is_valid(item_barcode.barcode):
- frappe.throw(_("Barcode {0} is not a valid {1} code").format(
- item_barcode.barcode, item_barcode.barcode_type), InvalidBarcode)
+ frappe.throw(
+ _("Barcode {0} is not a valid {1} code").format(
+ item_barcode.barcode, item_barcode.barcode_type
+ ),
+ InvalidBarcode,
+ )
def validate_warehouse_for_reorder(self):
- '''Validate Reorder level table for duplicate and conditional mandatory'''
+ """Validate Reorder level table for duplicate and conditional mandatory"""
warehouse = []
for d in self.get("reorder_levels"):
if not d.warehouse_group:
@@ -357,20 +403,30 @@
if d.get("warehouse") and d.get("warehouse") not in warehouse:
warehouse += [d.get("warehouse")]
else:
- frappe.throw(_("Row {0}: An Reorder entry already exists for this warehouse {1}")
- .format(d.idx, d.warehouse), DuplicateReorderRows)
+ frappe.throw(
+ _("Row {0}: An Reorder entry already exists for this warehouse {1}").format(
+ d.idx, d.warehouse
+ ),
+ DuplicateReorderRows,
+ )
if d.warehouse_reorder_level and not d.warehouse_reorder_qty:
frappe.throw(_("Row #{0}: Please set reorder quantity").format(d.idx))
def stock_ledger_created(self):
- if not hasattr(self, '_stock_ledger_created'):
- self._stock_ledger_created = len(frappe.db.sql("""select name from `tabStock Ledger Entry`
- where item_code = %s and is_cancelled = 0 limit 1""", self.name))
+ if not hasattr(self, "_stock_ledger_created"):
+ self._stock_ledger_created = len(
+ frappe.db.sql(
+ """select name from `tabStock Ledger Entry`
+ where item_code = %s and is_cancelled = 0 limit 1""",
+ self.name,
+ )
+ )
return self._stock_ledger_created
def update_item_price(self):
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE `tabItem Price`
SET
item_name=%(item_name)s,
@@ -382,8 +438,8 @@
item_name=self.item_name,
item_description=self.description,
brand=self.brand,
- item_code=self.name
- )
+ item_code=self.name,
+ ),
)
def on_trash(self):
@@ -405,8 +461,11 @@
def after_rename(self, old_name, new_name, merge):
if merge:
self.validate_duplicate_item_in_stock_reconciliation(old_name, new_name)
- frappe.msgprint(_("It can take upto few hours for accurate stock values to be visible after merging items."),
- indicator="orange", title="Note")
+ frappe.msgprint(
+ _("It can take upto few hours for accurate stock values to be visible after merging items."),
+ indicator="orange",
+ title="Note",
+ )
if self.published_in_website:
invalidate_cache_for_item(self)
@@ -418,39 +477,54 @@
self.recalculate_bin_qty(new_name)
for dt in ("Sales Taxes and Charges", "Purchase Taxes and Charges"):
- for d in frappe.db.sql("""select name, item_wise_tax_detail from `tab{0}`
- where ifnull(item_wise_tax_detail, '') != ''""".format(dt), as_dict=1):
+ for d in frappe.db.sql(
+ """select name, item_wise_tax_detail from `tab{0}`
+ where ifnull(item_wise_tax_detail, '') != ''""".format(
+ dt
+ ),
+ as_dict=1,
+ ):
item_wise_tax_detail = json.loads(d.item_wise_tax_detail)
if isinstance(item_wise_tax_detail, dict) and old_name in item_wise_tax_detail:
item_wise_tax_detail[new_name] = item_wise_tax_detail[old_name]
item_wise_tax_detail.pop(old_name)
- frappe.db.set_value(dt, d.name, "item_wise_tax_detail",
- json.dumps(item_wise_tax_detail), update_modified=False)
+ frappe.db.set_value(
+ dt, d.name, "item_wise_tax_detail", json.dumps(item_wise_tax_detail), update_modified=False
+ )
def delete_old_bins(self, old_name):
frappe.db.delete("Bin", {"item_code": old_name})
def validate_duplicate_item_in_stock_reconciliation(self, old_name, new_name):
- records = frappe.db.sql(""" SELECT parent, COUNT(*) as records
+ records = frappe.db.sql(
+ """ SELECT parent, COUNT(*) as records
FROM `tabStock Reconciliation Item`
WHERE item_code = %s and docstatus = 1
GROUP By item_code, warehouse, parent
HAVING records > 1
- """, new_name, as_dict=1)
+ """,
+ new_name,
+ as_dict=1,
+ )
- if not records: return
+ if not records:
+ return
document = _("Stock Reconciliation") if len(records) == 1 else _("Stock Reconciliations")
msg = _("The items {0} and {1} are present in the following {2} :").format(
- frappe.bold(old_name), frappe.bold(new_name), document)
+ frappe.bold(old_name), frappe.bold(new_name), document
+ )
- msg += ' <br>'
- msg += ', '.join([get_link_to_form("Stock Reconciliation", d.parent) for d in records]) + "<br><br>"
+ msg += " <br>"
+ msg += (
+ ", ".join([get_link_to_form("Stock Reconciliation", d.parent) for d in records]) + "<br><br>"
+ )
- msg += _("Note: To merge the items, create a separate Stock Reconciliation for the old item {0}").format(
- frappe.bold(old_name))
+ msg += _(
+ "Note: To merge the items, create a separate Stock Reconciliation for the old item {0}"
+ ).format(frappe.bold(old_name))
frappe.throw(_(msg), title=_("Cannot Merge"), exc=DataValidationError)
@@ -469,8 +543,8 @@
def validate_duplicate_product_bundles_before_merge(self, old_name, new_name):
"Block merge if both old and new items have product bundles."
- old_bundle = frappe.get_value("Product Bundle",filters={"new_item_code": old_name})
- new_bundle = frappe.get_value("Product Bundle",filters={"new_item_code": new_name})
+ old_bundle = frappe.get_value("Product Bundle", filters={"new_item_code": old_name})
+ new_bundle = frappe.get_value("Product Bundle", filters={"new_item_code": new_name})
if old_bundle and new_bundle:
bundle_link = get_link_to_form("Product Bundle", old_bundle)
@@ -483,15 +557,14 @@
def validate_duplicate_website_item_before_merge(self, old_name, new_name):
"""
- Block merge if both old and new items have website items against them.
- This is to avoid duplicate website items after merging.
+ Block merge if both old and new items have website items against them.
+ This is to avoid duplicate website items after merging.
"""
web_items = frappe.get_all(
"Website Item",
- filters={
- "item_code": ["in", [old_name, new_name]]
- },
- fields=["item_code", "name"])
+ filters={"item_code": ["in", [old_name, new_name]]},
+ fields=["item_code", "name"],
+ )
if len(web_items) <= 1:
return
@@ -509,11 +582,19 @@
def recalculate_bin_qty(self, new_name):
from erpnext.stock.stock_balance import repost_stock
- existing_allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock")
+
+ existing_allow_negative_stock = frappe.db.get_value(
+ "Stock Settings", None, "allow_negative_stock"
+ )
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
- repost_stock_for_warehouses = frappe.get_all("Stock Ledger Entry",
- "warehouse", filters={"item_code": new_name}, pluck="warehouse", distinct=True)
+ repost_stock_for_warehouses = frappe.get_all(
+ "Stock Ledger Entry",
+ "warehouse",
+ filters={"item_code": new_name},
+ pluck="warehouse",
+ distinct=True,
+ )
# Delete all existing bins to avoid duplicate bins for the same item and warehouse
frappe.db.delete("Bin", {"item_code": new_name})
@@ -521,30 +602,41 @@
for warehouse in repost_stock_for_warehouses:
repost_stock(new_name, warehouse)
- frappe.db.set_value("Stock Settings", None, "allow_negative_stock", existing_allow_negative_stock)
+ frappe.db.set_value(
+ "Stock Settings", None, "allow_negative_stock", existing_allow_negative_stock
+ )
def update_bom_item_desc(self):
if self.is_new():
return
- if self.db_get('description') != self.description:
- frappe.db.sql("""
+ if self.db_get("description") != self.description:
+ frappe.db.sql(
+ """
update `tabBOM`
set description = %s
where item = %s and docstatus < 2
- """, (self.description, self.name))
+ """,
+ (self.description, self.name),
+ )
- frappe.db.sql("""
+ frappe.db.sql(
+ """
update `tabBOM Item`
set description = %s
where item_code = %s and docstatus < 2
- """, (self.description, self.name))
+ """,
+ (self.description, self.name),
+ )
- frappe.db.sql("""
+ frappe.db.sql(
+ """
update `tabBOM Explosion Item`
set description = %s
where item_code = %s and docstatus < 2
- """, (self.description, self.name))
+ """,
+ (self.description, self.name),
+ )
def validate_item_defaults(self):
companies = {row.company for row in self.item_defaults}
@@ -554,41 +646,61 @@
validate_item_default_company_links(self.item_defaults)
-
def update_defaults_from_item_group(self):
"""Get defaults from Item Group"""
if self.item_defaults or not self.item_group:
return
- item_defaults = frappe.db.get_values("Item Default", {"parent": self.item_group},
- ['company', 'default_warehouse','default_price_list','buying_cost_center','default_supplier',
- 'expense_account','selling_cost_center','income_account'], as_dict = 1)
+ item_defaults = frappe.db.get_values(
+ "Item Default",
+ {"parent": self.item_group},
+ [
+ "company",
+ "default_warehouse",
+ "default_price_list",
+ "buying_cost_center",
+ "default_supplier",
+ "expense_account",
+ "selling_cost_center",
+ "income_account",
+ ],
+ as_dict=1,
+ )
if item_defaults:
for item in item_defaults:
- self.append('item_defaults', {
- 'company': item.company,
- 'default_warehouse': item.default_warehouse,
- 'default_price_list': item.default_price_list,
- 'buying_cost_center': item.buying_cost_center,
- 'default_supplier': item.default_supplier,
- 'expense_account': item.expense_account,
- 'selling_cost_center': item.selling_cost_center,
- 'income_account': item.income_account
- })
+ self.append(
+ "item_defaults",
+ {
+ "company": item.company,
+ "default_warehouse": item.default_warehouse,
+ "default_price_list": item.default_price_list,
+ "buying_cost_center": item.buying_cost_center,
+ "default_supplier": item.default_supplier,
+ "expense_account": item.expense_account,
+ "selling_cost_center": item.selling_cost_center,
+ "income_account": item.income_account,
+ },
+ )
else:
defaults = frappe.defaults.get_defaults() or {}
# To check default warehouse is belong to the default company
- if defaults.get("default_warehouse") and defaults.company and frappe.db.exists("Warehouse",
- {'name': defaults.default_warehouse, 'company': defaults.company}):
- self.append("item_defaults", {
- "company": defaults.get("company"),
- "default_warehouse": defaults.default_warehouse
- })
+ if (
+ defaults.get("default_warehouse")
+ and defaults.company
+ and frappe.db.exists(
+ "Warehouse", {"name": defaults.default_warehouse, "company": defaults.company}
+ )
+ ):
+ self.append(
+ "item_defaults",
+ {"company": defaults.get("company"), "default_warehouse": defaults.default_warehouse},
+ )
def update_variants(self):
- if self.flags.dont_update_variants or \
- frappe.db.get_single_value('Item Variant Settings', 'do_not_update_variants'):
+ if self.flags.dont_update_variants or frappe.db.get_single_value(
+ "Item Variant Settings", "do_not_update_variants"
+ ):
return
if self.has_variants:
variants = frappe.db.get_all("Item", fields=["item_code"], filters={"variant_of": self.name})
@@ -597,8 +709,13 @@
update_variants(variants, self, publish_progress=False)
frappe.msgprint(_("Item Variants updated"))
else:
- frappe.enqueue("erpnext.stock.doctype.item.item.update_variants",
- variants=variants, template=self, now=frappe.flags.in_test, timeout=600)
+ frappe.enqueue(
+ "erpnext.stock.doctype.item.item.update_variants",
+ variants=variants,
+ template=self,
+ now=frappe.flags.in_test,
+ timeout=600,
+ )
def validate_has_variants(self):
if not self.has_variants and frappe.db.get_value("Item", self.name, "has_variants"):
@@ -630,11 +747,8 @@
# fetch all attributes of these items
item_attributes = frappe.get_all(
"Item Variant Attribute",
- filters={
- "parent": ["in", items],
- "attribute": ["in", deleted_attribute]
- },
- fields=["attribute", "parent"]
+ filters={"parent": ["in", items], "attribute": ["in", deleted_attribute]},
+ fields=["attribute", "parent"],
)
not_included = defaultdict(list)
@@ -653,14 +767,18 @@
return """<tr>
<td>{0}</td>
<td>{1}</td>
- </tr>""".format(title, body)
+ </tr>""".format(
+ title, body
+ )
- rows = ''
+ rows = ""
for docname, attr_list in not_included.items():
link = "<a href='/app/Form/Item/{0}'>{0}</a>".format(frappe.bold(_(docname)))
rows += table_row(link, body(attr_list))
- error_description = _('The following deleted attributes exist in Variants but not in the Template. You can either delete the Variants or keep the attribute(s) in template.')
+ error_description = _(
+ "The following deleted attributes exist in Variants but not in the Template. You can either delete the Variants or keep the attribute(s) in template."
+ )
message = """
<div>{0}</div><br>
@@ -671,25 +789,37 @@
</thead>
{3}
</table>
- """.format(error_description, _('Variant Items'), _('Attributes'), rows)
+ """.format(
+ error_description, _("Variant Items"), _("Attributes"), rows
+ )
frappe.throw(message, title=_("Variant Attribute Error"), is_minimizable=True, wide=True)
-
def validate_stock_exists_for_template_item(self):
if self.stock_ledger_created() and self._doc_before_save:
- if (cint(self._doc_before_save.has_variants) != cint(self.has_variants)
- or self._doc_before_save.variant_of != self.variant_of):
- frappe.throw(_("Cannot change Variant properties after stock transaction. You will have to make a new Item to do this.").format(self.name),
- StockExistsForTemplate)
+ if (
+ cint(self._doc_before_save.has_variants) != cint(self.has_variants)
+ or self._doc_before_save.variant_of != self.variant_of
+ ):
+ frappe.throw(
+ _(
+ "Cannot change Variant properties after stock transaction. You will have to make a new Item to do this."
+ ).format(self.name),
+ StockExistsForTemplate,
+ )
if self.has_variants or self.variant_of:
- if not self.is_child_table_same('attributes'):
+ if not self.is_child_table_same("attributes"):
frappe.throw(
- _('Cannot change Attributes after stock transaction. Make a new Item and transfer stock to the new Item'))
+ _(
+ "Cannot change Attributes after stock transaction. Make a new Item and transfer stock to the new Item"
+ )
+ )
def validate_variant_based_on_change(self):
- if not self.is_new() and (self.variant_of or (self.has_variants and frappe.get_all("Item", {"variant_of": self.name}))):
+ if not self.is_new() and (
+ self.variant_of or (self.has_variants and frappe.get_all("Item", {"variant_of": self.name}))
+ ):
if self.variant_based_on != frappe.db.get_value("Item", self.name, "variant_based_on"):
frappe.throw(_("Variant Based On cannot be changed"))
@@ -702,8 +832,11 @@
if self.variant_of:
template_uom = frappe.db.get_value("Item", self.variant_of, "stock_uom")
if template_uom != self.stock_uom:
- frappe.throw(_("Default Unit of Measure for Variant '{0}' must be same as in Template '{1}'")
- .format(self.stock_uom, template_uom))
+ frappe.throw(
+ _("Default Unit of Measure for Variant '{0}' must be same as in Template '{1}'").format(
+ self.stock_uom, template_uom
+ )
+ )
def validate_uom_conversion_factor(self):
if self.uoms:
@@ -717,21 +850,22 @@
return
if not self.variant_based_on:
- self.variant_based_on = 'Item Attribute'
+ self.variant_based_on = "Item Attribute"
- if self.variant_based_on == 'Item Attribute':
+ if self.variant_based_on == "Item Attribute":
attributes = []
if not self.attributes:
frappe.throw(_("Attribute table is mandatory"))
for d in self.attributes:
if d.attribute in attributes:
frappe.throw(
- _("Attribute {0} selected multiple times in Attributes Table").format(d.attribute))
+ _("Attribute {0} selected multiple times in Attributes Table").format(d.attribute)
+ )
else:
attributes.append(d.attribute)
def validate_variant_attributes(self):
- if self.is_new() and self.variant_of and self.variant_based_on == 'Item Attribute':
+ if self.is_new() and self.variant_of and self.variant_based_on == "Item Attribute":
# remove attributes with no attribute_value set
self.attributes = [d for d in self.attributes if cstr(d.attribute_value).strip()]
@@ -742,8 +876,9 @@
variant = get_variant(self.variant_of, args, self.name)
if variant:
- frappe.throw(_("Item variant {0} exists with same attributes")
- .format(variant), ItemVariantExistsError)
+ frappe.throw(
+ _("Item variant {0} exists with same attributes").format(variant), ItemVariantExistsError
+ )
validate_item_variant_attributes(self, args)
@@ -758,31 +893,52 @@
fields = ("has_serial_no", "is_stock_item", "valuation_method", "has_batch_no")
values = frappe.db.get_value("Item", self.name, fields, as_dict=True)
- if not values.get('valuation_method') and self.get('valuation_method'):
- values['valuation_method'] = frappe.db.get_single_value("Stock Settings", "valuation_method") or "FIFO"
+ if not values.get("valuation_method") and self.get("valuation_method"):
+ values["valuation_method"] = (
+ frappe.db.get_single_value("Stock Settings", "valuation_method") or "FIFO"
+ )
if values:
for field in fields:
if cstr(self.get(field)) != cstr(values.get(field)):
if self.check_if_linked_document_exists(field):
- frappe.throw(_("As there are existing transactions against item {0}, you can not change the value of {1}").format(self.name, frappe.bold(self.meta.get_label(field))))
+ frappe.throw(
+ _(
+ "As there are existing transactions against item {0}, you can not change the value of {1}"
+ ).format(self.name, frappe.bold(self.meta.get_label(field)))
+ )
def check_if_linked_document_exists(self, field):
- linked_doctypes = ["Delivery Note Item", "Sales Invoice Item", "POS Invoice Item", "Purchase Receipt Item",
- "Purchase Invoice Item", "Stock Entry Detail", "Stock Reconciliation Item"]
+ linked_doctypes = [
+ "Delivery Note Item",
+ "Sales Invoice Item",
+ "POS Invoice Item",
+ "Purchase Receipt Item",
+ "Purchase Invoice Item",
+ "Stock Entry Detail",
+ "Stock Reconciliation Item",
+ ]
# For "Is Stock Item", following doctypes is important
# because reserved_qty, ordered_qty and requested_qty updated from these doctypes
if field == "is_stock_item":
- linked_doctypes += ["Sales Order Item", "Purchase Order Item", "Material Request Item", "Product Bundle"]
+ linked_doctypes += [
+ "Sales Order Item",
+ "Purchase Order Item",
+ "Material Request Item",
+ "Product Bundle",
+ ]
for doctype in linked_doctypes:
- filters={"item_code": self.name, "docstatus": 1}
+ filters = {"item_code": self.name, "docstatus": 1}
if doctype == "Product Bundle":
- filters={"new_item_code": self.name}
+ filters = {"new_item_code": self.name}
- if doctype in ("Purchase Invoice Item", "Sales Invoice Item",):
+ if doctype in (
+ "Purchase Invoice Item",
+ "Sales Invoice Item",
+ ):
# If Invoice has Stock impact, only then consider it.
if self.stock_ledger_created():
return True
@@ -792,37 +948,48 @@
def validate_auto_reorder_enabled_in_stock_settings(self):
if self.reorder_levels:
- enabled = frappe.db.get_single_value('Stock Settings', 'auto_indent')
+ enabled = frappe.db.get_single_value("Stock Settings", "auto_indent")
if not enabled:
- frappe.msgprint(msg=_("You have to enable auto re-order in Stock Settings to maintain re-order levels."), title=_("Enable Auto Re-Order"), indicator="orange")
+ frappe.msgprint(
+ msg=_("You have to enable auto re-order in Stock Settings to maintain re-order levels."),
+ title=_("Enable Auto Re-Order"),
+ indicator="orange",
+ )
def make_item_price(item, price_list_name, item_price):
- frappe.get_doc({
- 'doctype': 'Item Price',
- 'price_list': price_list_name,
- 'item_code': item,
- 'price_list_rate': item_price
- }).insert()
+ frappe.get_doc(
+ {
+ "doctype": "Item Price",
+ "price_list": price_list_name,
+ "item_code": item,
+ "price_list_rate": item_price,
+ }
+ ).insert()
+
def get_timeline_data(doctype, name):
"""get timeline data based on Stock Ledger Entry. This is displayed as heatmap on the item page."""
- items = frappe.db.sql("""select unix_timestamp(posting_date), count(*)
+ items = frappe.db.sql(
+ """select unix_timestamp(posting_date), count(*)
from `tabStock Ledger Entry`
where item_code=%s and posting_date > date_sub(curdate(), interval 1 year)
- group by posting_date""", name)
+ group by posting_date""",
+ name,
+ )
return dict(items)
-
def validate_end_of_life(item_code, end_of_life=None, disabled=None):
if (not end_of_life) or (disabled is None):
end_of_life, disabled = frappe.db.get_value("Item", item_code, ["end_of_life", "disabled"])
if end_of_life and end_of_life != "0000-00-00" and getdate(end_of_life) <= now_datetime().date():
- frappe.throw(_("Item {0} has reached its end of life on {1}").format(item_code, formatdate(end_of_life)))
+ frappe.throw(
+ _("Item {0} has reached its end of life on {1}").format(item_code, formatdate(end_of_life))
+ )
if disabled:
frappe.throw(_("Item {0} is disabled").format(item_code))
@@ -843,11 +1010,13 @@
if docstatus == 2:
frappe.throw(_("Item {0} is cancelled").format(item_code))
+
def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
"""returns last purchase details in stock uom"""
# get last purchase order item details
- last_purchase_order = frappe.db.sql("""\
+ last_purchase_order = frappe.db.sql(
+ """\
select po.name, po.transaction_date, po.conversion_rate,
po_item.conversion_factor, po_item.base_price_list_rate,
po_item.discount_percentage, po_item.base_rate, po_item.base_net_rate
@@ -855,11 +1024,14 @@
where po.docstatus = 1 and po_item.item_code = %s and po.name != %s and
po.name = po_item.parent
order by po.transaction_date desc, po.name desc
- limit 1""", (item_code, cstr(doc_name)), as_dict=1)
-
+ limit 1""",
+ (item_code, cstr(doc_name)),
+ as_dict=1,
+ )
# get last purchase receipt item details
- last_purchase_receipt = frappe.db.sql("""\
+ last_purchase_receipt = frappe.db.sql(
+ """\
select pr.name, pr.posting_date, pr.posting_time, pr.conversion_rate,
pr_item.conversion_factor, pr_item.base_price_list_rate, pr_item.discount_percentage,
pr_item.base_rate, pr_item.base_net_rate
@@ -867,20 +1039,29 @@
where pr.docstatus = 1 and pr_item.item_code = %s and pr.name != %s and
pr.name = pr_item.parent
order by pr.posting_date desc, pr.posting_time desc, pr.name desc
- limit 1""", (item_code, cstr(doc_name)), as_dict=1)
+ limit 1""",
+ (item_code, cstr(doc_name)),
+ as_dict=1,
+ )
- purchase_order_date = getdate(last_purchase_order and last_purchase_order[0].transaction_date
- or "1900-01-01")
- purchase_receipt_date = getdate(last_purchase_receipt and
- last_purchase_receipt[0].posting_date or "1900-01-01")
+ purchase_order_date = getdate(
+ last_purchase_order and last_purchase_order[0].transaction_date or "1900-01-01"
+ )
+ purchase_receipt_date = getdate(
+ last_purchase_receipt and last_purchase_receipt[0].posting_date or "1900-01-01"
+ )
- if last_purchase_order and (purchase_order_date >= purchase_receipt_date or not last_purchase_receipt):
+ if last_purchase_order and (
+ purchase_order_date >= purchase_receipt_date or not last_purchase_receipt
+ ):
# use purchase order
last_purchase = last_purchase_order[0]
purchase_date = purchase_order_date
- elif last_purchase_receipt and (purchase_receipt_date > purchase_order_date or not last_purchase_order):
+ elif last_purchase_receipt and (
+ purchase_receipt_date > purchase_order_date or not last_purchase_order
+ ):
# use purchase receipt
last_purchase = last_purchase_receipt[0]
purchase_date = purchase_receipt_date
@@ -889,22 +1070,25 @@
return frappe._dict()
conversion_factor = flt(last_purchase.conversion_factor)
- out = frappe._dict({
- "base_price_list_rate": flt(last_purchase.base_price_list_rate) / conversion_factor,
- "base_rate": flt(last_purchase.base_rate) / conversion_factor,
- "base_net_rate": flt(last_purchase.base_net_rate) / conversion_factor,
- "discount_percentage": flt(last_purchase.discount_percentage),
- "purchase_date": purchase_date
- })
-
+ out = frappe._dict(
+ {
+ "base_price_list_rate": flt(last_purchase.base_price_list_rate) / conversion_factor,
+ "base_rate": flt(last_purchase.base_rate) / conversion_factor,
+ "base_net_rate": flt(last_purchase.base_net_rate) / conversion_factor,
+ "discount_percentage": flt(last_purchase.discount_percentage),
+ "purchase_date": purchase_date,
+ }
+ )
conversion_rate = flt(conversion_rate) or 1.0
- out.update({
- "price_list_rate": out.base_price_list_rate / conversion_rate,
- "rate": out.base_rate / conversion_rate,
- "base_rate": out.base_rate,
- "base_net_rate": out.base_net_rate
- })
+ out.update(
+ {
+ "price_list_rate": out.base_price_list_rate / conversion_rate,
+ "rate": out.base_rate / conversion_rate,
+ "base_rate": out.base_rate,
+ "base_net_rate": out.base_net_rate,
+ }
+ )
return out
@@ -927,39 +1111,51 @@
is_web_item = doc.get("published_in_website") or doc.get("published")
if doc.has_variants and is_web_item:
item_code = doc.item_code
- elif doc.variant_of and frappe.db.get_value('Item', doc.variant_of, 'published_in_website'):
+ elif doc.variant_of and frappe.db.get_value("Item", doc.variant_of, "published_in_website"):
item_code = doc.variant_of
if item_code:
item_cache = ItemVariantsCacheManager(item_code)
item_cache.rebuild_cache()
+
def check_stock_uom_with_bin(item, stock_uom):
if stock_uom == frappe.db.get_value("Item", item, "stock_uom"):
return
- ref_uom = frappe.db.get_value("Stock Ledger Entry",
- {"item_code": item}, "stock_uom")
+ ref_uom = frappe.db.get_value("Stock Ledger Entry", {"item_code": item}, "stock_uom")
if ref_uom:
if cstr(ref_uom) != cstr(stock_uom):
- frappe.throw(_("Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You will need to create a new Item to use a different Default UOM.").format(item))
+ frappe.throw(
+ _(
+ "Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You will need to create a new Item to use a different Default UOM."
+ ).format(item)
+ )
- bin_list = frappe.db.sql("""
+ bin_list = frappe.db.sql(
+ """
select * from tabBin where item_code = %s
and (reserved_qty > 0 or ordered_qty > 0 or indented_qty > 0 or planned_qty > 0)
and stock_uom != %s
- """, (item, stock_uom), as_dict=1)
+ """,
+ (item, stock_uom),
+ as_dict=1,
+ )
if bin_list:
- frappe.throw(_("Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You need to either cancel the linked documents or create a new Item.").format(item))
+ frappe.throw(
+ _(
+ "Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You need to either cancel the linked documents or create a new Item."
+ ).format(item)
+ )
# No SLE or documents against item. Bin UOM can be changed safely.
frappe.db.sql("""update tabBin set stock_uom=%s where item_code=%s""", (stock_uom, item))
def get_item_defaults(item_code, company):
- item = frappe.get_cached_doc('Item', item_code)
+ item = frappe.get_cached_doc("Item", item_code)
out = item.as_dict()
@@ -970,8 +1166,9 @@
out.update(row)
return out
+
def set_item_default(item_code, company, fieldname, value):
- item = frappe.get_cached_doc('Item', item_code)
+ item = frappe.get_cached_doc("Item", item_code)
for d in item.item_defaults:
if d.company == company:
@@ -980,10 +1177,11 @@
return
# no row found, add a new row for the company
- d = item.append('item_defaults', {fieldname: value, "company": company})
+ d = item.append("item_defaults", {fieldname: value, "company": company})
d.db_insert()
item.clear_cache()
+
@frappe.whitelist()
def get_item_details(item_code, company=None):
out = frappe._dict()
@@ -995,30 +1193,36 @@
return out
+
@frappe.whitelist()
def get_uom_conv_factor(uom, stock_uom):
- """ Get UOM conversion factor from uom to stock_uom
- e.g. uom = "Kg", stock_uom = "Gram" then returns 1000.0
+ """Get UOM conversion factor from uom to stock_uom
+ e.g. uom = "Kg", stock_uom = "Gram" then returns 1000.0
"""
if uom == stock_uom:
return 1.0
- from_uom, to_uom = uom, stock_uom # renaming for readability
+ from_uom, to_uom = uom, stock_uom # renaming for readability
- exact_match = frappe.db.get_value("UOM Conversion Factor", {"to_uom": to_uom, "from_uom": from_uom}, ["value"], as_dict=1)
+ exact_match = frappe.db.get_value(
+ "UOM Conversion Factor", {"to_uom": to_uom, "from_uom": from_uom}, ["value"], as_dict=1
+ )
if exact_match:
return exact_match.value
- inverse_match = frappe.db.get_value("UOM Conversion Factor", {"to_uom": from_uom, "from_uom": to_uom}, ["value"], as_dict=1)
+ inverse_match = frappe.db.get_value(
+ "UOM Conversion Factor", {"to_uom": from_uom, "from_uom": to_uom}, ["value"], as_dict=1
+ )
if inverse_match:
return 1 / inverse_match.value
# This attempts to try and get conversion from intermediate UOM.
# case:
- # g -> mg = 1000
- # g -> kg = 0.001
+ # g -> mg = 1000
+ # g -> kg = 0.001
# therefore kg -> mg = 1000 / 0.001 = 1,000,000
- intermediate_match = frappe.db.sql("""
+ intermediate_match = frappe.db.sql(
+ """
select (first.value / second.value) as value
from `tabUOM Conversion Factor` first
join `tabUOM Conversion Factor` second
@@ -1027,7 +1231,10 @@
first.to_uom = %(to_uom)s
and second.to_uom = %(from_uom)s
limit 1
- """, {"to_uom": to_uom, "from_uom": from_uom}, as_dict=1)
+ """,
+ {"to_uom": to_uom, "from_uom": from_uom},
+ as_dict=1,
+ )
if intermediate_match:
return intermediate_match[0].value
@@ -1039,8 +1246,12 @@
if not frappe.has_permission("Item"):
frappe.throw(_("No Permission"))
- return frappe.get_all("Item Attribute Value", fields = ["attribute_value"],
- filters = {'parent': parent, 'attribute_value': ("like", f"%{attribute_value}%")})
+ return frappe.get_all(
+ "Item Attribute Value",
+ fields=["attribute_value"],
+ filters={"parent": parent, "attribute_value": ("like", f"%{attribute_value}%")},
+ )
+
def update_variants(variants, template, publish_progress=True):
total = len(variants)
@@ -1051,6 +1262,7 @@
if publish_progress:
frappe.publish_progress(count / total * 100, title=_("Updating Variants..."))
+
@erpnext.allow_regional
def set_item_tax_from_hsn_code(item):
pass
@@ -1059,23 +1271,25 @@
def validate_item_default_company_links(item_defaults: List[ItemDefault]) -> None:
for item_default in item_defaults:
for doctype, field in [
- ['Warehouse', 'default_warehouse'],
- ['Cost Center', 'buying_cost_center'],
- ['Cost Center', 'selling_cost_center'],
- ['Account', 'expense_account'],
- ['Account', 'income_account']
+ ["Warehouse", "default_warehouse"],
+ ["Cost Center", "buying_cost_center"],
+ ["Cost Center", "selling_cost_center"],
+ ["Account", "expense_account"],
+ ["Account", "income_account"],
]:
if item_default.get(field):
- company = frappe.db.get_value(doctype, item_default.get(field), 'company', cache=True)
+ company = frappe.db.get_value(doctype, item_default.get(field), "company", cache=True)
if company and company != item_default.company:
- frappe.throw(_("Row #{}: {} {} doesn't belong to Company {}. Please select valid {}.")
- .format(
+ frappe.throw(
+ _("Row #{}: {} {} doesn't belong to Company {}. Please select valid {}.").format(
item_default.idx,
doctype,
frappe.bold(item_default.get(field)),
frappe.bold(item_default.company),
- frappe.bold(frappe.unscrub(field))
- ), title=_("Invalid Item Defaults"))
+ frappe.bold(frappe.unscrub(field)),
+ ),
+ title=_("Invalid Item Defaults"),
+ )
@frappe.whitelist()
@@ -1083,4 +1297,3 @@
from erpnext.assets.doctype.asset.asset import get_asset_naming_series
return get_asset_naming_series()
-
diff --git a/erpnext/stock/doctype/item/item_dashboard.py b/erpnext/stock/doctype/item/item_dashboard.py
index e16f5bb..33acf4b 100644
--- a/erpnext/stock/doctype/item/item_dashboard.py
+++ b/erpnext/stock/doctype/item/item_dashboard.py
@@ -3,45 +3,34 @@
def get_data():
return {
- 'heatmap': True,
- 'heatmap_message': _('This is based on stock movement. See {0} for details')\
- .format('<a href="#query-report/Stock Ledger">' + _('Stock Ledger') + '</a>'),
- 'fieldname': 'item_code',
- 'non_standard_fieldnames': {
- 'Work Order': 'production_item',
- 'Product Bundle': 'new_item_code',
- 'BOM': 'item',
- 'Batch': 'item'
+ "heatmap": True,
+ "heatmap_message": _("This is based on stock movement. See {0} for details").format(
+ '<a href="#query-report/Stock Ledger">' + _("Stock Ledger") + "</a>"
+ ),
+ "fieldname": "item_code",
+ "non_standard_fieldnames": {
+ "Work Order": "production_item",
+ "Product Bundle": "new_item_code",
+ "BOM": "item",
+ "Batch": "item",
},
- 'transactions': [
+ "transactions": [
+ {"label": _("Groups"), "items": ["BOM", "Product Bundle", "Item Alternative"]},
+ {"label": _("Pricing"), "items": ["Item Price", "Pricing Rule"]},
+ {"label": _("Sell"), "items": ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]},
{
- 'label': _('Groups'),
- 'items': ['BOM', 'Product Bundle', 'Item Alternative']
+ "label": _("Buy"),
+ "items": [
+ "Material Request",
+ "Supplier Quotation",
+ "Request for Quotation",
+ "Purchase Order",
+ "Purchase Receipt",
+ "Purchase Invoice",
+ ],
},
- {
- 'label': _('Pricing'),
- 'items': ['Item Price', 'Pricing Rule']
- },
- {
- 'label': _('Sell'),
- 'items': ['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice']
- },
- {
- 'label': _('Buy'),
- 'items': ['Material Request', 'Supplier Quotation', 'Request for Quotation',
- 'Purchase Order', 'Purchase Receipt', 'Purchase Invoice']
- },
- {
- 'label': _('Manufacture'),
- 'items': ['Production Plan', 'Work Order', 'Item Manufacturer']
- },
- {
- 'label': _('Traceability'),
- 'items': ['Serial No', 'Batch']
- },
- {
- 'label': _('Move'),
- 'items': ['Stock Entry']
- }
- ]
+ {"label": _("Manufacture"), "items": ["Production Plan", "Work Order", "Item Manufacturer"]},
+ {"label": _("Traceability"), "items": ["Serial No", "Batch"]},
+ {"label": _("Move"), "items": ["Stock Entry"]},
+ ],
}
diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py
index 05e6e76..328d937 100644
--- a/erpnext/stock/doctype/item/test_item.py
+++ b/erpnext/stock/doctype/item/test_item.py
@@ -30,6 +30,7 @@
test_ignore = ["BOM"]
test_dependencies = ["Warehouse", "Item Group", "Item Tax Template", "Brand", "Item Attribute"]
+
def make_item(item_code=None, properties=None):
if not item_code:
item_code = frappe.generate_hash(length=16)
@@ -37,13 +38,15 @@
if frappe.db.exists("Item", item_code):
return frappe.get_doc("Item", item_code)
- item = frappe.get_doc({
- "doctype": "Item",
- "item_code": item_code,
- "item_name": item_code,
- "description": item_code,
- "item_group": "Products"
- })
+ item = frappe.get_doc(
+ {
+ "doctype": "Item",
+ "item_code": item_code,
+ "item_name": item_code,
+ "description": item_code,
+ "item_group": "Products",
+ }
+ )
if properties:
item.update(properties)
@@ -56,6 +59,7 @@
return item
+
class TestItem(FrappeTestCase):
def setUp(self):
super().setUp()
@@ -98,56 +102,91 @@
make_test_objects("Item Price")
company = "_Test Company"
- currency = frappe.get_cached_value("Company", company, "default_currency")
+ currency = frappe.get_cached_value("Company", company, "default_currency")
- details = get_item_details({
- "item_code": "_Test Item",
- "company": company,
- "price_list": "_Test Price List",
- "currency": currency,
- "doctype": "Sales Order",
- "conversion_rate": 1,
- "price_list_currency": currency,
- "plc_conversion_rate": 1,
- "order_type": "Sales",
- "customer": "_Test Customer",
- "conversion_factor": 1,
- "price_list_uom_dependant": 1,
- "ignore_pricing_rule": 1
- })
+ details = get_item_details(
+ {
+ "item_code": "_Test Item",
+ "company": company,
+ "price_list": "_Test Price List",
+ "currency": currency,
+ "doctype": "Sales Order",
+ "conversion_rate": 1,
+ "price_list_currency": currency,
+ "plc_conversion_rate": 1,
+ "order_type": "Sales",
+ "customer": "_Test Customer",
+ "conversion_factor": 1,
+ "price_list_uom_dependant": 1,
+ "ignore_pricing_rule": 1,
+ }
+ )
for key, value in to_check.items():
self.assertEqual(value, details.get(key))
def test_item_tax_template(self):
expected_item_tax_template = [
- {"item_code": "_Test Item With Item Tax Template", "tax_category": "",
- "item_tax_template": "_Test Account Excise Duty @ 10 - _TC"},
- {"item_code": "_Test Item With Item Tax Template", "tax_category": "_Test Tax Category 1",
- "item_tax_template": "_Test Account Excise Duty @ 12 - _TC"},
- {"item_code": "_Test Item With Item Tax Template", "tax_category": "_Test Tax Category 2",
- "item_tax_template": None},
-
- {"item_code": "_Test Item Inherit Group Item Tax Template 1", "tax_category": "",
- "item_tax_template": "_Test Account Excise Duty @ 10 - _TC"},
- {"item_code": "_Test Item Inherit Group Item Tax Template 1", "tax_category": "_Test Tax Category 1",
- "item_tax_template": "_Test Account Excise Duty @ 12 - _TC"},
- {"item_code": "_Test Item Inherit Group Item Tax Template 1", "tax_category": "_Test Tax Category 2",
- "item_tax_template": None},
-
- {"item_code": "_Test Item Inherit Group Item Tax Template 2", "tax_category": "",
- "item_tax_template": "_Test Account Excise Duty @ 15 - _TC"},
- {"item_code": "_Test Item Inherit Group Item Tax Template 2", "tax_category": "_Test Tax Category 1",
- "item_tax_template": "_Test Account Excise Duty @ 12 - _TC"},
- {"item_code": "_Test Item Inherit Group Item Tax Template 2", "tax_category": "_Test Tax Category 2",
- "item_tax_template": None},
-
- {"item_code": "_Test Item Override Group Item Tax Template", "tax_category": "",
- "item_tax_template": "_Test Account Excise Duty @ 20 - _TC"},
- {"item_code": "_Test Item Override Group Item Tax Template", "tax_category": "_Test Tax Category 1",
- "item_tax_template": "_Test Item Tax Template 1 - _TC"},
- {"item_code": "_Test Item Override Group Item Tax Template", "tax_category": "_Test Tax Category 2",
- "item_tax_template": None},
+ {
+ "item_code": "_Test Item With Item Tax Template",
+ "tax_category": "",
+ "item_tax_template": "_Test Account Excise Duty @ 10 - _TC",
+ },
+ {
+ "item_code": "_Test Item With Item Tax Template",
+ "tax_category": "_Test Tax Category 1",
+ "item_tax_template": "_Test Account Excise Duty @ 12 - _TC",
+ },
+ {
+ "item_code": "_Test Item With Item Tax Template",
+ "tax_category": "_Test Tax Category 2",
+ "item_tax_template": None,
+ },
+ {
+ "item_code": "_Test Item Inherit Group Item Tax Template 1",
+ "tax_category": "",
+ "item_tax_template": "_Test Account Excise Duty @ 10 - _TC",
+ },
+ {
+ "item_code": "_Test Item Inherit Group Item Tax Template 1",
+ "tax_category": "_Test Tax Category 1",
+ "item_tax_template": "_Test Account Excise Duty @ 12 - _TC",
+ },
+ {
+ "item_code": "_Test Item Inherit Group Item Tax Template 1",
+ "tax_category": "_Test Tax Category 2",
+ "item_tax_template": None,
+ },
+ {
+ "item_code": "_Test Item Inherit Group Item Tax Template 2",
+ "tax_category": "",
+ "item_tax_template": "_Test Account Excise Duty @ 15 - _TC",
+ },
+ {
+ "item_code": "_Test Item Inherit Group Item Tax Template 2",
+ "tax_category": "_Test Tax Category 1",
+ "item_tax_template": "_Test Account Excise Duty @ 12 - _TC",
+ },
+ {
+ "item_code": "_Test Item Inherit Group Item Tax Template 2",
+ "tax_category": "_Test Tax Category 2",
+ "item_tax_template": None,
+ },
+ {
+ "item_code": "_Test Item Override Group Item Tax Template",
+ "tax_category": "",
+ "item_tax_template": "_Test Account Excise Duty @ 20 - _TC",
+ },
+ {
+ "item_code": "_Test Item Override Group Item Tax Template",
+ "tax_category": "_Test Tax Category 1",
+ "item_tax_template": "_Test Item Tax Template 1 - _TC",
+ },
+ {
+ "item_code": "_Test Item Override Group Item Tax Template",
+ "tax_category": "_Test Tax Category 2",
+ "item_tax_template": None,
+ },
]
expected_item_tax_map = {
@@ -156,43 +195,55 @@
"_Test Account Excise Duty @ 12 - _TC": {"_Test Account Excise Duty - _TC": 12},
"_Test Account Excise Duty @ 15 - _TC": {"_Test Account Excise Duty - _TC": 15},
"_Test Account Excise Duty @ 20 - _TC": {"_Test Account Excise Duty - _TC": 20},
- "_Test Item Tax Template 1 - _TC": {"_Test Account Excise Duty - _TC": 5, "_Test Account Education Cess - _TC": 10,
- "_Test Account S&H Education Cess - _TC": 15}
+ "_Test Item Tax Template 1 - _TC": {
+ "_Test Account Excise Duty - _TC": 5,
+ "_Test Account Education Cess - _TC": 10,
+ "_Test Account S&H Education Cess - _TC": 15,
+ },
}
for data in expected_item_tax_template:
- details = get_item_details({
- "item_code": data['item_code'],
- "tax_category": data['tax_category'],
- "company": "_Test Company",
- "price_list": "_Test Price List",
- "currency": "_Test Currency",
- "doctype": "Sales Order",
- "conversion_rate": 1,
- "price_list_currency": "_Test Currency",
- "plc_conversion_rate": 1,
- "order_type": "Sales",
- "customer": "_Test Customer",
- "conversion_factor": 1,
- "price_list_uom_dependant": 1,
- "ignore_pricing_rule": 1
- })
+ details = get_item_details(
+ {
+ "item_code": data["item_code"],
+ "tax_category": data["tax_category"],
+ "company": "_Test Company",
+ "price_list": "_Test Price List",
+ "currency": "_Test Currency",
+ "doctype": "Sales Order",
+ "conversion_rate": 1,
+ "price_list_currency": "_Test Currency",
+ "plc_conversion_rate": 1,
+ "order_type": "Sales",
+ "customer": "_Test Customer",
+ "conversion_factor": 1,
+ "price_list_uom_dependant": 1,
+ "ignore_pricing_rule": 1,
+ }
+ )
- self.assertEqual(details.item_tax_template, data['item_tax_template'])
- self.assertEqual(json.loads(details.item_tax_rate), expected_item_tax_map[details.item_tax_template])
+ self.assertEqual(details.item_tax_template, data["item_tax_template"])
+ self.assertEqual(
+ json.loads(details.item_tax_rate), expected_item_tax_map[details.item_tax_template]
+ )
def test_item_defaults(self):
frappe.delete_doc_if_exists("Item", "Test Item With Defaults", force=1)
- make_item("Test Item With Defaults", {
- "item_group": "_Test Item Group",
- "brand": "_Test Brand With Item Defaults",
- "item_defaults": [{
- "company": "_Test Company",
- "default_warehouse": "_Test Warehouse 2 - _TC", # no override
- "expense_account": "_Test Account Stock Expenses - _TC", # override brand default
- "buying_cost_center": "_Test Write Off Cost Center - _TC", # override item group default
- }]
- })
+ make_item(
+ "Test Item With Defaults",
+ {
+ "item_group": "_Test Item Group",
+ "brand": "_Test Brand With Item Defaults",
+ "item_defaults": [
+ {
+ "company": "_Test Company",
+ "default_warehouse": "_Test Warehouse 2 - _TC", # no override
+ "expense_account": "_Test Account Stock Expenses - _TC", # override brand default
+ "buying_cost_center": "_Test Write Off Cost Center - _TC", # override item group default
+ }
+ ],
+ },
+ )
sales_item_check = {
"item_code": "Test Item With Defaults",
@@ -201,17 +252,19 @@
"expense_account": "_Test Account Stock Expenses - _TC", # from item
"cost_center": "_Test Cost Center 2 - _TC", # from item group
}
- sales_item_details = get_item_details({
- "item_code": "Test Item With Defaults",
- "company": "_Test Company",
- "price_list": "_Test Price List",
- "currency": "_Test Currency",
- "doctype": "Sales Invoice",
- "conversion_rate": 1,
- "price_list_currency": "_Test Currency",
- "plc_conversion_rate": 1,
- "customer": "_Test Customer",
- })
+ sales_item_details = get_item_details(
+ {
+ "item_code": "Test Item With Defaults",
+ "company": "_Test Company",
+ "price_list": "_Test Price List",
+ "currency": "_Test Currency",
+ "doctype": "Sales Invoice",
+ "conversion_rate": 1,
+ "price_list_currency": "_Test Currency",
+ "plc_conversion_rate": 1,
+ "customer": "_Test Customer",
+ }
+ )
for key, value in sales_item_check.items():
self.assertEqual(value, sales_item_details.get(key))
@@ -220,38 +273,47 @@
"warehouse": "_Test Warehouse 2 - _TC", # from item
"expense_account": "_Test Account Stock Expenses - _TC", # from item
"income_account": "_Test Account Sales - _TC", # from brand
- "cost_center": "_Test Write Off Cost Center - _TC" # from item
+ "cost_center": "_Test Write Off Cost Center - _TC", # from item
}
- purchase_item_details = get_item_details({
- "item_code": "Test Item With Defaults",
- "company": "_Test Company",
- "price_list": "_Test Price List",
- "currency": "_Test Currency",
- "doctype": "Purchase Invoice",
- "conversion_rate": 1,
- "price_list_currency": "_Test Currency",
- "plc_conversion_rate": 1,
- "supplier": "_Test Supplier",
- })
+ purchase_item_details = get_item_details(
+ {
+ "item_code": "Test Item With Defaults",
+ "company": "_Test Company",
+ "price_list": "_Test Price List",
+ "currency": "_Test Currency",
+ "doctype": "Purchase Invoice",
+ "conversion_rate": 1,
+ "price_list_currency": "_Test Currency",
+ "plc_conversion_rate": 1,
+ "supplier": "_Test Supplier",
+ }
+ )
for key, value in purchase_item_check.items():
self.assertEqual(value, purchase_item_details.get(key))
def test_item_default_validations(self):
with self.assertRaises(frappe.ValidationError) as ve:
- make_item("Bad Item defaults", {
- "item_group": "_Test Item Group",
- "item_defaults": [{
- "company": "_Test Company 1",
- "default_warehouse": "_Test Warehouse - _TC",
- "expense_account": "Stock In Hand - _TC",
- "buying_cost_center": "_Test Cost Center - _TC",
- "selling_cost_center": "_Test Cost Center - _TC",
- }]
- })
+ make_item(
+ "Bad Item defaults",
+ {
+ "item_group": "_Test Item Group",
+ "item_defaults": [
+ {
+ "company": "_Test Company 1",
+ "default_warehouse": "_Test Warehouse - _TC",
+ "expense_account": "Stock In Hand - _TC",
+ "buying_cost_center": "_Test Cost Center - _TC",
+ "selling_cost_center": "_Test Cost Center - _TC",
+ }
+ ],
+ },
+ )
- self.assertTrue("belong to company" in str(ve.exception).lower(),
- msg="Mismatching company entities in item defaults should not be allowed.")
+ self.assertTrue(
+ "belong to company" in str(ve.exception).lower(),
+ msg="Mismatching company entities in item defaults should not be allowed.",
+ )
def test_item_attribute_change_after_variant(self):
frappe.delete_doc_if_exists("Item", "_Test Variant Item-L", force=1)
@@ -259,7 +321,7 @@
variant = create_variant("_Test Variant Item", {"Test Size": "Large"})
variant.save()
- attribute = frappe.get_doc('Item Attribute', 'Test Size')
+ attribute = frappe.get_doc("Item Attribute", "Test Size")
attribute.item_attribute_values = []
# reset flags
@@ -282,20 +344,18 @@
def test_copy_fields_from_template_to_variants(self):
frappe.delete_doc_if_exists("Item", "_Test Variant Item-XL", force=1)
- fields = [{'field_name': 'item_group'}, {'field_name': 'is_stock_item'}]
- allow_fields = [d.get('field_name') for d in fields]
+ fields = [{"field_name": "item_group"}, {"field_name": "is_stock_item"}]
+ allow_fields = [d.get("field_name") for d in fields]
set_item_variant_settings(fields)
- if not frappe.db.get_value('Item Attribute Value',
- {'parent': 'Test Size', 'attribute_value': 'Extra Large'}, 'name'):
- item_attribute = frappe.get_doc('Item Attribute', 'Test Size')
- item_attribute.append('item_attribute_values', {
- 'attribute_value' : 'Extra Large',
- 'abbr': 'XL'
- })
+ if not frappe.db.get_value(
+ "Item Attribute Value", {"parent": "Test Size", "attribute_value": "Extra Large"}, "name"
+ ):
+ item_attribute = frappe.get_doc("Item Attribute", "Test Size")
+ item_attribute.append("item_attribute_values", {"attribute_value": "Extra Large", "abbr": "XL"})
item_attribute.save()
- template = frappe.get_doc('Item', '_Test Variant Item')
+ template = frappe.get_doc("Item", "_Test Variant Item")
template.item_group = "_Test Item Group D"
template.save()
@@ -304,70 +364,71 @@
variant.item_name = "_Test Variant Item-XL"
variant.save()
- variant = frappe.get_doc('Item', '_Test Variant Item-XL')
+ variant = frappe.get_doc("Item", "_Test Variant Item-XL")
for fieldname in allow_fields:
self.assertEqual(template.get(fieldname), variant.get(fieldname))
- template = frappe.get_doc('Item', '_Test Variant Item')
+ template = frappe.get_doc("Item", "_Test Variant Item")
template.item_group = "_Test Item Group Desktops"
template.save()
def test_make_item_variant_with_numeric_values(self):
# cleanup
- for d in frappe.db.get_all('Item', filters={'variant_of':
- '_Test Numeric Template Item'}):
+ for d in frappe.db.get_all("Item", filters={"variant_of": "_Test Numeric Template Item"}):
frappe.delete_doc_if_exists("Item", d.name)
frappe.delete_doc_if_exists("Item", "_Test Numeric Template Item")
frappe.delete_doc_if_exists("Item Attribute", "Test Item Length")
- frappe.db.sql('''delete from `tabItem Variant Attribute`
- where attribute="Test Item Length"''')
+ frappe.db.sql(
+ '''delete from `tabItem Variant Attribute`
+ where attribute="Test Item Length"'''
+ )
frappe.flags.attribute_values = None
# make item attribute
- frappe.get_doc({
- "doctype": "Item Attribute",
- "attribute_name": "Test Item Length",
- "numeric_values": 1,
- "from_range": 0.0,
- "to_range": 100.0,
- "increment": 0.5
- }).insert()
+ frappe.get_doc(
+ {
+ "doctype": "Item Attribute",
+ "attribute_name": "Test Item Length",
+ "numeric_values": 1,
+ "from_range": 0.0,
+ "to_range": 100.0,
+ "increment": 0.5,
+ }
+ ).insert()
# make template item
- make_item("_Test Numeric Template Item", {
- "attributes": [
- {
- "attribute": "Test Size"
- },
- {
- "attribute": "Test Item Length",
- "numeric_values": 1,
- "from_range": 0.0,
- "to_range": 100.0,
- "increment": 0.5
- }
- ],
- "item_defaults": [
- {
- "default_warehouse": "_Test Warehouse - _TC",
- "company": "_Test Company"
- }
- ],
- "has_variants": 1
- })
+ make_item(
+ "_Test Numeric Template Item",
+ {
+ "attributes": [
+ {"attribute": "Test Size"},
+ {
+ "attribute": "Test Item Length",
+ "numeric_values": 1,
+ "from_range": 0.0,
+ "to_range": 100.0,
+ "increment": 0.5,
+ },
+ ],
+ "item_defaults": [{"default_warehouse": "_Test Warehouse - _TC", "company": "_Test Company"}],
+ "has_variants": 1,
+ },
+ )
- variant = create_variant("_Test Numeric Template Item",
- {"Test Size": "Large", "Test Item Length": 1.1})
+ variant = create_variant(
+ "_Test Numeric Template Item", {"Test Size": "Large", "Test Item Length": 1.1}
+ )
self.assertEqual(variant.item_code, "_Test Numeric Template Item-L-1.1")
variant.item_code = "_Test Numeric Variant-L-1.1"
variant.item_name = "_Test Numeric Variant Large 1.1m"
self.assertRaises(InvalidItemAttributeValueError, variant.save)
- variant = create_variant("_Test Numeric Template Item",
- {"Test Size": "Large", "Test Item Length": 1.5})
+ variant = create_variant(
+ "_Test Numeric Template Item", {"Test Size": "Large", "Test Item Length": 1.5}
+ )
self.assertEqual(variant.item_code, "_Test Numeric Template Item-L-1.5")
variant.item_code = "_Test Numeric Variant-L-1.5"
variant.item_name = "_Test Numeric Variant Large 1.5m"
@@ -377,21 +438,20 @@
old = create_item(frappe.generate_hash(length=20)).name
new = create_item(frappe.generate_hash(length=20)).name
- make_stock_entry(item_code=old, target="_Test Warehouse - _TC",
- qty=1, rate=100)
- make_stock_entry(item_code=old, target="_Test Warehouse 1 - _TC",
- qty=1, rate=100)
- make_stock_entry(item_code=new, target="_Test Warehouse 1 - _TC",
- qty=1, rate=100)
+ make_stock_entry(item_code=old, target="_Test Warehouse - _TC", qty=1, rate=100)
+ make_stock_entry(item_code=old, target="_Test Warehouse 1 - _TC", qty=1, rate=100)
+ make_stock_entry(item_code=new, target="_Test Warehouse 1 - _TC", qty=1, rate=100)
frappe.rename_doc("Item", old, new, merge=True)
self.assertFalse(frappe.db.exists("Item", old))
- self.assertTrue(frappe.db.get_value("Bin",
- {"item_code": new, "warehouse": "_Test Warehouse - _TC"}))
- self.assertTrue(frappe.db.get_value("Bin",
- {"item_code": new, "warehouse": "_Test Warehouse 1 - _TC"}))
+ self.assertTrue(
+ frappe.db.get_value("Bin", {"item_code": new, "warehouse": "_Test Warehouse - _TC"})
+ )
+ self.assertTrue(
+ frappe.db.get_value("Bin", {"item_code": new, "warehouse": "_Test Warehouse 1 - _TC"})
+ )
def test_item_merging_with_product_bundle(self):
from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
@@ -414,13 +474,12 @@
self.assertFalse(frappe.db.exists("Item", "Test Item Bundle Item 1"))
def test_uom_conversion_factor(self):
- if frappe.db.exists('Item', 'Test Item UOM'):
- frappe.delete_doc('Item', 'Test Item UOM')
+ if frappe.db.exists("Item", "Test Item UOM"):
+ frappe.delete_doc("Item", "Test Item UOM")
- item_doc = make_item("Test Item UOM", {
- "stock_uom": "Gram",
- "uoms": [dict(uom='Carat'), dict(uom='Kg')]
- })
+ item_doc = make_item(
+ "Test Item UOM", {"stock_uom": "Gram", "uoms": [dict(uom="Carat"), dict(uom="Kg")]}
+ )
for d in item_doc.uoms:
value = get_uom_conv_factor(d.uom, item_doc.stock_uom)
@@ -440,48 +499,46 @@
self.assertEqual(factor, 1.0)
def test_item_variant_by_manufacturer(self):
- fields = [{'field_name': 'description'}, {'field_name': 'variant_based_on'}]
+ fields = [{"field_name": "description"}, {"field_name": "variant_based_on"}]
set_item_variant_settings(fields)
- if frappe.db.exists('Item', '_Test Variant Mfg'):
- frappe.delete_doc('Item', '_Test Variant Mfg')
- if frappe.db.exists('Item', '_Test Variant Mfg-1'):
- frappe.delete_doc('Item', '_Test Variant Mfg-1')
- if frappe.db.exists('Manufacturer', 'MSG1'):
- frappe.delete_doc('Manufacturer', 'MSG1')
+ if frappe.db.exists("Item", "_Test Variant Mfg"):
+ frappe.delete_doc("Item", "_Test Variant Mfg")
+ if frappe.db.exists("Item", "_Test Variant Mfg-1"):
+ frappe.delete_doc("Item", "_Test Variant Mfg-1")
+ if frappe.db.exists("Manufacturer", "MSG1"):
+ frappe.delete_doc("Manufacturer", "MSG1")
- template = frappe.get_doc(dict(
- doctype='Item',
- item_code='_Test Variant Mfg',
- has_variant=1,
- item_group='Products',
- variant_based_on='Manufacturer'
- )).insert()
+ template = frappe.get_doc(
+ dict(
+ doctype="Item",
+ item_code="_Test Variant Mfg",
+ has_variant=1,
+ item_group="Products",
+ variant_based_on="Manufacturer",
+ )
+ ).insert()
- manufacturer = frappe.get_doc(dict(
- doctype='Manufacturer',
- short_name='MSG1'
- )).insert()
+ manufacturer = frappe.get_doc(dict(doctype="Manufacturer", short_name="MSG1")).insert()
variant = get_variant(template.name, manufacturer=manufacturer.name)
- self.assertEqual(variant.item_code, '_Test Variant Mfg-1')
- self.assertEqual(variant.description, '_Test Variant Mfg')
- self.assertEqual(variant.manufacturer, 'MSG1')
+ self.assertEqual(variant.item_code, "_Test Variant Mfg-1")
+ self.assertEqual(variant.description, "_Test Variant Mfg")
+ self.assertEqual(variant.manufacturer, "MSG1")
variant.insert()
- variant = get_variant(template.name, manufacturer=manufacturer.name,
- manufacturer_part_no='007')
- self.assertEqual(variant.item_code, '_Test Variant Mfg-2')
- self.assertEqual(variant.description, '_Test Variant Mfg')
- self.assertEqual(variant.manufacturer, 'MSG1')
- self.assertEqual(variant.manufacturer_part_no, '007')
+ variant = get_variant(template.name, manufacturer=manufacturer.name, manufacturer_part_no="007")
+ self.assertEqual(variant.item_code, "_Test Variant Mfg-2")
+ self.assertEqual(variant.description, "_Test Variant Mfg")
+ self.assertEqual(variant.manufacturer, "MSG1")
+ self.assertEqual(variant.manufacturer_part_no, "007")
def test_stock_exists_against_template_item(self):
- stock_item = frappe.get_all('Stock Ledger Entry', fields = ["item_code"], limit=1)
+ stock_item = frappe.get_all("Stock Ledger Entry", fields=["item_code"], limit=1)
if stock_item:
item_code = stock_item[0].item_code
- item_doc = frappe.get_doc('Item', item_code)
+ item_doc = frappe.get_doc("Item", item_code)
item_doc.has_variants = 1
self.assertRaises(StockExistsForTemplate, item_doc.save)
@@ -494,37 +551,27 @@
# Create new item and add barcodes
barcode_properties_list = [
- {
- "barcode": "0012345678905",
- "barcode_type": "EAN"
- },
- {
- "barcode": "012345678905",
- "barcode_type": "UAN"
- },
+ {"barcode": "0012345678905", "barcode_type": "EAN"},
+ {"barcode": "012345678905", "barcode_type": "UAN"},
{
"barcode": "ARBITRARY_TEXT",
- }
+ },
]
create_item(item_code)
for barcode_properties in barcode_properties_list:
- item_doc = frappe.get_doc('Item', item_code)
- new_barcode = item_doc.append('barcodes')
+ item_doc = frappe.get_doc("Item", item_code)
+ new_barcode = item_doc.append("barcodes")
new_barcode.update(barcode_properties)
item_doc.save()
# Check values saved correctly
barcodes = frappe.get_all(
- 'Item Barcode',
- fields=['barcode', 'barcode_type'],
- filters={'parent': item_code})
+ "Item Barcode", fields=["barcode", "barcode_type"], filters={"parent": item_code}
+ )
for barcode_properties in barcode_properties_list:
- barcode_to_find = barcode_properties['barcode']
- matching_barcodes = [
- x for x in barcodes
- if x['barcode'] == barcode_to_find
- ]
+ barcode_to_find = barcode_properties["barcode"]
+ matching_barcodes = [x for x in barcodes if x["barcode"] == barcode_to_find]
self.assertEqual(len(matching_barcodes), 1)
details = matching_barcodes[0]
@@ -532,20 +579,21 @@
self.assertEqual(value, details.get(key))
# Add barcode again - should cause DuplicateEntryError
- item_doc = frappe.get_doc('Item', item_code)
- new_barcode = item_doc.append('barcodes')
+ item_doc = frappe.get_doc("Item", item_code)
+ new_barcode = item_doc.append("barcodes")
new_barcode.update(barcode_properties_list[0])
self.assertRaises(frappe.UniqueValidationError, item_doc.save)
# Add invalid barcode - should cause InvalidBarcode
- item_doc = frappe.get_doc('Item', item_code)
- new_barcode = item_doc.append('barcodes')
- new_barcode.barcode = '9999999999999'
- new_barcode.barcode_type = 'EAN'
+ item_doc = frappe.get_doc("Item", item_code)
+ new_barcode = item_doc.append("barcodes")
+ new_barcode.barcode = "9999999999999"
+ new_barcode.barcode_type = "EAN"
self.assertRaises(InvalidBarcode, item_doc.save)
def test_heatmap_data(self):
import time
+
data = get_timeline_data("Item", "_Test Item")
self.assertTrue(isinstance(data, dict))
@@ -588,20 +636,17 @@
def test_check_stock_uom_with_bin_no_sle(self):
from erpnext.stock.stock_balance import update_bin_qty
+
item = create_item("_Item with bin qty")
item.stock_uom = "Gram"
item.save()
- update_bin_qty(item.item_code, "_Test Warehouse - _TC", {
- "reserved_qty": 10
- })
+ update_bin_qty(item.item_code, "_Test Warehouse - _TC", {"reserved_qty": 10})
item.stock_uom = "Kilometer"
self.assertRaises(frappe.ValidationError, item.save)
- update_bin_qty(item.item_code, "_Test Warehouse - _TC", {
- "reserved_qty": 0
- })
+ update_bin_qty(item.item_code, "_Test Warehouse - _TC", {"reserved_qty": 0})
item.load_from_db()
item.stock_uom = "Kilometer"
@@ -636,7 +681,7 @@
@change_settings("Stock Settings", {"allow_negative_stock": 0})
def test_item_wise_negative_stock(self):
- """ When global settings are disabled check that item that allows
+ """When global settings are disabled check that item that allows
negative stock can still consume material in all known stock
transactions that consume inventory."""
from erpnext.stock.stock_ledger import is_negative_stock_allowed
@@ -648,17 +693,22 @@
@change_settings("Stock Settings", {"allow_negative_stock": 0})
def test_backdated_negative_stock(self):
- """ same as test above but backdated entries """
+ """same as test above but backdated entries"""
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
+
item = make_item("_TestNegativeItemSetting", {"allow_negative_stock": 1, "valuation_rate": 100})
# create a future entry so all new entries are backdated
- make_stock_entry(qty=1, item_code=item.name, target="_Test Warehouse - _TC", posting_date = add_days(today(), 5))
+ make_stock_entry(
+ qty=1, item_code=item.name, target="_Test Warehouse - _TC", posting_date=add_days(today(), 5)
+ )
self.consume_item_code_with_differet_stock_transactions(item_code=item.name)
@change_settings("Stock Settings", {"sample_retention_warehouse": "_Test Warehouse - _TC"})
def test_retain_sample(self):
- item = make_item("_TestRetainSample", {'has_batch_no': 1, 'retain_sample': 1, 'sample_quantity': 1})
+ item = make_item(
+ "_TestRetainSample", {"has_batch_no": 1, "retain_sample": 1, "sample_quantity": 1}
+ )
self.assertEqual(item.has_batch_no, 1)
self.assertEqual(item.retain_sample, 1)
@@ -670,7 +720,9 @@
self.assertEqual(item.sample_quantity, None)
item.delete()
- def consume_item_code_with_differet_stock_transactions(self, item_code, warehouse="_Test Warehouse - _TC"):
+ def consume_item_code_with_differet_stock_transactions(
+ self, item_code, warehouse="_Test Warehouse - _TC"
+ ):
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
@@ -694,10 +746,11 @@
def set_item_variant_settings(fields):
- doc = frappe.get_doc('Item Variant Settings')
- doc.set('fields', fields)
+ doc = frappe.get_doc("Item Variant Settings")
+ doc.set("fields", fields)
doc.save()
+
def make_item_variant():
if not frappe.db.exists("Item", "_Test Variant Item-S"):
variant = create_variant("_Test Variant Item", """{"Test Size": "Small"}""")
@@ -705,11 +758,23 @@
variant.item_name = "_Test Variant Item-S"
variant.save()
-test_records = frappe.get_test_records('Item')
-def create_item(item_code, is_stock_item=1, valuation_rate=0, warehouse="_Test Warehouse - _TC",
- is_customer_provided_item=None, customer=None, is_purchase_item=None, opening_stock=0, is_fixed_asset=0,
- asset_category=None, company="_Test Company"):
+test_records = frappe.get_test_records("Item")
+
+
+def create_item(
+ item_code,
+ is_stock_item=1,
+ valuation_rate=0,
+ warehouse="_Test Warehouse - _TC",
+ is_customer_provided_item=None,
+ customer=None,
+ is_purchase_item=None,
+ opening_stock=0,
+ is_fixed_asset=0,
+ asset_category=None,
+ company="_Test Company",
+):
if not frappe.db.exists("Item", item_code):
item = frappe.new_doc("Item")
item.item_code = item_code
@@ -723,11 +788,8 @@
item.valuation_rate = valuation_rate
item.is_purchase_item = is_purchase_item
item.is_customer_provided_item = is_customer_provided_item
- item.customer = customer or ''
- item.append("item_defaults", {
- "default_warehouse": warehouse,
- "company": company
- })
+ item.customer = customer or ""
+ item.append("item_defaults", {"default_warehouse": warehouse, "company": company})
item.save()
else:
item = frappe.get_doc("Item", item_code)
diff --git a/erpnext/stock/doctype/item_alternative/item_alternative.py b/erpnext/stock/doctype/item_alternative/item_alternative.py
index 766647b..0f93bb9 100644
--- a/erpnext/stock/doctype/item_alternative/item_alternative.py
+++ b/erpnext/stock/doctype/item_alternative/item_alternative.py
@@ -14,8 +14,7 @@
self.validate_duplicate()
def has_alternative_item(self):
- if (self.item_code and
- not frappe.db.get_value('Item', self.item_code, 'allow_alternative_item')):
+ if self.item_code and not frappe.db.get_value("Item", self.item_code, "allow_alternative_item"):
frappe.throw(_("Not allow to set alternative item for the item {0}").format(self.item_code))
def validate_alternative_item(self):
@@ -23,19 +22,32 @@
frappe.throw(_("Alternative item must not be same as item code"))
item_meta = frappe.get_meta("Item")
- fields = ["is_stock_item", "include_item_in_manufacturing","has_serial_no", "has_batch_no", "allow_alternative_item"]
+ fields = [
+ "is_stock_item",
+ "include_item_in_manufacturing",
+ "has_serial_no",
+ "has_batch_no",
+ "allow_alternative_item",
+ ]
item_data = frappe.db.get_value("Item", self.item_code, fields, as_dict=1)
- alternative_item_data = frappe.db.get_value("Item", self.alternative_item_code, fields, as_dict=1)
+ alternative_item_data = frappe.db.get_value(
+ "Item", self.alternative_item_code, fields, as_dict=1
+ )
for field in fields:
- if item_data.get(field) != alternative_item_data.get(field):
+ if item_data.get(field) != alternative_item_data.get(field):
raise_exception, alert = [1, False] if field == "is_stock_item" else [0, True]
- frappe.msgprint(_("The value of {0} differs between Items {1} and {2}") \
- .format(frappe.bold(item_meta.get_label(field)),
- frappe.bold(self.alternative_item_code),
- frappe.bold(self.item_code)),
- alert=alert, raise_exception=raise_exception, indicator="Orange")
+ frappe.msgprint(
+ _("The value of {0} differs between Items {1} and {2}").format(
+ frappe.bold(item_meta.get_label(field)),
+ frappe.bold(self.alternative_item_code),
+ frappe.bold(self.item_code),
+ ),
+ alert=alert,
+ raise_exception=raise_exception,
+ indicator="Orange",
+ )
alternate_item_check_msg = _("Allow Alternative Item must be checked on Item {}")
@@ -44,24 +56,30 @@
if self.two_way and not alternative_item_data.allow_alternative_item:
frappe.throw(alternate_item_check_msg.format(self.item_code))
-
-
-
def validate_duplicate(self):
- if frappe.db.get_value("Item Alternative", {'item_code': self.item_code,
- 'alternative_item_code': self.alternative_item_code, 'name': ('!=', self.name)}):
+ if frappe.db.get_value(
+ "Item Alternative",
+ {
+ "item_code": self.item_code,
+ "alternative_item_code": self.alternative_item_code,
+ "name": ("!=", self.name),
+ },
+ ):
frappe.throw(_("Already record exists for the item {0}").format(self.item_code))
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_alternative_items(doctype, txt, searchfield, start, page_len, filters):
- return frappe.db.sql(""" (select alternative_item_code from `tabItem Alternative`
+ return frappe.db.sql(
+ """ (select alternative_item_code from `tabItem Alternative`
where item_code = %(item_code)s and alternative_item_code like %(txt)s)
union
(select item_code from `tabItem Alternative`
where alternative_item_code = %(item_code)s and item_code like %(txt)s
and two_way = 1) limit {0}, {1}
- """.format(start, page_len), {
- "item_code": filters.get('item_code'),
- "txt": '%' + txt + '%'
- })
+ """.format(
+ start, page_len
+ ),
+ {"item_code": filters.get("item_code"), "txt": "%" + txt + "%"},
+ )
diff --git a/erpnext/stock/doctype/item_alternative/test_item_alternative.py b/erpnext/stock/doctype/item_alternative/test_item_alternative.py
index 501c1c1..d829b2c 100644
--- a/erpnext/stock/doctype/item_alternative/test_item_alternative.py
+++ b/erpnext/stock/doctype/item_alternative/test_item_alternative.py
@@ -27,121 +27,181 @@
make_items()
def test_alternative_item_for_subcontract_rm(self):
- frappe.db.set_value('Buying Settings', None,
- 'backflush_raw_materials_of_subcontract_based_on', 'BOM')
+ frappe.db.set_value(
+ "Buying Settings", None, "backflush_raw_materials_of_subcontract_based_on", "BOM"
+ )
- create_stock_reconciliation(item_code='Alternate Item For A RW 1', warehouse='_Test Warehouse - _TC',
- qty=5, rate=2000)
- create_stock_reconciliation(item_code='Test FG A RW 2', warehouse='_Test Warehouse - _TC',
- qty=5, rate=2000)
+ create_stock_reconciliation(
+ item_code="Alternate Item For A RW 1", warehouse="_Test Warehouse - _TC", qty=5, rate=2000
+ )
+ create_stock_reconciliation(
+ item_code="Test FG A RW 2", warehouse="_Test Warehouse - _TC", qty=5, rate=2000
+ )
supplier_warehouse = "Test Supplier Warehouse - _TC"
- po = create_purchase_order(item = "Test Finished Goods - A",
- is_subcontracted='Yes', qty=5, rate=3000, supplier_warehouse=supplier_warehouse)
+ po = create_purchase_order(
+ item="Test Finished Goods - A",
+ is_subcontracted="Yes",
+ qty=5,
+ rate=3000,
+ supplier_warehouse=supplier_warehouse,
+ )
- rm_item = [{"item_code": "Test Finished Goods - A", "rm_item_code": "Test FG A RW 1", "item_name":"Test FG A RW 1",
- "qty":5, "warehouse":"_Test Warehouse - _TC", "rate":2000, "amount":10000, "stock_uom":"Nos"},
- {"item_code": "Test Finished Goods - A", "rm_item_code": "Test FG A RW 2", "item_name":"Test FG A RW 2",
- "qty":5, "warehouse":"_Test Warehouse - _TC", "rate":2000, "amount":10000, "stock_uom":"Nos"}]
+ rm_item = [
+ {
+ "item_code": "Test Finished Goods - A",
+ "rm_item_code": "Test FG A RW 1",
+ "item_name": "Test FG A RW 1",
+ "qty": 5,
+ "warehouse": "_Test Warehouse - _TC",
+ "rate": 2000,
+ "amount": 10000,
+ "stock_uom": "Nos",
+ },
+ {
+ "item_code": "Test Finished Goods - A",
+ "rm_item_code": "Test FG A RW 2",
+ "item_name": "Test FG A RW 2",
+ "qty": 5,
+ "warehouse": "_Test Warehouse - _TC",
+ "rate": 2000,
+ "amount": 10000,
+ "stock_uom": "Nos",
+ },
+ ]
rm_item_string = json.dumps(rm_item)
- reserved_qty_for_sub_contract = frappe.db.get_value('Bin',
- {'item_code': 'Test FG A RW 1', 'warehouse': '_Test Warehouse - _TC'}, 'reserved_qty_for_sub_contract')
+ reserved_qty_for_sub_contract = frappe.db.get_value(
+ "Bin",
+ {"item_code": "Test FG A RW 1", "warehouse": "_Test Warehouse - _TC"},
+ "reserved_qty_for_sub_contract",
+ )
se = frappe.get_doc(make_rm_stock_entry(po.name, rm_item_string))
se.to_warehouse = supplier_warehouse
se.insert()
- doc = frappe.get_doc('Stock Entry', se.name)
+ doc = frappe.get_doc("Stock Entry", se.name)
for item in doc.items:
- if item.item_code == 'Test FG A RW 1':
- item.item_code = 'Alternate Item For A RW 1'
- item.item_name = 'Alternate Item For A RW 1'
- item.description = 'Alternate Item For A RW 1'
- item.original_item = 'Test FG A RW 1'
+ if item.item_code == "Test FG A RW 1":
+ item.item_code = "Alternate Item For A RW 1"
+ item.item_name = "Alternate Item For A RW 1"
+ item.description = "Alternate Item For A RW 1"
+ item.original_item = "Test FG A RW 1"
doc.save()
doc.submit()
- after_transfer_reserved_qty_for_sub_contract = frappe.db.get_value('Bin',
- {'item_code': 'Test FG A RW 1', 'warehouse': '_Test Warehouse - _TC'}, 'reserved_qty_for_sub_contract')
+ after_transfer_reserved_qty_for_sub_contract = frappe.db.get_value(
+ "Bin",
+ {"item_code": "Test FG A RW 1", "warehouse": "_Test Warehouse - _TC"},
+ "reserved_qty_for_sub_contract",
+ )
- self.assertEqual(after_transfer_reserved_qty_for_sub_contract, flt(reserved_qty_for_sub_contract - 5))
+ self.assertEqual(
+ after_transfer_reserved_qty_for_sub_contract, flt(reserved_qty_for_sub_contract - 5)
+ )
pr = make_purchase_receipt(po.name)
pr.save()
- pr = frappe.get_doc('Purchase Receipt', pr.name)
+ pr = frappe.get_doc("Purchase Receipt", pr.name)
status = False
for d in pr.supplied_items:
- if d.rm_item_code == 'Alternate Item For A RW 1':
+ if d.rm_item_code == "Alternate Item For A RW 1":
status = True
self.assertEqual(status, True)
- frappe.db.set_value('Buying Settings', None,
- 'backflush_raw_materials_of_subcontract_based_on', 'Material Transferred for Subcontract')
+ frappe.db.set_value(
+ "Buying Settings",
+ None,
+ "backflush_raw_materials_of_subcontract_based_on",
+ "Material Transferred for Subcontract",
+ )
def test_alternative_item_for_production_rm(self):
- create_stock_reconciliation(item_code='Alternate Item For A RW 1',
- warehouse='_Test Warehouse - _TC',qty=5, rate=2000)
- create_stock_reconciliation(item_code='Test FG A RW 2', warehouse='_Test Warehouse - _TC',
- qty=5, rate=2000)
- pro_order = make_wo_order_test_record(production_item='Test Finished Goods - A',
- qty=5, source_warehouse='_Test Warehouse - _TC', wip_warehouse='Test Supplier Warehouse - _TC')
+ create_stock_reconciliation(
+ item_code="Alternate Item For A RW 1", warehouse="_Test Warehouse - _TC", qty=5, rate=2000
+ )
+ create_stock_reconciliation(
+ item_code="Test FG A RW 2", warehouse="_Test Warehouse - _TC", qty=5, rate=2000
+ )
+ pro_order = make_wo_order_test_record(
+ production_item="Test Finished Goods - A",
+ qty=5,
+ source_warehouse="_Test Warehouse - _TC",
+ wip_warehouse="Test Supplier Warehouse - _TC",
+ )
- reserved_qty_for_production = frappe.db.get_value('Bin',
- {'item_code': 'Test FG A RW 1', 'warehouse': '_Test Warehouse - _TC'}, 'reserved_qty_for_production')
+ reserved_qty_for_production = frappe.db.get_value(
+ "Bin",
+ {"item_code": "Test FG A RW 1", "warehouse": "_Test Warehouse - _TC"},
+ "reserved_qty_for_production",
+ )
ste = frappe.get_doc(make_stock_entry(pro_order.name, "Material Transfer for Manufacture", 5))
ste.insert()
for item in ste.items:
- if item.item_code == 'Test FG A RW 1':
- item.item_code = 'Alternate Item For A RW 1'
- item.item_name = 'Alternate Item For A RW 1'
- item.description = 'Alternate Item For A RW 1'
- item.original_item = 'Test FG A RW 1'
+ if item.item_code == "Test FG A RW 1":
+ item.item_code = "Alternate Item For A RW 1"
+ item.item_name = "Alternate Item For A RW 1"
+ item.description = "Alternate Item For A RW 1"
+ item.original_item = "Test FG A RW 1"
ste.submit()
- reserved_qty_for_production_after_transfer = frappe.db.get_value('Bin',
- {'item_code': 'Test FG A RW 1', 'warehouse': '_Test Warehouse - _TC'}, 'reserved_qty_for_production')
+ reserved_qty_for_production_after_transfer = frappe.db.get_value(
+ "Bin",
+ {"item_code": "Test FG A RW 1", "warehouse": "_Test Warehouse - _TC"},
+ "reserved_qty_for_production",
+ )
- self.assertEqual(reserved_qty_for_production_after_transfer, flt(reserved_qty_for_production - 5))
+ self.assertEqual(
+ reserved_qty_for_production_after_transfer, flt(reserved_qty_for_production - 5)
+ )
ste1 = frappe.get_doc(make_stock_entry(pro_order.name, "Manufacture", 5))
status = False
for d in ste1.items:
- if d.item_code == 'Alternate Item For A RW 1':
+ if d.item_code == "Alternate Item For A RW 1":
status = True
self.assertEqual(status, True)
ste1.submit()
+
def make_items():
- items = ['Test Finished Goods - A', 'Test FG A RW 1', 'Test FG A RW 2', 'Alternate Item For A RW 1']
+ items = [
+ "Test Finished Goods - A",
+ "Test FG A RW 1",
+ "Test FG A RW 2",
+ "Alternate Item For A RW 1",
+ ]
for item_code in items:
- if not frappe.db.exists('Item', item_code):
+ if not frappe.db.exists("Item", item_code):
create_item(item_code)
- create_stock_reconciliation(item_code="Test FG A RW 1",
- warehouse='_Test Warehouse - _TC', qty=10, rate=2000)
+ create_stock_reconciliation(
+ item_code="Test FG A RW 1", warehouse="_Test Warehouse - _TC", qty=10, rate=2000
+ )
- if frappe.db.exists('Item', 'Test FG A RW 1'):
- doc = frappe.get_doc('Item', 'Test FG A RW 1')
+ if frappe.db.exists("Item", "Test FG A RW 1"):
+ doc = frappe.get_doc("Item", "Test FG A RW 1")
doc.allow_alternative_item = 1
doc.save()
- if frappe.db.exists('Item', 'Test Finished Goods - A'):
- doc = frappe.get_doc('Item', 'Test Finished Goods - A')
+ if frappe.db.exists("Item", "Test Finished Goods - A"):
+ doc = frappe.get_doc("Item", "Test Finished Goods - A")
doc.is_sub_contracted_item = 1
doc.save()
- if not frappe.db.get_value('BOM',
- {'item': 'Test Finished Goods - A', 'docstatus': 1}):
- make_bom(item = 'Test Finished Goods - A', raw_materials = ['Test FG A RW 1', 'Test FG A RW 2'])
+ if not frappe.db.get_value("BOM", {"item": "Test Finished Goods - A", "docstatus": 1}):
+ make_bom(item="Test Finished Goods - A", raw_materials=["Test FG A RW 1", "Test FG A RW 2"])
- if not frappe.db.get_value('Warehouse', {'warehouse_name': 'Test Supplier Warehouse'}):
- frappe.get_doc({
- 'doctype': 'Warehouse',
- 'warehouse_name': 'Test Supplier Warehouse',
- 'company': '_Test Company'
- }).insert(ignore_permissions=True)
+ if not frappe.db.get_value("Warehouse", {"warehouse_name": "Test Supplier Warehouse"}):
+ frappe.get_doc(
+ {
+ "doctype": "Warehouse",
+ "warehouse_name": "Test Supplier Warehouse",
+ "company": "_Test Company",
+ }
+ ).insert(ignore_permissions=True)
diff --git a/erpnext/stock/doctype/item_attribute/item_attribute.py b/erpnext/stock/doctype/item_attribute/item_attribute.py
index 5a28a9e..391ff06 100644
--- a/erpnext/stock/doctype/item_attribute/item_attribute.py
+++ b/erpnext/stock/doctype/item_attribute/item_attribute.py
@@ -14,7 +14,9 @@
)
-class ItemAttributeIncrementError(frappe.ValidationError): pass
+class ItemAttributeIncrementError(frappe.ValidationError):
+ pass
+
class ItemAttribute(Document):
def __setup__(self):
@@ -29,11 +31,12 @@
self.validate_exising_items()
def validate_exising_items(self):
- '''Validate that if there are existing items with attributes, they are valid'''
+ """Validate that if there are existing items with attributes, they are valid"""
attributes_list = [d.attribute_value for d in self.item_attribute_values]
# Get Item Variant Attribute details of variant items
- items = frappe.db.sql("""
+ items = frappe.db.sql(
+ """
select
i.name, iva.attribute_value as value
from
@@ -41,13 +44,18 @@
where
iva.attribute = %(attribute)s
and iva.parent = i.name and
- i.variant_of is not null and i.variant_of != ''""", {"attribute" : self.name}, as_dict=1)
+ i.variant_of is not null and i.variant_of != ''""",
+ {"attribute": self.name},
+ as_dict=1,
+ )
for item in items:
if self.numeric_values:
validate_is_incremental(self, self.name, item.value, item.name)
else:
- validate_item_attribute_value(attributes_list, self.name, item.value, item.name, from_variant=False)
+ validate_item_attribute_value(
+ attributes_list, self.name, item.value, item.name, from_variant=False
+ )
def validate_numeric(self):
if self.numeric_values:
diff --git a/erpnext/stock/doctype/item_attribute/test_item_attribute.py b/erpnext/stock/doctype/item_attribute/test_item_attribute.py
index 055c22e..a30f0e9 100644
--- a/erpnext/stock/doctype/item_attribute/test_item_attribute.py
+++ b/erpnext/stock/doctype/item_attribute/test_item_attribute.py
@@ -4,7 +4,7 @@
import frappe
-test_records = frappe.get_test_records('Item Attribute')
+test_records = frappe.get_test_records("Item Attribute")
from frappe.tests.utils import FrappeTestCase
@@ -18,14 +18,16 @@
frappe.delete_doc("Item Attribute", "_Test_Length")
def test_numeric_item_attribute(self):
- item_attribute = frappe.get_doc({
- "doctype": "Item Attribute",
- "attribute_name": "_Test_Length",
- "numeric_values": 1,
- "from_range": 0.0,
- "to_range": 100.0,
- "increment": 0
- })
+ item_attribute = frappe.get_doc(
+ {
+ "doctype": "Item Attribute",
+ "attribute_name": "_Test_Length",
+ "numeric_values": 1,
+ "from_range": 0.0,
+ "to_range": 100.0,
+ "increment": 0,
+ }
+ )
self.assertRaises(ItemAttributeIncrementError, item_attribute.save)
diff --git a/erpnext/stock/doctype/item_barcode/item_barcode.py b/erpnext/stock/doctype/item_barcode/item_barcode.py
index 64c39da..c2c0421 100644
--- a/erpnext/stock/doctype/item_barcode/item_barcode.py
+++ b/erpnext/stock/doctype/item_barcode/item_barcode.py
@@ -6,4 +6,4 @@
class ItemBarcode(Document):
- pass
+ pass
diff --git a/erpnext/stock/doctype/item_manufacturer/item_manufacturer.py b/erpnext/stock/doctype/item_manufacturer/item_manufacturer.py
index 469ccd8..b65ba98 100644
--- a/erpnext/stock/doctype/item_manufacturer/item_manufacturer.py
+++ b/erpnext/stock/doctype/item_manufacturer/item_manufacturer.py
@@ -18,14 +18,17 @@
def validate_duplicate_entry(self):
if self.is_new():
filters = {
- 'item_code': self.item_code,
- 'manufacturer': self.manufacturer,
- 'manufacturer_part_no': self.manufacturer_part_no
+ "item_code": self.item_code,
+ "manufacturer": self.manufacturer,
+ "manufacturer_part_no": self.manufacturer_part_no,
}
if frappe.db.exists("Item Manufacturer", filters):
- frappe.throw(_("Duplicate entry against the item code {0} and manufacturer {1}")
- .format(self.item_code, self.manufacturer))
+ frappe.throw(
+ _("Duplicate entry against the item code {0} and manufacturer {1}").format(
+ self.item_code, self.manufacturer
+ )
+ )
def manage_default_item_manufacturer(self, delete=False):
from frappe.model.utils import set_default
@@ -37,11 +40,9 @@
if not self.is_default:
# if unchecked and default in Item master, clear it.
if default_manufacturer == self.manufacturer and default_part_no == self.manufacturer_part_no:
- frappe.db.set_value("Item", item.name,
- {
- "default_item_manufacturer": None,
- "default_manufacturer_part_no": None
- })
+ frappe.db.set_value(
+ "Item", item.name, {"default_item_manufacturer": None, "default_manufacturer_part_no": None}
+ )
elif self.is_default:
set_default(self, "item_code")
@@ -50,18 +51,26 @@
if delete:
manufacturer, manufacturer_part_no = None, None
- elif (default_manufacturer != self.manufacturer) or \
- (default_manufacturer == self.manufacturer and default_part_no != self.manufacturer_part_no):
+ elif (default_manufacturer != self.manufacturer) or (
+ default_manufacturer == self.manufacturer and default_part_no != self.manufacturer_part_no
+ ):
manufacturer = self.manufacturer
manufacturer_part_no = self.manufacturer_part_no
- frappe.db.set_value("Item", item.name,
- {
- "default_item_manufacturer": manufacturer,
- "default_manufacturer_part_no": manufacturer_part_no
- })
+ frappe.db.set_value(
+ "Item",
+ item.name,
+ {
+ "default_item_manufacturer": manufacturer,
+ "default_manufacturer_part_no": manufacturer_part_no,
+ },
+ )
+
@frappe.whitelist()
def get_item_manufacturer_part_no(item_code, manufacturer):
- return frappe.db.get_value("Item Manufacturer",
- {'item_code': item_code, 'manufacturer': manufacturer}, 'manufacturer_part_no')
+ return frappe.db.get_value(
+ "Item Manufacturer",
+ {"item_code": item_code, "manufacturer": manufacturer},
+ "manufacturer_part_no",
+ )
diff --git a/erpnext/stock/doctype/item_price/item_price.py b/erpnext/stock/doctype/item_price/item_price.py
index 010e01a..562f7b9 100644
--- a/erpnext/stock/doctype/item_price/item_price.py
+++ b/erpnext/stock/doctype/item_price/item_price.py
@@ -13,7 +13,6 @@
class ItemPrice(Document):
-
def validate(self):
self.validate_item()
self.validate_dates()
@@ -32,22 +31,26 @@
def update_price_list_details(self):
if self.price_list:
- price_list_details = frappe.db.get_value("Price List",
- {"name": self.price_list, "enabled": 1},
- ["buying", "selling", "currency"])
+ price_list_details = frappe.db.get_value(
+ "Price List", {"name": self.price_list, "enabled": 1}, ["buying", "selling", "currency"]
+ )
if not price_list_details:
- link = frappe.utils.get_link_to_form('Price List', self.price_list)
+ link = frappe.utils.get_link_to_form("Price List", self.price_list)
frappe.throw("The price list {0} does not exist or is disabled".format(link))
self.buying, self.selling, self.currency = price_list_details
def update_item_details(self):
if self.item_code:
- self.item_name, self.item_description = frappe.db.get_value("Item", self.item_code,["item_name", "description"])
+ self.item_name, self.item_description = frappe.db.get_value(
+ "Item", self.item_code, ["item_name", "description"]
+ )
def check_duplicates(self):
- conditions = """where item_code = %(item_code)s and price_list = %(price_list)s and name != %(name)s"""
+ conditions = (
+ """where item_code = %(item_code)s and price_list = %(price_list)s and name != %(name)s"""
+ )
for field in [
"uom",
@@ -56,21 +59,31 @@
"packing_unit",
"customer",
"supplier",
- "batch_no"]:
+ "batch_no",
+ ]:
if self.get(field):
conditions += " and {0} = %({0})s ".format(field)
else:
conditions += "and (isnull({0}) or {0} = '')".format(field)
- price_list_rate = frappe.db.sql("""
+ price_list_rate = frappe.db.sql(
+ """
select price_list_rate
from `tabItem Price`
{conditions}
- """.format(conditions=conditions),
- self.as_dict(),)
+ """.format(
+ conditions=conditions
+ ),
+ self.as_dict(),
+ )
if price_list_rate:
- frappe.throw(_("Item Price appears multiple times based on Price List, Supplier/Customer, Currency, Item, Batch, UOM, Qty, and Dates."), ItemPriceDuplicateItem,)
+ frappe.throw(
+ _(
+ "Item Price appears multiple times based on Price List, Supplier/Customer, Currency, Item, Batch, UOM, Qty, and Dates."
+ ),
+ ItemPriceDuplicateItem,
+ )
def before_save(self):
if self.selling:
diff --git a/erpnext/stock/doctype/item_price/test_item_price.py b/erpnext/stock/doctype/item_price/test_item_price.py
index 6ceba3f..30d933e 100644
--- a/erpnext/stock/doctype/item_price/test_item_price.py
+++ b/erpnext/stock/doctype/item_price/test_item_price.py
@@ -23,8 +23,14 @@
def test_addition_of_new_fields(self):
# Based on https://github.com/frappe/erpnext/issues/8456
test_fields_existance = [
- 'supplier', 'customer', 'uom', 'lead_time_days',
- 'packing_unit', 'valid_from', 'valid_upto', 'note'
+ "supplier",
+ "customer",
+ "uom",
+ "lead_time_days",
+ "packing_unit",
+ "valid_from",
+ "valid_upto",
+ "note",
]
doc_fields = frappe.copy_doc(test_records[1]).__dict__.keys()
@@ -45,10 +51,10 @@
args = {
"price_list": doc.price_list,
- "customer": doc.customer,
- "uom": "_Test UOM",
- "transaction_date": '2017-04-18',
- "qty": 10
+ "customer": doc.customer,
+ "uom": "_Test UOM",
+ "transaction_date": "2017-04-18",
+ "qty": 10,
}
price = get_price_list_rate_for(process_args(args), doc.item_code)
@@ -61,13 +67,12 @@
"price_list": doc.price_list,
"customer": doc.customer,
"uom": "_Test UOM",
- "transaction_date": '2017-04-18',
+ "transaction_date": "2017-04-18",
}
price = get_price_list_rate_for(args, doc.item_code)
self.assertEqual(price, None)
-
def test_prices_at_date(self):
# Check correct price at first date
doc = frappe.copy_doc(test_records[2])
@@ -76,35 +81,35 @@
"price_list": doc.price_list,
"customer": "_Test Customer",
"uom": "_Test UOM",
- "transaction_date": '2017-04-18',
- "qty": 7
+ "transaction_date": "2017-04-18",
+ "qty": 7,
}
price = get_price_list_rate_for(args, doc.item_code)
self.assertEqual(price, 20)
def test_prices_at_invalid_date(self):
- #Check correct price at invalid date
+ # Check correct price at invalid date
doc = frappe.copy_doc(test_records[3])
args = {
"price_list": doc.price_list,
"qty": 7,
"uom": "_Test UOM",
- "transaction_date": "01-15-2019"
+ "transaction_date": "01-15-2019",
}
price = get_price_list_rate_for(args, doc.item_code)
self.assertEqual(price, None)
def test_prices_outside_of_date(self):
- #Check correct price when outside of the date
+ # Check correct price when outside of the date
doc = frappe.copy_doc(test_records[4])
args = {
"price_list": doc.price_list,
- "customer": "_Test Customer",
- "uom": "_Test UOM",
+ "customer": "_Test Customer",
+ "uom": "_Test UOM",
"transaction_date": "2017-04-25",
"qty": 7,
}
@@ -113,7 +118,7 @@
self.assertEqual(price, None)
def test_lowest_price_when_no_date_provided(self):
- #Check lowest price when no date provided
+ # Check lowest price when no date provided
doc = frappe.copy_doc(test_records[1])
args = {
@@ -125,7 +130,6 @@
price = get_price_list_rate_for(args, doc.item_code)
self.assertEqual(price, 10)
-
def test_invalid_item(self):
doc = frappe.copy_doc(test_records[1])
# Enter invalid item code
@@ -150,8 +154,8 @@
args = {
"price_list": doc.price_list,
"uom": "_Test UOM",
- "transaction_date": '2017-04-18',
- "qty": 7
+ "transaction_date": "2017-04-18",
+ "qty": 7,
}
price = get_price_list_rate_for(args, doc.item_code)
@@ -159,4 +163,5 @@
self.assertEqual(price, 21)
-test_records = frappe.get_test_records('Item Price')
+
+test_records = frappe.get_test_records("Item Price")
diff --git a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.py b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.py
index be1517e..cec5e21 100644
--- a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.py
+++ b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.py
@@ -8,29 +8,46 @@
class ItemVariantSettings(Document):
- invalid_fields_for_copy_fields_in_variants = ['barcodes']
+ invalid_fields_for_copy_fields_in_variants = ["barcodes"]
def set_default_fields(self):
self.fields = []
- fields = frappe.get_meta('Item').fields
- exclude_fields = {"naming_series", "item_code", "item_name", "published_in_website",
- "standard_rate", "opening_stock", "image", "description",
- "variant_of", "valuation_rate", "description", "barcodes",
- "has_variants", "attributes"}
+ fields = frappe.get_meta("Item").fields
+ exclude_fields = {
+ "naming_series",
+ "item_code",
+ "item_name",
+ "published_in_website",
+ "standard_rate",
+ "opening_stock",
+ "image",
+ "description",
+ "variant_of",
+ "valuation_rate",
+ "description",
+ "barcodes",
+ "has_variants",
+ "attributes",
+ }
for d in fields:
- if not d.no_copy and d.fieldname not in exclude_fields and \
- d.fieldtype not in ['HTML', 'Section Break', 'Column Break', 'Button', 'Read Only']:
- self.append('fields', {
- 'field_name': d.fieldname
- })
+ if (
+ not d.no_copy
+ and d.fieldname not in exclude_fields
+ and d.fieldtype not in ["HTML", "Section Break", "Column Break", "Button", "Read Only"]
+ ):
+ self.append("fields", {"field_name": d.fieldname})
def remove_invalid_fields_for_copy_fields_in_variants(self):
- fields = [row for row in self.fields if row.field_name not in self.invalid_fields_for_copy_fields_in_variants]
+ fields = [
+ row
+ for row in self.fields
+ if row.field_name not in self.invalid_fields_for_copy_fields_in_variants
+ ]
self.fields = fields
self.save()
def validate(self):
for d in self.fields:
if d.field_name in self.invalid_fields_for_copy_fields_in_variants:
- frappe.throw(_('Cannot set the field <b>{0}</b> for copying in variants').format(d.field_name))
+ frappe.throw(_("Cannot set the field <b>{0}</b> for copying in variants").format(d.field_name))
diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
index 7aff95d..b3af309 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
+++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
@@ -19,13 +19,19 @@
self.set("items", [])
for pr in self.get("purchase_receipts"):
if pr.receipt_document_type and pr.receipt_document:
- pr_items = frappe.db.sql("""select pr_item.item_code, pr_item.description,
+ pr_items = frappe.db.sql(
+ """select pr_item.item_code, pr_item.description,
pr_item.qty, pr_item.base_rate, pr_item.base_amount, pr_item.name,
pr_item.cost_center, pr_item.is_fixed_asset
from `tab{doctype} Item` pr_item where parent = %s
and exists(select name from tabItem
where name = pr_item.item_code and (is_stock_item = 1 or is_fixed_asset=1))
- """.format(doctype=pr.receipt_document_type), pr.receipt_document, as_dict=True)
+ """.format(
+ doctype=pr.receipt_document_type
+ ),
+ pr.receipt_document,
+ as_dict=True,
+ )
for d in pr_items:
item = self.append("items")
@@ -33,8 +39,7 @@
item.description = d.description
item.qty = d.qty
item.rate = d.base_rate
- item.cost_center = d.cost_center or \
- erpnext.get_default_cost_center(self.company)
+ item.cost_center = d.cost_center or erpnext.get_default_cost_center(self.company)
item.amount = d.base_amount
item.receipt_document_type = pr.receipt_document_type
item.receipt_document = pr.receipt_document
@@ -52,26 +57,30 @@
self.set_applicable_charges_on_item()
self.validate_applicable_charges_for_item()
-
def check_mandatory(self):
if not self.get("purchase_receipts"):
frappe.throw(_("Please enter Receipt Document"))
-
def validate_receipt_documents(self):
receipt_documents = []
for d in self.get("purchase_receipts"):
docstatus = frappe.db.get_value(d.receipt_document_type, d.receipt_document, "docstatus")
if docstatus != 1:
- msg = f"Row {d.idx}: {d.receipt_document_type} {frappe.bold(d.receipt_document)} must be submitted"
+ msg = (
+ f"Row {d.idx}: {d.receipt_document_type} {frappe.bold(d.receipt_document)} must be submitted"
+ )
frappe.throw(_(msg), title=_("Invalid Document"))
if d.receipt_document_type == "Purchase Invoice":
update_stock = frappe.db.get_value(d.receipt_document_type, d.receipt_document, "update_stock")
if not update_stock:
- msg = _("Row {0}: Purchase Invoice {1} has no stock impact.").format(d.idx, frappe.bold(d.receipt_document))
- msg += "<br>" + _("Please create Landed Cost Vouchers against Invoices that have 'Update Stock' enabled.")
+ msg = _("Row {0}: Purchase Invoice {1} has no stock impact.").format(
+ d.idx, frappe.bold(d.receipt_document)
+ )
+ msg += "<br>" + _(
+ "Please create Landed Cost Vouchers against Invoices that have 'Update Stock' enabled."
+ )
frappe.throw(msg, title=_("Incorrect Invoice"))
receipt_documents.append(d.receipt_document)
@@ -81,52 +90,64 @@
frappe.throw(_("Item must be added using 'Get Items from Purchase Receipts' button"))
elif item.receipt_document not in receipt_documents:
- frappe.throw(_("Item Row {0}: {1} {2} does not exist in above '{1}' table")
- .format(item.idx, item.receipt_document_type, item.receipt_document))
+ frappe.throw(
+ _("Item Row {0}: {1} {2} does not exist in above '{1}' table").format(
+ item.idx, item.receipt_document_type, item.receipt_document
+ )
+ )
if not item.cost_center:
- frappe.throw(_("Row {0}: Cost center is required for an item {1}")
- .format(item.idx, item.item_code))
+ frappe.throw(
+ _("Row {0}: Cost center is required for an item {1}").format(item.idx, item.item_code)
+ )
def set_total_taxes_and_charges(self):
self.total_taxes_and_charges = sum(flt(d.base_amount) for d in self.get("taxes"))
def set_applicable_charges_on_item(self):
- if self.get('taxes') and self.distribute_charges_based_on != 'Distribute Manually':
+ if self.get("taxes") and self.distribute_charges_based_on != "Distribute Manually":
total_item_cost = 0.0
total_charges = 0.0
item_count = 0
based_on_field = frappe.scrub(self.distribute_charges_based_on)
- for item in self.get('items'):
+ for item in self.get("items"):
total_item_cost += item.get(based_on_field)
- for item in self.get('items'):
- item.applicable_charges = flt(flt(item.get(based_on_field)) * (flt(self.total_taxes_and_charges) / flt(total_item_cost)),
- item.precision('applicable_charges'))
+ for item in self.get("items"):
+ item.applicable_charges = flt(
+ flt(item.get(based_on_field)) * (flt(self.total_taxes_and_charges) / flt(total_item_cost)),
+ item.precision("applicable_charges"),
+ )
total_charges += item.applicable_charges
item_count += 1
if total_charges != self.total_taxes_and_charges:
diff = self.total_taxes_and_charges - total_charges
- self.get('items')[item_count - 1].applicable_charges += diff
+ self.get("items")[item_count - 1].applicable_charges += diff
def validate_applicable_charges_for_item(self):
based_on = self.distribute_charges_based_on.lower()
- if based_on != 'distribute manually':
+ if based_on != "distribute manually":
total = sum(flt(d.get(based_on)) for d in self.get("items"))
else:
# consider for proportion while distributing manually
- total = sum(flt(d.get('applicable_charges')) for d in self.get("items"))
+ total = sum(flt(d.get("applicable_charges")) for d in self.get("items"))
if not total:
- frappe.throw(_("Total {0} for all items is zero, may be you should change 'Distribute Charges Based On'").format(based_on))
+ frappe.throw(
+ _(
+ "Total {0} for all items is zero, may be you should change 'Distribute Charges Based On'"
+ ).format(based_on)
+ )
total_applicable_charges = sum(flt(d.applicable_charges) for d in self.get("items"))
- precision = get_field_precision(frappe.get_meta("Landed Cost Item").get_field("applicable_charges"),
- currency=frappe.get_cached_value('Company', self.company, "default_currency"))
+ precision = get_field_precision(
+ frappe.get_meta("Landed Cost Item").get_field("applicable_charges"),
+ currency=frappe.get_cached_value("Company", self.company, "default_currency"),
+ )
diff = flt(self.total_taxes_and_charges) - flt(total_applicable_charges)
diff = flt(diff, precision)
@@ -134,7 +155,11 @@
if abs(diff) < (2.0 / (10**precision)):
self.items[-1].applicable_charges += diff
else:
- frappe.throw(_("Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges"))
+ frappe.throw(
+ _(
+ "Total Applicable Charges in Purchase Receipt Items table must be same as Total Taxes and Charges"
+ )
+ )
def on_submit(self):
self.update_landed_cost()
@@ -177,25 +202,41 @@
doc.repost_future_sle_and_gle()
def validate_asset_qty_and_status(self, receipt_document_type, receipt_document):
- for item in self.get('items'):
+ for item in self.get("items"):
if item.is_fixed_asset:
- receipt_document_type = 'purchase_invoice' if item.receipt_document_type == 'Purchase Invoice' \
- else 'purchase_receipt'
- docs = frappe.db.get_all('Asset', filters={ receipt_document_type: item.receipt_document,
- 'item_code': item.item_code }, fields=['name', 'docstatus'])
+ receipt_document_type = (
+ "purchase_invoice" if item.receipt_document_type == "Purchase Invoice" else "purchase_receipt"
+ )
+ docs = frappe.db.get_all(
+ "Asset",
+ filters={receipt_document_type: item.receipt_document, "item_code": item.item_code},
+ fields=["name", "docstatus"],
+ )
if not docs or len(docs) != item.qty:
- frappe.throw(_('There are not enough asset created or linked to {0}. Please create or link {1} Assets with respective document.').format(
- item.receipt_document, item.qty))
+ frappe.throw(
+ _(
+ "There are not enough asset created or linked to {0}. Please create or link {1} Assets with respective document."
+ ).format(item.receipt_document, item.qty)
+ )
if docs:
for d in docs:
if d.docstatus == 1:
- frappe.throw(_('{2} <b>{0}</b> has submitted Assets. Remove Item <b>{1}</b> from table to continue.').format(
- item.receipt_document, item.item_code, item.receipt_document_type))
+ frappe.throw(
+ _(
+ "{2} <b>{0}</b> has submitted Assets. Remove Item <b>{1}</b> from table to continue."
+ ).format(
+ item.receipt_document, item.item_code, item.receipt_document_type
+ )
+ )
def update_rate_in_serial_no_for_non_asset_items(self, receipt_document):
for item in receipt_document.get("items"):
if not item.is_fixed_asset and 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))
+ 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),
+ )
diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
index 6dc4fee..1af9953 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
+++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
@@ -2,7 +2,6 @@
# License: GNU General Public License v3. See license.txt
-
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_to_date, flt, now
@@ -22,34 +21,50 @@
def test_landed_cost_voucher(self):
frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1)
- pr = make_purchase_receipt(company="_Test Company with perpetual inventory",
- warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1",
- get_multiple_items = True, get_taxes_and_charges = True)
+ pr = make_purchase_receipt(
+ company="_Test Company with perpetual inventory",
+ warehouse="Stores - TCP1",
+ supplier_warehouse="Work in Progress - TCP1",
+ get_multiple_items=True,
+ get_taxes_and_charges=True,
+ )
- last_sle = frappe.db.get_value("Stock Ledger Entry", {
+ last_sle = frappe.db.get_value(
+ "Stock Ledger Entry",
+ {
"voucher_type": pr.doctype,
"voucher_no": pr.name,
"item_code": "_Test Item",
"warehouse": "Stores - TCP1",
"is_cancelled": 0,
},
- fieldname=["qty_after_transaction", "stock_value"], as_dict=1)
+ fieldname=["qty_after_transaction", "stock_value"],
+ as_dict=1,
+ )
create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company)
- pr_lc_value = frappe.db.get_value("Purchase Receipt Item", {"parent": pr.name}, "landed_cost_voucher_amount")
+ pr_lc_value = frappe.db.get_value(
+ "Purchase Receipt Item", {"parent": pr.name}, "landed_cost_voucher_amount"
+ )
self.assertEqual(pr_lc_value, 25.0)
- last_sle_after_landed_cost = frappe.db.get_value("Stock Ledger Entry", {
+ last_sle_after_landed_cost = frappe.db.get_value(
+ "Stock Ledger Entry",
+ {
"voucher_type": pr.doctype,
"voucher_no": pr.name,
"item_code": "_Test Item",
"warehouse": "Stores - TCP1",
"is_cancelled": 0,
},
- fieldname=["qty_after_transaction", "stock_value"], as_dict=1)
+ fieldname=["qty_after_transaction", "stock_value"],
+ as_dict=1,
+ )
- self.assertEqual(last_sle.qty_after_transaction, last_sle_after_landed_cost.qty_after_transaction)
+ self.assertEqual(
+ last_sle.qty_after_transaction, last_sle_after_landed_cost.qty_after_transaction
+ )
self.assertEqual(last_sle_after_landed_cost.stock_value - last_sle.stock_value, 25.0)
# assert after submit
@@ -57,24 +72,20 @@
# Mess up cancelled SLE modified timestamp to check
# if they aren't effective in any business logic.
- frappe.db.set_value("Stock Ledger Entry",
- {
- "is_cancelled": 1,
- "voucher_type": pr.doctype,
- "voucher_no": pr.name
- },
- "is_cancelled", 1,
- modified=add_to_date(now(), hours=1, as_datetime=True, as_string=True)
+ frappe.db.set_value(
+ "Stock Ledger Entry",
+ {"is_cancelled": 1, "voucher_type": pr.doctype, "voucher_no": pr.name},
+ "is_cancelled",
+ 1,
+ modified=add_to_date(now(), hours=1, as_datetime=True, as_string=True),
)
items, warehouses = pr.get_items_and_warehouses()
- update_gl_entries_after(pr.posting_date, pr.posting_time,
- warehouses, items, company=pr.company)
+ update_gl_entries_after(pr.posting_date, pr.posting_time, warehouses, items, company=pr.company)
# reassert after reposting
self.assertPurchaseReceiptLCVGLEntries(pr)
-
def assertPurchaseReceiptLCVGLEntries(self, pr):
gl_entries = get_gl_entries("Purchase Receipt", pr.name)
@@ -90,54 +101,74 @@
"Stock Received But Not Billed - TCP1": [0.0, 500.0],
"Expenses Included In Valuation - TCP1": [0.0, 50.0],
"_Test Account Customs Duty - TCP1": [0.0, 150],
- "_Test Account Shipping Charges - TCP1": [0.0, 100.00]
+ "_Test Account Shipping Charges - TCP1": [0.0, 100.00],
}
else:
expected_values = {
stock_in_hand_account: [400.0, 0.0],
fixed_asset_account: [400.0, 0.0],
"Stock Received But Not Billed - TCP1": [0.0, 500.0],
- "Expenses Included In Valuation - TCP1": [0.0, 300.0]
+ "Expenses Included In Valuation - TCP1": [0.0, 300.0],
}
for gle in gl_entries:
- if not gle.get('is_cancelled'):
- self.assertEqual(expected_values[gle.account][0], gle.debit, msg=f"incorrect debit for {gle.account}")
- self.assertEqual(expected_values[gle.account][1], gle.credit, msg=f"incorrect credit for {gle.account}")
-
+ if not gle.get("is_cancelled"):
+ self.assertEqual(
+ expected_values[gle.account][0], gle.debit, msg=f"incorrect debit for {gle.account}"
+ )
+ self.assertEqual(
+ expected_values[gle.account][1], gle.credit, msg=f"incorrect credit for {gle.account}"
+ )
def test_landed_cost_voucher_against_purchase_invoice(self):
- pi = make_purchase_invoice(update_stock=1, posting_date=frappe.utils.nowdate(),
- posting_time=frappe.utils.nowtime(), cash_bank_account="Cash - TCP1",
- company="_Test Company with perpetual inventory", supplier_warehouse="Work In Progress - TCP1",
- warehouse= "Stores - TCP1", cost_center = "Main - TCP1",
- expense_account ="_Test Account Cost for Goods Sold - TCP1")
+ pi = make_purchase_invoice(
+ update_stock=1,
+ posting_date=frappe.utils.nowdate(),
+ posting_time=frappe.utils.nowtime(),
+ cash_bank_account="Cash - TCP1",
+ company="_Test Company with perpetual inventory",
+ supplier_warehouse="Work In Progress - TCP1",
+ warehouse="Stores - TCP1",
+ cost_center="Main - TCP1",
+ expense_account="_Test Account Cost for Goods Sold - TCP1",
+ )
- last_sle = frappe.db.get_value("Stock Ledger Entry", {
+ last_sle = frappe.db.get_value(
+ "Stock Ledger Entry",
+ {
"voucher_type": pi.doctype,
"voucher_no": pi.name,
"item_code": "_Test Item",
- "warehouse": "Stores - TCP1"
+ "warehouse": "Stores - TCP1",
},
- fieldname=["qty_after_transaction", "stock_value"], as_dict=1)
+ fieldname=["qty_after_transaction", "stock_value"],
+ as_dict=1,
+ )
create_landed_cost_voucher("Purchase Invoice", pi.name, pi.company)
- pi_lc_value = frappe.db.get_value("Purchase Invoice Item", {"parent": pi.name},
- "landed_cost_voucher_amount")
+ pi_lc_value = frappe.db.get_value(
+ "Purchase Invoice Item", {"parent": pi.name}, "landed_cost_voucher_amount"
+ )
self.assertEqual(pi_lc_value, 50.0)
- last_sle_after_landed_cost = frappe.db.get_value("Stock Ledger Entry", {
+ last_sle_after_landed_cost = frappe.db.get_value(
+ "Stock Ledger Entry",
+ {
"voucher_type": pi.doctype,
"voucher_no": pi.name,
"item_code": "_Test Item",
- "warehouse": "Stores - TCP1"
+ "warehouse": "Stores - TCP1",
},
- fieldname=["qty_after_transaction", "stock_value"], as_dict=1)
+ fieldname=["qty_after_transaction", "stock_value"],
+ as_dict=1,
+ )
- self.assertEqual(last_sle.qty_after_transaction, last_sle_after_landed_cost.qty_after_transaction)
+ self.assertEqual(
+ last_sle.qty_after_transaction, last_sle_after_landed_cost.qty_after_transaction
+ )
self.assertEqual(last_sle_after_landed_cost.stock_value - last_sle.stock_value, 50.0)
@@ -149,20 +180,26 @@
expected_values = {
stock_in_hand_account: [300.0, 0.0],
"Creditors - TCP1": [0.0, 250.0],
- "Expenses Included In Valuation - TCP1": [0.0, 50.0]
+ "Expenses Included In Valuation - TCP1": [0.0, 50.0],
}
for gle in gl_entries:
- if not gle.get('is_cancelled'):
+ if not gle.get("is_cancelled"):
self.assertEqual(expected_values[gle.account][0], gle.debit)
self.assertEqual(expected_values[gle.account][1], gle.credit)
-
def test_landed_cost_voucher_for_serialized_item(self):
- frappe.db.sql("delete from `tabSerial No` where name in ('SN001', 'SN002', 'SN003', 'SN004', 'SN005')")
- pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1",
- supplier_warehouse = "Work in Progress - TCP1", get_multiple_items = True,
- get_taxes_and_charges = True, do_not_submit = True)
+ frappe.db.sql(
+ "delete from `tabSerial No` where name in ('SN001', 'SN002', 'SN003', 'SN004', 'SN005')"
+ )
+ pr = make_purchase_receipt(
+ company="_Test Company with perpetual inventory",
+ warehouse="Stores - TCP1",
+ supplier_warehouse="Work in Progress - TCP1",
+ get_multiple_items=True,
+ get_taxes_and_charges=True,
+ do_not_submit=True,
+ )
pr.items[0].item_code = "_Test Serialized Item"
pr.items[0].serial_no = "SN001\nSN002\nSN003\nSN004\nSN005"
@@ -172,8 +209,7 @@
create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company)
- serial_no = frappe.db.get_value("Serial No", "SN001",
- ["warehouse", "purchase_rate"], as_dict=1)
+ serial_no = frappe.db.get_value("Serial No", "SN001", ["warehouse", "purchase_rate"], as_dict=1)
self.assertEqual(serial_no.purchase_rate - serial_no_rate, 5.0)
self.assertEqual(serial_no.warehouse, "Stores - TCP1")
@@ -183,60 +219,82 @@
landed costs, this should be allowed for serial nos too.
Case:
- - receipt a serial no @ X rate
- - delivery the serial no @ X rate
- - add LCV to receipt X + Y
- - LCV should be successful
- - delivery should reflect X+Y valuation.
+ - receipt a serial no @ X rate
+ - delivery the serial no @ X rate
+ - add LCV to receipt X + Y
+ - LCV should be successful
+ - delivery should reflect X+Y valuation.
"""
serial_no = "LCV_TEST_SR_NO"
item_code = "_Test Serialized Item"
warehouse = "Stores - TCP1"
- pr = make_purchase_receipt(company="_Test Company with perpetual inventory",
- warehouse=warehouse, qty=1, rate=200,
- item_code=item_code, serial_no=serial_no)
+ pr = make_purchase_receipt(
+ company="_Test Company with perpetual inventory",
+ warehouse=warehouse,
+ qty=1,
+ rate=200,
+ item_code=item_code,
+ serial_no=serial_no,
+ )
serial_no_rate = frappe.db.get_value("Serial No", serial_no, "purchase_rate")
# deliver it before creating LCV
- dn = create_delivery_note(item_code=item_code,
- company='_Test Company with perpetual inventory', warehouse='Stores - TCP1',
- serial_no=serial_no, qty=1, rate=500,
- cost_center = 'Main - TCP1', expense_account = "Cost of Goods Sold - TCP1")
+ dn = create_delivery_note(
+ item_code=item_code,
+ company="_Test Company with perpetual inventory",
+ warehouse="Stores - TCP1",
+ serial_no=serial_no,
+ qty=1,
+ rate=500,
+ cost_center="Main - TCP1",
+ expense_account="Cost of Goods Sold - TCP1",
+ )
charges = 10
create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company, charges=charges)
new_purchase_rate = serial_no_rate + charges
- serial_no = frappe.db.get_value("Serial No", serial_no,
- ["warehouse", "purchase_rate"], as_dict=1)
+ serial_no = frappe.db.get_value(
+ "Serial No", serial_no, ["warehouse", "purchase_rate"], as_dict=1
+ )
self.assertEqual(serial_no.purchase_rate, new_purchase_rate)
- stock_value_difference = frappe.db.get_value("Stock Ledger Entry",
- filters={
- "voucher_no": dn.name,
- "voucher_type": dn.doctype,
- "is_cancelled": 0 # LCV cancels with same name.
- },
- fieldname="stock_value_difference")
+ stock_value_difference = frappe.db.get_value(
+ "Stock Ledger Entry",
+ filters={
+ "voucher_no": dn.name,
+ "voucher_type": dn.doctype,
+ "is_cancelled": 0, # LCV cancels with same name.
+ },
+ fieldname="stock_value_difference",
+ )
# reposting should update the purchase rate in future delivery
self.assertEqual(stock_value_difference, -new_purchase_rate)
- def test_landed_cost_voucher_for_odd_numbers (self):
- pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1", do_not_save=True)
+ def test_landed_cost_voucher_for_odd_numbers(self):
+ pr = make_purchase_receipt(
+ company="_Test Company with perpetual inventory",
+ warehouse="Stores - TCP1",
+ supplier_warehouse="Work in Progress - TCP1",
+ do_not_save=True,
+ )
pr.items[0].cost_center = "Main - TCP1"
for x in range(2):
- pr.append("items", {
- "item_code": "_Test Item",
- "warehouse": "Stores - TCP1",
- "cost_center": "Main - TCP1",
- "qty": 5,
- "rate": 50
- })
+ pr.append(
+ "items",
+ {
+ "item_code": "_Test Item",
+ "warehouse": "Stores - TCP1",
+ "cost_center": "Main - TCP1",
+ "qty": 5,
+ "rate": 50,
+ },
+ )
pr.submit()
lcv = create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company, 123.22)
@@ -245,37 +303,50 @@
self.assertEqual(flt(lcv.items[2].applicable_charges, 2), 41.08)
def test_multiple_landed_cost_voucher_against_pr(self):
- pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1",
- supplier_warehouse = "Stores - TCP1", do_not_save=True)
+ pr = make_purchase_receipt(
+ company="_Test Company with perpetual inventory",
+ warehouse="Stores - TCP1",
+ supplier_warehouse="Stores - TCP1",
+ do_not_save=True,
+ )
- pr.append("items", {
- "item_code": "_Test Item",
- "warehouse": "Stores - TCP1",
- "cost_center": "Main - TCP1",
- "qty": 5,
- "rate": 100
- })
+ pr.append(
+ "items",
+ {
+ "item_code": "_Test Item",
+ "warehouse": "Stores - TCP1",
+ "cost_center": "Main - TCP1",
+ "qty": 5,
+ "rate": 100,
+ },
+ )
pr.submit()
- lcv1 = make_landed_cost_voucher(company = pr.company, receipt_document_type = 'Purchase Receipt',
- receipt_document=pr.name, charges=100, do_not_save=True)
+ lcv1 = make_landed_cost_voucher(
+ company=pr.company,
+ receipt_document_type="Purchase Receipt",
+ receipt_document=pr.name,
+ charges=100,
+ do_not_save=True,
+ )
lcv1.insert()
- lcv1.set('items', [
- lcv1.get('items')[0]
- ])
+ lcv1.set("items", [lcv1.get("items")[0]])
distribute_landed_cost_on_items(lcv1)
lcv1.submit()
- lcv2 = make_landed_cost_voucher(company = pr.company, receipt_document_type = 'Purchase Receipt',
- receipt_document=pr.name, charges=100, do_not_save=True)
+ lcv2 = make_landed_cost_voucher(
+ company=pr.company,
+ receipt_document_type="Purchase Receipt",
+ receipt_document=pr.name,
+ charges=100,
+ do_not_save=True,
+ )
lcv2.insert()
- lcv2.set('items', [
- lcv2.get('items')[1]
- ])
+ lcv2.set("items", [lcv2.get("items")[1]])
distribute_landed_cost_on_items(lcv2)
lcv2.submit()
@@ -294,22 +365,31 @@
save_new_records(test_records)
## Create USD Shipping charges_account
- usd_shipping = create_account(account_name="Shipping Charges USD",
- parent_account="Duties and Taxes - TCP1", company="_Test Company with perpetual inventory",
- account_currency="USD")
+ usd_shipping = create_account(
+ account_name="Shipping Charges USD",
+ parent_account="Duties and Taxes - TCP1",
+ company="_Test Company with perpetual inventory",
+ account_currency="USD",
+ )
- pr = make_purchase_receipt(company="_Test Company with perpetual inventory", warehouse = "Stores - TCP1",
- supplier_warehouse = "Stores - TCP1")
+ pr = make_purchase_receipt(
+ company="_Test Company with perpetual inventory",
+ warehouse="Stores - TCP1",
+ supplier_warehouse="Stores - TCP1",
+ )
pr.submit()
- lcv = make_landed_cost_voucher(company = pr.company, receipt_document_type = "Purchase Receipt",
- receipt_document=pr.name, charges=100, do_not_save=True)
+ lcv = make_landed_cost_voucher(
+ company=pr.company,
+ receipt_document_type="Purchase Receipt",
+ receipt_document=pr.name,
+ charges=100,
+ do_not_save=True,
+ )
- lcv.append("taxes", {
- "description": "Shipping Charges",
- "expense_account": usd_shipping,
- "amount": 10
- })
+ lcv.append(
+ "taxes", {"description": "Shipping Charges", "expense_account": usd_shipping, "amount": 10}
+ )
lcv.save()
lcv.submit()
@@ -319,12 +399,18 @@
self.assertEqual(lcv.total_taxes_and_charges, 729)
self.assertEqual(pr.items[0].landed_cost_voucher_amount, 729)
- gl_entries = frappe.get_all("GL Entry", fields=["account", "credit", "credit_in_account_currency"],
- filters={"voucher_no": pr.name, "account": ("in", ["Shipping Charges USD - TCP1", "Expenses Included In Valuation - TCP1"])})
+ gl_entries = frappe.get_all(
+ "GL Entry",
+ fields=["account", "credit", "credit_in_account_currency"],
+ filters={
+ "voucher_no": pr.name,
+ "account": ("in", ["Shipping Charges USD - TCP1", "Expenses Included In Valuation - TCP1"]),
+ },
+ )
expected_gl_entries = {
"Shipping Charges USD - TCP1": [629, 10],
- "Expenses Included In Valuation - TCP1": [100, 100]
+ "Expenses Included In Valuation - TCP1": [100, 100],
}
for entry in gl_entries:
@@ -334,7 +420,9 @@
def test_asset_lcv(self):
"Check if LCV for an Asset updates the Assets Gross Purchase Amount correctly."
- frappe.db.set_value("Company", "_Test Company", "capital_work_in_progress_account", "CWIP Account - _TC")
+ frappe.db.set_value(
+ "Company", "_Test Company", "capital_work_in_progress_account", "CWIP Account - _TC"
+ )
if not frappe.db.exists("Asset Category", "Computers"):
create_asset_category()
@@ -345,15 +433,16 @@
pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=50000)
# check if draft asset was created
- assets = frappe.db.get_all('Asset', filters={'purchase_receipt': pr.name})
+ assets = frappe.db.get_all("Asset", filters={"purchase_receipt": pr.name})
self.assertEqual(len(assets), 1)
lcv = make_landed_cost_voucher(
- company = pr.company,
- receipt_document_type = "Purchase Receipt",
+ company=pr.company,
+ receipt_document_type="Purchase Receipt",
receipt_document=pr.name,
charges=80,
- expense_account="Expenses Included In Valuation - _TC")
+ expense_account="Expenses Included In Valuation - _TC",
+ )
lcv.save()
lcv.submit()
@@ -365,27 +454,38 @@
lcv.cancel()
pr.cancel()
-def make_landed_cost_voucher(** args):
+
+def make_landed_cost_voucher(**args):
args = frappe._dict(args)
ref_doc = frappe.get_doc(args.receipt_document_type, args.receipt_document)
- lcv = frappe.new_doc('Landed Cost Voucher')
- lcv.company = args.company or '_Test Company'
- lcv.distribute_charges_based_on = 'Amount'
+ lcv = frappe.new_doc("Landed Cost Voucher")
+ lcv.company = args.company or "_Test Company"
+ lcv.distribute_charges_based_on = "Amount"
- lcv.set('purchase_receipts', [{
- "receipt_document_type": args.receipt_document_type,
- "receipt_document": args.receipt_document,
- "supplier": ref_doc.supplier,
- "posting_date": ref_doc.posting_date,
- "grand_total": ref_doc.grand_total
- }])
+ lcv.set(
+ "purchase_receipts",
+ [
+ {
+ "receipt_document_type": args.receipt_document_type,
+ "receipt_document": args.receipt_document,
+ "supplier": ref_doc.supplier,
+ "posting_date": ref_doc.posting_date,
+ "grand_total": ref_doc.grand_total,
+ }
+ ],
+ )
- lcv.set("taxes", [{
- "description": "Shipping Charges",
- "expense_account": args.expense_account or "Expenses Included In Valuation - TCP1",
- "amount": args.charges
- }])
+ lcv.set(
+ "taxes",
+ [
+ {
+ "description": "Shipping Charges",
+ "expense_account": args.expense_account or "Expenses Included In Valuation - TCP1",
+ "amount": args.charges,
+ }
+ ],
+ )
if not args.do_not_save:
lcv.insert()
@@ -400,21 +500,31 @@
lcv = frappe.new_doc("Landed Cost Voucher")
lcv.company = company
- lcv.distribute_charges_based_on = 'Amount'
+ lcv.distribute_charges_based_on = "Amount"
- lcv.set("purchase_receipts", [{
- "receipt_document_type": receipt_document_type,
- "receipt_document": receipt_document,
- "supplier": ref_doc.supplier,
- "posting_date": ref_doc.posting_date,
- "grand_total": ref_doc.base_grand_total
- }])
+ lcv.set(
+ "purchase_receipts",
+ [
+ {
+ "receipt_document_type": receipt_document_type,
+ "receipt_document": receipt_document,
+ "supplier": ref_doc.supplier,
+ "posting_date": ref_doc.posting_date,
+ "grand_total": ref_doc.base_grand_total,
+ }
+ ],
+ )
- lcv.set("taxes", [{
- "description": "Insurance Charges",
- "expense_account": "Expenses Included In Valuation - TCP1",
- "amount": charges
- }])
+ lcv.set(
+ "taxes",
+ [
+ {
+ "description": "Insurance Charges",
+ "expense_account": "Expenses Included In Valuation - TCP1",
+ "amount": charges,
+ }
+ ],
+ )
lcv.insert()
@@ -424,6 +534,7 @@
return lcv
+
def distribute_landed_cost_on_items(lcv):
based_on = lcv.distribute_charges_based_on.lower()
total = sum(flt(d.get(based_on)) for d in lcv.get("items"))
@@ -432,4 +543,5 @@
item.applicable_charges = flt(item.get(based_on)) * flt(lcv.total_taxes_and_charges) / flt(total)
item.applicable_charges = flt(item.applicable_charges, lcv.precision("applicable_charges", item))
-test_records = frappe.get_test_records('Landed Cost Voucher')
+
+test_records = frappe.get_test_records("Landed Cost Voucher")
diff --git a/erpnext/stock/doctype/manufacturer/test_manufacturer.py b/erpnext/stock/doctype/manufacturer/test_manufacturer.py
index 6632347..e176b28 100644
--- a/erpnext/stock/doctype/manufacturer/test_manufacturer.py
+++ b/erpnext/stock/doctype/manufacturer/test_manufacturer.py
@@ -5,5 +5,6 @@
# test_records = frappe.get_test_records('Manufacturer')
+
class TestManufacturer(unittest.TestCase):
pass
diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py
index 49fefae..4524914 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -18,9 +18,8 @@
from erpnext.stock.doctype.item.item import get_item_defaults
from erpnext.stock.stock_balance import get_indented_qty, update_bin_qty
-form_grid_templates = {
- "items": "templates/form_grid/material_request_grid.html"
-}
+form_grid_templates = {"items": "templates/form_grid/material_request_grid.html"}
+
class MaterialRequest(BuyingController):
def get_feed(self):
@@ -30,8 +29,8 @@
pass
def validate_qty_against_so(self):
- so_items = {} # Format --> {'SO/00001': {'Item/001': 120, 'Item/002': 24}}
- for d in self.get('items'):
+ so_items = {} # Format --> {'SO/00001': {'Item/001': 120, 'Item/002': 24}}
+ for d in self.get("items"):
if d.sales_order:
if not d.sales_order in so_items:
so_items[d.sales_order] = {d.item_code: flt(d.qty)}
@@ -43,24 +42,34 @@
for so_no in so_items.keys():
for item in so_items[so_no].keys():
- already_indented = frappe.db.sql("""select sum(qty)
+ already_indented = frappe.db.sql(
+ """select sum(qty)
from `tabMaterial Request Item`
where item_code = %s and sales_order = %s and
- docstatus = 1 and parent != %s""", (item, so_no, self.name))
+ docstatus = 1 and parent != %s""",
+ (item, so_no, self.name),
+ )
already_indented = already_indented and flt(already_indented[0][0]) or 0
- actual_so_qty = frappe.db.sql("""select sum(stock_qty) from `tabSales Order Item`
- where parent = %s and item_code = %s and docstatus = 1""", (so_no, item))
+ actual_so_qty = frappe.db.sql(
+ """select sum(stock_qty) from `tabSales Order Item`
+ where parent = %s and item_code = %s and docstatus = 1""",
+ (so_no, item),
+ )
actual_so_qty = actual_so_qty and flt(actual_so_qty[0][0]) or 0
if actual_so_qty and (flt(so_items[so_no][item]) + already_indented > actual_so_qty):
- frappe.throw(_("Material Request of maximum {0} can be made for Item {1} against Sales Order {2}").format(actual_so_qty - already_indented, item, so_no))
+ frappe.throw(
+ _("Material Request of maximum {0} can be made for Item {1} against Sales Order {2}").format(
+ actual_so_qty - already_indented, item, so_no
+ )
+ )
def validate(self):
super(MaterialRequest, self).validate()
self.validate_schedule_date()
- self.check_for_on_hold_or_closed_status('Sales Order', 'sales_order')
+ self.check_for_on_hold_or_closed_status("Sales Order", "sales_order")
self.validate_uom_is_integer("uom", "qty")
self.validate_material_request_type()
@@ -68,9 +77,22 @@
self.status = "Draft"
from erpnext.controllers.status_updater import validate_status
- validate_status(self.status,
- ["Draft", "Submitted", "Stopped", "Cancelled", "Pending",
- "Partially Ordered", "Ordered", "Issued", "Transferred", "Received"])
+
+ validate_status(
+ self.status,
+ [
+ "Draft",
+ "Submitted",
+ "Stopped",
+ "Cancelled",
+ "Pending",
+ "Partially Ordered",
+ "Ordered",
+ "Issued",
+ "Transferred",
+ "Received",
+ ],
+ )
validate_for_items(self)
@@ -86,22 +108,22 @@
self.validate_schedule_date()
def validate_material_request_type(self):
- """ Validate fields in accordance with selected type """
+ """Validate fields in accordance with selected type"""
if self.material_request_type != "Customer Provided":
self.customer = None
def set_title(self):
- '''Set title as comma separated list of items'''
+ """Set title as comma separated list of items"""
if not self.title:
- items = ', '.join([d.item_name for d in self.items][:3])
- self.title = _('{0} Request for {1}').format(self.material_request_type, items)[:100]
+ items = ", ".join([d.item_name for d in self.items][:3])
+ self.title = _("{0} Request for {1}").format(self.material_request_type, items)[:100]
def on_submit(self):
# frappe.db.set(self, 'status', 'Submitted')
self.update_requested_qty()
self.update_requested_qty_in_production_plan()
- if self.material_request_type == 'Purchase':
+ if self.material_request_type == "Purchase":
self.validate_budget()
def before_save(self):
@@ -114,13 +136,15 @@
# if MRQ is already closed, no point saving the document
check_on_hold_or_closed_status(self.doctype, self.name)
- self.set_status(update=True, status='Cancelled')
+ self.set_status(update=True, status="Cancelled")
def check_modified_date(self):
- mod_db = frappe.db.sql("""select modified from `tabMaterial Request` where name = %s""",
- self.name)
- date_diff = frappe.db.sql("""select TIMEDIFF('%s', '%s')"""
- % (mod_db[0][0], cstr(self.modified)))
+ mod_db = frappe.db.sql(
+ """select modified from `tabMaterial Request` where name = %s""", self.name
+ )
+ date_diff = frappe.db.sql(
+ """select TIMEDIFF('%s', '%s')""" % (mod_db[0][0], cstr(self.modified))
+ )
if date_diff and date_diff[0][0]:
frappe.throw(_("{0} {1} has been modified. Please refresh.").format(_(self.doctype), self.name))
@@ -136,22 +160,24 @@
validates that `status` is acceptable for the present controller status
and throws an Exception if otherwise.
"""
- if self.status and self.status == 'Cancelled':
+ if self.status and self.status == "Cancelled":
# cancelled documents cannot change
if status != self.status:
frappe.throw(
- _("{0} {1} is cancelled so the action cannot be completed").
- format(_(self.doctype), self.name),
- frappe.InvalidStatusError
+ _("{0} {1} is cancelled so the action cannot be completed").format(
+ _(self.doctype), self.name
+ ),
+ frappe.InvalidStatusError,
)
- elif self.status and self.status == 'Draft':
+ elif self.status and self.status == "Draft":
# draft document to pending only
- if status != 'Pending':
+ if status != "Pending":
frappe.throw(
- _("{0} {1} has not been submitted so the action cannot be completed").
- format(_(self.doctype), self.name),
- frappe.InvalidStatusError
+ _("{0} {1} has not been submitted so the action cannot be completed").format(
+ _(self.doctype), self.name
+ ),
+ frappe.InvalidStatusError,
)
def on_cancel(self):
@@ -168,67 +194,90 @@
for d in self.get("items"):
if d.name in mr_items:
if self.material_request_type in ("Material Issue", "Material Transfer", "Customer Provided"):
- d.ordered_qty = flt(frappe.db.sql("""select sum(transfer_qty)
+ d.ordered_qty = flt(
+ frappe.db.sql(
+ """select sum(transfer_qty)
from `tabStock Entry Detail` where material_request = %s
and material_request_item = %s and docstatus = 1""",
- (self.name, d.name))[0][0])
- mr_qty_allowance = frappe.db.get_single_value('Stock Settings', 'mr_qty_allowance')
+ (self.name, d.name),
+ )[0][0]
+ )
+ mr_qty_allowance = frappe.db.get_single_value("Stock Settings", "mr_qty_allowance")
if mr_qty_allowance:
- allowed_qty = d.qty + (d.qty * (mr_qty_allowance/100))
+ allowed_qty = d.qty + (d.qty * (mr_qty_allowance / 100))
if d.ordered_qty and d.ordered_qty > allowed_qty:
- frappe.throw(_("The total Issue / Transfer quantity {0} in Material Request {1} \
- cannot be greater than allowed requested quantity {2} for Item {3}").format(d.ordered_qty, d.parent, allowed_qty, d.item_code))
+ frappe.throw(
+ _(
+ "The total Issue / Transfer quantity {0} in Material Request {1} \
+ cannot be greater than allowed requested quantity {2} for Item {3}"
+ ).format(d.ordered_qty, d.parent, allowed_qty, d.item_code)
+ )
elif d.ordered_qty and d.ordered_qty > d.stock_qty:
- frappe.throw(_("The total Issue / Transfer quantity {0} in Material Request {1} \
- cannot be greater than requested quantity {2} for Item {3}").format(d.ordered_qty, d.parent, d.qty, d.item_code))
+ frappe.throw(
+ _(
+ "The total Issue / Transfer quantity {0} in Material Request {1} \
+ cannot be greater than requested quantity {2} for Item {3}"
+ ).format(d.ordered_qty, d.parent, d.qty, d.item_code)
+ )
elif self.material_request_type == "Manufacture":
- d.ordered_qty = flt(frappe.db.sql("""select sum(qty)
+ d.ordered_qty = flt(
+ frappe.db.sql(
+ """select sum(qty)
from `tabWork Order` where material_request = %s
and material_request_item = %s and docstatus = 1""",
- (self.name, d.name))[0][0])
+ (self.name, d.name),
+ )[0][0]
+ )
frappe.db.set_value(d.doctype, d.name, "ordered_qty", d.ordered_qty)
- self._update_percent_field({
- "target_dt": "Material Request Item",
- "target_parent_dt": self.doctype,
- "target_parent_field": "per_ordered",
- "target_ref_field": "stock_qty",
- "target_field": "ordered_qty",
- "name": self.name,
- }, update_modified)
+ self._update_percent_field(
+ {
+ "target_dt": "Material Request Item",
+ "target_parent_dt": self.doctype,
+ "target_parent_field": "per_ordered",
+ "target_ref_field": "stock_qty",
+ "target_field": "ordered_qty",
+ "name": self.name,
+ },
+ update_modified,
+ )
def update_requested_qty(self, mr_item_rows=None):
"""update requested qty (before ordered_qty is updated)"""
item_wh_list = []
for d in self.get("items"):
- if (not mr_item_rows or d.name in mr_item_rows) and [d.item_code, d.warehouse] not in item_wh_list \
- and d.warehouse and frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1 :
+ if (
+ (not mr_item_rows or d.name in mr_item_rows)
+ and [d.item_code, d.warehouse] not in item_wh_list
+ and d.warehouse
+ and frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1
+ ):
item_wh_list.append([d.item_code, d.warehouse])
for item_code, warehouse in item_wh_list:
- update_bin_qty(item_code, warehouse, {
- "indented_qty": get_indented_qty(item_code, warehouse)
- })
+ update_bin_qty(item_code, warehouse, {"indented_qty": get_indented_qty(item_code, warehouse)})
def update_requested_qty_in_production_plan(self):
production_plans = []
- for d in self.get('items'):
+ for d in self.get("items"):
if d.production_plan and d.material_request_plan_item:
qty = d.qty if self.docstatus == 1 else 0
- frappe.db.set_value('Material Request Plan Item',
- d.material_request_plan_item, 'requested_qty', qty)
+ frappe.db.set_value(
+ "Material Request Plan Item", d.material_request_plan_item, "requested_qty", qty
+ )
if d.production_plan not in production_plans:
production_plans.append(d.production_plan)
for production_plan in production_plans:
- doc = frappe.get_doc('Production Plan', production_plan)
+ doc = frappe.get_doc("Production Plan", production_plan)
doc.set_status()
- doc.db_set('status', doc.status)
+ doc.db_set("status", doc.status)
+
def update_completed_and_requested_qty(stock_entry, method):
if stock_entry.doctype == "Stock Entry":
@@ -243,43 +292,55 @@
mr_obj = frappe.get_doc("Material Request", mr)
if mr_obj.status in ["Stopped", "Cancelled"]:
- frappe.throw(_("{0} {1} is cancelled or stopped").format(_("Material Request"), mr),
- frappe.InvalidStatusError)
+ frappe.throw(
+ _("{0} {1} is cancelled or stopped").format(_("Material Request"), mr),
+ frappe.InvalidStatusError,
+ )
mr_obj.update_completed_qty(mr_item_rows)
mr_obj.update_requested_qty(mr_item_rows)
+
def set_missing_values(source, target_doc):
- if target_doc.doctype == "Purchase Order" and getdate(target_doc.schedule_date) < getdate(nowdate()):
+ if target_doc.doctype == "Purchase Order" and getdate(target_doc.schedule_date) < getdate(
+ nowdate()
+ ):
target_doc.schedule_date = None
target_doc.run_method("set_missing_values")
target_doc.run_method("calculate_taxes_and_totals")
+
def update_item(obj, target, source_parent):
target.conversion_factor = obj.conversion_factor
- target.qty = flt(flt(obj.stock_qty) - flt(obj.ordered_qty))/ target.conversion_factor
- target.stock_qty = (target.qty * target.conversion_factor)
+ target.qty = flt(flt(obj.stock_qty) - flt(obj.ordered_qty)) / target.conversion_factor
+ target.stock_qty = target.qty * target.conversion_factor
if getdate(target.schedule_date) < getdate(nowdate()):
target.schedule_date = None
+
def get_list_context(context=None):
from erpnext.controllers.website_list_for_contact import get_list_context
+
list_context = get_list_context(context)
- list_context.update({
- 'show_sidebar': True,
- 'show_search': True,
- 'no_breadcrumbs': True,
- 'title': _('Material Request'),
- })
+ list_context.update(
+ {
+ "show_sidebar": True,
+ "show_search": True,
+ "no_breadcrumbs": True,
+ "title": _("Material Request"),
+ }
+ )
return list_context
+
@frappe.whitelist()
def update_status(name, status):
- material_request = frappe.get_doc('Material Request', name)
- material_request.check_permission('write')
+ material_request = frappe.get_doc("Material Request", name)
+ material_request.check_permission("write")
material_request.update_status(status)
+
@frappe.whitelist()
def make_purchase_order(source_name, target_doc=None, args=None):
if args is None:
@@ -292,7 +353,7 @@
# items only for given default supplier
supplier_items = []
for d in target_doc.items:
- default_supplier = get_item_defaults(d.item_code, target_doc.company).get('default_supplier')
+ default_supplier = get_item_defaults(d.item_code, target_doc.company).get("default_supplier")
if frappe.flags.args.default_supplier == default_supplier:
supplier_items.append(d)
target_doc.items = supplier_items
@@ -300,58 +361,65 @@
set_missing_values(source, target_doc)
def select_item(d):
- filtered_items = args.get('filtered_children', [])
+ filtered_items = args.get("filtered_children", [])
child_filter = d.name in filtered_items if filtered_items else True
return d.ordered_qty < d.stock_qty and child_filter
- doclist = get_mapped_doc("Material Request", source_name, {
- "Material Request": {
- "doctype": "Purchase Order",
- "validation": {
- "docstatus": ["=", 1],
- "material_request_type": ["=", "Purchase"]
- }
+ doclist = get_mapped_doc(
+ "Material Request",
+ source_name,
+ {
+ "Material Request": {
+ "doctype": "Purchase Order",
+ "validation": {"docstatus": ["=", 1], "material_request_type": ["=", "Purchase"]},
+ },
+ "Material Request Item": {
+ "doctype": "Purchase Order Item",
+ "field_map": [
+ ["name", "material_request_item"],
+ ["parent", "material_request"],
+ ["uom", "stock_uom"],
+ ["uom", "uom"],
+ ["sales_order", "sales_order"],
+ ["sales_order_item", "sales_order_item"],
+ ],
+ "postprocess": update_item,
+ "condition": select_item,
+ },
},
- "Material Request Item": {
- "doctype": "Purchase Order Item",
- "field_map": [
- ["name", "material_request_item"],
- ["parent", "material_request"],
- ["uom", "stock_uom"],
- ["uom", "uom"],
- ["sales_order", "sales_order"],
- ["sales_order_item", "sales_order_item"]
- ],
- "postprocess": update_item,
- "condition": select_item
- }
- }, target_doc, postprocess)
+ target_doc,
+ postprocess,
+ )
return doclist
+
@frappe.whitelist()
def make_request_for_quotation(source_name, target_doc=None):
- doclist = get_mapped_doc("Material Request", source_name, {
- "Material Request": {
- "doctype": "Request for Quotation",
- "validation": {
- "docstatus": ["=", 1],
- "material_request_type": ["=", "Purchase"]
- }
+ doclist = get_mapped_doc(
+ "Material Request",
+ source_name,
+ {
+ "Material Request": {
+ "doctype": "Request for Quotation",
+ "validation": {"docstatus": ["=", 1], "material_request_type": ["=", "Purchase"]},
+ },
+ "Material Request Item": {
+ "doctype": "Request for Quotation Item",
+ "field_map": [
+ ["name", "material_request_item"],
+ ["parent", "material_request"],
+ ["uom", "uom"],
+ ],
+ },
},
- "Material Request Item": {
- "doctype": "Request for Quotation Item",
- "field_map": [
- ["name", "material_request_item"],
- ["parent", "material_request"],
- ["uom", "uom"]
- ]
- }
- }, target_doc)
+ target_doc,
+ )
return doclist
+
@frappe.whitelist()
def make_purchase_order_based_on_supplier(source_name, target_doc=None, args=None):
mr = source_name
@@ -362,43 +430,59 @@
target_doc.supplier = args.get("supplier")
if getdate(target_doc.schedule_date) < getdate(nowdate()):
target_doc.schedule_date = None
- target_doc.set("items", [d for d in target_doc.get("items")
- if d.get("item_code") in supplier_items and d.get("qty") > 0])
+ target_doc.set(
+ "items",
+ [
+ d for d in target_doc.get("items") if d.get("item_code") in supplier_items and d.get("qty") > 0
+ ],
+ )
set_missing_values(source, target_doc)
- target_doc = get_mapped_doc("Material Request", mr, {
- "Material Request": {
- "doctype": "Purchase Order",
+ target_doc = get_mapped_doc(
+ "Material Request",
+ mr,
+ {
+ "Material Request": {
+ "doctype": "Purchase Order",
+ },
+ "Material Request Item": {
+ "doctype": "Purchase Order Item",
+ "field_map": [
+ ["name", "material_request_item"],
+ ["parent", "material_request"],
+ ["uom", "stock_uom"],
+ ["uom", "uom"],
+ ],
+ "postprocess": update_item,
+ "condition": lambda doc: doc.ordered_qty < doc.qty,
+ },
},
- "Material Request Item": {
- "doctype": "Purchase Order Item",
- "field_map": [
- ["name", "material_request_item"],
- ["parent", "material_request"],
- ["uom", "stock_uom"],
- ["uom", "uom"]
- ],
- "postprocess": update_item,
- "condition": lambda doc: doc.ordered_qty < doc.qty
- }
- }, target_doc, postprocess)
+ target_doc,
+ postprocess,
+ )
return target_doc
+
@frappe.whitelist()
def get_items_based_on_default_supplier(supplier):
- supplier_items = [d.parent for d in frappe.db.get_all("Item Default",
- {"default_supplier": supplier, "parenttype": "Item"}, 'parent')]
+ supplier_items = [
+ d.parent
+ for d in frappe.db.get_all(
+ "Item Default", {"default_supplier": supplier, "parenttype": "Item"}, "parent"
+ )
+ ]
return supplier_items
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_material_requests_based_on_supplier(doctype, txt, searchfield, start, page_len, filters):
conditions = ""
if txt:
- conditions += "and mr.name like '%%"+txt+"%%' "
+ conditions += "and mr.name like '%%" + txt + "%%' "
if filters.get("transaction_date"):
date = filters.get("transaction_date")[1]
@@ -410,7 +494,8 @@
if not supplier_items:
frappe.throw(_("{0} is not the default supplier for any items.").format(supplier))
- material_requests = frappe.db.sql("""select distinct mr.name, transaction_date,company
+ material_requests = frappe.db.sql(
+ """select distinct mr.name, transaction_date,company
from `tabMaterial Request` mr, `tabMaterial Request Item` mr_item
where mr.name = mr_item.parent
and mr_item.item_code in ({0})
@@ -421,12 +506,16 @@
and mr.company = '{1}'
{2}
order by mr_item.item_code ASC
- limit {3} offset {4} """ \
- .format(', '.join(['%s']*len(supplier_items)), filters.get("company"), conditions, page_len, start),
- tuple(supplier_items), as_dict=1)
+ limit {3} offset {4} """.format(
+ ", ".join(["%s"] * len(supplier_items)), filters.get("company"), conditions, page_len, start
+ ),
+ tuple(supplier_items),
+ as_dict=1,
+ )
return material_requests
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_default_supplier_query(doctype, txt, searchfield, start, page_len, filters):
@@ -435,47 +524,63 @@
for d in doc.items:
item_list.append(d.item_code)
- return frappe.db.sql("""select default_supplier
+ return frappe.db.sql(
+ """select default_supplier
from `tabItem Default`
where parent in ({0}) and
default_supplier IS NOT NULL
- """.format(', '.join(['%s']*len(item_list))),tuple(item_list))
+ """.format(
+ ", ".join(["%s"] * len(item_list))
+ ),
+ tuple(item_list),
+ )
+
@frappe.whitelist()
def make_supplier_quotation(source_name, target_doc=None):
def postprocess(source, target_doc):
set_missing_values(source, target_doc)
- doclist = get_mapped_doc("Material Request", source_name, {
- "Material Request": {
- "doctype": "Supplier Quotation",
- "validation": {
- "docstatus": ["=", 1],
- "material_request_type": ["=", "Purchase"]
- }
+ doclist = get_mapped_doc(
+ "Material Request",
+ source_name,
+ {
+ "Material Request": {
+ "doctype": "Supplier Quotation",
+ "validation": {"docstatus": ["=", 1], "material_request_type": ["=", "Purchase"]},
+ },
+ "Material Request Item": {
+ "doctype": "Supplier Quotation Item",
+ "field_map": {
+ "name": "material_request_item",
+ "parent": "material_request",
+ "sales_order": "sales_order",
+ },
+ },
},
- "Material Request Item": {
- "doctype": "Supplier Quotation Item",
- "field_map": {
- "name": "material_request_item",
- "parent": "material_request",
- "sales_order": "sales_order"
- }
- }
- }, target_doc, postprocess)
+ target_doc,
+ postprocess,
+ )
return doclist
+
@frappe.whitelist()
def make_stock_entry(source_name, target_doc=None):
def update_item(obj, target, source_parent):
- qty = flt(flt(obj.stock_qty) - flt(obj.ordered_qty))/ target.conversion_factor \
- if flt(obj.stock_qty) > flt(obj.ordered_qty) else 0
+ qty = (
+ flt(flt(obj.stock_qty) - flt(obj.ordered_qty)) / target.conversion_factor
+ if flt(obj.stock_qty) > flt(obj.ordered_qty)
+ else 0
+ )
target.qty = qty
target.transfer_qty = qty * obj.conversion_factor
target.conversion_factor = obj.conversion_factor
- if source_parent.material_request_type == "Material Transfer" or source_parent.material_request_type == "Customer Provided":
+ if (
+ source_parent.material_request_type == "Material Transfer"
+ or source_parent.material_request_type == "Customer Provided"
+ ):
target.t_warehouse = obj.warehouse
else:
target.s_warehouse = obj.warehouse
@@ -489,7 +594,7 @@
def set_missing_values(source, target):
target.purpose = source.material_request_type
if source.job_card:
- target.purpose = 'Material Transfer for Manufacture'
+ target.purpose = "Material Transfer for Manufacture"
if source.material_request_type == "Customer Provided":
target.purpose = "Material Receipt"
@@ -498,101 +603,119 @@
target.set_stock_entry_type()
target.set_job_card_data()
- doclist = get_mapped_doc("Material Request", source_name, {
- "Material Request": {
- "doctype": "Stock Entry",
- "validation": {
- "docstatus": ["=", 1],
- "material_request_type": ["in", ["Material Transfer", "Material Issue", "Customer Provided"]]
- }
- },
- "Material Request Item": {
- "doctype": "Stock Entry Detail",
- "field_map": {
- "name": "material_request_item",
- "parent": "material_request",
- "uom": "stock_uom",
- "job_card_item": "job_card_item"
+ doclist = get_mapped_doc(
+ "Material Request",
+ source_name,
+ {
+ "Material Request": {
+ "doctype": "Stock Entry",
+ "validation": {
+ "docstatus": ["=", 1],
+ "material_request_type": ["in", ["Material Transfer", "Material Issue", "Customer Provided"]],
+ },
},
- "postprocess": update_item,
- "condition": lambda doc: doc.ordered_qty < doc.stock_qty
- }
- }, target_doc, set_missing_values)
+ "Material Request Item": {
+ "doctype": "Stock Entry Detail",
+ "field_map": {
+ "name": "material_request_item",
+ "parent": "material_request",
+ "uom": "stock_uom",
+ "job_card_item": "job_card_item",
+ },
+ "postprocess": update_item,
+ "condition": lambda doc: doc.ordered_qty < doc.stock_qty,
+ },
+ },
+ target_doc,
+ set_missing_values,
+ )
return doclist
+
@frappe.whitelist()
def raise_work_orders(material_request):
- mr= frappe.get_doc("Material Request", material_request)
- errors =[]
+ mr = frappe.get_doc("Material Request", material_request)
+ errors = []
work_orders = []
- default_wip_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_wip_warehouse")
+ default_wip_warehouse = frappe.db.get_single_value(
+ "Manufacturing Settings", "default_wip_warehouse"
+ )
for d in mr.items:
if (d.stock_qty - d.ordered_qty) > 0:
if frappe.db.exists("BOM", {"item": d.item_code, "is_default": 1}):
wo_order = frappe.new_doc("Work Order")
- wo_order.update({
- "production_item": d.item_code,
- "qty": d.stock_qty - d.ordered_qty,
- "fg_warehouse": d.warehouse,
- "wip_warehouse": default_wip_warehouse,
- "description": d.description,
- "stock_uom": d.stock_uom,
- "expected_delivery_date": d.schedule_date,
- "sales_order": d.sales_order,
- "sales_order_item": d.get("sales_order_item"),
- "bom_no": get_item_details(d.item_code).bom_no,
- "material_request": mr.name,
- "material_request_item": d.name,
- "planned_start_date": mr.transaction_date,
- "company": mr.company
- })
+ wo_order.update(
+ {
+ "production_item": d.item_code,
+ "qty": d.stock_qty - d.ordered_qty,
+ "fg_warehouse": d.warehouse,
+ "wip_warehouse": default_wip_warehouse,
+ "description": d.description,
+ "stock_uom": d.stock_uom,
+ "expected_delivery_date": d.schedule_date,
+ "sales_order": d.sales_order,
+ "sales_order_item": d.get("sales_order_item"),
+ "bom_no": get_item_details(d.item_code).bom_no,
+ "material_request": mr.name,
+ "material_request_item": d.name,
+ "planned_start_date": mr.transaction_date,
+ "company": mr.company,
+ }
+ )
wo_order.set_work_order_operations()
wo_order.save()
work_orders.append(wo_order.name)
else:
- errors.append(_("Row {0}: Bill of Materials not found for the Item {1}")
- .format(d.idx, get_link_to_form("Item", d.item_code)))
+ errors.append(
+ _("Row {0}: Bill of Materials not found for the Item {1}").format(
+ d.idx, get_link_to_form("Item", d.item_code)
+ )
+ )
if work_orders:
work_orders_list = [get_link_to_form("Work Order", d) for d in work_orders]
if len(work_orders) > 1:
- msgprint(_("The following {0} were created: {1}")
- .format(frappe.bold(_("Work Orders")), '<br>' + ', '.join(work_orders_list)))
+ msgprint(
+ _("The following {0} were created: {1}").format(
+ frappe.bold(_("Work Orders")), "<br>" + ", ".join(work_orders_list)
+ )
+ )
else:
- msgprint(_("The {0} {1} created sucessfully")
- .format(frappe.bold(_("Work Order")), work_orders_list[0]))
+ msgprint(
+ _("The {0} {1} created sucessfully").format(frappe.bold(_("Work Order")), work_orders_list[0])
+ )
if errors:
- frappe.throw(_("Work Order cannot be created for following reason: <br> {0}")
- .format(new_line_sep(errors)))
+ frappe.throw(
+ _("Work Order cannot be created for following reason: <br> {0}").format(new_line_sep(errors))
+ )
return work_orders
+
@frappe.whitelist()
def create_pick_list(source_name, target_doc=None):
- doc = get_mapped_doc('Material Request', source_name, {
- 'Material Request': {
- 'doctype': 'Pick List',
- 'field_map': {
- 'material_request_type': 'purpose'
+ doc = get_mapped_doc(
+ "Material Request",
+ source_name,
+ {
+ "Material Request": {
+ "doctype": "Pick List",
+ "field_map": {"material_request_type": "purpose"},
+ "validation": {"docstatus": ["=", 1]},
},
- 'validation': {
- 'docstatus': ['=', 1]
- }
- },
- 'Material Request Item': {
- 'doctype': 'Pick List Item',
- 'field_map': {
- 'name': 'material_request_item',
- 'qty': 'stock_qty'
+ "Material Request Item": {
+ "doctype": "Pick List Item",
+ "field_map": {"name": "material_request_item", "qty": "stock_qty"},
},
},
- }, target_doc)
+ target_doc,
+ )
doc.set_item_locations()
diff --git a/erpnext/stock/doctype/material_request/material_request_dashboard.py b/erpnext/stock/doctype/material_request/material_request_dashboard.py
index c1ce0a9..b073e6a 100644
--- a/erpnext/stock/doctype/material_request/material_request_dashboard.py
+++ b/erpnext/stock/doctype/material_request/material_request_dashboard.py
@@ -3,20 +3,13 @@
def get_data():
return {
- 'fieldname': 'material_request',
- 'transactions': [
+ "fieldname": "material_request",
+ "transactions": [
{
- 'label': _('Reference'),
- 'items': ['Request for Quotation', 'Supplier Quotation', 'Purchase Order']
+ "label": _("Reference"),
+ "items": ["Request for Quotation", "Supplier Quotation", "Purchase Order"],
},
- {
- 'label': _('Stock'),
- 'items': ['Stock Entry', 'Purchase Receipt', 'Pick List']
-
- },
- {
- 'label': _('Manufacturing'),
- 'items': ['Work Order']
- }
- ]
+ {"label": _("Stock"), "items": ["Stock Entry", "Purchase Receipt", "Pick List"]},
+ {"label": _("Manufacturing"), "items": ["Work Order"]},
+ ],
}
diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py
index 866f3ab..78af153 100644
--- a/erpnext/stock/doctype/material_request/test_material_request.py
+++ b/erpnext/stock/doctype/material_request/test_material_request.py
@@ -22,8 +22,7 @@
def test_make_purchase_order(self):
mr = frappe.copy_doc(test_records[0]).insert()
- self.assertRaises(frappe.ValidationError, make_purchase_order,
- mr.name)
+ self.assertRaises(frappe.ValidationError, make_purchase_order, mr.name)
mr = frappe.get_doc("Material Request", mr.name)
mr.submit()
@@ -44,7 +43,6 @@
self.assertEqual(sq.doctype, "Supplier Quotation")
self.assertEqual(len(sq.get("items")), len(mr.get("items")))
-
def test_make_stock_entry(self):
mr = frappe.copy_doc(test_records[0]).insert()
@@ -58,42 +56,44 @@
self.assertEqual(se.doctype, "Stock Entry")
self.assertEqual(len(se.get("items")), len(mr.get("items")))
- def _insert_stock_entry(self, qty1, qty2, warehouse = None ):
- se = frappe.get_doc({
- "company": "_Test Company",
- "doctype": "Stock Entry",
- "posting_date": "2013-03-01",
- "posting_time": "00:00:00",
- "purpose": "Material Receipt",
- "items": [
- {
- "conversion_factor": 1.0,
- "doctype": "Stock Entry Detail",
- "item_code": "_Test Item Home Desktop 100",
- "parentfield": "items",
- "basic_rate": 100,
- "qty": qty1,
- "stock_uom": "_Test UOM 1",
- "transfer_qty": qty1,
- "uom": "_Test UOM 1",
- "t_warehouse": warehouse or "_Test Warehouse 1 - _TC",
- "cost_center": "_Test Cost Center - _TC"
- },
- {
- "conversion_factor": 1.0,
- "doctype": "Stock Entry Detail",
- "item_code": "_Test Item Home Desktop 200",
- "parentfield": "items",
- "basic_rate": 100,
- "qty": qty2,
- "stock_uom": "_Test UOM 1",
- "transfer_qty": qty2,
- "uom": "_Test UOM 1",
- "t_warehouse": warehouse or "_Test Warehouse 1 - _TC",
- "cost_center": "_Test Cost Center - _TC"
- }
- ]
- })
+ def _insert_stock_entry(self, qty1, qty2, warehouse=None):
+ se = frappe.get_doc(
+ {
+ "company": "_Test Company",
+ "doctype": "Stock Entry",
+ "posting_date": "2013-03-01",
+ "posting_time": "00:00:00",
+ "purpose": "Material Receipt",
+ "items": [
+ {
+ "conversion_factor": 1.0,
+ "doctype": "Stock Entry Detail",
+ "item_code": "_Test Item Home Desktop 100",
+ "parentfield": "items",
+ "basic_rate": 100,
+ "qty": qty1,
+ "stock_uom": "_Test UOM 1",
+ "transfer_qty": qty1,
+ "uom": "_Test UOM 1",
+ "t_warehouse": warehouse or "_Test Warehouse 1 - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ },
+ {
+ "conversion_factor": 1.0,
+ "doctype": "Stock Entry Detail",
+ "item_code": "_Test Item Home Desktop 200",
+ "parentfield": "items",
+ "basic_rate": 100,
+ "qty": qty2,
+ "stock_uom": "_Test UOM 1",
+ "transfer_qty": qty2,
+ "uom": "_Test UOM 1",
+ "t_warehouse": warehouse or "_Test Warehouse 1 - _TC",
+ "cost_center": "_Test Cost Center - _TC",
+ },
+ ],
+ }
+ )
se.set_stock_entry_type()
se.insert()
@@ -106,19 +106,19 @@
mr.load_from_db()
mr.cancel()
- self.assertRaises(frappe.ValidationError, mr.update_status, 'Stopped')
+ self.assertRaises(frappe.ValidationError, mr.update_status, "Stopped")
def test_mr_changes_from_stopped_to_pending_after_reopen(self):
mr = frappe.copy_doc(test_records[0])
mr.insert()
mr.submit()
- self.assertEqual('Pending', mr.status)
+ self.assertEqual("Pending", mr.status)
- mr.update_status('Stopped')
- self.assertEqual('Stopped', mr.status)
+ mr.update_status("Stopped")
+ self.assertEqual("Stopped", mr.status)
- mr.update_status('Submitted')
- self.assertEqual('Pending', mr.status)
+ mr.update_status("Submitted")
+ self.assertEqual("Pending", mr.status)
def test_cannot_submit_cancelled_mr(self):
mr = frappe.copy_doc(test_records[0])
@@ -133,7 +133,7 @@
mr.insert()
mr.submit()
mr.cancel()
- self.assertEqual('Cancelled', mr.status)
+ self.assertEqual("Cancelled", mr.status)
def test_cannot_change_cancelled_mr(self):
mr = frappe.copy_doc(test_records[0])
@@ -142,12 +142,12 @@
mr.load_from_db()
mr.cancel()
- self.assertRaises(frappe.InvalidStatusError, mr.update_status, 'Draft')
- self.assertRaises(frappe.InvalidStatusError, mr.update_status, 'Stopped')
- self.assertRaises(frappe.InvalidStatusError, mr.update_status, 'Ordered')
- self.assertRaises(frappe.InvalidStatusError, mr.update_status, 'Issued')
- self.assertRaises(frappe.InvalidStatusError, mr.update_status, 'Transferred')
- self.assertRaises(frappe.InvalidStatusError, mr.update_status, 'Pending')
+ self.assertRaises(frappe.InvalidStatusError, mr.update_status, "Draft")
+ self.assertRaises(frappe.InvalidStatusError, mr.update_status, "Stopped")
+ self.assertRaises(frappe.InvalidStatusError, mr.update_status, "Ordered")
+ self.assertRaises(frappe.InvalidStatusError, mr.update_status, "Issued")
+ self.assertRaises(frappe.InvalidStatusError, mr.update_status, "Transferred")
+ self.assertRaises(frappe.InvalidStatusError, mr.update_status, "Pending")
def test_cannot_submit_deleted_material_request(self):
mr = frappe.copy_doc(test_records[0])
@@ -169,9 +169,9 @@
mr.submit()
mr.load_from_db()
- mr.update_status('Stopped')
- mr.update_status('Submitted')
- self.assertEqual(mr.status, 'Pending')
+ mr.update_status("Stopped")
+ mr.update_status("Submitted")
+ self.assertEqual(mr.status, "Pending")
def test_pending_mr_changes_to_stopped_after_stop(self):
mr = frappe.copy_doc(test_records[0])
@@ -179,17 +179,21 @@
mr.submit()
mr.load_from_db()
- mr.update_status('Stopped')
- self.assertEqual(mr.status, 'Stopped')
+ mr.update_status("Stopped")
+ self.assertEqual(mr.status, "Stopped")
def test_cannot_stop_unsubmitted_mr(self):
mr = frappe.copy_doc(test_records[0])
mr.insert()
- self.assertRaises(frappe.InvalidStatusError, mr.update_status, 'Stopped')
+ self.assertRaises(frappe.InvalidStatusError, mr.update_status, "Stopped")
def test_completed_qty_for_purchase(self):
- existing_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
- existing_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
+ existing_requested_qty_item1 = self._get_requested_qty(
+ "_Test Item Home Desktop 100", "_Test Warehouse - _TC"
+ )
+ existing_requested_qty_item2 = self._get_requested_qty(
+ "_Test Item Home Desktop 200", "_Test Warehouse - _TC"
+ )
# submit material request of type Purchase
mr = frappe.copy_doc(test_records[0])
@@ -206,19 +210,18 @@
po_doc.get("items")[0].schedule_date = "2013-07-09"
po_doc.get("items")[1].schedule_date = "2013-07-09"
-
# check for stopped status of Material Request
po = frappe.copy_doc(po_doc)
po.insert()
po.load_from_db()
- mr.update_status('Stopped')
+ mr.update_status("Stopped")
self.assertRaises(frappe.InvalidStatusError, po.submit)
frappe.db.set(po, "docstatus", 1)
self.assertRaises(frappe.InvalidStatusError, po.cancel)
# resubmit and check for per complete
mr.load_from_db()
- mr.update_status('Submitted')
+ mr.update_status("Submitted")
po = frappe.copy_doc(po_doc)
po.insert()
po.submit()
@@ -229,8 +232,12 @@
self.assertEqual(mr.get("items")[0].ordered_qty, 27.0)
self.assertEqual(mr.get("items")[1].ordered_qty, 1.5)
- current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
- current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
+ current_requested_qty_item1 = self._get_requested_qty(
+ "_Test Item Home Desktop 100", "_Test Warehouse - _TC"
+ )
+ current_requested_qty_item2 = self._get_requested_qty(
+ "_Test Item Home Desktop 200", "_Test Warehouse - _TC"
+ )
self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 + 27.0)
self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 + 1.5)
@@ -242,15 +249,23 @@
self.assertEqual(mr.get("items")[0].ordered_qty, 0)
self.assertEqual(mr.get("items")[1].ordered_qty, 0)
- current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
- current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
+ current_requested_qty_item1 = self._get_requested_qty(
+ "_Test Item Home Desktop 100", "_Test Warehouse - _TC"
+ )
+ current_requested_qty_item2 = self._get_requested_qty(
+ "_Test Item Home Desktop 200", "_Test Warehouse - _TC"
+ )
self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 + 54.0)
self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 + 3.0)
def test_completed_qty_for_transfer(self):
- existing_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
- existing_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
+ existing_requested_qty_item1 = self._get_requested_qty(
+ "_Test Item Home Desktop 100", "_Test Warehouse - _TC"
+ )
+ existing_requested_qty_item2 = self._get_requested_qty(
+ "_Test Item Home Desktop 200", "_Test Warehouse - _TC"
+ )
# submit material request of type Purchase
mr = frappe.copy_doc(test_records[0])
@@ -264,31 +279,31 @@
self.assertEqual(mr.get("items")[0].ordered_qty, 0)
self.assertEqual(mr.get("items")[1].ordered_qty, 0)
- current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
- current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
+ current_requested_qty_item1 = self._get_requested_qty(
+ "_Test Item Home Desktop 100", "_Test Warehouse - _TC"
+ )
+ current_requested_qty_item2 = self._get_requested_qty(
+ "_Test Item Home Desktop 200", "_Test Warehouse - _TC"
+ )
self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 + 54.0)
self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 + 3.0)
# map a stock entry
se_doc = make_stock_entry(mr.name)
- se_doc.update({
- "posting_date": "2013-03-01",
- "posting_time": "01:00",
- "fiscal_year": "_Test Fiscal Year 2013",
- })
- se_doc.get("items")[0].update({
- "qty": 27.0,
- "transfer_qty": 27.0,
- "s_warehouse": "_Test Warehouse 1 - _TC",
- "basic_rate": 1.0
- })
- se_doc.get("items")[1].update({
- "qty": 1.5,
- "transfer_qty": 1.5,
- "s_warehouse": "_Test Warehouse 1 - _TC",
- "basic_rate": 1.0
- })
+ se_doc.update(
+ {
+ "posting_date": "2013-03-01",
+ "posting_time": "01:00",
+ "fiscal_year": "_Test Fiscal Year 2013",
+ }
+ )
+ se_doc.get("items")[0].update(
+ {"qty": 27.0, "transfer_qty": 27.0, "s_warehouse": "_Test Warehouse 1 - _TC", "basic_rate": 1.0}
+ )
+ se_doc.get("items")[1].update(
+ {"qty": 1.5, "transfer_qty": 1.5, "s_warehouse": "_Test Warehouse 1 - _TC", "basic_rate": 1.0}
+ )
# make available the qty in _Test Warehouse 1 before transfer
self._insert_stock_entry(27.0, 1.5)
@@ -296,17 +311,17 @@
# check for stopped status of Material Request
se = frappe.copy_doc(se_doc)
se.insert()
- mr.update_status('Stopped')
+ mr.update_status("Stopped")
self.assertRaises(frappe.InvalidStatusError, se.submit)
- mr.update_status('Submitted')
+ mr.update_status("Submitted")
se.flags.ignore_validate_update_after_submit = True
se.submit()
- mr.update_status('Stopped')
+ mr.update_status("Stopped")
self.assertRaises(frappe.InvalidStatusError, se.cancel)
- mr.update_status('Submitted')
+ mr.update_status("Submitted")
se = frappe.copy_doc(se_doc)
se.insert()
se.submit()
@@ -317,8 +332,12 @@
self.assertEqual(mr.get("items")[0].ordered_qty, 27.0)
self.assertEqual(mr.get("items")[1].ordered_qty, 1.5)
- current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
- current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
+ current_requested_qty_item1 = self._get_requested_qty(
+ "_Test Item Home Desktop 100", "_Test Warehouse - _TC"
+ )
+ current_requested_qty_item2 = self._get_requested_qty(
+ "_Test Item Home Desktop 200", "_Test Warehouse - _TC"
+ )
self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 + 27.0)
self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 + 1.5)
@@ -330,56 +349,70 @@
self.assertEqual(mr.get("items")[0].ordered_qty, 0)
self.assertEqual(mr.get("items")[1].ordered_qty, 0)
- current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
- current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
+ current_requested_qty_item1 = self._get_requested_qty(
+ "_Test Item Home Desktop 100", "_Test Warehouse - _TC"
+ )
+ current_requested_qty_item2 = self._get_requested_qty(
+ "_Test Item Home Desktop 200", "_Test Warehouse - _TC"
+ )
self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 + 54.0)
self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 + 3.0)
def test_over_transfer_qty_allowance(self):
- mr = frappe.new_doc('Material Request')
+ mr = frappe.new_doc("Material Request")
mr.company = "_Test Company"
mr.scheduled_date = today()
- mr.append('items',{
- "item_code": "_Test FG Item",
- "item_name": "_Test FG Item",
- "qty": 10,
- "schedule_date": today(),
- "uom": "_Test UOM 1",
- "warehouse": "_Test Warehouse - _TC"
- })
+ mr.append(
+ "items",
+ {
+ "item_code": "_Test FG Item",
+ "item_name": "_Test FG Item",
+ "qty": 10,
+ "schedule_date": today(),
+ "uom": "_Test UOM 1",
+ "warehouse": "_Test Warehouse - _TC",
+ },
+ )
mr.material_request_type = "Material Transfer"
mr.insert()
mr.submit()
- frappe.db.set_value('Stock Settings', None, 'mr_qty_allowance', 20)
+ frappe.db.set_value("Stock Settings", None, "mr_qty_allowance", 20)
# map a stock entry
se_doc = make_stock_entry(mr.name)
- se_doc.update({
- "posting_date": today(),
- "posting_time": "00:00",
- })
- se_doc.get("items")[0].update({
- "qty": 13,
- "transfer_qty": 12.0,
- "s_warehouse": "_Test Warehouse - _TC",
- "t_warehouse": "_Test Warehouse 1 - _TC",
- "basic_rate": 1.0
- })
+ se_doc.update(
+ {
+ "posting_date": today(),
+ "posting_time": "00:00",
+ }
+ )
+ se_doc.get("items")[0].update(
+ {
+ "qty": 13,
+ "transfer_qty": 12.0,
+ "s_warehouse": "_Test Warehouse - _TC",
+ "t_warehouse": "_Test Warehouse 1 - _TC",
+ "basic_rate": 1.0,
+ }
+ )
# make available the qty in _Test Warehouse 1 before transfer
sr = frappe.new_doc("Stock Reconciliation")
sr.company = "_Test Company"
sr.purpose = "Opening Stock"
- sr.append('items', {
- "item_code": "_Test FG Item",
- "warehouse": "_Test Warehouse - _TC",
- "qty": 20,
- "valuation_rate": 0.01
- })
+ sr.append(
+ "items",
+ {
+ "item_code": "_Test FG Item",
+ "warehouse": "_Test Warehouse - _TC",
+ "qty": 20,
+ "valuation_rate": 0.01,
+ },
+ )
sr.insert()
sr.submit()
se = frappe.copy_doc(se_doc)
@@ -389,8 +422,12 @@
se.submit()
def test_completed_qty_for_over_transfer(self):
- existing_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
- existing_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
+ existing_requested_qty_item1 = self._get_requested_qty(
+ "_Test Item Home Desktop 100", "_Test Warehouse - _TC"
+ )
+ existing_requested_qty_item2 = self._get_requested_qty(
+ "_Test Item Home Desktop 200", "_Test Warehouse - _TC"
+ )
# submit material request of type Purchase
mr = frappe.copy_doc(test_records[0])
@@ -401,23 +438,19 @@
# map a stock entry
se_doc = make_stock_entry(mr.name)
- se_doc.update({
- "posting_date": "2013-03-01",
- "posting_time": "00:00",
- "fiscal_year": "_Test Fiscal Year 2013",
- })
- se_doc.get("items")[0].update({
- "qty": 54.0,
- "transfer_qty": 54.0,
- "s_warehouse": "_Test Warehouse 1 - _TC",
- "basic_rate": 1.0
- })
- se_doc.get("items")[1].update({
- "qty": 3.0,
- "transfer_qty": 3.0,
- "s_warehouse": "_Test Warehouse 1 - _TC",
- "basic_rate": 1.0
- })
+ se_doc.update(
+ {
+ "posting_date": "2013-03-01",
+ "posting_time": "00:00",
+ "fiscal_year": "_Test Fiscal Year 2013",
+ }
+ )
+ se_doc.get("items")[0].update(
+ {"qty": 54.0, "transfer_qty": 54.0, "s_warehouse": "_Test Warehouse 1 - _TC", "basic_rate": 1.0}
+ )
+ se_doc.get("items")[1].update(
+ {"qty": 3.0, "transfer_qty": 3.0, "s_warehouse": "_Test Warehouse 1 - _TC", "basic_rate": 1.0}
+ )
# make available the qty in _Test Warehouse 1 before transfer
self._insert_stock_entry(60.0, 3.0)
@@ -426,11 +459,11 @@
se = frappe.copy_doc(se_doc)
se.set_stock_entry_type()
se.insert()
- mr.update_status('Stopped')
+ mr.update_status("Stopped")
self.assertRaises(frappe.InvalidStatusError, se.submit)
self.assertRaises(frappe.InvalidStatusError, se.cancel)
- mr.update_status('Submitted')
+ mr.update_status("Submitted")
se = frappe.copy_doc(se_doc)
se.set_stock_entry_type()
se.insert()
@@ -443,8 +476,12 @@
self.assertEqual(mr.get("items")[0].ordered_qty, 54.0)
self.assertEqual(mr.get("items")[1].ordered_qty, 3.0)
- current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
- current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
+ current_requested_qty_item1 = self._get_requested_qty(
+ "_Test Item Home Desktop 100", "_Test Warehouse - _TC"
+ )
+ current_requested_qty_item2 = self._get_requested_qty(
+ "_Test Item Home Desktop 200", "_Test Warehouse - _TC"
+ )
self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1)
self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2)
@@ -456,8 +493,12 @@
self.assertEqual(mr.get("items")[0].ordered_qty, 0)
self.assertEqual(mr.get("items")[1].ordered_qty, 0)
- current_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
- current_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
+ current_requested_qty_item1 = self._get_requested_qty(
+ "_Test Item Home Desktop 100", "_Test Warehouse - _TC"
+ )
+ current_requested_qty_item2 = self._get_requested_qty(
+ "_Test Item Home Desktop 200", "_Test Warehouse - _TC"
+ )
self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 + 54.0)
self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 + 3.0)
@@ -470,25 +511,31 @@
mr.submit()
se_doc = make_stock_entry(mr.name)
- se_doc.update({
- "posting_date": "2013-03-01",
- "posting_time": "00:00",
- "fiscal_year": "_Test Fiscal Year 2013",
- })
- se_doc.get("items")[0].update({
- "qty": 60.0,
- "transfer_qty": 60.0,
- "s_warehouse": "_Test Warehouse - _TC",
- "t_warehouse": "_Test Warehouse 1 - _TC",
- "basic_rate": 1.0
- })
- se_doc.get("items")[1].update({
- "item_code": "_Test Item Home Desktop 100",
- "qty": 3.0,
- "transfer_qty": 3.0,
- "s_warehouse": "_Test Warehouse 1 - _TC",
- "basic_rate": 1.0
- })
+ se_doc.update(
+ {
+ "posting_date": "2013-03-01",
+ "posting_time": "00:00",
+ "fiscal_year": "_Test Fiscal Year 2013",
+ }
+ )
+ se_doc.get("items")[0].update(
+ {
+ "qty": 60.0,
+ "transfer_qty": 60.0,
+ "s_warehouse": "_Test Warehouse - _TC",
+ "t_warehouse": "_Test Warehouse 1 - _TC",
+ "basic_rate": 1.0,
+ }
+ )
+ se_doc.get("items")[1].update(
+ {
+ "item_code": "_Test Item Home Desktop 100",
+ "qty": 3.0,
+ "transfer_qty": 3.0,
+ "s_warehouse": "_Test Warehouse 1 - _TC",
+ "basic_rate": 1.0,
+ }
+ )
# check for stopped status of Material Request
se = frappe.copy_doc(se_doc)
@@ -505,18 +552,20 @@
def test_warehouse_company_validation(self):
from erpnext.stock.utils import InvalidWarehouseCompany
+
mr = frappe.copy_doc(test_records[0])
mr.company = "_Test Company 1"
self.assertRaises(InvalidWarehouseCompany, mr.insert)
def _get_requested_qty(self, item_code, warehouse):
- return flt(frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "indented_qty"))
+ return flt(
+ frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "indented_qty")
+ )
def test_make_stock_entry_for_material_issue(self):
mr = frappe.copy_doc(test_records[0]).insert()
- self.assertRaises(frappe.ValidationError, make_stock_entry,
- mr.name)
+ self.assertRaises(frappe.ValidationError, make_stock_entry, mr.name)
mr = frappe.get_doc("Material Request", mr.name)
mr.material_request_type = "Material Issue"
@@ -528,8 +577,13 @@
def test_completed_qty_for_issue(self):
def _get_requested_qty():
- return flt(frappe.db.get_value("Bin", {"item_code": "_Test Item Home Desktop 100",
- "warehouse": "_Test Warehouse - _TC"}, "indented_qty"))
+ return flt(
+ frappe.db.get_value(
+ "Bin",
+ {"item_code": "_Test Item Home Desktop 100", "warehouse": "_Test Warehouse - _TC"},
+ "indented_qty",
+ )
+ )
existing_requested_qty = _get_requested_qty()
@@ -537,7 +591,7 @@
mr.material_request_type = "Material Issue"
mr.submit()
- #testing bin value after material request is submitted
+ # testing bin value after material request is submitted
self.assertEqual(_get_requested_qty(), existing_requested_qty - 54.0)
# receive items to allow issue
@@ -556,7 +610,7 @@
self.assertEqual(mr.get("items")[0].ordered_qty, 54.0)
self.assertEqual(mr.get("items")[1].ordered_qty, 3.0)
- #testing bin requested qty after issuing stock against material request
+ # testing bin requested qty after issuing stock against material request
self.assertEqual(_get_requested_qty(), existing_requested_qty)
def test_material_request_type_manufacture(self):
@@ -564,8 +618,11 @@
mr = frappe.get_doc("Material Request", mr.name)
mr.submit()
completed_qty = mr.items[0].ordered_qty
- requested_qty = frappe.db.sql("""select indented_qty from `tabBin` where \
- item_code= %s and warehouse= %s """, (mr.items[0].item_code, mr.items[0].warehouse))[0][0]
+ requested_qty = frappe.db.sql(
+ """select indented_qty from `tabBin` where \
+ item_code= %s and warehouse= %s """,
+ (mr.items[0].item_code, mr.items[0].warehouse),
+ )[0][0]
prod_order = raise_work_orders(mr.name)
po = frappe.get_doc("Work Order", prod_order[0])
@@ -575,8 +632,11 @@
mr = frappe.get_doc("Material Request", mr.name)
self.assertEqual(completed_qty + po.qty, mr.items[0].ordered_qty)
- new_requested_qty = frappe.db.sql("""select indented_qty from `tabBin` where \
- item_code= %s and warehouse= %s """, (mr.items[0].item_code, mr.items[0].warehouse))[0][0]
+ new_requested_qty = frappe.db.sql(
+ """select indented_qty from `tabBin` where \
+ item_code= %s and warehouse= %s """,
+ (mr.items[0].item_code, mr.items[0].warehouse),
+ )[0][0]
self.assertEqual(requested_qty - po.qty, new_requested_qty)
@@ -585,17 +645,24 @@
mr = frappe.get_doc("Material Request", mr.name)
self.assertEqual(completed_qty, mr.items[0].ordered_qty)
- new_requested_qty = frappe.db.sql("""select indented_qty from `tabBin` where \
- item_code= %s and warehouse= %s """, (mr.items[0].item_code, mr.items[0].warehouse))[0][0]
+ new_requested_qty = frappe.db.sql(
+ """select indented_qty from `tabBin` where \
+ item_code= %s and warehouse= %s """,
+ (mr.items[0].item_code, mr.items[0].warehouse),
+ )[0][0]
self.assertEqual(requested_qty, new_requested_qty)
def test_requested_qty_multi_uom(self):
- existing_requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC')
+ existing_requested_qty = self._get_requested_qty("_Test FG Item", "_Test Warehouse - _TC")
- mr = make_material_request(item_code='_Test FG Item', material_request_type='Manufacture',
- uom="_Test UOM 1", conversion_factor=12)
+ mr = make_material_request(
+ item_code="_Test FG Item",
+ material_request_type="Manufacture",
+ uom="_Test UOM 1",
+ conversion_factor=12,
+ )
- requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC')
+ requested_qty = self._get_requested_qty("_Test FG Item", "_Test Warehouse - _TC")
self.assertEqual(requested_qty, existing_requested_qty + 120)
@@ -605,42 +672,36 @@
wo.wip_warehouse = "_Test Warehouse 1 - _TC"
wo.submit()
- requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC')
+ requested_qty = self._get_requested_qty("_Test FG Item", "_Test Warehouse - _TC")
self.assertEqual(requested_qty, existing_requested_qty + 70)
wo.cancel()
- requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC')
+ requested_qty = self._get_requested_qty("_Test FG Item", "_Test Warehouse - _TC")
self.assertEqual(requested_qty, existing_requested_qty + 120)
mr.reload()
mr.cancel()
- requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC')
+ requested_qty = self._get_requested_qty("_Test FG Item", "_Test Warehouse - _TC")
self.assertEqual(requested_qty, existing_requested_qty)
-
def test_multi_uom_for_purchase(self):
mr = frappe.copy_doc(test_records[0])
- mr.material_request_type = 'Purchase'
+ mr.material_request_type = "Purchase"
item = mr.items[0]
mr.schedule_date = today()
- if not frappe.db.get_value('UOM Conversion Detail',
- {'parent': item.item_code, 'uom': 'Kg'}):
- item_doc = frappe.get_doc('Item', item.item_code)
- item_doc.append('uoms', {
- 'uom': 'Kg',
- 'conversion_factor': 5
- })
+ if not frappe.db.get_value("UOM Conversion Detail", {"parent": item.item_code, "uom": "Kg"}):
+ item_doc = frappe.get_doc("Item", item.item_code)
+ item_doc.append("uoms", {"uom": "Kg", "conversion_factor": 5})
item_doc.save(ignore_permissions=True)
- item.uom = 'Kg'
+ item.uom = "Kg"
for item in mr.items:
item.schedule_date = mr.schedule_date
mr.insert()
- self.assertRaises(frappe.ValidationError, make_purchase_order,
- mr.name)
+ self.assertRaises(frappe.ValidationError, make_purchase_order, mr.name)
mr = frappe.get_doc("Material Request", mr.name)
mr.submit()
@@ -654,17 +715,19 @@
self.assertEqual(po.doctype, "Purchase Order")
self.assertEqual(len(po.get("items")), len(mr.get("items")))
- po.supplier = '_Test Supplier'
+ po.supplier = "_Test Supplier"
po.insert()
po.submit()
mr = frappe.get_doc("Material Request", mr.name)
self.assertEqual(mr.per_ordered, 100)
def test_customer_provided_parts_mr(self):
- create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
+ create_item(
+ "CUST-0987", is_customer_provided_item=1, customer="_Test Customer", is_purchase_item=0
+ )
existing_requested_qty = self._get_requested_qty("_Test Customer", "_Test Warehouse - _TC")
- mr = make_material_request(item_code='CUST-0987', material_request_type='Customer Provided')
+ mr = make_material_request(item_code="CUST-0987", material_request_type="Customer Provided")
se = make_stock_entry(mr.name)
se.insert()
se.submit()
@@ -677,25 +740,30 @@
self.assertEqual(mr.per_ordered, 100)
self.assertEqual(existing_requested_qty, current_requested_qty)
+
def make_material_request(**args):
args = frappe._dict(args)
mr = frappe.new_doc("Material Request")
mr.material_request_type = args.material_request_type or "Purchase"
mr.company = args.company or "_Test Company"
- mr.customer = args.customer or '_Test Customer'
- mr.append("items", {
- "item_code": args.item_code or "_Test Item",
- "qty": args.qty or 10,
- "uom": args.uom or "_Test UOM",
- "conversion_factor": args.conversion_factor or 1,
- "schedule_date": args.schedule_date or today(),
- "warehouse": args.warehouse or "_Test Warehouse - _TC",
- "cost_center": args.cost_center or "_Test Cost Center - _TC"
- })
+ mr.customer = args.customer or "_Test Customer"
+ mr.append(
+ "items",
+ {
+ "item_code": args.item_code or "_Test Item",
+ "qty": args.qty or 10,
+ "uom": args.uom or "_Test UOM",
+ "conversion_factor": args.conversion_factor or 1,
+ "schedule_date": args.schedule_date or today(),
+ "warehouse": args.warehouse or "_Test Warehouse - _TC",
+ "cost_center": args.cost_center or "_Test Cost Center - _TC",
+ },
+ )
mr.insert()
if not args.do_not_submit:
mr.submit()
return mr
+
test_dependencies = ["Currency Exchange", "BOM"]
-test_records = frappe.get_test_records('Material Request')
+test_records = frappe.get_test_records("Material Request")
diff --git a/erpnext/stock/doctype/material_request_item/material_request_item.py b/erpnext/stock/doctype/material_request_item/material_request_item.py
index 32407d0..08c9ed2 100644
--- a/erpnext/stock/doctype/material_request_item/material_request_item.py
+++ b/erpnext/stock/doctype/material_request_item/material_request_item.py
@@ -11,5 +11,6 @@
class MaterialRequestItem(Document):
pass
+
def on_doctype_update():
frappe.db.add_index("Material Request Item", ["item_code", "warehouse"])
diff --git a/erpnext/stock/doctype/packed_item/packed_item.py b/erpnext/stock/doctype/packed_item/packed_item.py
index f9c00c5..026dd4e 100644
--- a/erpnext/stock/doctype/packed_item/packed_item.py
+++ b/erpnext/stock/doctype/packed_item/packed_item.py
@@ -23,7 +23,9 @@
return
parent_items_price, reset = {}, False
- set_price_from_children = frappe.db.get_single_value("Selling Settings", "editable_bundle_item_rates")
+ set_price_from_children = frappe.db.get_single_value(
+ "Selling Settings", "editable_bundle_item_rates"
+ )
stale_packed_items_table = get_indexed_packed_items_table(doc)
@@ -33,9 +35,11 @@
if frappe.db.exists("Product Bundle", {"new_item_code": item_row.item_code}):
for bundle_item in get_product_bundle_items(item_row.item_code):
pi_row = add_packed_item_row(
- doc=doc, packing_item=bundle_item,
- main_item_row=item_row, packed_items_table=stale_packed_items_table,
- reset=reset
+ doc=doc,
+ packing_item=bundle_item,
+ main_item_row=item_row,
+ packed_items_table=stale_packed_items_table,
+ reset=reset,
)
item_data = get_packed_item_details(bundle_item.item_code, doc.company)
update_packed_item_basic_data(item_row, pi_row, bundle_item, item_data)
@@ -43,18 +47,19 @@
update_packed_item_price_data(pi_row, item_data, doc)
update_packed_item_from_cancelled_doc(item_row, bundle_item, pi_row, doc)
- if set_price_from_children: # create/update bundle item wise price dict
+ if set_price_from_children: # create/update bundle item wise price dict
update_product_bundle_rate(parent_items_price, pi_row)
if parent_items_price:
- set_product_bundle_rate_amount(doc, parent_items_price) # set price in bundle item
+ set_product_bundle_rate_amount(doc, parent_items_price) # set price in bundle item
+
def get_indexed_packed_items_table(doc):
"""
- Create dict from stale packed items table like:
- {(Parent Item 1, Bundle Item 1, ae4b5678): {...}, (key): {value}}
+ Create dict from stale packed items table like:
+ {(Parent Item 1, Bundle Item 1, ae4b5678): {...}, (key): {value}}
- Use: to quickly retrieve/check if row existed in table instead of looping n times
+ Use: to quickly retrieve/check if row existed in table instead of looping n times
"""
indexed_table = {}
for packed_item in doc.get("packed_items"):
@@ -63,6 +68,7 @@
return indexed_table
+
def reset_packing_list(doc):
"Conditionally reset the table and return if it was reset or not."
reset_table = False
@@ -86,33 +92,34 @@
doc.set("packed_items", [])
return reset_table
+
def get_product_bundle_items(item_code):
product_bundle = frappe.qb.DocType("Product Bundle")
product_bundle_item = frappe.qb.DocType("Product Bundle Item")
query = (
frappe.qb.from_(product_bundle_item)
- .join(product_bundle).on(product_bundle_item.parent == product_bundle.name)
+ .join(product_bundle)
+ .on(product_bundle_item.parent == product_bundle.name)
.select(
product_bundle_item.item_code,
product_bundle_item.qty,
product_bundle_item.uom,
- product_bundle_item.description
- ).where(
- product_bundle.new_item_code == item_code
- ).orderby(
- product_bundle_item.idx
+ product_bundle_item.description,
)
+ .where(product_bundle.new_item_code == item_code)
+ .orderby(product_bundle_item.idx)
)
return query.run(as_dict=True)
+
def add_packed_item_row(doc, packing_item, main_item_row, packed_items_table, reset):
"""Add and return packed item row.
- doc: Transaction document
- packing_item (dict): Packed Item details
- main_item_row (dict): Items table row corresponding to packed item
- packed_items_table (dict): Packed Items table before save (indexed)
- reset (bool): State if table is reset or preserved as is
+ doc: Transaction document
+ packing_item (dict): Packed Item details
+ main_item_row (dict): Items table row corresponding to packed item
+ packed_items_table (dict): Packed Items table before save (indexed)
+ reset (bool): State if table is reset or preserved as is
"""
exists, pi_row = False, {}
@@ -122,33 +129,34 @@
pi_row, exists = packed_items_table.get(key), True
if not exists:
- pi_row = doc.append('packed_items', {})
- elif reset: # add row if row exists but table is reset
+ pi_row = doc.append("packed_items", {})
+ elif reset: # add row if row exists but table is reset
pi_row.idx, pi_row.name = None, None
- pi_row = doc.append('packed_items', pi_row)
+ pi_row = doc.append("packed_items", pi_row)
return pi_row
+
def get_packed_item_details(item_code, company):
item = frappe.qb.DocType("Item")
item_default = frappe.qb.DocType("Item Default")
query = (
frappe.qb.from_(item)
.left_join(item_default)
- .on(
- (item_default.parent == item.name)
- & (item_default.company == company)
- ).select(
- item.item_name, item.is_stock_item,
- item.description, item.stock_uom,
+ .on((item_default.parent == item.name) & (item_default.company == company))
+ .select(
+ item.item_name,
+ item.is_stock_item,
+ item.description,
+ item.stock_uom,
item.valuation_rate,
- item_default.default_warehouse
- ).where(
- item.name == item_code
+ item_default.default_warehouse,
)
+ .where(item.name == item_code)
)
return query.run(as_dict=True)[0]
+
def update_packed_item_basic_data(main_item_row, pi_row, packing_item, item_data):
pi_row.parent_item = main_item_row.item_code
pi_row.parent_detail_docname = main_item_row.name
@@ -161,12 +169,16 @@
if not pi_row.description:
pi_row.description = packing_item.get("description")
+
def update_packed_item_stock_data(main_item_row, pi_row, packing_item, item_data, doc):
# TODO batch_no, actual_batch_qty, incoming_rate
if not pi_row.warehouse and not doc.amended_from:
- fetch_warehouse = (doc.get('is_pos') or item_data.is_stock_item or not item_data.default_warehouse)
- pi_row.warehouse = (main_item_row.warehouse if (fetch_warehouse and main_item_row.warehouse)
- else item_data.default_warehouse)
+ fetch_warehouse = doc.get("is_pos") or item_data.is_stock_item or not item_data.default_warehouse
+ pi_row.warehouse = (
+ main_item_row.warehouse
+ if (fetch_warehouse and main_item_row.warehouse)
+ else item_data.default_warehouse
+ )
if not pi_row.target_warehouse:
pi_row.target_warehouse = main_item_row.get("target_warehouse")
@@ -175,6 +187,7 @@
pi_row.actual_qty = flt(bin.get("actual_qty"))
pi_row.projected_qty = flt(bin.get("projected_qty"))
+
def update_packed_item_price_data(pi_row, item_data, doc):
"Set price as per price list or from the Item master."
if pi_row.rate:
@@ -182,50 +195,60 @@
item_doc = frappe.get_cached_doc("Item", pi_row.item_code)
row_data = pi_row.as_dict().copy()
- row_data.update({
- "company": doc.get("company"),
- "price_list": doc.get("selling_price_list"),
- "currency": doc.get("currency"),
- "conversion_rate": doc.get("conversion_rate"),
- })
+ row_data.update(
+ {
+ "company": doc.get("company"),
+ "price_list": doc.get("selling_price_list"),
+ "currency": doc.get("currency"),
+ "conversion_rate": doc.get("conversion_rate"),
+ }
+ )
rate = get_price_list_rate(row_data, item_doc).get("price_list_rate")
pi_row.rate = rate or item_data.get("valuation_rate") or 0.0
+
def update_packed_item_from_cancelled_doc(main_item_row, packing_item, pi_row, doc):
"Update packed item row details from cancelled doc into amended doc."
prev_doc_packed_items_map = None
if doc.amended_from:
prev_doc_packed_items_map = get_cancelled_doc_packed_item_details(doc.packed_items)
- if prev_doc_packed_items_map and prev_doc_packed_items_map.get((packing_item.item_code, main_item_row.item_code)):
+ if prev_doc_packed_items_map and prev_doc_packed_items_map.get(
+ (packing_item.item_code, main_item_row.item_code)
+ ):
prev_doc_row = prev_doc_packed_items_map.get((packing_item.item_code, main_item_row.item_code))
pi_row.batch_no = prev_doc_row[0].batch_no
pi_row.serial_no = prev_doc_row[0].serial_no
pi_row.warehouse = prev_doc_row[0].warehouse
+
def get_packed_item_bin_qty(item, warehouse):
bin_data = frappe.db.get_values(
"Bin",
fieldname=["actual_qty", "projected_qty"],
filters={"item_code": item, "warehouse": warehouse},
- as_dict=True
+ as_dict=True,
)
return bin_data[0] if bin_data else {}
+
def get_cancelled_doc_packed_item_details(old_packed_items):
prev_doc_packed_items_map = {}
for items in old_packed_items:
- prev_doc_packed_items_map.setdefault((items.item_code ,items.parent_item), []).append(items.as_dict())
+ prev_doc_packed_items_map.setdefault((items.item_code, items.parent_item), []).append(
+ items.as_dict()
+ )
return prev_doc_packed_items_map
+
def update_product_bundle_rate(parent_items_price, pi_row):
"""
- Update the price dict of Product Bundles based on the rates of the Items in the bundle.
+ Update the price dict of Product Bundles based on the rates of the Items in the bundle.
- Stucture:
- {(Bundle Item 1, ae56fgji): 150.0, (Bundle Item 2, bc78fkjo): 200.0}
+ Stucture:
+ {(Bundle Item 1, ae56fgji): 150.0, (Bundle Item 2, bc78fkjo): 200.0}
"""
key = (pi_row.parent_item, pi_row.parent_detail_docname)
rate = parent_items_price.get(key)
@@ -234,6 +257,7 @@
parent_items_price[key] += flt(pi_row.rate)
+
def set_product_bundle_rate_amount(doc, parent_items_price):
"Set cumulative rate and amount in bundle item."
for item in doc.get("items"):
@@ -242,6 +266,7 @@
item.rate = bundle_rate
item.amount = flt(bundle_rate * item.qty)
+
def on_doctype_update():
frappe.db.add_index("Packed Item", ["item_code", "warehouse"])
@@ -252,10 +277,7 @@
bundled_items = get_product_bundle_items(row["item_code"])
for item in bundled_items:
- row.update({
- "item_code": item.item_code,
- "qty": flt(row["quantity"]) * flt(item.qty)
- })
+ row.update({"item_code": item.item_code, "qty": flt(row["quantity"]) * flt(item.qty)})
items.append(get_item_details(row))
return items
diff --git a/erpnext/stock/doctype/packed_item/test_packed_item.py b/erpnext/stock/doctype/packed_item/test_packed_item.py
index 5f1b954..fe1b0d9 100644
--- a/erpnext/stock/doctype/packed_item/test_packed_item.py
+++ b/erpnext/stock/doctype/packed_item/test_packed_item.py
@@ -14,6 +14,7 @@
class TestPackedItem(FrappeTestCase):
"Test impact on Packed Items table in various scenarios."
+
@classmethod
def setUpClass(cls) -> None:
super().setUpClass()
@@ -39,8 +40,7 @@
def test_adding_bundle_item(self):
"Test impact on packed items if bundle item row is added."
- so = make_sales_order(item_code = self.bundle, qty=1,
- do_not_submit=True)
+ so = make_sales_order(item_code=self.bundle, qty=1, do_not_submit=True)
self.assertEqual(so.items[0].qty, 1)
self.assertEqual(len(so.packed_items), 2)
@@ -51,7 +51,7 @@
"Test impact on packed items if bundle item row is updated."
so = make_sales_order(item_code=self.bundle, qty=1, do_not_submit=True)
- so.items[0].qty = 2 # change qty
+ so.items[0].qty = 2 # change qty
so.save()
self.assertEqual(so.packed_items[0].qty, 4)
@@ -67,12 +67,9 @@
"Test impact on packed items if same bundle item is added and removed."
so_items = []
for qty in [2, 4, 6, 8]:
- so_items.append({
- "item_code": self.bundle,
- "qty": qty,
- "rate": 400,
- "warehouse": "_Test Warehouse - _TC"
- })
+ so_items.append(
+ {"item_code": self.bundle, "qty": qty, "rate": 400, "warehouse": "_Test Warehouse - _TC"}
+ )
# create SO with recurring bundle item
so = make_sales_order(item_list=so_items, do_not_submit=True)
@@ -120,18 +117,15 @@
"Test impact on packed items in newly mapped DN from SO."
so_items = []
for qty in [2, 4]:
- so_items.append({
- "item_code": self.bundle,
- "qty": qty,
- "rate": 400,
- "warehouse": "_Test Warehouse - _TC"
- })
+ so_items.append(
+ {"item_code": self.bundle, "qty": qty, "rate": 400, "warehouse": "_Test Warehouse - _TC"}
+ )
# create SO with recurring bundle item
so = make_sales_order(item_list=so_items)
dn = make_delivery_note(so.name)
- dn.items[1].qty = 3 # change second row qty for inserting doc
+ dn.items[1].qty = 3 # change second row qty for inserting doc
dn.save()
self.assertEqual(len(dn.packed_items), 4)
@@ -148,7 +142,7 @@
for item in self.bundle_items:
make_stock_entry(item_code=item, to_warehouse=warehouse, qty=10, rate=100, posting_date=today)
- so = make_sales_order(item_code = self.bundle, qty=1, company=company, warehouse=warehouse)
+ so = make_sales_order(item_code=self.bundle, qty=1, company=company, warehouse=warehouse)
dn = make_delivery_note(so.name)
dn.save()
@@ -159,7 +153,9 @@
# backdated stock entry
for item in self.bundle_items:
- make_stock_entry(item_code=item, to_warehouse=warehouse, qty=10, rate=200, posting_date=yesterday)
+ make_stock_entry(
+ item_code=item, to_warehouse=warehouse, qty=10, rate=200, posting_date=yesterday
+ )
# assert correct reposting
gles = get_gl_entries(dn.doctype, dn.name)
@@ -173,8 +169,7 @@
sort_function = lambda p: (p.parent_item, p.item_code, p.qty)
for sent, returned in zip(
- sorted(original, key=sort_function),
- sorted(returned, key=sort_function)
+ sorted(original, key=sort_function), sorted(returned, key=sort_function)
):
self.assertEqual(sent.item_code, returned.item_code)
self.assertEqual(sent.parent_item, returned.parent_item)
@@ -195,7 +190,7 @@
"warehouse": self.warehouse,
"qty": 1,
"rate": 100,
- }
+ },
]
so = make_sales_order(item_list=item_list, warehouse=self.warehouse)
@@ -224,7 +219,7 @@
"warehouse": self.warehouse,
"qty": 1,
"rate": 100,
- }
+ },
]
so = make_sales_order(item_list=item_list, warehouse=self.warehouse)
@@ -246,11 +241,10 @@
expected_returns = [d for d in dn.packed_items if d.parent_item == self.bundle]
self.assertReturns(expected_returns, dn_ret.packed_items)
-
def test_returning_partial_bundle_qty(self):
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return
- so = make_sales_order(item_code=self.bundle, warehouse=self.warehouse, qty = 2)
+ so = make_sales_order(item_code=self.bundle, warehouse=self.warehouse, qty=2)
dn = make_delivery_note(so.name)
dn.save()
diff --git a/erpnext/stock/doctype/packing_slip/packing_slip.py b/erpnext/stock/doctype/packing_slip/packing_slip.py
index b092862..e9ccf5f 100644
--- a/erpnext/stock/doctype/packing_slip/packing_slip.py
+++ b/erpnext/stock/doctype/packing_slip/packing_slip.py
@@ -10,14 +10,13 @@
class PackingSlip(Document):
-
def validate(self):
"""
- * Validate existence of submitted Delivery Note
- * Case nos do not overlap
- * Check if packed qty doesn't exceed actual qty of delivery note
+ * Validate existence of submitted Delivery Note
+ * Case nos do not overlap
+ * Check if packed qty doesn't exceed actual qty of delivery note
- It is necessary to validate case nos before checking quantity
+ It is necessary to validate case nos before checking quantity
"""
self.validate_delivery_note()
self.validate_items_mandatory()
@@ -25,12 +24,13 @@
self.validate_qty()
from erpnext.utilities.transaction_base import validate_uom_is_integer
+
validate_uom_is_integer(self, "stock_uom", "qty")
validate_uom_is_integer(self, "weight_uom", "net_weight")
def validate_delivery_note(self):
"""
- Validates if delivery note has status as draft
+ Validates if delivery note has status as draft
"""
if cint(frappe.db.get_value("Delivery Note", self.delivery_note, "docstatus")) != 0:
frappe.throw(_("Delivery Note {0} must not be submitted").format(self.delivery_note))
@@ -42,27 +42,33 @@
def validate_case_nos(self):
"""
- Validate if case nos overlap. If they do, recommend next case no.
+ Validate if case nos overlap. If they do, recommend next case no.
"""
if not cint(self.from_case_no):
frappe.msgprint(_("Please specify a valid 'From Case No.'"), raise_exception=1)
elif not self.to_case_no:
self.to_case_no = self.from_case_no
elif cint(self.from_case_no) > cint(self.to_case_no):
- frappe.msgprint(_("'To Case No.' cannot be less than 'From Case No.'"),
- raise_exception=1)
+ frappe.msgprint(_("'To Case No.' cannot be less than 'From Case No.'"), raise_exception=1)
- res = frappe.db.sql("""SELECT name FROM `tabPacking Slip`
+ res = frappe.db.sql(
+ """SELECT name FROM `tabPacking Slip`
WHERE delivery_note = %(delivery_note)s AND docstatus = 1 AND
((from_case_no BETWEEN %(from_case_no)s AND %(to_case_no)s)
OR (to_case_no BETWEEN %(from_case_no)s AND %(to_case_no)s)
OR (%(from_case_no)s BETWEEN from_case_no AND to_case_no))
- """, {"delivery_note":self.delivery_note,
- "from_case_no":self.from_case_no,
- "to_case_no":self.to_case_no})
+ """,
+ {
+ "delivery_note": self.delivery_note,
+ "from_case_no": self.from_case_no,
+ "to_case_no": self.to_case_no,
+ },
+ )
if res:
- frappe.throw(_("""Case No(s) already in use. Try from Case No {0}""").format(self.get_recommended_case_no()))
+ frappe.throw(
+ _("""Case No(s) already in use. Try from Case No {0}""").format(self.get_recommended_case_no())
+ )
def validate_qty(self):
"""Check packed qty across packing slips and delivery note"""
@@ -70,36 +76,37 @@
dn_details, ps_item_qty, no_of_cases = self.get_details_for_packing()
for item in dn_details:
- new_packed_qty = (flt(ps_item_qty[item['item_code']]) * no_of_cases) + \
- flt(item['packed_qty'])
- if new_packed_qty > flt(item['qty']) and no_of_cases:
+ new_packed_qty = (flt(ps_item_qty[item["item_code"]]) * no_of_cases) + flt(item["packed_qty"])
+ if new_packed_qty > flt(item["qty"]) and no_of_cases:
self.recommend_new_qty(item, ps_item_qty, no_of_cases)
-
def get_details_for_packing(self):
"""
- Returns
- * 'Delivery Note Items' query result as a list of dict
- * Item Quantity dict of current packing slip doc
- * No. of Cases of this packing slip
+ Returns
+ * 'Delivery Note Items' query result as a list of dict
+ * Item Quantity dict of current packing slip doc
+ * No. of Cases of this packing slip
"""
rows = [d.item_code for d in self.get("items")]
# also pick custom fields from delivery note
- custom_fields = ', '.join('dni.`{0}`'.format(d.fieldname)
+ custom_fields = ", ".join(
+ "dni.`{0}`".format(d.fieldname)
for d in frappe.get_meta("Delivery Note Item").get_custom_fields()
- if d.fieldtype not in no_value_fields)
+ if d.fieldtype not in no_value_fields
+ )
if custom_fields:
- custom_fields = ', ' + custom_fields
+ custom_fields = ", " + custom_fields
condition = ""
if rows:
- condition = " and item_code in (%s)" % (", ".join(["%s"]*len(rows)))
+ condition = " and item_code in (%s)" % (", ".join(["%s"] * len(rows)))
# gets item code, qty per item code, latest packed qty per item code and stock uom
- res = frappe.db.sql("""select item_code, sum(qty) as qty,
+ res = frappe.db.sql(
+ """select item_code, sum(qty) as qty,
(select sum(psi.qty * (abs(ps.to_case_no - ps.from_case_no) + 1))
from `tabPacking Slip` ps, `tabPacking Slip Item` psi
where ps.name = psi.parent and ps.docstatus = 1
@@ -107,47 +114,57 @@
stock_uom, item_name, description, dni.batch_no {custom_fields}
from `tabDelivery Note Item` dni
where parent=%s {condition}
- group by item_code""".format(condition=condition, custom_fields=custom_fields),
- tuple([self.delivery_note] + rows), as_dict=1)
+ group by item_code""".format(
+ condition=condition, custom_fields=custom_fields
+ ),
+ tuple([self.delivery_note] + rows),
+ as_dict=1,
+ )
ps_item_qty = dict([[d.item_code, d.qty] for d in self.get("items")])
no_of_cases = cint(self.to_case_no) - cint(self.from_case_no) + 1
return res, ps_item_qty, no_of_cases
-
def recommend_new_qty(self, item, ps_item_qty, no_of_cases):
"""
- Recommend a new quantity and raise a validation exception
+ Recommend a new quantity and raise a validation exception
"""
- item['recommended_qty'] = (flt(item['qty']) - flt(item['packed_qty'])) / no_of_cases
- item['specified_qty'] = flt(ps_item_qty[item['item_code']])
- if not item['packed_qty']: item['packed_qty'] = 0
+ item["recommended_qty"] = (flt(item["qty"]) - flt(item["packed_qty"])) / no_of_cases
+ item["specified_qty"] = flt(ps_item_qty[item["item_code"]])
+ if not item["packed_qty"]:
+ item["packed_qty"] = 0
- frappe.throw(_("Quantity for Item {0} must be less than {1}").format(item.get("item_code"), item.get("recommended_qty")))
+ frappe.throw(
+ _("Quantity for Item {0} must be less than {1}").format(
+ item.get("item_code"), item.get("recommended_qty")
+ )
+ )
def update_item_details(self):
"""
- Fill empty columns in Packing Slip Item
+ Fill empty columns in Packing Slip Item
"""
if not self.from_case_no:
self.from_case_no = self.get_recommended_case_no()
for d in self.get("items"):
- res = frappe.db.get_value("Item", d.item_code,
- ["weight_per_unit", "weight_uom"], as_dict=True)
+ res = frappe.db.get_value("Item", d.item_code, ["weight_per_unit", "weight_uom"], as_dict=True)
- if res and len(res)>0:
+ if res and len(res) > 0:
d.net_weight = res["weight_per_unit"]
d.weight_uom = res["weight_uom"]
def get_recommended_case_no(self):
"""
- Returns the next case no. for a new packing slip for a delivery
- note
+ Returns the next case no. for a new packing slip for a delivery
+ note
"""
- recommended_case_no = frappe.db.sql("""SELECT MAX(to_case_no) FROM `tabPacking Slip`
- WHERE delivery_note = %s AND docstatus=1""", self.delivery_note)
+ recommended_case_no = frappe.db.sql(
+ """SELECT MAX(to_case_no) FROM `tabPacking Slip`
+ WHERE delivery_note = %s AND docstatus=1""",
+ self.delivery_note,
+ )
return cint(recommended_case_no[0][0]) + 1
@@ -160,7 +177,7 @@
dn_details = self.get_details_for_packing()[0]
for item in dn_details:
if flt(item.qty) > flt(item.packed_qty):
- ch = self.append('items', {})
+ ch = self.append("items", {})
ch.item_code = item.item_code
ch.item_name = item.item_name
ch.stock_uom = item.stock_uom
@@ -175,14 +192,18 @@
self.update_item_details()
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def item_details(doctype, txt, searchfield, start, page_len, filters):
from erpnext.controllers.queries import get_match_cond
- return frappe.db.sql("""select name, item_name, description from `tabItem`
+
+ return frappe.db.sql(
+ """select name, item_name, description from `tabItem`
where name in ( select item_code FROM `tabDelivery Note Item`
where parent= %s)
and %s like "%s" %s
- limit %s, %s """ % ("%s", searchfield, "%s",
- get_match_cond(doctype), "%s", "%s"),
- ((filters or {}).get("delivery_note"), "%%%s%%" % txt, start, page_len))
+ limit %s, %s """
+ % ("%s", searchfield, "%s", get_match_cond(doctype), "%s", "%s"),
+ ((filters or {}).get("delivery_note"), "%%%s%%" % txt, start, page_len),
+ )
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index 35cbc2f..7061ee1 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -19,6 +19,7 @@
# TODO: Prioritize SO or WO group warehouse
+
class PickList(Document):
def validate(self):
self.validate_for_qty()
@@ -27,8 +28,11 @@
self.set_item_locations()
# set percentage picked in SO
- for location in self.get('locations'):
- if location.sales_order and frappe.db.get_value("Sales Order",location.sales_order,"per_picked") == 100:
+ for location in self.get("locations"):
+ if (
+ location.sales_order
+ and frappe.db.get_value("Sales Order", location.sales_order, "per_picked") == 100
+ ):
frappe.throw("Row " + str(location.idx) + " has been picked already!")
def before_submit(self):
@@ -39,44 +43,62 @@
if item.sales_order_item:
# update the picked_qty in SO Item
- self.update_so(item.sales_order_item,item.picked_qty,item.item_code)
+ self.update_so(item.sales_order_item, item.picked_qty, item.item_code)
- if not frappe.get_cached_value('Item', item.item_code, 'has_serial_no'):
+ if not frappe.get_cached_value("Item", item.item_code, "has_serial_no"):
continue
if not item.serial_no:
- frappe.throw(_("Row #{0}: {1} does not have any available serial numbers in {2}").format(
- frappe.bold(item.idx), frappe.bold(item.item_code), frappe.bold(item.warehouse)),
- title=_("Serial Nos Required"))
- if len(item.serial_no.split('\n')) == item.picked_qty:
+ frappe.throw(
+ _("Row #{0}: {1} does not have any available serial numbers in {2}").format(
+ frappe.bold(item.idx), frappe.bold(item.item_code), frappe.bold(item.warehouse)
+ ),
+ title=_("Serial Nos Required"),
+ )
+ if len(item.serial_no.split("\n")) == item.picked_qty:
continue
- frappe.throw(_('For item {0} at row {1}, count of serial numbers does not match with the picked quantity')
- .format(frappe.bold(item.item_code), frappe.bold(item.idx)), title=_("Quantity Mismatch"))
+ frappe.throw(
+ _(
+ "For item {0} at row {1}, count of serial numbers does not match with the picked quantity"
+ ).format(frappe.bold(item.item_code), frappe.bold(item.idx)),
+ title=_("Quantity Mismatch"),
+ )
def before_cancel(self):
- #update picked_qty in SO Item on cancel of PL
- for item in self.get('locations'):
+ # update picked_qty in SO Item on cancel of PL
+ for item in self.get("locations"):
if item.sales_order_item:
self.update_so(item.sales_order_item, -1 * item.picked_qty, item.item_code)
- def update_so(self,so_item,picked_qty,item_code):
- so_doc = frappe.get_doc("Sales Order",frappe.db.get_value("Sales Order Item",so_item,"parent"))
- already_picked,actual_qty = frappe.db.get_value("Sales Order Item",so_item,["picked_qty","qty"])
+ def update_so(self, so_item, picked_qty, item_code):
+ so_doc = frappe.get_doc(
+ "Sales Order", frappe.db.get_value("Sales Order Item", so_item, "parent")
+ )
+ already_picked, actual_qty = frappe.db.get_value(
+ "Sales Order Item", so_item, ["picked_qty", "qty"]
+ )
if self.docstatus == 1:
- if (((already_picked + picked_qty)/ actual_qty)*100) > (100 + flt(frappe.db.get_single_value('Stock Settings', 'over_delivery_receipt_allowance'))):
- frappe.throw('You are picking more than required quantity for ' + item_code + '. Check if there is any other pick list created for '+so_doc.name)
+ if (((already_picked + picked_qty) / actual_qty) * 100) > (
+ 100 + flt(frappe.db.get_single_value("Stock Settings", "over_delivery_receipt_allowance"))
+ ):
+ frappe.throw(
+ "You are picking more than required quantity for "
+ + item_code
+ + ". Check if there is any other pick list created for "
+ + so_doc.name
+ )
- frappe.db.set_value("Sales Order Item",so_item,"picked_qty",already_picked+picked_qty)
+ frappe.db.set_value("Sales Order Item", so_item, "picked_qty", already_picked + picked_qty)
total_picked_qty = 0
total_so_qty = 0
- for item in so_doc.get('items'):
+ for item in so_doc.get("items"):
total_picked_qty += flt(item.picked_qty)
total_so_qty += flt(item.stock_qty)
- total_picked_qty=total_picked_qty + picked_qty
- per_picked = total_picked_qty/total_so_qty * 100
+ total_picked_qty = total_picked_qty + picked_qty
+ per_picked = total_picked_qty / total_so_qty * 100
- so_doc.db_set("per_picked", flt(per_picked) ,update_modified=False)
+ so_doc.db_set("per_picked", flt(per_picked), update_modified=False)
@frappe.whitelist()
def set_item_locations(self, save=False):
@@ -86,20 +108,26 @@
from_warehouses = None
if self.parent_warehouse:
- from_warehouses = frappe.db.get_descendants('Warehouse', self.parent_warehouse)
+ from_warehouses = frappe.db.get_descendants("Warehouse", self.parent_warehouse)
# Create replica before resetting, to handle empty table on update after submit.
- locations_replica = self.get('locations')
+ locations_replica = self.get("locations")
# reset
- self.delete_key('locations')
+ self.delete_key("locations")
for item_doc in items:
item_code = item_doc.item_code
- self.item_location_map.setdefault(item_code,
- get_available_item_locations(item_code, from_warehouses, self.item_count_map.get(item_code), self.company))
+ self.item_location_map.setdefault(
+ item_code,
+ get_available_item_locations(
+ item_code, from_warehouses, self.item_count_map.get(item_code), self.company
+ ),
+ )
- locations = get_items_with_location_and_quantity(item_doc, self.item_location_map, self.docstatus)
+ locations = get_items_with_location_and_quantity(
+ item_doc, self.item_location_map, self.docstatus
+ )
item_doc.idx = None
item_doc.name = None
@@ -107,23 +135,28 @@
for row in locations:
location = item_doc.as_dict()
location.update(row)
- self.append('locations', location)
+ self.append("locations", location)
# If table is empty on update after submit, set stock_qty, picked_qty to 0 so that indicator is red
# and give feedback to the user. This is to avoid empty Pick Lists.
- if not self.get('locations') and self.docstatus == 1:
+ if not self.get("locations") and self.docstatus == 1:
for location in locations_replica:
location.stock_qty = 0
location.picked_qty = 0
- self.append('locations', location)
- frappe.msgprint(_("Please Restock Items and Update the Pick List to continue. To discontinue, cancel the Pick List."),
- title=_("Out of Stock"), indicator="red")
+ self.append("locations", location)
+ frappe.msgprint(
+ _(
+ "Please Restock Items and Update the Pick List to continue. To discontinue, cancel the Pick List."
+ ),
+ title=_("Out of Stock"),
+ indicator="red",
+ )
if save:
self.save()
def aggregate_item_qty(self):
- locations = self.get('locations')
+ locations = self.get("locations")
self.item_count_map = {}
# aggregate qty for same item
item_map = OrderedDict()
@@ -150,8 +183,9 @@
return item_map.values()
def validate_for_qty(self):
- if self.purpose == "Material Transfer for Manufacture" \
- and (self.for_qty is None or self.for_qty == 0):
+ if self.purpose == "Material Transfer for Manufacture" and (
+ self.for_qty is None or self.for_qty == 0
+ ):
frappe.throw(_("Qty of Finished Goods Item should be greater than 0."))
def before_print(self, settings=None):
@@ -163,7 +197,7 @@
group_picked_qty = defaultdict(float)
for item in self.locations:
- group_item_qty[(item.item_code, item.warehouse)] += item.qty
+ group_item_qty[(item.item_code, item.warehouse)] += item.qty
group_picked_qty[(item.item_code, item.warehouse)] += item.picked_qty
duplicate_list = []
@@ -187,37 +221,47 @@
if not pick_list.locations:
frappe.throw(_("Add items in the Item Locations table"))
+
def get_items_with_location_and_quantity(item_doc, item_location_map, docstatus):
available_locations = item_location_map.get(item_doc.item_code)
locations = []
# if stock qty is zero on submitted entry, show positive remaining qty to recalculate in case of restock.
- remaining_stock_qty = item_doc.qty if (docstatus == 1 and item_doc.stock_qty == 0) else item_doc.stock_qty
+ remaining_stock_qty = (
+ item_doc.qty if (docstatus == 1 and item_doc.stock_qty == 0) else item_doc.stock_qty
+ )
while remaining_stock_qty > 0 and available_locations:
item_location = available_locations.pop(0)
item_location = frappe._dict(item_location)
- stock_qty = remaining_stock_qty if item_location.qty >= remaining_stock_qty else item_location.qty
+ stock_qty = (
+ remaining_stock_qty if item_location.qty >= remaining_stock_qty else item_location.qty
+ )
qty = stock_qty / (item_doc.conversion_factor or 1)
- uom_must_be_whole_number = frappe.db.get_value('UOM', item_doc.uom, 'must_be_whole_number')
+ uom_must_be_whole_number = frappe.db.get_value("UOM", item_doc.uom, "must_be_whole_number")
if uom_must_be_whole_number:
qty = floor(qty)
stock_qty = qty * item_doc.conversion_factor
- if not stock_qty: break
+ if not stock_qty:
+ break
serial_nos = None
if item_location.serial_no:
- serial_nos = '\n'.join(item_location.serial_no[0: cint(stock_qty)])
+ serial_nos = "\n".join(item_location.serial_no[0 : cint(stock_qty)])
- locations.append(frappe._dict({
- 'qty': qty,
- 'stock_qty': stock_qty,
- 'warehouse': item_location.warehouse,
- 'serial_no': serial_nos,
- 'batch_no': item_location.batch_no
- }))
+ locations.append(
+ frappe._dict(
+ {
+ "qty": qty,
+ "stock_qty": stock_qty,
+ "warehouse": item_location.warehouse,
+ "serial_no": serial_nos,
+ "batch_no": item_location.batch_no,
+ }
+ )
+ )
remaining_stock_qty -= stock_qty
@@ -227,55 +271,69 @@
item_location.qty = qty_diff
if item_location.serial_no:
# set remaining serial numbers
- item_location.serial_no = item_location.serial_no[-int(qty_diff):]
+ item_location.serial_no = item_location.serial_no[-int(qty_diff) :]
available_locations = [item_location] + available_locations
# update available locations for the item
item_location_map[item_doc.item_code] = available_locations
return locations
-def get_available_item_locations(item_code, from_warehouses, required_qty, company, ignore_validation=False):
+
+def get_available_item_locations(
+ item_code, from_warehouses, required_qty, company, ignore_validation=False
+):
locations = []
- has_serial_no = frappe.get_cached_value('Item', item_code, 'has_serial_no')
- has_batch_no = frappe.get_cached_value('Item', item_code, 'has_batch_no')
+ has_serial_no = frappe.get_cached_value("Item", item_code, "has_serial_no")
+ has_batch_no = frappe.get_cached_value("Item", item_code, "has_batch_no")
if has_batch_no and has_serial_no:
- locations = get_available_item_locations_for_serial_and_batched_item(item_code, from_warehouses, required_qty, company)
+ locations = get_available_item_locations_for_serial_and_batched_item(
+ item_code, from_warehouses, required_qty, company
+ )
elif has_serial_no:
- locations = get_available_item_locations_for_serialized_item(item_code, from_warehouses, required_qty, company)
+ locations = get_available_item_locations_for_serialized_item(
+ item_code, from_warehouses, required_qty, company
+ )
elif has_batch_no:
- locations = get_available_item_locations_for_batched_item(item_code, from_warehouses, required_qty, company)
+ locations = get_available_item_locations_for_batched_item(
+ item_code, from_warehouses, required_qty, company
+ )
else:
- locations = get_available_item_locations_for_other_item(item_code, from_warehouses, required_qty, company)
+ locations = get_available_item_locations_for_other_item(
+ item_code, from_warehouses, required_qty, company
+ )
- total_qty_available = sum(location.get('qty') for location in locations)
+ total_qty_available = sum(location.get("qty") for location in locations)
remaining_qty = required_qty - total_qty_available
if remaining_qty > 0 and not ignore_validation:
- frappe.msgprint(_('{0} units of Item {1} is not available.')
- .format(remaining_qty, frappe.get_desk_link('Item', item_code)),
- title=_("Insufficient Stock"))
+ frappe.msgprint(
+ _("{0} units of Item {1} is not available.").format(
+ remaining_qty, frappe.get_desk_link("Item", item_code)
+ ),
+ title=_("Insufficient Stock"),
+ )
return locations
-def get_available_item_locations_for_serialized_item(item_code, from_warehouses, required_qty, company):
- filters = frappe._dict({
- 'item_code': item_code,
- 'company': company,
- 'warehouse': ['!=', '']
- })
+def get_available_item_locations_for_serialized_item(
+ item_code, from_warehouses, required_qty, company
+):
+ filters = frappe._dict({"item_code": item_code, "company": company, "warehouse": ["!=", ""]})
if from_warehouses:
- filters.warehouse = ['in', from_warehouses]
+ filters.warehouse = ["in", from_warehouses]
- serial_nos = frappe.get_all('Serial No',
- fields=['name', 'warehouse'],
+ serial_nos = frappe.get_all(
+ "Serial No",
+ fields=["name", "warehouse"],
filters=filters,
limit=required_qty,
- order_by='purchase_date',
- as_list=1)
+ order_by="purchase_date",
+ as_list=1,
+ )
warehouse_serial_nos_map = frappe._dict()
for serial_no, warehouse in serial_nos:
@@ -283,17 +341,17 @@
locations = []
for warehouse, serial_nos in warehouse_serial_nos_map.items():
- locations.append({
- 'qty': len(serial_nos),
- 'warehouse': warehouse,
- 'serial_no': serial_nos
- })
+ locations.append({"qty": len(serial_nos), "warehouse": warehouse, "serial_no": serial_nos})
return locations
-def get_available_item_locations_for_batched_item(item_code, from_warehouses, required_qty, company):
- warehouse_condition = 'and warehouse in %(warehouses)s' if from_warehouses else ''
- batch_locations = frappe.db.sql("""
+
+def get_available_item_locations_for_batched_item(
+ item_code, from_warehouses, required_qty, company
+):
+ warehouse_condition = "and warehouse in %(warehouses)s" if from_warehouses else ""
+ batch_locations = frappe.db.sql(
+ """
SELECT
sle.`warehouse`,
sle.`batch_no`,
@@ -314,84 +372,94 @@
sle.`item_code`
HAVING `qty` > 0
ORDER BY IFNULL(batch.`expiry_date`, '2200-01-01'), batch.`creation`
- """.format(warehouse_condition=warehouse_condition), { #nosec
- 'item_code': item_code,
- 'company': company,
- 'today': today(),
- 'warehouses': from_warehouses
- }, as_dict=1)
+ """.format(
+ warehouse_condition=warehouse_condition
+ ),
+ { # nosec
+ "item_code": item_code,
+ "company": company,
+ "today": today(),
+ "warehouses": from_warehouses,
+ },
+ as_dict=1,
+ )
return batch_locations
-def get_available_item_locations_for_serial_and_batched_item(item_code, from_warehouses, required_qty, company):
- # Get batch nos by FIFO
- locations = get_available_item_locations_for_batched_item(item_code, from_warehouses, required_qty, company)
- filters = frappe._dict({
- 'item_code': item_code,
- 'company': company,
- 'warehouse': ['!=', ''],
- 'batch_no': ''
- })
+def get_available_item_locations_for_serial_and_batched_item(
+ item_code, from_warehouses, required_qty, company
+):
+ # Get batch nos by FIFO
+ locations = get_available_item_locations_for_batched_item(
+ item_code, from_warehouses, required_qty, company
+ )
+
+ filters = frappe._dict(
+ {"item_code": item_code, "company": company, "warehouse": ["!=", ""], "batch_no": ""}
+ )
# Get Serial Nos by FIFO for Batch No
for location in locations:
filters.batch_no = location.batch_no
filters.warehouse = location.warehouse
- location.qty = required_qty if location.qty > required_qty else location.qty # if extra qty in batch
+ location.qty = (
+ required_qty if location.qty > required_qty else location.qty
+ ) # if extra qty in batch
- serial_nos = frappe.get_list('Serial No',
- fields=['name'],
- filters=filters,
- limit=location.qty,
- order_by='purchase_date')
+ serial_nos = frappe.get_list(
+ "Serial No", fields=["name"], filters=filters, limit=location.qty, order_by="purchase_date"
+ )
serial_nos = [sn.name for sn in serial_nos]
location.serial_no = serial_nos
return locations
+
def get_available_item_locations_for_other_item(item_code, from_warehouses, required_qty, company):
# gets all items available in different warehouses
- warehouses = [x.get('name') for x in frappe.get_list("Warehouse", {'company': company}, "name")]
+ warehouses = [x.get("name") for x in frappe.get_list("Warehouse", {"company": company}, "name")]
- filters = frappe._dict({
- 'item_code': item_code,
- 'warehouse': ['in', warehouses],
- 'actual_qty': ['>', 0]
- })
+ filters = frappe._dict(
+ {"item_code": item_code, "warehouse": ["in", warehouses], "actual_qty": [">", 0]}
+ )
if from_warehouses:
- filters.warehouse = ['in', from_warehouses]
+ filters.warehouse = ["in", from_warehouses]
- item_locations = frappe.get_all('Bin',
- fields=['warehouse', 'actual_qty as qty'],
+ item_locations = frappe.get_all(
+ "Bin",
+ fields=["warehouse", "actual_qty as qty"],
filters=filters,
limit=required_qty,
- order_by='creation')
+ order_by="creation",
+ )
return item_locations
@frappe.whitelist()
def create_delivery_note(source_name, target_doc=None):
- pick_list = frappe.get_doc('Pick List', source_name)
+ pick_list = frappe.get_doc("Pick List", source_name)
validate_item_locations(pick_list)
sales_dict = dict()
sales_orders = []
delivery_note = None
for location in pick_list.locations:
if location.sales_order:
- sales_orders.append([frappe.db.get_value("Sales Order",location.sales_order,'customer'),location.sales_order])
+ sales_orders.append(
+ [frappe.db.get_value("Sales Order", location.sales_order, "customer"), location.sales_order]
+ )
# Group sales orders by customer
- for key,keydata in groupby(sales_orders,key=itemgetter(0)):
+ for key, keydata in groupby(sales_orders, key=itemgetter(0)):
sales_dict[key] = set([d[1] for d in keydata])
if sales_dict:
- delivery_note = create_dn_with_so(sales_dict,pick_list)
+ delivery_note = create_dn_with_so(sales_dict, pick_list)
is_item_wo_so = 0
- for location in pick_list.locations :
+ for location in pick_list.locations:
if not location.sales_order:
is_item_wo_so = 1
break
@@ -399,64 +467,69 @@
# Create a DN for items without sales orders as well
delivery_note = create_dn_wo_so(pick_list)
- frappe.msgprint(_('Delivery Note(s) created for the Pick List'))
+ frappe.msgprint(_("Delivery Note(s) created for the Pick List"))
return delivery_note
+
def create_dn_wo_so(pick_list):
- delivery_note = frappe.new_doc("Delivery Note")
+ delivery_note = frappe.new_doc("Delivery Note")
- item_table_mapper_without_so = {
- 'doctype': 'Delivery Note Item',
- 'field_map': {
- 'rate': 'rate',
- 'name': 'name',
- 'parent': '',
- }
- }
- map_pl_locations(pick_list,item_table_mapper_without_so,delivery_note)
- delivery_note.insert(ignore_mandatory = True)
+ item_table_mapper_without_so = {
+ "doctype": "Delivery Note Item",
+ "field_map": {
+ "rate": "rate",
+ "name": "name",
+ "parent": "",
+ },
+ }
+ map_pl_locations(pick_list, item_table_mapper_without_so, delivery_note)
+ delivery_note.insert(ignore_mandatory=True)
- return delivery_note
+ return delivery_note
-def create_dn_with_so(sales_dict,pick_list):
+def create_dn_with_so(sales_dict, pick_list):
delivery_note = None
for customer in sales_dict:
for so in sales_dict[customer]:
delivery_note = None
- delivery_note = create_delivery_note_from_sales_order(so,
- delivery_note, skip_item_mapping=True)
+ delivery_note = create_delivery_note_from_sales_order(so, delivery_note, skip_item_mapping=True)
item_table_mapper = {
- 'doctype': 'Delivery Note Item',
- 'field_map': {
- 'rate': 'rate',
- 'name': 'so_detail',
- 'parent': 'against_sales_order',
+ "doctype": "Delivery Note Item",
+ "field_map": {
+ "rate": "rate",
+ "name": "so_detail",
+ "parent": "against_sales_order",
},
- 'condition': lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1
+ "condition": lambda doc: abs(doc.delivered_qty) < abs(doc.qty)
+ and doc.delivered_by_supplier != 1,
}
break
if delivery_note:
# map all items of all sales orders of that customer
for so in sales_dict[customer]:
- map_pl_locations(pick_list,item_table_mapper,delivery_note,so)
- delivery_note.insert(ignore_mandatory = True)
+ map_pl_locations(pick_list, item_table_mapper, delivery_note, so)
+ delivery_note.insert(ignore_mandatory=True)
return delivery_note
-def map_pl_locations(pick_list,item_mapper,delivery_note,sales_order = None):
+
+def map_pl_locations(pick_list, item_mapper, delivery_note, sales_order=None):
for location in pick_list.locations:
if location.sales_order == sales_order:
if location.sales_order_item:
- sales_order_item = frappe.get_cached_doc('Sales Order Item', {'name':location.sales_order_item})
+ sales_order_item = frappe.get_cached_doc(
+ "Sales Order Item", {"name": location.sales_order_item}
+ )
else:
sales_order_item = None
- source_doc, table_mapper = [sales_order_item, item_mapper] if sales_order_item \
- else [location, item_mapper]
+ source_doc, table_mapper = (
+ [sales_order_item, item_mapper] if sales_order_item else [location, item_mapper]
+ )
dn_item = map_child_doc(source_doc, delivery_note, table_mapper)
@@ -471,7 +544,7 @@
delivery_note.pick_list = pick_list.name
delivery_note.company = pick_list.company
- delivery_note.customer = frappe.get_value("Sales Order",sales_order,"customer")
+ delivery_note.customer = frappe.get_value("Sales Order", sales_order, "customer")
@frappe.whitelist()
@@ -479,17 +552,17 @@
pick_list = frappe.get_doc(json.loads(pick_list))
validate_item_locations(pick_list)
- if stock_entry_exists(pick_list.get('name')):
- return frappe.msgprint(_('Stock Entry has been already created against this Pick List'))
+ if stock_entry_exists(pick_list.get("name")):
+ return frappe.msgprint(_("Stock Entry has been already created against this Pick List"))
- stock_entry = frappe.new_doc('Stock Entry')
- stock_entry.pick_list = pick_list.get('name')
- stock_entry.purpose = pick_list.get('purpose')
+ stock_entry = frappe.new_doc("Stock Entry")
+ stock_entry.pick_list = pick_list.get("name")
+ stock_entry.purpose = pick_list.get("purpose")
stock_entry.set_stock_entry_type()
- if pick_list.get('work_order'):
+ if pick_list.get("work_order"):
stock_entry = update_stock_entry_based_on_work_order(pick_list, stock_entry)
- elif pick_list.get('material_request'):
+ elif pick_list.get("material_request"):
stock_entry = update_stock_entry_based_on_material_request(pick_list, stock_entry)
else:
stock_entry = update_stock_entry_items_with_no_reference(pick_list, stock_entry)
@@ -499,9 +572,11 @@
return stock_entry.as_dict()
+
@frappe.whitelist()
def get_pending_work_orders(doctype, txt, searchfield, start, page_length, filters, as_dict):
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
SELECT
`name`, `company`, `planned_start_date`
FROM
@@ -517,25 +592,27 @@
LIMIT
%(start)s, %(page_length)s""",
{
- 'txt': "%%%s%%" % txt,
- '_txt': txt.replace('%', ''),
- 'start': start,
- 'page_length': frappe.utils.cint(page_length),
- 'company': filters.get('company')
- }, as_dict=as_dict)
+ "txt": "%%%s%%" % txt,
+ "_txt": txt.replace("%", ""),
+ "start": start,
+ "page_length": frappe.utils.cint(page_length),
+ "company": filters.get("company"),
+ },
+ as_dict=as_dict,
+ )
+
@frappe.whitelist()
def target_document_exists(pick_list_name, purpose):
- if purpose == 'Delivery':
- return frappe.db.exists('Delivery Note', {
- 'pick_list': pick_list_name
- })
+ if purpose == "Delivery":
+ return frappe.db.exists("Delivery Note", {"pick_list": pick_list_name})
return stock_entry_exists(pick_list_name)
+
@frappe.whitelist()
def get_item_details(item_code, uom=None):
- details = frappe.db.get_value('Item', item_code, ['stock_uom', 'name'], as_dict=1)
+ details = frappe.db.get_value("Item", item_code, ["stock_uom", "name"], as_dict=1)
details.uom = uom or details.stock_uom
if uom:
details.update(get_conversion_factor(item_code, uom))
@@ -544,37 +621,37 @@
def update_delivery_note_item(source, target, delivery_note):
- cost_center = frappe.db.get_value('Project', delivery_note.project, 'cost_center')
+ cost_center = frappe.db.get_value("Project", delivery_note.project, "cost_center")
if not cost_center:
- cost_center = get_cost_center(source.item_code, 'Item', delivery_note.company)
+ cost_center = get_cost_center(source.item_code, "Item", delivery_note.company)
if not cost_center:
- cost_center = get_cost_center(source.item_group, 'Item Group', delivery_note.company)
+ cost_center = get_cost_center(source.item_group, "Item Group", delivery_note.company)
target.cost_center = cost_center
+
def get_cost_center(for_item, from_doctype, company):
- '''Returns Cost Center for Item or Item Group'''
- return frappe.db.get_value('Item Default',
- fieldname=['buying_cost_center'],
- filters={
- 'parent': for_item,
- 'parenttype': from_doctype,
- 'company': company
- })
+ """Returns Cost Center for Item or Item Group"""
+ return frappe.db.get_value(
+ "Item Default",
+ fieldname=["buying_cost_center"],
+ filters={"parent": for_item, "parenttype": from_doctype, "company": company},
+ )
+
def set_delivery_note_missing_values(target):
- target.run_method('set_missing_values')
- target.run_method('set_po_nos')
- target.run_method('calculate_taxes_and_totals')
+ target.run_method("set_missing_values")
+ target.run_method("set_po_nos")
+ target.run_method("calculate_taxes_and_totals")
+
def stock_entry_exists(pick_list_name):
- return frappe.db.exists('Stock Entry', {
- 'pick_list': pick_list_name
- })
+ return frappe.db.exists("Stock Entry", {"pick_list": pick_list_name})
+
def update_stock_entry_based_on_work_order(pick_list, stock_entry):
- work_order = frappe.get_doc("Work Order", pick_list.get('work_order'))
+ work_order = frappe.get_doc("Work Order", pick_list.get("work_order"))
stock_entry.work_order = work_order.name
stock_entry.company = work_order.company
@@ -583,10 +660,11 @@
stock_entry.use_multi_level_bom = work_order.use_multi_level_bom
stock_entry.fg_completed_qty = pick_list.for_qty
if work_order.bom_no:
- stock_entry.inspection_required = frappe.db.get_value('BOM',
- work_order.bom_no, 'inspection_required')
+ stock_entry.inspection_required = frappe.db.get_value(
+ "BOM", work_order.bom_no, "inspection_required"
+ )
- is_wip_warehouse_group = frappe.db.get_value('Warehouse', work_order.wip_warehouse, 'is_group')
+ is_wip_warehouse_group = frappe.db.get_value("Warehouse", work_order.wip_warehouse, "is_group")
if not (is_wip_warehouse_group and work_order.skip_transfer):
wip_warehouse = work_order.wip_warehouse
else:
@@ -600,32 +678,36 @@
update_common_item_properties(item, location)
item.t_warehouse = wip_warehouse
- stock_entry.append('items', item)
+ stock_entry.append("items", item)
return stock_entry
+
def update_stock_entry_based_on_material_request(pick_list, stock_entry):
for location in pick_list.locations:
target_warehouse = None
if location.material_request_item:
- target_warehouse = frappe.get_value('Material Request Item',
- location.material_request_item, 'warehouse')
+ target_warehouse = frappe.get_value(
+ "Material Request Item", location.material_request_item, "warehouse"
+ )
item = frappe._dict()
update_common_item_properties(item, location)
item.t_warehouse = target_warehouse
- stock_entry.append('items', item)
+ stock_entry.append("items", item)
return stock_entry
+
def update_stock_entry_items_with_no_reference(pick_list, stock_entry):
for location in pick_list.locations:
item = frappe._dict()
update_common_item_properties(item, location)
- stock_entry.append('items', item)
+ stock_entry.append("items", item)
return stock_entry
+
def update_common_item_properties(item, location):
item.item_code = location.item_code
item.s_warehouse = location.warehouse
diff --git a/erpnext/stock/doctype/pick_list/pick_list_dashboard.py b/erpnext/stock/doctype/pick_list/pick_list_dashboard.py
index ec3047e..92e57be 100644
--- a/erpnext/stock/doctype/pick_list/pick_list_dashboard.py
+++ b/erpnext/stock/doctype/pick_list/pick_list_dashboard.py
@@ -1,9 +1,7 @@
def get_data():
return {
- 'fieldname': 'pick_list',
- 'transactions': [
- {
- 'items': ['Stock Entry', 'Delivery Note']
- },
- ]
+ "fieldname": "pick_list",
+ "transactions": [
+ {"items": ["Stock Entry", "Delivery Note"]},
+ ],
}
diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py
index f60104c..7496b6b 100644
--- a/erpnext/stock/doctype/pick_list/test_pick_list.py
+++ b/erpnext/stock/doctype/pick_list/test_pick_list.py
@@ -4,7 +4,7 @@
import frappe
from frappe import _dict
-test_dependencies = ['Item', 'Sales Invoice', 'Stock Entry', 'Batch']
+test_dependencies = ["Item", "Sales Invoice", "Stock Entry", "Batch"]
from frappe.tests.utils import FrappeTestCase
@@ -19,146 +19,174 @@
class TestPickList(FrappeTestCase):
def test_pick_list_picks_warehouse_for_each_item(self):
try:
- frappe.get_doc({
- 'doctype': 'Stock Reconciliation',
- 'company': '_Test Company',
- 'purpose': 'Opening Stock',
- 'expense_account': 'Temporary Opening - _TC',
- 'items': [{
- 'item_code': '_Test Item',
- 'warehouse': '_Test Warehouse - _TC',
- 'valuation_rate': 100,
- 'qty': 5
- }]
- }).submit()
+ frappe.get_doc(
+ {
+ "doctype": "Stock Reconciliation",
+ "company": "_Test Company",
+ "purpose": "Opening Stock",
+ "expense_account": "Temporary Opening - _TC",
+ "items": [
+ {
+ "item_code": "_Test Item",
+ "warehouse": "_Test Warehouse - _TC",
+ "valuation_rate": 100,
+ "qty": 5,
+ }
+ ],
+ }
+ ).submit()
except EmptyStockReconciliationItemsError:
pass
- pick_list = frappe.get_doc({
- 'doctype': 'Pick List',
- 'company': '_Test Company',
- 'customer': '_Test Customer',
- 'items_based_on': 'Sales Order',
- 'purpose': 'Delivery',
- 'locations': [{
- 'item_code': '_Test Item',
- 'qty': 5,
- 'stock_qty': 5,
- 'conversion_factor': 1,
- 'sales_order': '_T-Sales Order-1',
- 'sales_order_item': '_T-Sales Order-1_item',
- }]
- })
+ pick_list = frappe.get_doc(
+ {
+ "doctype": "Pick List",
+ "company": "_Test Company",
+ "customer": "_Test Customer",
+ "items_based_on": "Sales Order",
+ "purpose": "Delivery",
+ "locations": [
+ {
+ "item_code": "_Test Item",
+ "qty": 5,
+ "stock_qty": 5,
+ "conversion_factor": 1,
+ "sales_order": "_T-Sales Order-1",
+ "sales_order_item": "_T-Sales Order-1_item",
+ }
+ ],
+ }
+ )
pick_list.set_item_locations()
- self.assertEqual(pick_list.locations[0].item_code, '_Test Item')
- self.assertEqual(pick_list.locations[0].warehouse, '_Test Warehouse - _TC')
+ self.assertEqual(pick_list.locations[0].item_code, "_Test Item")
+ self.assertEqual(pick_list.locations[0].warehouse, "_Test Warehouse - _TC")
self.assertEqual(pick_list.locations[0].qty, 5)
def test_pick_list_splits_row_according_to_warehouse_availability(self):
try:
- frappe.get_doc({
- 'doctype': 'Stock Reconciliation',
- 'company': '_Test Company',
- 'purpose': 'Opening Stock',
- 'expense_account': 'Temporary Opening - _TC',
- 'items': [{
- 'item_code': '_Test Item Warehouse Group Wise Reorder',
- 'warehouse': '_Test Warehouse Group-C1 - _TC',
- 'valuation_rate': 100,
- 'qty': 5
- }]
- }).submit()
+ frappe.get_doc(
+ {
+ "doctype": "Stock Reconciliation",
+ "company": "_Test Company",
+ "purpose": "Opening Stock",
+ "expense_account": "Temporary Opening - _TC",
+ "items": [
+ {
+ "item_code": "_Test Item Warehouse Group Wise Reorder",
+ "warehouse": "_Test Warehouse Group-C1 - _TC",
+ "valuation_rate": 100,
+ "qty": 5,
+ }
+ ],
+ }
+ ).submit()
except EmptyStockReconciliationItemsError:
pass
try:
- frappe.get_doc({
- 'doctype': 'Stock Reconciliation',
- 'company': '_Test Company',
- 'purpose': 'Opening Stock',
- 'expense_account': 'Temporary Opening - _TC',
- 'items': [{
- 'item_code': '_Test Item Warehouse Group Wise Reorder',
- 'warehouse': '_Test Warehouse 2 - _TC',
- 'valuation_rate': 400,
- 'qty': 10
- }]
- }).submit()
+ frappe.get_doc(
+ {
+ "doctype": "Stock Reconciliation",
+ "company": "_Test Company",
+ "purpose": "Opening Stock",
+ "expense_account": "Temporary Opening - _TC",
+ "items": [
+ {
+ "item_code": "_Test Item Warehouse Group Wise Reorder",
+ "warehouse": "_Test Warehouse 2 - _TC",
+ "valuation_rate": 400,
+ "qty": 10,
+ }
+ ],
+ }
+ ).submit()
except EmptyStockReconciliationItemsError:
pass
- pick_list = frappe.get_doc({
- 'doctype': 'Pick List',
- 'company': '_Test Company',
- 'customer': '_Test Customer',
- 'items_based_on': 'Sales Order',
- 'purpose': 'Delivery',
- 'locations': [{
- 'item_code': '_Test Item Warehouse Group Wise Reorder',
- 'qty': 1000,
- 'stock_qty': 1000,
- 'conversion_factor': 1,
- 'sales_order': '_T-Sales Order-1',
- 'sales_order_item': '_T-Sales Order-1_item',
- }]
- })
+ pick_list = frappe.get_doc(
+ {
+ "doctype": "Pick List",
+ "company": "_Test Company",
+ "customer": "_Test Customer",
+ "items_based_on": "Sales Order",
+ "purpose": "Delivery",
+ "locations": [
+ {
+ "item_code": "_Test Item Warehouse Group Wise Reorder",
+ "qty": 1000,
+ "stock_qty": 1000,
+ "conversion_factor": 1,
+ "sales_order": "_T-Sales Order-1",
+ "sales_order_item": "_T-Sales Order-1_item",
+ }
+ ],
+ }
+ )
pick_list.set_item_locations()
- self.assertEqual(pick_list.locations[0].item_code, '_Test Item Warehouse Group Wise Reorder')
- self.assertEqual(pick_list.locations[0].warehouse, '_Test Warehouse Group-C1 - _TC')
+ self.assertEqual(pick_list.locations[0].item_code, "_Test Item Warehouse Group Wise Reorder")
+ self.assertEqual(pick_list.locations[0].warehouse, "_Test Warehouse Group-C1 - _TC")
self.assertEqual(pick_list.locations[0].qty, 5)
- self.assertEqual(pick_list.locations[1].item_code, '_Test Item Warehouse Group Wise Reorder')
- self.assertEqual(pick_list.locations[1].warehouse, '_Test Warehouse 2 - _TC')
+ self.assertEqual(pick_list.locations[1].item_code, "_Test Item Warehouse Group Wise Reorder")
+ self.assertEqual(pick_list.locations[1].warehouse, "_Test Warehouse 2 - _TC")
self.assertEqual(pick_list.locations[1].qty, 10)
def test_pick_list_shows_serial_no_for_serialized_item(self):
- stock_reconciliation = frappe.get_doc({
- 'doctype': 'Stock Reconciliation',
- 'purpose': 'Stock Reconciliation',
- 'company': '_Test Company',
- 'items': [{
- 'item_code': '_Test Serialized Item',
- 'warehouse': '_Test Warehouse - _TC',
- 'valuation_rate': 100,
- 'qty': 5,
- 'serial_no': '123450\n123451\n123452\n123453\n123454'
- }]
- })
+ stock_reconciliation = frappe.get_doc(
+ {
+ "doctype": "Stock Reconciliation",
+ "purpose": "Stock Reconciliation",
+ "company": "_Test Company",
+ "items": [
+ {
+ "item_code": "_Test Serialized Item",
+ "warehouse": "_Test Warehouse - _TC",
+ "valuation_rate": 100,
+ "qty": 5,
+ "serial_no": "123450\n123451\n123452\n123453\n123454",
+ }
+ ],
+ }
+ )
try:
stock_reconciliation.submit()
except EmptyStockReconciliationItemsError:
pass
- pick_list = frappe.get_doc({
- 'doctype': 'Pick List',
- 'company': '_Test Company',
- 'customer': '_Test Customer',
- 'items_based_on': 'Sales Order',
- 'purpose': 'Delivery',
- 'locations': [{
- 'item_code': '_Test Serialized Item',
- 'qty': 1000,
- 'stock_qty': 1000,
- 'conversion_factor': 1,
- 'sales_order': '_T-Sales Order-1',
- 'sales_order_item': '_T-Sales Order-1_item',
- }]
- })
+ pick_list = frappe.get_doc(
+ {
+ "doctype": "Pick List",
+ "company": "_Test Company",
+ "customer": "_Test Customer",
+ "items_based_on": "Sales Order",
+ "purpose": "Delivery",
+ "locations": [
+ {
+ "item_code": "_Test Serialized Item",
+ "qty": 1000,
+ "stock_qty": 1000,
+ "conversion_factor": 1,
+ "sales_order": "_T-Sales Order-1",
+ "sales_order_item": "_T-Sales Order-1_item",
+ }
+ ],
+ }
+ )
pick_list.set_item_locations()
- self.assertEqual(pick_list.locations[0].item_code, '_Test Serialized Item')
- self.assertEqual(pick_list.locations[0].warehouse, '_Test Warehouse - _TC')
+ self.assertEqual(pick_list.locations[0].item_code, "_Test Serialized Item")
+ self.assertEqual(pick_list.locations[0].warehouse, "_Test Warehouse - _TC")
self.assertEqual(pick_list.locations[0].qty, 5)
- self.assertEqual(pick_list.locations[0].serial_no, '123450\n123451\n123452\n123453\n123454')
+ self.assertEqual(pick_list.locations[0].serial_no, "123450\n123451\n123452\n123453\n123454")
def test_pick_list_shows_batch_no_for_batched_item(self):
# check if oldest batch no is picked
- item = frappe.db.exists("Item", {'item_name': 'Batched Item'})
+ item = frappe.db.exists("Item", {"item_name": "Batched Item"})
if not item:
item = create_item("Batched Item")
item.has_batch_no = 1
@@ -166,7 +194,7 @@
item.batch_number_series = "B-BATCH-.##"
item.save()
else:
- item = frappe.get_doc("Item", {'item_name': 'Batched Item'})
+ item = frappe.get_doc("Item", {"item_name": "Batched Item"})
pr1 = make_purchase_receipt(item_code="Batched Item", qty=1, rate=100.0)
@@ -175,27 +203,30 @@
pr2 = make_purchase_receipt(item_code="Batched Item", qty=2, rate=100.0)
- pick_list = frappe.get_doc({
- 'doctype': 'Pick List',
- 'company': '_Test Company',
- 'purpose': 'Material Transfer',
- 'locations': [{
- 'item_code': 'Batched Item',
- 'qty': 1,
- 'stock_qty': 1,
- 'conversion_factor': 1,
- }]
- })
+ pick_list = frappe.get_doc(
+ {
+ "doctype": "Pick List",
+ "company": "_Test Company",
+ "purpose": "Material Transfer",
+ "locations": [
+ {
+ "item_code": "Batched Item",
+ "qty": 1,
+ "stock_qty": 1,
+ "conversion_factor": 1,
+ }
+ ],
+ }
+ )
pick_list.set_item_locations()
self.assertEqual(pick_list.locations[0].batch_no, oldest_batch_no)
pr1.cancel()
pr2.cancel()
-
def test_pick_list_for_batched_and_serialised_item(self):
# check if oldest batch no and serial nos are picked
- item = frappe.db.exists("Item", {'item_name': 'Batched and Serialised Item'})
+ item = frappe.db.exists("Item", {"item_name": "Batched and Serialised Item"})
if not item:
item = create_item("Batched and Serialised Item")
item.has_batch_no = 1
@@ -205,7 +236,7 @@
item.serial_no_series = "S-.####"
item.save()
else:
- item = frappe.get_doc("Item", {'item_name': 'Batched and Serialised Item'})
+ item = frappe.get_doc("Item", {"item_name": "Batched and Serialised Item"})
pr1 = make_purchase_receipt(item_code="Batched and Serialised Item", qty=2, rate=100.0)
@@ -215,17 +246,21 @@
pr2 = make_purchase_receipt(item_code="Batched and Serialised Item", qty=2, rate=100.0)
- pick_list = frappe.get_doc({
- 'doctype': 'Pick List',
- 'company': '_Test Company',
- 'purpose': 'Material Transfer',
- 'locations': [{
- 'item_code': 'Batched and Serialised Item',
- 'qty': 2,
- 'stock_qty': 2,
- 'conversion_factor': 1,
- }]
- })
+ pick_list = frappe.get_doc(
+ {
+ "doctype": "Pick List",
+ "company": "_Test Company",
+ "purpose": "Material Transfer",
+ "locations": [
+ {
+ "item_code": "Batched and Serialised Item",
+ "qty": 2,
+ "stock_qty": 2,
+ "conversion_factor": 1,
+ }
+ ],
+ }
+ )
pick_list.set_item_locations()
self.assertEqual(pick_list.locations[0].batch_no, oldest_batch_no)
@@ -236,64 +271,71 @@
def test_pick_list_for_items_from_multiple_sales_orders(self):
try:
- frappe.get_doc({
- 'doctype': 'Stock Reconciliation',
- 'company': '_Test Company',
- 'purpose': 'Opening Stock',
- 'expense_account': 'Temporary Opening - _TC',
- 'items': [{
- 'item_code': '_Test Item',
- 'warehouse': '_Test Warehouse - _TC',
- 'valuation_rate': 100,
- 'qty': 10
- }]
- }).submit()
+ frappe.get_doc(
+ {
+ "doctype": "Stock Reconciliation",
+ "company": "_Test Company",
+ "purpose": "Opening Stock",
+ "expense_account": "Temporary Opening - _TC",
+ "items": [
+ {
+ "item_code": "_Test Item",
+ "warehouse": "_Test Warehouse - _TC",
+ "valuation_rate": 100,
+ "qty": 10,
+ }
+ ],
+ }
+ ).submit()
except EmptyStockReconciliationItemsError:
pass
- sales_order = frappe.get_doc({
- 'doctype': "Sales Order",
- 'customer': '_Test Customer',
- 'company': '_Test Company',
- 'items': [{
- 'item_code': '_Test Item',
- 'qty': 10,
- 'delivery_date': frappe.utils.today()
- }],
- })
+ sales_order = frappe.get_doc(
+ {
+ "doctype": "Sales Order",
+ "customer": "_Test Customer",
+ "company": "_Test Company",
+ "items": [{"item_code": "_Test Item", "qty": 10, "delivery_date": frappe.utils.today()}],
+ }
+ )
sales_order.submit()
- pick_list = frappe.get_doc({
- 'doctype': 'Pick List',
- 'company': '_Test Company',
- 'customer': '_Test Customer',
- 'items_based_on': 'Sales Order',
- 'purpose': 'Delivery',
- 'locations': [{
- 'item_code': '_Test Item',
- 'qty': 5,
- 'stock_qty': 5,
- 'conversion_factor': 1,
- 'sales_order': '_T-Sales Order-1',
- 'sales_order_item': '_T-Sales Order-1_item',
- }, {
- 'item_code': '_Test Item',
- 'qty': 5,
- 'stock_qty': 5,
- 'conversion_factor': 1,
- 'sales_order': sales_order.name,
- 'sales_order_item': sales_order.items[0].name,
- }]
- })
+ pick_list = frappe.get_doc(
+ {
+ "doctype": "Pick List",
+ "company": "_Test Company",
+ "customer": "_Test Customer",
+ "items_based_on": "Sales Order",
+ "purpose": "Delivery",
+ "locations": [
+ {
+ "item_code": "_Test Item",
+ "qty": 5,
+ "stock_qty": 5,
+ "conversion_factor": 1,
+ "sales_order": "_T-Sales Order-1",
+ "sales_order_item": "_T-Sales Order-1_item",
+ },
+ {
+ "item_code": "_Test Item",
+ "qty": 5,
+ "stock_qty": 5,
+ "conversion_factor": 1,
+ "sales_order": sales_order.name,
+ "sales_order_item": sales_order.items[0].name,
+ },
+ ],
+ }
+ )
pick_list.set_item_locations()
- self.assertEqual(pick_list.locations[0].item_code, '_Test Item')
- self.assertEqual(pick_list.locations[0].warehouse, '_Test Warehouse - _TC')
+ self.assertEqual(pick_list.locations[0].item_code, "_Test Item")
+ self.assertEqual(pick_list.locations[0].warehouse, "_Test Warehouse - _TC")
self.assertEqual(pick_list.locations[0].qty, 5)
- self.assertEqual(pick_list.locations[0].sales_order_item, '_T-Sales Order-1_item')
+ self.assertEqual(pick_list.locations[0].sales_order_item, "_T-Sales Order-1_item")
- self.assertEqual(pick_list.locations[1].item_code, '_Test Item')
- self.assertEqual(pick_list.locations[1].warehouse, '_Test Warehouse - _TC')
+ self.assertEqual(pick_list.locations[1].item_code, "_Test Item")
+ self.assertEqual(pick_list.locations[1].warehouse, "_Test Warehouse - _TC")
self.assertEqual(pick_list.locations[1].qty, 5)
self.assertEqual(pick_list.locations[1].sales_order_item, sales_order.items[0].name)
@@ -301,47 +343,57 @@
purchase_receipt = make_purchase_receipt(item_code="_Test Item", qty=10)
purchase_receipt.submit()
- sales_order = frappe.get_doc({
- 'doctype': 'Sales Order',
- 'customer': '_Test Customer',
- 'company': '_Test Company',
- 'items': [{
- 'item_code': '_Test Item',
- 'qty': 1,
- 'conversion_factor': 5,
- 'stock_qty':5,
- 'delivery_date': frappe.utils.today()
- }, {
- 'item_code': '_Test Item',
- 'qty': 1,
- 'conversion_factor': 1,
- 'delivery_date': frappe.utils.today()
- }],
- }).insert()
+ sales_order = frappe.get_doc(
+ {
+ "doctype": "Sales Order",
+ "customer": "_Test Customer",
+ "company": "_Test Company",
+ "items": [
+ {
+ "item_code": "_Test Item",
+ "qty": 1,
+ "conversion_factor": 5,
+ "stock_qty": 5,
+ "delivery_date": frappe.utils.today(),
+ },
+ {
+ "item_code": "_Test Item",
+ "qty": 1,
+ "conversion_factor": 1,
+ "delivery_date": frappe.utils.today(),
+ },
+ ],
+ }
+ ).insert()
sales_order.submit()
- pick_list = frappe.get_doc({
- 'doctype': 'Pick List',
- 'company': '_Test Company',
- 'customer': '_Test Customer',
- 'items_based_on': 'Sales Order',
- 'purpose': 'Delivery',
- 'locations': [{
- 'item_code': '_Test Item',
- 'qty': 2,
- 'stock_qty': 1,
- 'conversion_factor': 0.5,
- 'sales_order': sales_order.name,
- 'sales_order_item': sales_order.items[0].name ,
- }, {
- 'item_code': '_Test Item',
- 'qty': 1,
- 'stock_qty': 1,
- 'conversion_factor': 1,
- 'sales_order': sales_order.name,
- 'sales_order_item': sales_order.items[1].name ,
- }]
- })
+ pick_list = frappe.get_doc(
+ {
+ "doctype": "Pick List",
+ "company": "_Test Company",
+ "customer": "_Test Customer",
+ "items_based_on": "Sales Order",
+ "purpose": "Delivery",
+ "locations": [
+ {
+ "item_code": "_Test Item",
+ "qty": 2,
+ "stock_qty": 1,
+ "conversion_factor": 0.5,
+ "sales_order": sales_order.name,
+ "sales_order_item": sales_order.items[0].name,
+ },
+ {
+ "item_code": "_Test Item",
+ "qty": 1,
+ "stock_qty": 1,
+ "conversion_factor": 1,
+ "sales_order": sales_order.name,
+ "sales_order_item": sales_order.items[1].name,
+ },
+ ],
+ }
+ )
pick_list.set_item_locations()
pick_list.submit()
@@ -349,7 +401,9 @@
self.assertEqual(pick_list.locations[0].qty, delivery_note.items[0].qty)
self.assertEqual(pick_list.locations[1].qty, delivery_note.items[1].qty)
- self.assertEqual(sales_order.items[0].conversion_factor, delivery_note.items[0].conversion_factor)
+ self.assertEqual(
+ sales_order.items[0].conversion_factor, delivery_note.items[0].conversion_factor
+ )
pick_list.cancel()
sales_order.cancel()
@@ -362,22 +416,30 @@
self.assertEqual(b.get(key), value, msg=f"{key} doesn't match")
# nothing should be grouped
- pl = frappe.get_doc(doctype="Pick List", group_same_items=True, locations=[
- _dict(item_code="A", warehouse="X", qty=1, picked_qty=2),
- _dict(item_code="B", warehouse="X", qty=1, picked_qty=2),
- _dict(item_code="A", warehouse="Y", qty=1, picked_qty=2),
- _dict(item_code="B", warehouse="Y", qty=1, picked_qty=2),
- ])
+ pl = frappe.get_doc(
+ doctype="Pick List",
+ group_same_items=True,
+ locations=[
+ _dict(item_code="A", warehouse="X", qty=1, picked_qty=2),
+ _dict(item_code="B", warehouse="X", qty=1, picked_qty=2),
+ _dict(item_code="A", warehouse="Y", qty=1, picked_qty=2),
+ _dict(item_code="B", warehouse="Y", qty=1, picked_qty=2),
+ ],
+ )
pl.before_print()
self.assertEqual(len(pl.locations), 4)
# grouping should halve the number of items
- pl = frappe.get_doc(doctype="Pick List", group_same_items=True, locations=[
- _dict(item_code="A", warehouse="X", qty=5, picked_qty=1),
- _dict(item_code="B", warehouse="Y", qty=4, picked_qty=2),
- _dict(item_code="A", warehouse="X", qty=3, picked_qty=2),
- _dict(item_code="B", warehouse="Y", qty=2, picked_qty=2),
- ])
+ pl = frappe.get_doc(
+ doctype="Pick List",
+ group_same_items=True,
+ locations=[
+ _dict(item_code="A", warehouse="X", qty=5, picked_qty=1),
+ _dict(item_code="B", warehouse="Y", qty=4, picked_qty=2),
+ _dict(item_code="A", warehouse="X", qty=3, picked_qty=2),
+ _dict(item_code="B", warehouse="Y", qty=2, picked_qty=2),
+ ],
+ )
pl.before_print()
self.assertEqual(len(pl.locations), 2)
@@ -389,93 +451,118 @@
_compare_dicts(expected_item, created_item)
def test_multiple_dn_creation(self):
- sales_order_1 = frappe.get_doc({
- 'doctype': 'Sales Order',
- 'customer': '_Test Customer',
- 'company': '_Test Company',
- 'items': [{
- 'item_code': '_Test Item',
- 'qty': 1,
- 'conversion_factor': 1,
- 'delivery_date': frappe.utils.today()
- }],
- }).insert()
- sales_order_1.submit()
- sales_order_2 = frappe.get_doc({
- 'doctype': 'Sales Order',
- 'customer': '_Test Customer 1',
- 'company': '_Test Company',
- 'items': [{
- 'item_code': '_Test Item 2',
- 'qty': 1,
- 'conversion_factor': 1,
- 'delivery_date': frappe.utils.today()
- },
+ sales_order_1 = frappe.get_doc(
+ {
+ "doctype": "Sales Order",
+ "customer": "_Test Customer",
+ "company": "_Test Company",
+ "items": [
+ {
+ "item_code": "_Test Item",
+ "qty": 1,
+ "conversion_factor": 1,
+ "delivery_date": frappe.utils.today(),
+ }
],
- }).insert()
- sales_order_2.submit()
- pick_list = frappe.get_doc({
- 'doctype': 'Pick List',
- 'company': '_Test Company',
- 'items_based_on': 'Sales Order',
- 'purpose': 'Delivery',
- 'picker':'P001',
- 'locations': [{
- 'item_code': '_Test Item ',
- 'qty': 1,
- 'stock_qty': 1,
- 'conversion_factor': 1,
- 'sales_order': sales_order_1.name,
- 'sales_order_item': sales_order_1.items[0].name ,
- }, {
- 'item_code': '_Test Item 2',
- 'qty': 1,
- 'stock_qty': 1,
- 'conversion_factor': 1,
- 'sales_order': sales_order_2.name,
- 'sales_order_item': sales_order_2.items[0].name ,
}
- ]
- })
+ ).insert()
+ sales_order_1.submit()
+ sales_order_2 = frappe.get_doc(
+ {
+ "doctype": "Sales Order",
+ "customer": "_Test Customer 1",
+ "company": "_Test Company",
+ "items": [
+ {
+ "item_code": "_Test Item 2",
+ "qty": 1,
+ "conversion_factor": 1,
+ "delivery_date": frappe.utils.today(),
+ },
+ ],
+ }
+ ).insert()
+ sales_order_2.submit()
+ pick_list = frappe.get_doc(
+ {
+ "doctype": "Pick List",
+ "company": "_Test Company",
+ "items_based_on": "Sales Order",
+ "purpose": "Delivery",
+ "picker": "P001",
+ "locations": [
+ {
+ "item_code": "_Test Item ",
+ "qty": 1,
+ "stock_qty": 1,
+ "conversion_factor": 1,
+ "sales_order": sales_order_1.name,
+ "sales_order_item": sales_order_1.items[0].name,
+ },
+ {
+ "item_code": "_Test Item 2",
+ "qty": 1,
+ "stock_qty": 1,
+ "conversion_factor": 1,
+ "sales_order": sales_order_2.name,
+ "sales_order_item": sales_order_2.items[0].name,
+ },
+ ],
+ }
+ )
pick_list.set_item_locations()
pick_list.submit()
create_delivery_note(pick_list.name)
- for dn in frappe.get_all("Delivery Note",filters={"pick_list":pick_list.name,"customer":"_Test Customer"},fields={"name"}):
- for dn_item in frappe.get_doc("Delivery Note",dn.name).get("items"):
- self.assertEqual(dn_item.item_code, '_Test Item')
- self.assertEqual(dn_item.against_sales_order,sales_order_1.name)
- for dn in frappe.get_all("Delivery Note",filters={"pick_list":pick_list.name,"customer":"_Test Customer 1"},fields={"name"}):
- for dn_item in frappe.get_doc("Delivery Note",dn.name).get("items"):
- self.assertEqual(dn_item.item_code, '_Test Item 2')
- self.assertEqual(dn_item.against_sales_order,sales_order_2.name)
- #test DN creation without so
- pick_list_1 = frappe.get_doc({
- 'doctype': 'Pick List',
- 'company': '_Test Company',
- 'purpose': 'Delivery',
- 'picker':'P001',
- 'locations': [{
- 'item_code': '_Test Item ',
- 'qty': 1,
- 'stock_qty': 1,
- 'conversion_factor': 1,
- }, {
- 'item_code': '_Test Item 2',
- 'qty': 2,
- 'stock_qty': 2,
- 'conversion_factor': 1,
+ for dn in frappe.get_all(
+ "Delivery Note",
+ filters={"pick_list": pick_list.name, "customer": "_Test Customer"},
+ fields={"name"},
+ ):
+ for dn_item in frappe.get_doc("Delivery Note", dn.name).get("items"):
+ self.assertEqual(dn_item.item_code, "_Test Item")
+ self.assertEqual(dn_item.against_sales_order, sales_order_1.name)
+ for dn in frappe.get_all(
+ "Delivery Note",
+ filters={"pick_list": pick_list.name, "customer": "_Test Customer 1"},
+ fields={"name"},
+ ):
+ for dn_item in frappe.get_doc("Delivery Note", dn.name).get("items"):
+ self.assertEqual(dn_item.item_code, "_Test Item 2")
+ self.assertEqual(dn_item.against_sales_order, sales_order_2.name)
+ # test DN creation without so
+ pick_list_1 = frappe.get_doc(
+ {
+ "doctype": "Pick List",
+ "company": "_Test Company",
+ "purpose": "Delivery",
+ "picker": "P001",
+ "locations": [
+ {
+ "item_code": "_Test Item ",
+ "qty": 1,
+ "stock_qty": 1,
+ "conversion_factor": 1,
+ },
+ {
+ "item_code": "_Test Item 2",
+ "qty": 2,
+ "stock_qty": 2,
+ "conversion_factor": 1,
+ },
+ ],
}
- ]
- })
+ )
pick_list_1.set_item_locations()
pick_list_1.submit()
create_delivery_note(pick_list_1.name)
- for dn in frappe.get_all("Delivery Note",filters={"pick_list":pick_list_1.name},fields={"name"}):
- for dn_item in frappe.get_doc("Delivery Note",dn.name).get("items"):
- if dn_item.item_code == '_Test Item':
- self.assertEqual(dn_item.qty,1)
- if dn_item.item_code == '_Test Item 2':
- self.assertEqual(dn_item.qty,2)
+ for dn in frappe.get_all(
+ "Delivery Note", filters={"pick_list": pick_list_1.name}, fields={"name"}
+ ):
+ for dn_item in frappe.get_doc("Delivery Note", dn.name).get("items"):
+ if dn_item.item_code == "_Test Item":
+ self.assertEqual(dn_item.qty, 1)
+ if dn_item.item_code == "_Test Item 2":
+ self.assertEqual(dn_item.qty, 2)
# def test_pick_list_skips_items_in_expired_batch(self):
# pass
diff --git a/erpnext/stock/doctype/price_list/price_list.py b/erpnext/stock/doctype/price_list/price_list.py
index 8a3172e..554055f 100644
--- a/erpnext/stock/doctype/price_list/price_list.py
+++ b/erpnext/stock/doctype/price_list/price_list.py
@@ -31,9 +31,11 @@
frappe.set_value("Buying Settings", "Buying Settings", "buying_price_list", self.name)
def update_item_price(self):
- frappe.db.sql("""update `tabItem Price` set currency=%s,
+ frappe.db.sql(
+ """update `tabItem Price` set currency=%s,
buying=%s, selling=%s, modified=NOW() where price_list=%s""",
- (self.currency, cint(self.buying), cint(self.selling), self.name))
+ (self.currency, cint(self.buying), cint(self.selling), self.name),
+ )
def check_impact_on_shopping_cart(self):
"Check if Price List currency change impacts E Commerce Cart."
@@ -66,12 +68,14 @@
def delete_price_list_details_key(self):
frappe.cache().hdel("price_list_details", self.name)
+
def get_price_list_details(price_list):
price_list_details = frappe.cache().hget("price_list_details", price_list)
if not price_list_details:
- price_list_details = frappe.get_cached_value("Price List", price_list,
- ["currency", "price_not_uom_dependent", "enabled"], as_dict=1)
+ price_list_details = frappe.get_cached_value(
+ "Price List", price_list, ["currency", "price_not_uom_dependent", "enabled"], as_dict=1
+ )
if not price_list_details or not price_list_details.get("enabled"):
throw(_("Price List {0} is disabled or does not exist").format(price_list))
diff --git a/erpnext/stock/doctype/price_list/test_price_list.py b/erpnext/stock/doctype/price_list/test_price_list.py
index b8218b9..9366093 100644
--- a/erpnext/stock/doctype/price_list/test_price_list.py
+++ b/erpnext/stock/doctype/price_list/test_price_list.py
@@ -6,4 +6,4 @@
# test_ignore = ["Item"]
-test_records = frappe.get_test_records('Price List')
+test_records = frappe.get_test_records("Price List")
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 4bf37fe..1e1c0b9 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -16,82 +16,85 @@
from erpnext.controllers.buying_controller import BuyingController
from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_transaction
-form_grid_templates = {
- "items": "templates/form_grid/item_grid.html"
-}
+form_grid_templates = {"items": "templates/form_grid/item_grid.html"}
+
class PurchaseReceipt(BuyingController):
def __init__(self, *args, **kwargs):
super(PurchaseReceipt, self).__init__(*args, **kwargs)
- self.status_updater = [{
- 'target_dt': 'Purchase Order Item',
- 'join_field': 'purchase_order_item',
- 'target_field': 'received_qty',
- 'target_parent_dt': 'Purchase Order',
- 'target_parent_field': 'per_received',
- 'target_ref_field': 'qty',
- 'source_dt': 'Purchase Receipt Item',
- 'source_field': 'received_qty',
- 'second_source_dt': 'Purchase Invoice Item',
- 'second_source_field': 'received_qty',
- 'second_join_field': 'po_detail',
- 'percent_join_field': 'purchase_order',
- 'overflow_type': 'receipt',
- 'second_source_extra_cond': """ and exists(select name from `tabPurchase Invoice`
- where name=`tabPurchase Invoice Item`.parent and update_stock = 1)"""
- },
- {
- 'source_dt': 'Purchase Receipt Item',
- 'target_dt': 'Material Request Item',
- 'join_field': 'material_request_item',
- 'target_field': 'received_qty',
- 'target_parent_dt': 'Material Request',
- 'target_parent_field': 'per_received',
- 'target_ref_field': 'stock_qty',
- 'source_field': 'stock_qty',
- 'percent_join_field': 'material_request'
- },
- {
- 'source_dt': 'Purchase Receipt Item',
- 'target_dt': 'Purchase Invoice Item',
- 'join_field': 'purchase_invoice_item',
- 'target_field': 'received_qty',
- 'target_parent_dt': 'Purchase Invoice',
- 'target_parent_field': 'per_received',
- 'target_ref_field': 'qty',
- 'source_field': 'received_qty',
- 'percent_join_field': 'purchase_invoice',
- 'overflow_type': 'receipt'
- }]
+ self.status_updater = [
+ {
+ "target_dt": "Purchase Order Item",
+ "join_field": "purchase_order_item",
+ "target_field": "received_qty",
+ "target_parent_dt": "Purchase Order",
+ "target_parent_field": "per_received",
+ "target_ref_field": "qty",
+ "source_dt": "Purchase Receipt Item",
+ "source_field": "received_qty",
+ "second_source_dt": "Purchase Invoice Item",
+ "second_source_field": "received_qty",
+ "second_join_field": "po_detail",
+ "percent_join_field": "purchase_order",
+ "overflow_type": "receipt",
+ "second_source_extra_cond": """ and exists(select name from `tabPurchase Invoice`
+ where name=`tabPurchase Invoice Item`.parent and update_stock = 1)""",
+ },
+ {
+ "source_dt": "Purchase Receipt Item",
+ "target_dt": "Material Request Item",
+ "join_field": "material_request_item",
+ "target_field": "received_qty",
+ "target_parent_dt": "Material Request",
+ "target_parent_field": "per_received",
+ "target_ref_field": "stock_qty",
+ "source_field": "stock_qty",
+ "percent_join_field": "material_request",
+ },
+ {
+ "source_dt": "Purchase Receipt Item",
+ "target_dt": "Purchase Invoice Item",
+ "join_field": "purchase_invoice_item",
+ "target_field": "received_qty",
+ "target_parent_dt": "Purchase Invoice",
+ "target_parent_field": "per_received",
+ "target_ref_field": "qty",
+ "source_field": "received_qty",
+ "percent_join_field": "purchase_invoice",
+ "overflow_type": "receipt",
+ },
+ ]
if cint(self.is_return):
- self.status_updater.extend([
- {
- 'source_dt': 'Purchase Receipt Item',
- 'target_dt': 'Purchase Order Item',
- 'join_field': 'purchase_order_item',
- 'target_field': 'returned_qty',
- 'source_field': '-1 * qty',
- 'second_source_dt': 'Purchase Invoice Item',
- 'second_source_field': '-1 * qty',
- 'second_join_field': 'po_detail',
- 'extra_cond': """ and exists (select name from `tabPurchase Receipt`
+ self.status_updater.extend(
+ [
+ {
+ "source_dt": "Purchase Receipt Item",
+ "target_dt": "Purchase Order Item",
+ "join_field": "purchase_order_item",
+ "target_field": "returned_qty",
+ "source_field": "-1 * qty",
+ "second_source_dt": "Purchase Invoice Item",
+ "second_source_field": "-1 * qty",
+ "second_join_field": "po_detail",
+ "extra_cond": """ and exists (select name from `tabPurchase Receipt`
where name=`tabPurchase Receipt Item`.parent and is_return=1)""",
- 'second_source_extra_cond': """ and exists (select name from `tabPurchase Invoice`
- where name=`tabPurchase Invoice Item`.parent and is_return=1 and update_stock=1)"""
- },
- {
- 'source_dt': 'Purchase Receipt Item',
- 'target_dt': 'Purchase Receipt Item',
- 'join_field': 'purchase_receipt_item',
- 'target_field': 'returned_qty',
- 'target_parent_dt': 'Purchase Receipt',
- 'target_parent_field': 'per_returned',
- 'target_ref_field': 'received_stock_qty',
- 'source_field': '-1 * received_stock_qty',
- 'percent_join_field_parent': 'return_against'
- }
- ])
+ "second_source_extra_cond": """ and exists (select name from `tabPurchase Invoice`
+ where name=`tabPurchase Invoice Item`.parent and is_return=1 and update_stock=1)""",
+ },
+ {
+ "source_dt": "Purchase Receipt Item",
+ "target_dt": "Purchase Receipt Item",
+ "join_field": "purchase_receipt_item",
+ "target_field": "returned_qty",
+ "target_parent_dt": "Purchase Receipt",
+ "target_parent_field": "per_returned",
+ "target_ref_field": "received_stock_qty",
+ "source_field": "-1 * received_stock_qty",
+ "percent_join_field_parent": "return_against",
+ },
+ ]
+ )
def before_validate(self):
from erpnext.stock.doctype.putaway_rule.putaway_rule import apply_putaway_rule
@@ -103,8 +106,8 @@
self.validate_posting_time()
super(PurchaseReceipt, self).validate()
- if self._action=="submit":
- self.make_batches('warehouse')
+ if self._action == "submit":
+ self.make_batches("warehouse")
else:
self.set_status()
@@ -124,20 +127,23 @@
self.reset_default_field_value("rejected_warehouse", "items", "rejected_warehouse")
self.reset_default_field_value("set_from_warehouse", "items", "from_warehouse")
-
def validate_cwip_accounts(self):
- for item in self.get('items'):
+ for item in self.get("items"):
if item.is_fixed_asset and is_cwip_accounting_enabled(item.asset_category):
# check cwip accounts before making auto assets
# Improves UX by not giving messages of "Assets Created" before throwing error of not finding arbnb account
arbnb_account = self.get_company_default("asset_received_but_not_billed")
- cwip_account = get_asset_account("capital_work_in_progress_account", asset_category = item.asset_category, \
- company = self.company)
+ cwip_account = get_asset_account(
+ "capital_work_in_progress_account", asset_category=item.asset_category, company=self.company
+ )
break
def validate_provisional_expense_account(self):
- provisional_accounting_for_non_stock_items = \
- cint(frappe.db.get_value('Company', self.company, 'enable_provisional_accounting_for_non_stock_items'))
+ provisional_accounting_for_non_stock_items = cint(
+ frappe.db.get_value(
+ "Company", self.company, "enable_provisional_accounting_for_non_stock_items"
+ )
+ )
if provisional_accounting_for_non_stock_items:
default_provisional_account = self.get_company_default("default_provisional_account")
@@ -145,56 +151,68 @@
self.provisional_expense_account = default_provisional_account
def validate_with_previous_doc(self):
- super(PurchaseReceipt, self).validate_with_previous_doc({
- "Purchase Order": {
- "ref_dn_field": "purchase_order",
- "compare_fields": [["supplier", "="], ["company", "="], ["currency", "="]],
- },
- "Purchase Order Item": {
- "ref_dn_field": "purchase_order_item",
- "compare_fields": [["project", "="], ["uom", "="], ["item_code", "="]],
- "is_child_table": True,
- "allow_duplicate_prev_row_id": True
+ super(PurchaseReceipt, self).validate_with_previous_doc(
+ {
+ "Purchase Order": {
+ "ref_dn_field": "purchase_order",
+ "compare_fields": [["supplier", "="], ["company", "="], ["currency", "="]],
+ },
+ "Purchase Order Item": {
+ "ref_dn_field": "purchase_order_item",
+ "compare_fields": [["project", "="], ["uom", "="], ["item_code", "="]],
+ "is_child_table": True,
+ "allow_duplicate_prev_row_id": True,
+ },
}
- })
+ )
- if cint(frappe.db.get_single_value('Buying Settings', 'maintain_same_rate')) and not self.is_return:
- self.validate_rate_with_reference_doc([["Purchase Order", "purchase_order", "purchase_order_item"]])
+ if (
+ cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate")) and not self.is_return
+ ):
+ self.validate_rate_with_reference_doc(
+ [["Purchase Order", "purchase_order", "purchase_order_item"]]
+ )
def po_required(self):
- if frappe.db.get_value("Buying Settings", None, "po_required") == 'Yes':
- for d in self.get('items'):
+ if frappe.db.get_value("Buying Settings", None, "po_required") == "Yes":
+ for d in self.get("items"):
if not d.purchase_order:
frappe.throw(_("Purchase Order number required for Item {0}").format(d.item_code))
def get_already_received_qty(self, po, po_detail):
- qty = frappe.db.sql("""select sum(qty) from `tabPurchase Receipt Item`
+ qty = frappe.db.sql(
+ """select sum(qty) from `tabPurchase Receipt Item`
where purchase_order_item = %s and docstatus = 1
and purchase_order=%s
- and parent != %s""", (po_detail, po, self.name))
+ and parent != %s""",
+ (po_detail, po, self.name),
+ )
return qty and flt(qty[0][0]) or 0.0
def get_po_qty_and_warehouse(self, po_detail):
- po_qty, po_warehouse = frappe.db.get_value("Purchase Order Item", po_detail,
- ["qty", "warehouse"])
+ po_qty, po_warehouse = frappe.db.get_value(
+ "Purchase Order Item", po_detail, ["qty", "warehouse"]
+ )
return po_qty, po_warehouse
# Check for Closed status
def check_on_hold_or_closed_status(self):
- check_list =[]
- for d in self.get('items'):
- if (d.meta.get_field('purchase_order') and d.purchase_order
- and d.purchase_order not in check_list):
+ check_list = []
+ for d in self.get("items"):
+ if (
+ d.meta.get_field("purchase_order") and d.purchase_order and d.purchase_order not in check_list
+ ):
check_list.append(d.purchase_order)
- check_on_hold_or_closed_status('Purchase Order', d.purchase_order)
+ check_on_hold_or_closed_status("Purchase Order", d.purchase_order)
# on submit
def on_submit(self):
super(PurchaseReceipt, self).on_submit()
# Check for Approving Authority
- frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype,
- self.company, self.base_grand_total)
+ frappe.get_doc("Authorization Control").validate_approving_authority(
+ self.doctype, self.company, self.base_grand_total
+ )
self.update_prevdoc_status()
if flt(self.per_billed) < 100:
@@ -202,13 +220,13 @@
else:
self.db_set("status", "Completed")
-
# Updating stock ledger should always be called after updating prevdoc status,
# because updating ordered qty, reserved_qty_for_subcontract in bin
# depends upon updated ordered qty in PO
self.update_stock_ledger()
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
+
update_serial_nos_after_submit(self, "items")
self.make_gl_entries()
@@ -216,10 +234,12 @@
self.set_consumed_qty_in_po()
def check_next_docstatus(self):
- submit_rv = frappe.db.sql("""select t1.name
+ submit_rv = frappe.db.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.name))
+ (self.name),
+ )
if submit_rv:
frappe.throw(_("Purchase Invoice {0} is already submitted").format(self.submit_rv[0][0]))
@@ -228,10 +248,12 @@
self.check_on_hold_or_closed_status()
# Check if Purchase Invoice has been submitted against current Purchase Order
- submitted = frappe.db.sql("""select t1.name
+ submitted = frappe.db.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.name)
+ self.name,
+ )
if submitted:
frappe.throw(_("Purchase Invoice {0} is already submitted").format(submitted[0][0]))
@@ -243,19 +265,24 @@
self.update_stock_ledger()
self.make_gl_entries_on_cancel()
self.repost_future_sle_and_gle()
- self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation')
+ self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation")
self.delete_auto_created_batches()
self.set_consumed_qty_in_po()
@frappe.whitelist()
def get_current_stock(self):
- for d in self.get('supplied_items'):
+ for d in self.get("supplied_items"):
if self.supplier_warehouse:
- bin = frappe.db.sql("select actual_qty from `tabBin` where item_code = %s and warehouse = %s", (d.rm_item_code, self.supplier_warehouse), as_dict = 1)
- d.current_stock = bin and flt(bin[0]['actual_qty']) or 0
+ bin = frappe.db.sql(
+ "select actual_qty from `tabBin` where item_code = %s and warehouse = %s",
+ (d.rm_item_code, self.supplier_warehouse),
+ as_dict=1,
+ )
+ d.current_stock = bin and flt(bin[0]["actual_qty"]) or 0
def get_gl_entries(self, warehouse_account=None):
from erpnext.accounts.general_ledger import process_gl_map
+
gl_entries = []
self.make_item_gl_entries(gl_entries, warehouse_account=warehouse_account)
@@ -276,31 +303,46 @@
warehouse_with_no_account = []
stock_items = self.get_stock_items()
- provisional_accounting_for_non_stock_items = \
- cint(frappe.db.get_value('Company', self.company, 'enable_provisional_accounting_for_non_stock_items'))
+ provisional_accounting_for_non_stock_items = cint(
+ frappe.db.get_value(
+ "Company", self.company, "enable_provisional_accounting_for_non_stock_items"
+ )
+ )
exchange_rate_map, net_rate_map = get_purchase_document_details(self)
for d in self.get("items"):
if d.item_code in stock_items and flt(d.valuation_rate) and flt(d.qty):
if warehouse_account.get(d.warehouse):
- stock_value_diff = frappe.db.get_value("Stock Ledger Entry",
- {"voucher_type": "Purchase Receipt", "voucher_no": self.name,
- "voucher_detail_no": d.name, "warehouse": d.warehouse, "is_cancelled": 0}, "stock_value_difference")
+ stock_value_diff = frappe.db.get_value(
+ "Stock Ledger Entry",
+ {
+ "voucher_type": "Purchase Receipt",
+ "voucher_no": self.name,
+ "voucher_detail_no": d.name,
+ "warehouse": d.warehouse,
+ "is_cancelled": 0,
+ },
+ "stock_value_difference",
+ )
warehouse_account_name = warehouse_account[d.warehouse]["account"]
warehouse_account_currency = warehouse_account[d.warehouse]["account_currency"]
supplier_warehouse_account = warehouse_account.get(self.supplier_warehouse, {}).get("account")
- supplier_warehouse_account_currency = warehouse_account.get(self.supplier_warehouse, {}).get("account_currency")
+ supplier_warehouse_account_currency = warehouse_account.get(self.supplier_warehouse, {}).get(
+ "account_currency"
+ )
remarks = self.get("remarks") or _("Accounting Entry for Stock")
# If PR is sub-contracted and fg item rate is zero
# in that case if account for source and target warehouse are same,
# then GL entries should not be posted
- if flt(stock_value_diff) == flt(d.rm_supp_cost) \
- and warehouse_account.get(self.supplier_warehouse) \
- and warehouse_account_name == supplier_warehouse_account:
- continue
+ if (
+ flt(stock_value_diff) == flt(d.rm_supp_cost)
+ and warehouse_account.get(self.supplier_warehouse)
+ and warehouse_account_name == supplier_warehouse_account
+ ):
+ continue
self.add_gl_entry(
gl_entries=gl_entries,
@@ -311,18 +353,24 @@
remarks=remarks,
against_account=stock_rbnb,
account_currency=warehouse_account_currency,
- item=d)
+ item=d,
+ )
# GL Entry for from warehouse or Stock Received but not billed
# Intentionally passed negative debit amount to avoid incorrect GL Entry validation
- credit_currency = get_account_currency(warehouse_account[d.from_warehouse]['account']) \
- if d.from_warehouse else get_account_currency(stock_rbnb)
+ credit_currency = (
+ get_account_currency(warehouse_account[d.from_warehouse]["account"])
+ if d.from_warehouse
+ else get_account_currency(stock_rbnb)
+ )
- credit_amount = flt(d.base_net_amount, d.precision("base_net_amount")) \
- if credit_currency == self.company_currency else flt(d.net_amount, d.precision("net_amount"))
+ credit_amount = (
+ flt(d.base_net_amount, d.precision("base_net_amount"))
+ if credit_currency == self.company_currency
+ else flt(d.net_amount, d.precision("net_amount"))
+ )
if credit_amount:
- account = warehouse_account[d.from_warehouse]['account'] \
- if d.from_warehouse else stock_rbnb
+ account = warehouse_account[d.from_warehouse]["account"] if d.from_warehouse else stock_rbnb
self.add_gl_entry(
gl_entries=gl_entries,
@@ -334,16 +382,20 @@
against_account=warehouse_account_name,
debit_in_account_currency=-1 * credit_amount,
account_currency=credit_currency,
- item=d)
+ item=d,
+ )
# check if the exchange rate has changed
- if d.get('purchase_invoice'):
- if exchange_rate_map[d.purchase_invoice] and \
- self.conversion_rate != exchange_rate_map[d.purchase_invoice] and \
- d.net_rate == net_rate_map[d.purchase_invoice_item]:
+ if d.get("purchase_invoice"):
+ if (
+ exchange_rate_map[d.purchase_invoice]
+ and self.conversion_rate != exchange_rate_map[d.purchase_invoice]
+ and d.net_rate == net_rate_map[d.purchase_invoice_item]
+ ):
- discrepancy_caused_by_exchange_rate_difference = (d.qty * d.net_rate) * \
- (exchange_rate_map[d.purchase_invoice] - self.conversion_rate)
+ discrepancy_caused_by_exchange_rate_difference = (d.qty * d.net_rate) * (
+ exchange_rate_map[d.purchase_invoice] - self.conversion_rate
+ )
self.add_gl_entry(
gl_entries=gl_entries,
@@ -355,7 +407,8 @@
against_account=self.supplier,
debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference,
account_currency=credit_currency,
- item=d)
+ item=d,
+ )
self.add_gl_entry(
gl_entries=gl_entries,
@@ -367,14 +420,18 @@
against_account=self.supplier,
debit_in_account_currency=-1 * discrepancy_caused_by_exchange_rate_difference,
account_currency=credit_currency,
- item=d)
+ item=d,
+ )
# Amount added through landed-cos-voucher
if d.landed_cost_voucher_amount and landed_cost_entries:
for account, amount in landed_cost_entries[(d.item_code, d.name)].items():
account_currency = get_account_currency(account)
- credit_amount = (flt(amount["base_amount"]) if (amount["base_amount"] or
- account_currency!=self.company_currency) else flt(amount["amount"]))
+ credit_amount = (
+ flt(amount["base_amount"])
+ if (amount["base_amount"] or account_currency != self.company_currency)
+ else flt(amount["amount"])
+ )
self.add_gl_entry(
gl_entries=gl_entries,
@@ -387,7 +444,8 @@
credit_in_account_currency=flt(amount["amount"]),
account_currency=account_currency,
project=d.project,
- item=d)
+ item=d,
+ )
# sub-contracting warehouse
if flt(d.rm_supp_cost) and warehouse_account.get(self.supplier_warehouse):
@@ -400,22 +458,32 @@
remarks=remarks,
against_account=warehouse_account_name,
account_currency=supplier_warehouse_account_currency,
- item=d)
+ item=d,
+ )
# divisional loss adjustment
- valuation_amount_as_per_doc = flt(d.base_net_amount, d.precision("base_net_amount")) + \
- flt(d.landed_cost_voucher_amount) + flt(d.rm_supp_cost) + flt(d.item_tax_amount)
+ valuation_amount_as_per_doc = (
+ flt(d.base_net_amount, d.precision("base_net_amount"))
+ + flt(d.landed_cost_voucher_amount)
+ + flt(d.rm_supp_cost)
+ + flt(d.item_tax_amount)
+ )
- divisional_loss = flt(valuation_amount_as_per_doc - stock_value_diff,
- d.precision("base_net_amount"))
+ divisional_loss = flt(
+ valuation_amount_as_per_doc - stock_value_diff, d.precision("base_net_amount")
+ )
if divisional_loss:
if self.is_return or flt(d.item_tax_amount):
loss_account = expenses_included_in_valuation
else:
- loss_account = self.get_company_default("default_expense_account", ignore_validation=True) or stock_rbnb
+ loss_account = (
+ self.get_company_default("default_expense_account", ignore_validation=True) or stock_rbnb
+ )
- cost_center = d.cost_center or frappe.get_cached_value("Company", self.company, "cost_center")
+ cost_center = d.cost_center or frappe.get_cached_value(
+ "Company", self.company, "cost_center"
+ )
self.add_gl_entry(
gl_entries=gl_entries,
@@ -427,20 +495,31 @@
against_account=warehouse_account_name,
account_currency=credit_currency,
project=d.project,
- item=d)
+ item=d,
+ )
- elif d.warehouse not in warehouse_with_no_account or \
- d.rejected_warehouse not in warehouse_with_no_account:
- warehouse_with_no_account.append(d.warehouse)
- elif d.item_code not in stock_items and not d.is_fixed_asset and flt(d.qty) and provisional_accounting_for_non_stock_items:
+ elif (
+ d.warehouse not in warehouse_with_no_account
+ or d.rejected_warehouse not in warehouse_with_no_account
+ ):
+ warehouse_with_no_account.append(d.warehouse)
+ elif (
+ d.item_code not in stock_items
+ and not d.is_fixed_asset
+ and flt(d.qty)
+ and provisional_accounting_for_non_stock_items
+ ):
self.add_provisional_gl_entry(d, gl_entries, self.posting_date)
if warehouse_with_no_account:
- frappe.msgprint(_("No accounting entries for the following warehouses") + ": \n" +
- "\n".join(warehouse_with_no_account))
+ frappe.msgprint(
+ _("No accounting entries for the following warehouses")
+ + ": \n"
+ + "\n".join(warehouse_with_no_account)
+ )
def add_provisional_gl_entry(self, item, gl_entries, posting_date, reverse=0):
- provisional_expense_account = self.get('provisional_expense_account')
+ provisional_expense_account = self.get("provisional_expense_account")
credit_currency = get_account_currency(provisional_expense_account)
debit_currency = get_account_currency(item.expense_account)
expense_account = item.expense_account
@@ -449,7 +528,9 @@
if reverse:
multiplication_factor = -1
- expense_account = frappe.db.get_value('Purchase Receipt Item', {'name': item.get('pr_detail')}, ['expense_account'])
+ expense_account = frappe.db.get_value(
+ "Purchase Receipt Item", {"name": item.get("pr_detail")}, ["expense_account"]
+ )
self.add_gl_entry(
gl_entries=gl_entries,
@@ -463,7 +544,8 @@
project=item.project,
voucher_detail_no=item.name,
item=item,
- posting_date=posting_date)
+ posting_date=posting_date,
+ )
self.add_gl_entry(
gl_entries=gl_entries,
@@ -473,27 +555,35 @@
credit=0.0,
remarks=remarks,
against_account=provisional_expense_account,
- account_currency = debit_currency,
+ account_currency=debit_currency,
project=item.project,
voucher_detail_no=item.name,
item=item,
- posting_date=posting_date)
+ posting_date=posting_date,
+ )
def make_tax_gl_entries(self, gl_entries):
if erpnext.is_perpetual_inventory_enabled(self.company):
expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
- negative_expense_to_be_booked = sum([flt(d.item_tax_amount) for d in self.get('items')])
+ negative_expense_to_be_booked = sum([flt(d.item_tax_amount) for d in self.get("items")])
# Cost center-wise amount breakup for other charges included for valuation
valuation_tax = {}
for tax in self.get("taxes"):
- if tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount):
+ if tax.category in ("Valuation", "Valuation and Total") and flt(
+ tax.base_tax_amount_after_discount_amount
+ ):
if not tax.cost_center:
- frappe.throw(_("Cost Center is required in row {0} in Taxes table for type {1}").format(tax.idx, _(tax.category)))
+ frappe.throw(
+ _("Cost Center is required in row {0} in Taxes table for type {1}").format(
+ tax.idx, _(tax.category)
+ )
+ )
valuation_tax.setdefault(tax.name, 0)
- valuation_tax[tax.name] += \
- (tax.add_deduct_tax == "Add" and 1 or -1) * flt(tax.base_tax_amount_after_discount_amount)
+ valuation_tax[tax.name] += (tax.add_deduct_tax == "Add" and 1 or -1) * flt(
+ tax.base_tax_amount_after_discount_amount
+ )
if negative_expense_to_be_booked and valuation_tax:
# Backward compatibility:
@@ -502,10 +592,13 @@
# post valuation related charges on "Stock Received But Not Billed"
# introduced in 2014 for backward compatibility of expenses already booked in expenses_included_in_valuation account
- negative_expense_booked_in_pi = frappe.db.sql("""select name from `tabPurchase Invoice Item` pi
+ negative_expense_booked_in_pi = frappe.db.sql(
+ """select name from `tabPurchase Invoice Item` pi
where docstatus = 1 and purchase_receipt=%s
and exists(select name from `tabGL Entry` where voucher_type='Purchase Invoice'
- and voucher_no=pi.parent and account=%s)""", (self.name, expenses_included_in_valuation))
+ and voucher_no=pi.parent and account=%s)""",
+ (self.name, expenses_included_in_valuation),
+ )
against_account = ", ".join([d.account for d in gl_entries if flt(d.debit) > 0])
total_valuation_amount = sum(valuation_tax.values())
@@ -523,7 +616,9 @@
if i == len(valuation_tax):
applicable_amount = amount_including_divisional_loss
else:
- applicable_amount = negative_expense_to_be_booked * (valuation_tax[tax.name] / total_valuation_amount)
+ applicable_amount = negative_expense_to_be_booked * (
+ valuation_tax[tax.name] / total_valuation_amount
+ )
amount_including_divisional_loss -= applicable_amount
self.add_gl_entry(
@@ -534,13 +629,28 @@
credit=applicable_amount,
remarks=self.remarks or _("Accounting Entry for Stock"),
against_account=against_account,
- item=tax)
+ item=tax,
+ )
i += 1
- def add_gl_entry(self, gl_entries, account, cost_center, debit, credit, remarks, against_account,
- debit_in_account_currency=None, credit_in_account_currency=None, account_currency=None,
- project=None, voucher_detail_no=None, item=None, posting_date=None):
+ def add_gl_entry(
+ self,
+ gl_entries,
+ account,
+ cost_center,
+ debit,
+ credit,
+ remarks,
+ against_account,
+ debit_in_account_currency=None,
+ credit_in_account_currency=None,
+ account_currency=None,
+ project=None,
+ voucher_detail_no=None,
+ item=None,
+ posting_date=None,
+ ):
gl_entry = {
"account": account,
@@ -580,17 +690,19 @@
def add_asset_gl_entries(self, item, gl_entries):
arbnb_account = self.get_company_default("asset_received_but_not_billed")
# This returns category's cwip account if not then fallback to company's default cwip account
- cwip_account = get_asset_account("capital_work_in_progress_account", asset_category = item.asset_category, \
- company = self.company)
+ cwip_account = get_asset_account(
+ "capital_work_in_progress_account", asset_category=item.asset_category, company=self.company
+ )
- asset_amount = flt(item.net_amount) + flt(item.item_tax_amount/self.conversion_rate)
+ asset_amount = flt(item.net_amount) + flt(item.item_tax_amount / self.conversion_rate)
base_asset_amount = flt(item.base_net_amount + item.item_tax_amount)
remarks = self.get("remarks") or _("Accounting Entry for Asset")
cwip_account_currency = get_account_currency(cwip_account)
# debit cwip account
- debit_in_account_currency = (base_asset_amount
- if cwip_account_currency == self.company_currency else asset_amount)
+ debit_in_account_currency = (
+ base_asset_amount if cwip_account_currency == self.company_currency else asset_amount
+ )
self.add_gl_entry(
gl_entries=gl_entries,
@@ -601,12 +713,14 @@
remarks=remarks,
against_account=arbnb_account,
debit_in_account_currency=debit_in_account_currency,
- item=item)
+ item=item,
+ )
asset_rbnb_currency = get_account_currency(arbnb_account)
# credit arbnb account
- credit_in_account_currency = (base_asset_amount
- if asset_rbnb_currency == self.company_currency else asset_amount)
+ credit_in_account_currency = (
+ base_asset_amount if asset_rbnb_currency == self.company_currency else asset_amount
+ )
self.add_gl_entry(
gl_entries=gl_entries,
@@ -617,13 +731,17 @@
remarks=remarks,
against_account=cwip_account,
credit_in_account_currency=credit_in_account_currency,
- item=item)
+ item=item,
+ )
def add_lcv_gl_entries(self, item, gl_entries):
- expenses_included_in_asset_valuation = self.get_company_default("expenses_included_in_asset_valuation")
+ expenses_included_in_asset_valuation = self.get_company_default(
+ "expenses_included_in_asset_valuation"
+ )
if not is_cwip_accounting_enabled(item.asset_category):
- asset_account = get_asset_category_account(asset_category=item.asset_category, \
- fieldname='fixed_asset_account', company=self.company)
+ asset_account = get_asset_category_account(
+ asset_category=item.asset_category, fieldname="fixed_asset_account", company=self.company
+ )
else:
# This returns company's default cwip account
asset_account = get_asset_account("capital_work_in_progress_account", company=self.company)
@@ -639,7 +757,8 @@
remarks=remarks,
against_account=asset_account,
project=item.project,
- item=item)
+ item=item,
+ )
self.add_gl_entry(
gl_entries=gl_entries,
@@ -650,11 +769,12 @@
remarks=remarks,
against_account=expenses_included_in_asset_valuation,
project=item.project,
- item=item)
+ item=item,
+ )
def update_assets(self, item, valuation_rate):
- assets = frappe.db.get_all('Asset',
- filters={ 'purchase_receipt': self.name, 'item_code': item.item_code }
+ assets = frappe.db.get_all(
+ "Asset", filters={"purchase_receipt": self.name, "item_code": item.item_code}
)
for asset in assets:
@@ -670,7 +790,7 @@
updated_pr = [self.name]
for d in self.get("items"):
if d.get("purchase_invoice") and d.get("purchase_invoice_item"):
- d.db_set('billed_amt', d.amount, update_modified=update_modified)
+ d.db_set("billed_amt", d.amount, update_modified=update_modified)
elif d.purchase_order_item:
updated_pr += update_billed_amount_based_on_po(d.purchase_order_item, update_modified)
@@ -680,24 +800,35 @@
self.load_from_db()
+
def update_billed_amount_based_on_po(po_detail, update_modified=True):
# Billed against Sales Order directly
- billed_against_po = frappe.db.sql("""select sum(amount) from `tabPurchase Invoice Item`
- where po_detail=%s and (pr_detail is null or pr_detail = '') and docstatus=1""", po_detail)
+ billed_against_po = frappe.db.sql(
+ """select sum(amount) from `tabPurchase Invoice Item`
+ where po_detail=%s and (pr_detail is null or pr_detail = '') and docstatus=1""",
+ po_detail,
+ )
billed_against_po = billed_against_po and billed_against_po[0][0] or 0
# Get all Purchase Receipt Item rows against the Purchase Order Item row
- pr_details = frappe.db.sql("""select pr_item.name, pr_item.amount, pr_item.parent
+ pr_details = frappe.db.sql(
+ """select pr_item.name, pr_item.amount, pr_item.parent
from `tabPurchase Receipt Item` pr_item, `tabPurchase Receipt` pr
where pr.name=pr_item.parent and pr_item.purchase_order_item=%s
and pr.docstatus=1 and pr.is_return = 0
- order by pr.posting_date asc, pr.posting_time asc, pr.name asc""", po_detail, as_dict=1)
+ order by pr.posting_date asc, pr.posting_time asc, pr.name asc""",
+ po_detail,
+ as_dict=1,
+ )
updated_pr = []
for pr_item in pr_details:
# Get billed amount directly against Purchase Receipt
- billed_amt_agianst_pr = frappe.db.sql("""select sum(amount) from `tabPurchase Invoice Item`
- where pr_detail=%s and docstatus=1""", pr_item.name)
+ billed_amt_agianst_pr = frappe.db.sql(
+ """select sum(amount) from `tabPurchase Invoice Item`
+ where pr_detail=%s and docstatus=1""",
+ pr_item.name,
+ )
billed_amt_agianst_pr = billed_amt_agianst_pr and billed_amt_agianst_pr[0][0] or 0
# Distribute billed amount directly against PO between PRs based on FIFO
@@ -710,12 +841,19 @@
billed_amt_agianst_pr += billed_against_po
billed_against_po = 0
- frappe.db.set_value("Purchase Receipt Item", pr_item.name, "billed_amt", billed_amt_agianst_pr, update_modified=update_modified)
+ frappe.db.set_value(
+ "Purchase Receipt Item",
+ pr_item.name,
+ "billed_amt",
+ billed_amt_agianst_pr,
+ update_modified=update_modified,
+ )
updated_pr.append(pr_item.parent)
return updated_pr
+
def update_billing_percentage(pr_doc, update_modified=True):
# Reload as billed amount was set in db directly
pr_doc.load_from_db()
@@ -723,15 +861,15 @@
# Update Billing % based on pending accepted qty
total_amount, total_billed_amount = 0, 0
for item in pr_doc.items:
- return_data = frappe.db.get_list("Purchase Receipt",
- fields = [
- "sum(abs(`tabPurchase Receipt Item`.qty)) as qty"
- ],
- filters = [
+ return_data = frappe.db.get_list(
+ "Purchase Receipt",
+ fields=["sum(abs(`tabPurchase Receipt Item`.qty)) as qty"],
+ filters=[
["Purchase Receipt", "docstatus", "=", 1],
["Purchase Receipt", "is_return", "=", 1],
- ["Purchase Receipt Item", "purchase_receipt_item", "=", item.name]
- ])
+ ["Purchase Receipt Item", "purchase_receipt_item", "=", item.name],
+ ],
+ )
returned_qty = return_data[0].qty if return_data else 0
returned_amount = flt(returned_qty) * flt(item.rate)
@@ -749,11 +887,12 @@
pr_doc.set_status(update=True)
pr_doc.notify_update()
+
@frappe.whitelist()
def make_purchase_invoice(source_name, target_doc=None):
from erpnext.accounts.party import get_payment_terms_template
- doc = frappe.get_doc('Purchase Receipt', source_name)
+ doc = frappe.get_doc("Purchase Receipt", source_name)
returned_qty_map = get_returned_qty_map(source_name)
invoiced_qty_map = get_invoiced_qty_map(source_name)
@@ -762,7 +901,9 @@
frappe.throw(_("All items have already been Invoiced/Returned"))
doc = frappe.get_doc(target)
- doc.payment_terms_template = get_payment_terms_template(source.supplier, "Supplier", source.company)
+ doc.payment_terms_template = get_payment_terms_template(
+ source.supplier, "Supplier", source.company
+ )
doc.run_method("onload")
doc.run_method("set_missing_values")
doc.run_method("calculate_taxes_and_totals")
@@ -770,14 +911,20 @@
def update_item(source_doc, target_doc, source_parent):
target_doc.qty, returned_qty = get_pending_qty(source_doc)
- if frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"):
+ if frappe.db.get_single_value(
+ "Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"
+ ):
target_doc.rejected_qty = 0
- target_doc.stock_qty = flt(target_doc.qty) * flt(target_doc.conversion_factor, target_doc.precision("conversion_factor"))
+ target_doc.stock_qty = flt(target_doc.qty) * flt(
+ target_doc.conversion_factor, target_doc.precision("conversion_factor")
+ )
returned_qty_map[source_doc.name] = returned_qty
def get_pending_qty(item_row):
qty = item_row.qty
- if frappe.db.get_single_value("Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"):
+ if frappe.db.get_single_value(
+ "Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"
+ ):
qty = item_row.received_qty
pending_qty = qty - invoiced_qty_map.get(item_row.name, 0)
returned_qty = flt(returned_qty_map.get(item_row.name, 0))
@@ -790,69 +937,85 @@
returned_qty = 0
return pending_qty, returned_qty
-
- doclist = get_mapped_doc("Purchase Receipt", source_name, {
- "Purchase Receipt": {
- "doctype": "Purchase Invoice",
- "field_map": {
- "supplier_warehouse":"supplier_warehouse",
- "is_return": "is_return",
- "bill_date": "bill_date"
+ doclist = get_mapped_doc(
+ "Purchase Receipt",
+ source_name,
+ {
+ "Purchase Receipt": {
+ "doctype": "Purchase Invoice",
+ "field_map": {
+ "supplier_warehouse": "supplier_warehouse",
+ "is_return": "is_return",
+ "bill_date": "bill_date",
+ },
+ "validation": {
+ "docstatus": ["=", 1],
+ },
},
- "validation": {
- "docstatus": ["=", 1],
+ "Purchase Receipt Item": {
+ "doctype": "Purchase Invoice Item",
+ "field_map": {
+ "name": "pr_detail",
+ "parent": "purchase_receipt",
+ "purchase_order_item": "po_detail",
+ "purchase_order": "purchase_order",
+ "is_fixed_asset": "is_fixed_asset",
+ "asset_location": "asset_location",
+ "asset_category": "asset_category",
+ },
+ "postprocess": update_item,
+ "filter": lambda d: get_pending_qty(d)[0] <= 0
+ if not doc.get("is_return")
+ else get_pending_qty(d)[0] > 0,
},
+ "Purchase Taxes and Charges": {"doctype": "Purchase Taxes and Charges", "add_if_empty": True},
},
- "Purchase Receipt Item": {
- "doctype": "Purchase Invoice Item",
- "field_map": {
- "name": "pr_detail",
- "parent": "purchase_receipt",
- "purchase_order_item": "po_detail",
- "purchase_order": "purchase_order",
- "is_fixed_asset": "is_fixed_asset",
- "asset_location": "asset_location",
- "asset_category": 'asset_category'
- },
- "postprocess": update_item,
- "filter": lambda d: get_pending_qty(d)[0] <= 0 if not doc.get("is_return") else get_pending_qty(d)[0] > 0
- },
- "Purchase Taxes and Charges": {
- "doctype": "Purchase Taxes and Charges",
- "add_if_empty": True
- }
- }, target_doc, set_missing_values)
+ target_doc,
+ set_missing_values,
+ )
- doclist.set_onload('ignore_price_list', True)
+ doclist.set_onload("ignore_price_list", True)
return doclist
+
def get_invoiced_qty_map(purchase_receipt):
"""returns a map: {pr_detail: invoiced_qty}"""
invoiced_qty_map = {}
- for pr_detail, qty in frappe.db.sql("""select pr_detail, qty from `tabPurchase Invoice Item`
- where purchase_receipt=%s and docstatus=1""", purchase_receipt):
- if not invoiced_qty_map.get(pr_detail):
- invoiced_qty_map[pr_detail] = 0
- invoiced_qty_map[pr_detail] += qty
+ for pr_detail, qty in frappe.db.sql(
+ """select pr_detail, qty from `tabPurchase Invoice Item`
+ where purchase_receipt=%s and docstatus=1""",
+ purchase_receipt,
+ ):
+ if not invoiced_qty_map.get(pr_detail):
+ invoiced_qty_map[pr_detail] = 0
+ invoiced_qty_map[pr_detail] += qty
return invoiced_qty_map
+
def get_returned_qty_map(purchase_receipt):
"""returns a map: {so_detail: returned_qty}"""
- returned_qty_map = frappe._dict(frappe.db.sql("""select pr_item.purchase_receipt_item, abs(pr_item.qty) as qty
+ returned_qty_map = frappe._dict(
+ frappe.db.sql(
+ """select pr_item.purchase_receipt_item, abs(pr_item.qty) as qty
from `tabPurchase Receipt Item` pr_item, `tabPurchase Receipt` pr
where pr.name = pr_item.parent
and pr.docstatus = 1
and pr.is_return = 1
and pr.return_against = %s
- """, purchase_receipt))
+ """,
+ purchase_receipt,
+ )
+ )
return returned_qty_map
+
@frappe.whitelist()
def make_purchase_return(source_name, target_doc=None):
from erpnext.controllers.sales_and_purchase_return import make_return_doc
+
return make_return_doc("Purchase Receipt", source_name, target_doc)
@@ -861,35 +1024,47 @@
pr = frappe.get_doc("Purchase Receipt", docname)
pr.update_status(status)
+
@frappe.whitelist()
-def make_stock_entry(source_name,target_doc=None):
+def make_stock_entry(source_name, target_doc=None):
def set_missing_values(source, target):
target.stock_entry_type = "Material Transfer"
- target.purpose = "Material Transfer"
+ target.purpose = "Material Transfer"
- doclist = get_mapped_doc("Purchase Receipt", source_name,{
- "Purchase Receipt": {
- "doctype": "Stock Entry",
- },
- "Purchase Receipt Item": {
- "doctype": "Stock Entry Detail",
- "field_map": {
- "warehouse": "s_warehouse",
- "parent": "reference_purchase_receipt",
- "batch_no": "batch_no"
+ doclist = get_mapped_doc(
+ "Purchase Receipt",
+ source_name,
+ {
+ "Purchase Receipt": {
+ "doctype": "Stock Entry",
+ },
+ "Purchase Receipt Item": {
+ "doctype": "Stock Entry Detail",
+ "field_map": {
+ "warehouse": "s_warehouse",
+ "parent": "reference_purchase_receipt",
+ "batch_no": "batch_no",
+ },
},
},
- }, target_doc, set_missing_values)
+ target_doc,
+ set_missing_values,
+ )
return doclist
+
@frappe.whitelist()
def make_inter_company_delivery_note(source_name, target_doc=None):
return make_inter_company_transaction("Purchase Receipt", source_name, target_doc)
+
def get_item_account_wise_additional_cost(purchase_document):
- landed_cost_vouchers = frappe.get_all("Landed Cost Purchase Receipt", fields=["parent"],
- filters = {"receipt_document": purchase_document, "docstatus": 1})
+ landed_cost_vouchers = frappe.get_all(
+ "Landed Cost Purchase Receipt",
+ fields=["parent"],
+ filters={"receipt_document": purchase_document, "docstatus": 1},
+ )
if not landed_cost_vouchers:
return
@@ -899,9 +1074,9 @@
for lcv in landed_cost_vouchers:
landed_cost_voucher_doc = frappe.get_doc("Landed Cost Voucher", lcv.parent)
- #Use amount field for total item cost for manually cost distributed LCVs
- if landed_cost_voucher_doc.distribute_charges_based_on == 'Distribute Manually':
- based_on_field = 'amount'
+ # Use amount field for total item cost for manually cost distributed LCVs
+ if landed_cost_voucher_doc.distribute_charges_based_on == "Distribute Manually":
+ based_on_field = "amount"
else:
based_on_field = frappe.scrub(landed_cost_voucher_doc.distribute_charges_based_on)
@@ -914,18 +1089,20 @@
if item.receipt_document == purchase_document:
for account in landed_cost_voucher_doc.taxes:
item_account_wise_cost.setdefault((item.item_code, item.purchase_receipt_item), {})
- item_account_wise_cost[(item.item_code, item.purchase_receipt_item)].setdefault(account.expense_account, {
- "amount": 0.0,
- "base_amount": 0.0
- })
+ item_account_wise_cost[(item.item_code, item.purchase_receipt_item)].setdefault(
+ account.expense_account, {"amount": 0.0, "base_amount": 0.0}
+ )
- item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][account.expense_account]["amount"] += \
- account.amount * item.get(based_on_field) / total_item_cost
+ item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][account.expense_account][
+ "amount"
+ ] += (account.amount * item.get(based_on_field) / total_item_cost)
- item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][account.expense_account]["base_amount"] += \
- account.base_amount * item.get(based_on_field) / total_item_cost
+ item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][account.expense_account][
+ "base_amount"
+ ] += (account.base_amount * item.get(based_on_field) / total_item_cost)
return item_account_wise_cost
+
def on_doctype_update():
frappe.db.add_index("Purchase Receipt", ["supplier", "is_return", "return_against"])
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py
index bdc5435..06ba936 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py
@@ -3,35 +3,23 @@
def get_data():
return {
- 'fieldname': 'purchase_receipt_no',
- 'non_standard_fieldnames': {
- 'Purchase Invoice': 'purchase_receipt',
- 'Asset': 'purchase_receipt',
- 'Landed Cost Voucher': 'receipt_document',
- 'Auto Repeat': 'reference_document',
- 'Purchase Receipt': 'return_against'
+ "fieldname": "purchase_receipt_no",
+ "non_standard_fieldnames": {
+ "Purchase Invoice": "purchase_receipt",
+ "Asset": "purchase_receipt",
+ "Landed Cost Voucher": "receipt_document",
+ "Auto Repeat": "reference_document",
+ "Purchase Receipt": "return_against",
},
- 'internal_links': {
- 'Purchase Order': ['items', 'purchase_order'],
- 'Project': ['items', 'project'],
- 'Quality Inspection': ['items', 'quality_inspection'],
+ "internal_links": {
+ "Purchase Order": ["items", "purchase_order"],
+ "Project": ["items", "project"],
+ "Quality Inspection": ["items", "quality_inspection"],
},
- 'transactions': [
- {
- 'label': _('Related'),
- 'items': ['Purchase Invoice', 'Landed Cost Voucher', 'Asset']
- },
- {
- 'label': _('Reference'),
- 'items': ['Purchase Order', 'Quality Inspection', 'Project']
- },
- {
- 'label': _('Returns'),
- 'items': ['Purchase Receipt']
- },
- {
- 'label': _('Subscription'),
- 'items': ['Auto Repeat']
- },
- ]
+ "transactions": [
+ {"label": _("Related"), "items": ["Purchase Invoice", "Landed Cost Voucher", "Asset"]},
+ {"label": _("Reference"), "items": ["Purchase Order", "Quality Inspection", "Project"]},
+ {"label": _("Returns"), "items": ["Purchase Receipt"]},
+ {"label": _("Subscription"), "items": ["Auto Repeat"]},
+ ],
}
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 0017fa7..a6f82b0 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -26,15 +26,11 @@
def test_purchase_receipt_received_qty(self):
"""
- 1. Test if received qty is validated against accepted + rejected
- 2. Test if received qty is auto set on save
+ 1. Test if received qty is validated against accepted + rejected
+ 2. Test if received qty is auto set on save
"""
pr = make_purchase_receipt(
- qty=1,
- rejected_qty=1,
- received_qty=3,
- item_code="_Test Item Home Desktop 200",
- do_not_save=True
+ qty=1, rejected_qty=1, received_qty=3, item_code="_Test Item Home Desktop 200", do_not_save=True
)
self.assertRaises(QtyMismatchError, pr.save)
@@ -51,11 +47,8 @@
sl_entry = frappe.db.get_all(
"Stock Ledger Entry",
- {
- "voucher_type": "Purchase Receipt",
- "voucher_no": pr.name
- },
- ['actual_qty']
+ {"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
+ ["actual_qty"],
)
self.assertEqual(len(sl_entry), 1)
@@ -65,47 +58,44 @@
sl_entry_cancelled = frappe.db.get_all(
"Stock Ledger Entry",
- {
- "voucher_type": "Purchase Receipt",
- "voucher_no": pr.name
- },
- ['actual_qty'],
- order_by='creation'
+ {"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
+ ["actual_qty"],
+ order_by="creation",
)
self.assertEqual(len(sl_entry_cancelled), 2)
self.assertEqual(sl_entry_cancelled[1].actual_qty, -0.5)
def test_make_purchase_invoice(self):
- if not frappe.db.exists('Payment Terms Template', '_Test Payment Terms Template For Purchase Invoice'):
- frappe.get_doc({
- 'doctype': 'Payment Terms Template',
- 'template_name': '_Test Payment Terms Template For Purchase Invoice',
- 'allocate_payment_based_on_payment_terms': 1,
- 'terms': [
- {
- 'doctype': 'Payment Terms Template Detail',
- 'invoice_portion': 50.00,
- 'credit_days_based_on': 'Day(s) after invoice date',
- 'credit_days': 00
- },
- {
- 'doctype': 'Payment Terms Template Detail',
- 'invoice_portion': 50.00,
- 'credit_days_based_on': 'Day(s) after invoice date',
- 'credit_days': 30
- }]
- }).insert()
+ if not frappe.db.exists(
+ "Payment Terms Template", "_Test Payment Terms Template For Purchase Invoice"
+ ):
+ frappe.get_doc(
+ {
+ "doctype": "Payment Terms Template",
+ "template_name": "_Test Payment Terms Template For Purchase Invoice",
+ "allocate_payment_based_on_payment_terms": 1,
+ "terms": [
+ {
+ "doctype": "Payment Terms Template Detail",
+ "invoice_portion": 50.00,
+ "credit_days_based_on": "Day(s) after invoice date",
+ "credit_days": 00,
+ },
+ {
+ "doctype": "Payment Terms Template Detail",
+ "invoice_portion": 50.00,
+ "credit_days_based_on": "Day(s) after invoice date",
+ "credit_days": 30,
+ },
+ ],
+ }
+ ).insert()
template = frappe.db.get_value(
- "Payment Terms Template",
- "_Test Payment Terms Template For Purchase Invoice"
+ "Payment Terms Template", "_Test Payment Terms Template For Purchase Invoice"
)
- old_template_in_supplier = frappe.db.get_value(
- "Supplier",
- "_Test Supplier",
- "payment_terms"
- )
+ old_template_in_supplier = frappe.db.get_value("Supplier", "_Test Supplier", "payment_terms")
frappe.db.set_value("Supplier", "_Test Supplier", "payment_terms", template)
pr = make_purchase_receipt(do_not_save=True)
@@ -123,23 +113,17 @@
# test if payment terms are fetched and set in PI
self.assertEqual(pi.payment_terms_template, template)
- self.assertEqual(pi.payment_schedule[0].payment_amount, flt(pi.grand_total)/2)
+ self.assertEqual(pi.payment_schedule[0].payment_amount, flt(pi.grand_total) / 2)
self.assertEqual(pi.payment_schedule[0].invoice_portion, 50)
- self.assertEqual(pi.payment_schedule[1].payment_amount, flt(pi.grand_total)/2)
+ self.assertEqual(pi.payment_schedule[1].payment_amount, flt(pi.grand_total) / 2)
self.assertEqual(pi.payment_schedule[1].invoice_portion, 50)
# teardown
- pi.delete() # draft PI
+ pi.delete() # draft PI
pr.cancel()
- frappe.db.set_value(
- "Supplier",
- "_Test Supplier",
- "payment_terms",
- old_template_in_supplier
- )
+ frappe.db.set_value("Supplier", "_Test Supplier", "payment_terms", old_template_in_supplier)
frappe.get_doc(
- "Payment Terms Template",
- "_Test Payment Terms Template For Purchase Invoice"
+ "Payment Terms Template", "_Test Payment Terms Template For Purchase Invoice"
).delete()
def test_purchase_receipt_no_gl_entry(self):
@@ -147,27 +131,19 @@
existing_bin_qty, existing_bin_stock_value = frappe.db.get_value(
"Bin",
- {
- "item_code": "_Test Item",
- "warehouse": "_Test Warehouse - _TC"
- },
- ["actual_qty", "stock_value"]
+ {"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"},
+ ["actual_qty", "stock_value"],
)
if existing_bin_qty < 0:
make_stock_entry(
- item_code="_Test Item",
- target="_Test Warehouse - _TC",
- qty=abs(existing_bin_qty)
+ item_code="_Test Item", target="_Test Warehouse - _TC", qty=abs(existing_bin_qty)
)
existing_bin_qty, existing_bin_stock_value = frappe.db.get_value(
"Bin",
- {
- "item_code": "_Test Item",
- "warehouse": "_Test Warehouse - _TC"
- },
- ["actual_qty", "stock_value"]
+ {"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"},
+ ["actual_qty", "stock_value"],
)
pr = make_purchase_receipt()
@@ -178,20 +154,15 @@
"voucher_type": "Purchase Receipt",
"voucher_no": pr.name,
"item_code": "_Test Item",
- "warehouse": "_Test Warehouse - _TC"
+ "warehouse": "_Test Warehouse - _TC",
},
- "stock_value_difference"
+ "stock_value_difference",
)
self.assertEqual(stock_value_difference, 250)
current_bin_stock_value = frappe.db.get_value(
- "Bin",
- {
- "item_code": "_Test Item",
- "warehouse": "_Test Warehouse - _TC"
- },
- "stock_value"
+ "Bin", {"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"}, "stock_value"
)
self.assertEqual(current_bin_stock_value, existing_bin_stock_value + 250)
@@ -200,7 +171,7 @@
pr.cancel()
def test_batched_serial_no_purchase(self):
- item = frappe.db.exists("Item", {'item_name': 'Batched Serialized Item'})
+ item = frappe.db.exists("Item", {"item_name": "Batched Serialized Item"})
if not item:
item = create_item("Batched Serialized Item")
item.has_batch_no = 1
@@ -210,34 +181,30 @@
item.serial_no_series = "BS-.####"
item.save()
else:
- item = frappe.get_doc("Item", {'item_name': 'Batched Serialized Item'})
+ item = frappe.get_doc("Item", {"item_name": "Batched Serialized Item"})
pr = make_purchase_receipt(item_code=item.name, qty=5, rate=500)
- self.assertTrue(
- frappe.db.get_value('Batch', {'item': item.name, 'reference_name': pr.name})
- )
+ self.assertTrue(frappe.db.get_value("Batch", {"item": item.name, "reference_name": pr.name}))
pr.load_from_db()
batch_no = pr.items[0].batch_no
pr.cancel()
- self.assertFalse(
- frappe.db.get_value('Batch', {'item': item.name, 'reference_name': pr.name})
- )
- self.assertFalse(frappe.db.get_all('Serial No', {'batch_no': batch_no}))
+ self.assertFalse(frappe.db.get_value("Batch", {"item": item.name, "reference_name": pr.name}))
+ self.assertFalse(frappe.db.get_all("Serial No", {"batch_no": batch_no}))
def test_duplicate_serial_nos(self):
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
- item = frappe.db.exists("Item", {'item_name': 'Test Serialized Item 123'})
+ item = frappe.db.exists("Item", {"item_name": "Test Serialized Item 123"})
if not item:
item = create_item("Test Serialized Item 123")
item.has_serial_no = 1
item.serial_no_series = "TSI123-.####"
item.save()
else:
- item = frappe.get_doc("Item", {'item_name': 'Test Serialized Item 123'})
+ item = frappe.get_doc("Item", {"item_name": "Test Serialized Item 123"})
# First make purchase receipt
pr = make_purchase_receipt(item_code=item.name, qty=2, rate=500)
@@ -245,12 +212,8 @@
serial_nos = frappe.db.get_value(
"Stock Ledger Entry",
- {
- "voucher_type": "Purchase Receipt",
- "voucher_no": pr.name,
- "item_code": item.name
- },
- "serial_no"
+ {"voucher_type": "Purchase Receipt", "voucher_no": pr.name, "item_code": item.name},
+ "serial_no",
)
serial_nos = get_serial_nos(serial_nos)
@@ -262,21 +225,16 @@
item_code=item.name,
qty=2,
rate=500,
- serial_no='\n'.join(serial_nos),
- company='_Test Company 1',
+ serial_no="\n".join(serial_nos),
+ company="_Test Company 1",
do_not_submit=True,
- warehouse = 'Stores - _TC1'
+ warehouse="Stores - _TC1",
)
self.assertRaises(SerialNoDuplicateError, pr_different_company.submit)
# Then made delivery note to remove the serial nos from stock
- dn = create_delivery_note(
- item_code=item.name,
- qty=2,
- rate=1500,
- serial_no='\n'.join(serial_nos)
- )
+ dn = create_delivery_note(item_code=item.name, qty=2, rate=1500, serial_no="\n".join(serial_nos))
dn.load_from_db()
self.assertEquals(get_serial_nos(dn.items[0].serial_no), serial_nos)
@@ -288,8 +246,8 @@
qty=2,
rate=500,
posting_date=posting_date,
- serial_no='\n'.join(serial_nos),
- do_not_submit=True
+ serial_no="\n".join(serial_nos),
+ do_not_submit=True,
)
self.assertRaises(SerialNoExistsInFutureTransaction, pr1.submit)
@@ -300,29 +258,28 @@
qty=2,
rate=500,
posting_date=posting_date,
- serial_no='\n'.join(serial_nos),
+ serial_no="\n".join(serial_nos),
company="_Test Company 1",
do_not_submit=True,
- warehouse="Stores - _TC1"
+ warehouse="Stores - _TC1",
)
self.assertRaises(SerialNoExistsInFutureTransaction, pr2.submit)
# Receive the same serial nos after the delivery note posting date and time
- make_purchase_receipt(
- item_code=item.name,
- qty=2,
- rate=500,
- serial_no='\n'.join(serial_nos)
- )
+ make_purchase_receipt(item_code=item.name, qty=2, rate=500, serial_no="\n".join(serial_nos))
# Raise the error for backdated deliver note entry cancel
self.assertRaises(SerialNoExistsInFutureTransaction, dn.cancel)
def test_purchase_receipt_gl_entry(self):
- pr = make_purchase_receipt(company="_Test Company with perpetual inventory",
- warehouse = "Stores - TCP1", supplier_warehouse = "Work in Progress - TCP1",
- get_multiple_items = True, get_taxes_and_charges = True)
+ pr = make_purchase_receipt(
+ company="_Test Company with perpetual inventory",
+ warehouse="Stores - TCP1",
+ supplier_warehouse="Work in Progress - TCP1",
+ get_multiple_items=True,
+ get_taxes_and_charges=True,
+ )
self.assertEqual(cint(erpnext.is_perpetual_inventory_enabled(pr.company)), 1)
@@ -338,14 +295,14 @@
stock_in_hand_account: [750.0, 0.0],
"Stock Received But Not Billed - TCP1": [0.0, 500.0],
"_Test Account Shipping Charges - TCP1": [0.0, 100.0],
- "_Test Account Customs Duty - TCP1": [0.0, 150.0]
+ "_Test Account Customs Duty - TCP1": [0.0, 150.0],
}
else:
expected_values = {
stock_in_hand_account: [375.0, 0.0],
fixed_asset_account: [375.0, 0.0],
"Stock Received But Not Billed - TCP1": [0.0, 500.0],
- "_Test Account Shipping Charges - TCP1": [0.0, 250.0]
+ "_Test Account Shipping Charges - TCP1": [0.0, 250.0],
}
for gle in gl_entries:
self.assertEqual(expected_values[gle.account][0], gle.debit)
@@ -358,22 +315,19 @@
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
frappe.db.set_value(
- "Buying Settings", None,
- "backflush_raw_materials_of_subcontract_based_on", "BOM"
+ "Buying Settings", None, "backflush_raw_materials_of_subcontract_based_on", "BOM"
)
make_stock_entry(
- item_code="_Test Item", qty=100,
- target="_Test Warehouse 1 - _TC", basic_rate=100
+ item_code="_Test Item", qty=100, target="_Test Warehouse 1 - _TC", basic_rate=100
)
make_stock_entry(
- item_code="_Test Item Home Desktop 100", qty=100,
- target="_Test Warehouse 1 - _TC", basic_rate=100
+ item_code="_Test Item Home Desktop 100",
+ qty=100,
+ target="_Test Warehouse 1 - _TC",
+ basic_rate=100,
)
- pr = make_purchase_receipt(
- item_code="_Test FG Item", qty=10,
- rate=500, is_subcontracted="Yes"
- )
+ pr = make_purchase_receipt(item_code="_Test FG Item", qty=10, rate=500, is_subcontracted="Yes")
self.assertEqual(len(pr.get("supplied_items")), 2)
rm_supp_cost = sum(d.amount for d in pr.get("supplied_items"))
@@ -383,32 +337,35 @@
def test_subcontracting_gle_fg_item_rate_zero(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
+
frappe.db.set_value(
- "Buying Settings", None,
- "backflush_raw_materials_of_subcontract_based_on", "BOM"
+ "Buying Settings", None, "backflush_raw_materials_of_subcontract_based_on", "BOM"
)
se1 = make_stock_entry(
item_code="_Test Item",
target="Work In Progress - TCP1",
- qty=100, basic_rate=100,
- company="_Test Company with perpetual inventory"
+ qty=100,
+ basic_rate=100,
+ company="_Test Company with perpetual inventory",
)
se2 = make_stock_entry(
item_code="_Test Item Home Desktop 100",
target="Work In Progress - TCP1",
- qty=100, basic_rate=100,
- company="_Test Company with perpetual inventory"
+ qty=100,
+ basic_rate=100,
+ company="_Test Company with perpetual inventory",
)
pr = make_purchase_receipt(
item_code="_Test FG Item",
- qty=10, rate=0,
+ qty=10,
+ rate=0,
is_subcontracted="Yes",
company="_Test Company with perpetual inventory",
warehouse="Stores - TCP1",
- supplier_warehouse="Work In Progress - TCP1"
+ supplier_warehouse="Work In Progress - TCP1",
)
gl_entries = get_gl_entries("Purchase Receipt", pr.name)
@@ -421,9 +378,9 @@
def test_subcontracting_over_receipt(self):
"""
- Behaviour: Raise multiple PRs against one PO that in total
- receive more than the required qty in the PO.
- Expected Result: Error Raised for Over Receipt against PO.
+ Behaviour: Raise multiple PRs against one PO that in total
+ receive more than the required qty in the PO.
+ Expected Result: Error Raised for Over Receipt against PO.
"""
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
from erpnext.buying.doctype.purchase_order.purchase_order import (
@@ -440,24 +397,23 @@
item_code = "_Test Subcontracted FG Item 1"
make_subcontracted_item(item_code=item_code)
- po = create_purchase_order(item_code=item_code, qty=1, include_exploded_items=0,
- is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
+ po = create_purchase_order(
+ item_code=item_code,
+ qty=1,
+ include_exploded_items=0,
+ is_subcontracted="Yes",
+ supplier_warehouse="_Test Warehouse 1 - _TC",
+ )
# stock raw materials in a warehouse before transfer
make_stock_entry(
- target="_Test Warehouse - _TC",
- item_code = "Test Extra Item 1",
- qty=10, basic_rate=100
+ target="_Test Warehouse - _TC", item_code="Test Extra Item 1", qty=10, basic_rate=100
)
make_stock_entry(
- target="_Test Warehouse - _TC",
- item_code = "_Test FG Item",
- qty=1, basic_rate=100
+ target="_Test Warehouse - _TC", item_code="_Test FG Item", qty=1, basic_rate=100
)
make_stock_entry(
- target="_Test Warehouse - _TC",
- item_code = "Test Extra Item 2",
- qty=1, basic_rate=100
+ target="_Test Warehouse - _TC", item_code="Test Extra Item 2", qty=1, basic_rate=100
)
rm_items = [
@@ -467,7 +423,7 @@
"item_name": "_Test FG Item",
"qty": po.supplied_items[0].required_qty,
"warehouse": "_Test Warehouse - _TC",
- "stock_uom": "Nos"
+ "stock_uom": "Nos",
},
{
"item_code": item_code,
@@ -475,8 +431,8 @@
"item_name": "Test Extra Item 1",
"qty": po.supplied_items[1].required_qty,
"warehouse": "_Test Warehouse - _TC",
- "stock_uom": "Nos"
- }
+ "stock_uom": "Nos",
+ },
]
rm_item_string = json.dumps(rm_items)
se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string))
@@ -495,15 +451,10 @@
pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1)
pr_row_1_serial_no = pr.get("items")[0].serial_no
- self.assertEqual(
- frappe.db.get_value("Serial No", pr_row_1_serial_no, "supplier"),
- pr.supplier
- )
+ self.assertEqual(frappe.db.get_value("Serial No", pr_row_1_serial_no, "supplier"), pr.supplier)
pr.cancel()
- self.assertFalse(
- frappe.db.get_value("Serial No", pr_row_1_serial_no, "warehouse")
- )
+ self.assertFalse(frappe.db.get_value("Serial No", pr_row_1_serial_no, "warehouse"))
def test_rejected_serial_no(self):
pr = frappe.copy_doc(test_records[0])
@@ -518,32 +469,34 @@
accepted_serial_nos = pr.get("items")[0].serial_no.split("\n")
self.assertEqual(len(accepted_serial_nos), 3)
for serial_no in accepted_serial_nos:
- self.assertEqual(frappe.db.get_value("Serial No", serial_no, "warehouse"),
- pr.get("items")[0].warehouse)
+ self.assertEqual(
+ frappe.db.get_value("Serial No", serial_no, "warehouse"), pr.get("items")[0].warehouse
+ )
rejected_serial_nos = pr.get("items")[0].rejected_serial_no.split("\n")
self.assertEqual(len(rejected_serial_nos), 2)
for serial_no in rejected_serial_nos:
- self.assertEqual(frappe.db.get_value("Serial No", serial_no, "warehouse"),
- pr.get("items")[0].rejected_warehouse)
+ self.assertEqual(
+ frappe.db.get_value("Serial No", serial_no, "warehouse"), pr.get("items")[0].rejected_warehouse
+ )
pr.cancel()
def test_purchase_return_partial(self):
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
- warehouse = "Stores - TCP1",
- supplier_warehouse = "Work in Progress - TCP1"
+ warehouse="Stores - TCP1",
+ supplier_warehouse="Work in Progress - TCP1",
)
return_pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
- warehouse = "Stores - TCP1",
- supplier_warehouse = "Work in Progress - TCP1",
+ warehouse="Stores - TCP1",
+ supplier_warehouse="Work in Progress - TCP1",
is_return=1,
return_against=pr.name,
qty=-2,
- do_not_submit=1
+ do_not_submit=1,
)
return_pr.items[0].purchase_receipt_item = pr.items[0].name
return_pr.submit()
@@ -551,16 +504,12 @@
# check sle
outgoing_rate = frappe.db.get_value(
"Stock Ledger Entry",
- {
- "voucher_type": "Purchase Receipt",
- "voucher_no": return_pr.name
- },
- "outgoing_rate"
+ {"voucher_type": "Purchase Receipt", "voucher_no": return_pr.name},
+ "outgoing_rate",
)
self.assertEqual(outgoing_rate, 50)
-
# check gl entries for return
gl_entries = get_gl_entries("Purchase Receipt", return_pr.name)
@@ -586,6 +535,7 @@
self.assertEqual(pr.per_returned, 40)
from erpnext.controllers.sales_and_purchase_return import make_return_doc
+
return_pr_2 = make_return_doc("Purchase Receipt", pr.name)
# Check if unreturned amount is mapped in 2nd return
@@ -608,7 +558,7 @@
# PR should be completed on billing all unreturned amount
self.assertEqual(pr.items[0].billed_amt, 150)
self.assertEqual(pr.per_billed, 100)
- self.assertEqual(pr.status, 'Completed')
+ self.assertEqual(pr.status, "Completed")
pi.load_from_db()
pi.cancel()
@@ -622,18 +572,18 @@
def test_purchase_return_full(self):
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
- warehouse = "Stores - TCP1",
- supplier_warehouse = "Work in Progress - TCP1"
+ warehouse="Stores - TCP1",
+ supplier_warehouse="Work in Progress - TCP1",
)
return_pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
- warehouse = "Stores - TCP1",
- supplier_warehouse = "Work in Progress - TCP1",
+ warehouse="Stores - TCP1",
+ supplier_warehouse="Work in Progress - TCP1",
is_return=1,
return_against=pr.name,
qty=-5,
- do_not_submit=1
+ do_not_submit=1,
)
return_pr.items[0].purchase_receipt_item = pr.items[0].name
return_pr.submit()
@@ -646,7 +596,7 @@
# Check if Original PR updated
self.assertEqual(pr.items[0].returned_qty, 5)
self.assertEqual(pr.per_returned, 100)
- self.assertEqual(pr.status, 'Return Issued')
+ self.assertEqual(pr.status, "Return Issued")
return_pr.cancel()
pr.cancel()
@@ -654,32 +604,32 @@
def test_purchase_return_for_rejected_qty(self):
from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse
- rejected_warehouse="_Test Rejected Warehouse - TCP1"
+ rejected_warehouse = "_Test Rejected Warehouse - TCP1"
if not frappe.db.exists("Warehouse", rejected_warehouse):
get_warehouse(
- company = "_Test Company with perpetual inventory",
- abbr = " - TCP1",
- warehouse_name = "_Test Rejected Warehouse"
+ company="_Test Company with perpetual inventory",
+ abbr=" - TCP1",
+ warehouse_name="_Test Rejected Warehouse",
).name
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
- warehouse = "Stores - TCP1",
- supplier_warehouse = "Work in Progress - TCP1",
+ warehouse="Stores - TCP1",
+ supplier_warehouse="Work in Progress - TCP1",
qty=2,
rejected_qty=2,
- rejected_warehouse=rejected_warehouse
+ rejected_warehouse=rejected_warehouse,
)
return_pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
- warehouse = "Stores - TCP1",
- supplier_warehouse = "Work in Progress - TCP1",
+ warehouse="Stores - TCP1",
+ supplier_warehouse="Work in Progress - TCP1",
is_return=1,
return_against=pr.name,
qty=-2,
- rejected_qty = -2,
- rejected_warehouse=rejected_warehouse
+ rejected_qty=-2,
+ rejected_warehouse=rejected_warehouse,
)
actual_qty = frappe.db.get_value(
@@ -687,9 +637,9 @@
{
"voucher_type": "Purchase Receipt",
"voucher_no": return_pr.name,
- "warehouse": return_pr.items[0].rejected_warehouse
+ "warehouse": return_pr.items[0].rejected_warehouse,
},
- "actual_qty"
+ "actual_qty",
)
self.assertEqual(actual_qty, -2)
@@ -697,7 +647,6 @@
return_pr.cancel()
pr.cancel()
-
def test_purchase_return_for_serialized_items(self):
def _check_serial_no_values(serial_no, field_values):
serial_no = frappe.get_doc("Serial No", serial_no)
@@ -710,24 +659,22 @@
serial_no = get_serial_nos(pr.get("items")[0].serial_no)[0]
- _check_serial_no_values(serial_no, {
- "warehouse": "_Test Warehouse - _TC",
- "purchase_document_no": pr.name
- })
+ _check_serial_no_values(
+ serial_no, {"warehouse": "_Test Warehouse - _TC", "purchase_document_no": pr.name}
+ )
return_pr = make_purchase_receipt(
item_code="_Test Serialized Item With Series",
qty=-1,
is_return=1,
return_against=pr.name,
- serial_no=serial_no
+ serial_no=serial_no,
)
- _check_serial_no_values(serial_no, {
- "warehouse": "",
- "purchase_document_no": pr.name,
- "delivery_document_no": return_pr.name
- })
+ _check_serial_no_values(
+ serial_no,
+ {"warehouse": "", "purchase_document_no": pr.name, "delivery_document_no": return_pr.name},
+ )
return_pr.cancel()
pr.reload()
@@ -735,20 +682,12 @@
def test_purchase_return_for_multi_uom(self):
item_code = "_Test Purchase Return For Multi-UOM"
- if not frappe.db.exists('Item', item_code):
- item = make_item(item_code, {'stock_uom': 'Box'})
- row = item.append('uoms', {
- 'uom': 'Unit',
- 'conversion_factor': 0.1
- })
+ if not frappe.db.exists("Item", item_code):
+ item = make_item(item_code, {"stock_uom": "Box"})
+ row = item.append("uoms", {"uom": "Unit", "conversion_factor": 0.1})
row.db_update()
- pr = make_purchase_receipt(
- item_code=item_code,
- qty=1,
- uom="Box",
- conversion_factor=1.0
- )
+ pr = make_purchase_receipt(item_code=item_code, qty=1, uom="Box", conversion_factor=1.0)
return_pr = make_purchase_receipt(
item_code=item_code,
qty=-10,
@@ -756,7 +695,7 @@
stock_uom="Box",
conversion_factor=0.1,
is_return=1,
- return_against=pr.name
+ return_against=pr.name,
)
self.assertEqual(abs(return_pr.items[0].stock_qty), 1.0)
@@ -772,18 +711,16 @@
pr = make_purchase_receipt()
update_purchase_receipt_status(pr.name, "Closed")
- self.assertEqual(
- frappe.db.get_value("Purchase Receipt", pr.name, "status"), "Closed"
- )
+ self.assertEqual(frappe.db.get_value("Purchase Receipt", pr.name, "status"), "Closed")
pr.reload()
pr.cancel()
def test_pr_billing_status(self):
"""Flow:
- 1. PO -> PR1 -> PI
- 2. PO -> PI
- 3. PO -> PR2.
+ 1. PO -> PR1 -> PI
+ 2. PO -> PI
+ 3. PO -> PR2.
"""
from erpnext.buying.doctype.purchase_order.purchase_order import (
make_purchase_invoice as make_purchase_invoice_from_po,
@@ -845,19 +782,15 @@
item = make_item(item_code, dict(has_serial_no=1))
serial_no = "12903812901"
- pr_doc = make_purchase_receipt(item_code=item_code,
- qty=1, serial_no = serial_no)
+ pr_doc = make_purchase_receipt(item_code=item_code, qty=1, serial_no=serial_no)
self.assertEqual(
serial_no,
frappe.db.get_value(
"Serial No",
- {
- "purchase_document_type": "Purchase Receipt",
- "purchase_document_no": pr_doc.name
- },
- "name"
- )
+ {"purchase_document_type": "Purchase Receipt", "purchase_document_no": pr_doc.name},
+ "name",
+ ),
)
pr_doc.cancel()
@@ -874,12 +807,9 @@
serial_no,
frappe.db.get_value(
"Serial No",
- {
- "purchase_document_type": "Purchase Receipt",
- "purchase_document_no": new_pr_doc.name
- },
- "name"
- )
+ {"purchase_document_type": "Purchase Receipt", "purchase_document_no": new_pr_doc.name},
+ "name",
+ ),
)
new_pr_doc.cancel()
@@ -887,40 +817,52 @@
def test_auto_asset_creation(self):
asset_item = "Test Asset Item"
- if not frappe.db.exists('Item', asset_item):
- asset_category = frappe.get_all('Asset Category')
+ if not frappe.db.exists("Item", asset_item):
+ asset_category = frappe.get_all("Asset Category")
if asset_category:
asset_category = asset_category[0].name
if not asset_category:
- doc = frappe.get_doc({
- 'doctype': 'Asset Category',
- 'asset_category_name': 'Test Asset Category',
- 'depreciation_method': 'Straight Line',
- 'total_number_of_depreciations': 12,
- 'frequency_of_depreciation': 1,
- 'accounts': [{
- 'company_name': '_Test Company',
- 'fixed_asset_account': '_Test Fixed Asset - _TC',
- 'accumulated_depreciation_account': '_Test Accumulated Depreciations - _TC',
- 'depreciation_expense_account': '_Test Depreciations - _TC'
- }]
- }).insert()
+ doc = frappe.get_doc(
+ {
+ "doctype": "Asset Category",
+ "asset_category_name": "Test Asset Category",
+ "depreciation_method": "Straight Line",
+ "total_number_of_depreciations": 12,
+ "frequency_of_depreciation": 1,
+ "accounts": [
+ {
+ "company_name": "_Test Company",
+ "fixed_asset_account": "_Test Fixed Asset - _TC",
+ "accumulated_depreciation_account": "_Test Accumulated Depreciations - _TC",
+ "depreciation_expense_account": "_Test Depreciations - _TC",
+ }
+ ],
+ }
+ ).insert()
asset_category = doc.name
- item_data = make_item(asset_item, {'is_stock_item':0,
- 'stock_uom': 'Box', 'is_fixed_asset': 1, 'auto_create_assets': 1,
- 'asset_category': asset_category, 'asset_naming_series': 'ABC.###'})
+ item_data = make_item(
+ asset_item,
+ {
+ "is_stock_item": 0,
+ "stock_uom": "Box",
+ "is_fixed_asset": 1,
+ "auto_create_assets": 1,
+ "asset_category": asset_category,
+ "asset_naming_series": "ABC.###",
+ },
+ )
asset_item = item_data.item_code
pr = make_purchase_receipt(item_code=asset_item, qty=3)
- assets = frappe.db.get_all('Asset', filters={'purchase_receipt': pr.name})
+ assets = frappe.db.get_all("Asset", filters={"purchase_receipt": pr.name})
self.assertEqual(len(assets), 3)
- location = frappe.db.get_value('Asset', assets[0].name, 'location')
+ location = frappe.db.get_value("Asset", assets[0].name, "location")
self.assertEqual(location, "Test Location")
pr.cancel()
@@ -930,17 +872,18 @@
pr = make_purchase_receipt(item_code="Test Asset Item", qty=1)
- asset = frappe.get_doc("Asset", {
- 'purchase_receipt': pr.name
- })
+ asset = frappe.get_doc("Asset", {"purchase_receipt": pr.name})
asset.available_for_use_date = frappe.utils.nowdate()
asset.gross_purchase_amount = 50.0
- asset.append("finance_books", {
- "expected_value_after_useful_life": 10,
- "depreciation_method": "Straight Line",
- "total_number_of_depreciations": 3,
- "frequency_of_depreciation": 1
- })
+ asset.append(
+ "finance_books",
+ {
+ "expected_value_after_useful_life": 10,
+ "depreciation_method": "Straight Line",
+ "total_number_of_depreciations": 3,
+ "frequency_of_depreciation": 1,
+ },
+ )
asset.submit()
pr_return = make_purchase_return(pr.name)
@@ -960,36 +903,27 @@
cost_center = "_Test Cost Center for BS Account - TCP1"
create_cost_center(
cost_center_name="_Test Cost Center for BS Account",
- company="_Test Company with perpetual inventory"
+ company="_Test Company with perpetual inventory",
)
- if not frappe.db.exists('Location', 'Test Location'):
- frappe.get_doc({
- 'doctype': 'Location',
- 'location_name': 'Test Location'
- }).insert()
+ if not frappe.db.exists("Location", "Test Location"):
+ frappe.get_doc({"doctype": "Location", "location_name": "Test Location"}).insert()
pr = make_purchase_receipt(
cost_center=cost_center,
company="_Test Company with perpetual inventory",
- warehouse = "Stores - TCP1",
- supplier_warehouse = "Work in Progress - TCP1"
+ warehouse="Stores - TCP1",
+ supplier_warehouse="Work in Progress - TCP1",
)
- stock_in_hand_account = get_inventory_account(
- pr.company, pr.get("items")[0].warehouse
- )
+ stock_in_hand_account = get_inventory_account(pr.company, pr.get("items")[0].warehouse)
gl_entries = get_gl_entries("Purchase Receipt", pr.name)
self.assertTrue(gl_entries)
expected_values = {
- "Stock Received But Not Billed - TCP1": {
- "cost_center": cost_center
- },
- stock_in_hand_account: {
- "cost_center": cost_center
- }
+ "Stock Received But Not Billed - TCP1": {"cost_center": cost_center},
+ stock_in_hand_account: {"cost_center": cost_center},
}
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
@@ -997,33 +931,24 @@
pr.cancel()
def test_purchase_receipt_cost_center_with_balance_sheet_account(self):
- if not frappe.db.exists('Location', 'Test Location'):
- frappe.get_doc({
- 'doctype': 'Location',
- 'location_name': 'Test Location'
- }).insert()
+ if not frappe.db.exists("Location", "Test Location"):
+ frappe.get_doc({"doctype": "Location", "location_name": "Test Location"}).insert()
pr = make_purchase_receipt(
company="_Test Company with perpetual inventory",
- warehouse = "Stores - TCP1",
- supplier_warehouse = "Work in Progress - TCP1"
+ warehouse="Stores - TCP1",
+ supplier_warehouse="Work in Progress - TCP1",
)
- stock_in_hand_account = get_inventory_account(
- pr.company, pr.get("items")[0].warehouse
- )
+ stock_in_hand_account = get_inventory_account(pr.company, pr.get("items")[0].warehouse)
gl_entries = get_gl_entries("Purchase Receipt", pr.name)
self.assertTrue(gl_entries)
- cost_center = pr.get('items')[0].cost_center
+ cost_center = pr.get("items")[0].cost_center
expected_values = {
- "Stock Received But Not Billed - TCP1": {
- "cost_center": cost_center
- },
- stock_in_hand_account: {
- "cost_center": cost_center
- }
+ "Stock Received But Not Billed - TCP1": {"cost_center": cost_center},
+ stock_in_hand_account: {"cost_center": cost_center},
}
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
@@ -1039,11 +964,7 @@
po = create_purchase_order()
pr = create_pr_against_po(po.name)
- pr1 = make_purchase_receipt(
- qty=-1,
- is_return=1, return_against=pr.name,
- do_not_submit=True
- )
+ pr1 = make_purchase_receipt(qty=-1, is_return=1, return_against=pr.name, do_not_submit=True)
pr1.items[0].purchase_order = po.name
pr1.items[0].purchase_order_item = po.items[0].name
pr1.items[0].purchase_receipt_item = pr.items[0].name
@@ -1060,14 +981,17 @@
def test_make_purchase_invoice_from_pr_with_returned_qty_duplicate_items(self):
pr1 = make_purchase_receipt(qty=8, do_not_submit=True)
- pr1.append("items", {
- "item_code": "_Test Item",
- "warehouse": "_Test Warehouse - _TC",
- "qty": 1,
- "received_qty": 1,
- "rate": 100,
- "conversion_factor": 1.0,
- })
+ pr1.append(
+ "items",
+ {
+ "item_code": "_Test Item",
+ "warehouse": "_Test Warehouse - _TC",
+ "qty": 1,
+ "received_qty": 1,
+ "rate": 100,
+ "conversion_factor": 1.0,
+ },
+ )
pr1.submit()
pi1 = make_purchase_invoice(pr1.name)
@@ -1076,11 +1000,7 @@
pi1.save()
pi1.submit()
- pr2 = make_purchase_receipt(
- qty=-2,
- is_return=1, return_against=pr1.name,
- do_not_submit=True
- )
+ pr2 = make_purchase_receipt(qty=-2, is_return=1, return_against=pr1.name, do_not_submit=True)
pr2.items[0].purchase_receipt_item = pr1.items[0].name
pr2.submit()
@@ -1094,26 +1014,25 @@
pr1.cancel()
def test_stock_transfer_from_purchase_receipt(self):
- pr1 = make_purchase_receipt(warehouse = 'Work In Progress - TCP1',
- company="_Test Company with perpetual inventory")
+ pr1 = make_purchase_receipt(
+ warehouse="Work In Progress - TCP1", company="_Test Company with perpetual inventory"
+ )
- pr = make_purchase_receipt(company="_Test Company with perpetual inventory",
- warehouse = "Stores - TCP1", do_not_save=1)
+ pr = make_purchase_receipt(
+ company="_Test Company with perpetual inventory", warehouse="Stores - TCP1", do_not_save=1
+ )
- pr.supplier_warehouse = ''
- pr.items[0].from_warehouse = 'Work In Progress - TCP1'
+ pr.supplier_warehouse = ""
+ pr.items[0].from_warehouse = "Work In Progress - TCP1"
pr.submit()
- gl_entries = get_gl_entries('Purchase Receipt', pr.name)
- sl_entries = get_sl_entries('Purchase Receipt', pr.name)
+ gl_entries = get_gl_entries("Purchase Receipt", pr.name)
+ sl_entries = get_sl_entries("Purchase Receipt", pr.name)
self.assertFalse(gl_entries)
- expected_sle = {
- 'Work In Progress - TCP1': -5,
- 'Stores - TCP1': 5
- }
+ expected_sle = {"Work In Progress - TCP1": -5, "Stores - TCP1": 5}
for sle in sl_entries:
self.assertEqual(expected_sle[sle.warehouse], sle.actual_qty)
@@ -1125,48 +1044,45 @@
create_warehouse(
"_Test Warehouse for Valuation",
company="_Test Company with perpetual inventory",
- properties={"account": '_Test Account Stock In Hand - TCP1'}
+ properties={"account": "_Test Account Stock In Hand - TCP1"},
)
pr1 = make_purchase_receipt(
- warehouse = '_Test Warehouse for Valuation - TCP1',
- company="_Test Company with perpetual inventory"
+ warehouse="_Test Warehouse for Valuation - TCP1",
+ company="_Test Company with perpetual inventory",
)
pr = make_purchase_receipt(
- company="_Test Company with perpetual inventory",
- warehouse = "Stores - TCP1",
- do_not_save=1
+ company="_Test Company with perpetual inventory", warehouse="Stores - TCP1", do_not_save=1
)
- pr.items[0].from_warehouse = '_Test Warehouse for Valuation - TCP1'
- pr.supplier_warehouse = ''
+ pr.items[0].from_warehouse = "_Test Warehouse for Valuation - TCP1"
+ pr.supplier_warehouse = ""
-
- pr.append('taxes', {
- 'charge_type': 'On Net Total',
- 'account_head': '_Test Account Shipping Charges - TCP1',
- 'category': 'Valuation and Total',
- 'cost_center': 'Main - TCP1',
- 'description': 'Test',
- 'rate': 9
- })
+ pr.append(
+ "taxes",
+ {
+ "charge_type": "On Net Total",
+ "account_head": "_Test Account Shipping Charges - TCP1",
+ "category": "Valuation and Total",
+ "cost_center": "Main - TCP1",
+ "description": "Test",
+ "rate": 9,
+ },
+ )
pr.submit()
- gl_entries = get_gl_entries('Purchase Receipt', pr.name)
- sl_entries = get_sl_entries('Purchase Receipt', pr.name)
+ gl_entries = get_gl_entries("Purchase Receipt", pr.name)
+ sl_entries = get_sl_entries("Purchase Receipt", pr.name)
expected_gle = [
- ['Stock In Hand - TCP1', 272.5, 0.0],
- ['_Test Account Stock In Hand - TCP1', 0.0, 250.0],
- ['_Test Account Shipping Charges - TCP1', 0.0, 22.5]
+ ["Stock In Hand - TCP1", 272.5, 0.0],
+ ["_Test Account Stock In Hand - TCP1", 0.0, 250.0],
+ ["_Test Account Shipping Charges - TCP1", 0.0, 22.5],
]
- expected_sle = {
- '_Test Warehouse for Valuation - TCP1': -5,
- 'Stores - TCP1': 5
- }
+ expected_sle = {"_Test Warehouse for Valuation - TCP1": -5, "Stores - TCP1": 5}
for sle in sl_entries:
self.assertEqual(expected_sle[sle.warehouse], sle.actual_qty)
@@ -1179,7 +1095,6 @@
pr.cancel()
pr1.cancel()
-
def test_subcontracted_pr_for_multi_transfer_batches(self):
from erpnext.buying.doctype.purchase_order.purchase_order import (
make_purchase_receipt,
@@ -1194,49 +1109,57 @@
update_backflush_based_on("Material Transferred for Subcontract")
item_code = "_Test Subcontracted FG Item 3"
- make_item('Sub Contracted Raw Material 3', {
- 'is_stock_item': 1,
- 'is_sub_contracted_item': 1,
- 'has_batch_no': 1,
- 'create_new_batch': 1
- })
+ make_item(
+ "Sub Contracted Raw Material 3",
+ {"is_stock_item": 1, "is_sub_contracted_item": 1, "has_batch_no": 1, "create_new_batch": 1},
+ )
- create_subcontracted_item(item_code=item_code, has_batch_no=1,
- raw_materials=["Sub Contracted Raw Material 3"])
+ create_subcontracted_item(
+ item_code=item_code, has_batch_no=1, raw_materials=["Sub Contracted Raw Material 3"]
+ )
order_qty = 500
- po = create_purchase_order(item_code=item_code, qty=order_qty,
- is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
+ po = create_purchase_order(
+ item_code=item_code,
+ qty=order_qty,
+ is_subcontracted="Yes",
+ supplier_warehouse="_Test Warehouse 1 - _TC",
+ )
- ste1=make_stock_entry(target="_Test Warehouse - _TC",
- item_code = "Sub Contracted Raw Material 3", qty=300, basic_rate=100)
- ste2=make_stock_entry(target="_Test Warehouse - _TC",
- item_code = "Sub Contracted Raw Material 3", qty=200, basic_rate=100)
+ ste1 = make_stock_entry(
+ target="_Test Warehouse - _TC",
+ item_code="Sub Contracted Raw Material 3",
+ qty=300,
+ basic_rate=100,
+ )
+ ste2 = make_stock_entry(
+ target="_Test Warehouse - _TC",
+ item_code="Sub Contracted Raw Material 3",
+ qty=200,
+ basic_rate=100,
+ )
- transferred_batch = {
- ste1.items[0].batch_no : 300,
- ste2.items[0].batch_no : 200
- }
+ transferred_batch = {ste1.items[0].batch_no: 300, ste2.items[0].batch_no: 200}
rm_items = [
{
- "item_code":item_code,
- "rm_item_code":"Sub Contracted Raw Material 3",
- "item_name":"_Test Item",
- "qty":300,
- "warehouse":"_Test Warehouse - _TC",
- "stock_uom":"Nos",
- "name": po.supplied_items[0].name
+ "item_code": item_code,
+ "rm_item_code": "Sub Contracted Raw Material 3",
+ "item_name": "_Test Item",
+ "qty": 300,
+ "warehouse": "_Test Warehouse - _TC",
+ "stock_uom": "Nos",
+ "name": po.supplied_items[0].name,
},
{
- "item_code":item_code,
- "rm_item_code":"Sub Contracted Raw Material 3",
- "item_name":"_Test Item",
- "qty":200,
- "warehouse":"_Test Warehouse - _TC",
- "stock_uom":"Nos",
- "name": po.supplied_items[0].name
- }
+ "item_code": item_code,
+ "rm_item_code": "Sub Contracted Raw Material 3",
+ "item_name": "_Test Item",
+ "qty": 200,
+ "warehouse": "_Test Warehouse - _TC",
+ "stock_uom": "Nos",
+ "name": po.supplied_items[0].name,
+ },
]
rm_item_string = json.dumps(rm_items)
@@ -1248,11 +1171,8 @@
supplied_qty = frappe.db.get_value(
"Purchase Order Item Supplied",
- {
- "parent": po.name,
- "rm_item_code": "Sub Contracted Raw Material 3"
- },
- "supplied_qty"
+ {"parent": po.name, "rm_item_code": "Sub Contracted Raw Material 3"},
+ "supplied_qty",
)
self.assertEqual(supplied_qty, 500.00)
@@ -1272,12 +1192,11 @@
ste1.cancel()
po.cancel()
-
def test_po_to_pi_and_po_to_pr_worflow_full(self):
"""Test following behaviour:
- - Create PO
- - Create PI from PO and submit
- - Create PR from PO and submit
+ - Create PO
+ - Create PI from PO and submit
+ - Create PR from PO and submit
"""
from erpnext.buying.doctype.purchase_order import purchase_order, test_purchase_order
@@ -1296,16 +1215,16 @@
def test_po_to_pi_and_po_to_pr_worflow_partial(self):
"""Test following behaviour:
- - Create PO
- - Create partial PI from PO and submit
- - Create PR from PO and submit
+ - Create PO
+ - Create partial PI from PO and submit
+ - Create PR from PO and submit
"""
from erpnext.buying.doctype.purchase_order import purchase_order, test_purchase_order
po = test_purchase_order.create_purchase_order()
pi = purchase_order.make_purchase_invoice(po.name)
- pi.items[0].qty /= 2 # roughly 50%, ^ this function only creates PI with 1 item.
+ pi.items[0].qty /= 2 # roughly 50%, ^ this function only creates PI with 1 item.
pi.submit()
pr = purchase_order.make_purchase_receipt(po.name)
@@ -1329,11 +1248,14 @@
make_purchase_invoice as create_purchase_invoice,
)
- pi = create_purchase_invoice(company="_Test Company with perpetual inventory",
- cost_center = "Main - TCP1",
- warehouse = "Stores - TCP1",
- expense_account ="_Test Account Cost for Goods Sold - TCP1",
- currency = "USD", conversion_rate = 70)
+ pi = create_purchase_invoice(
+ company="_Test Company with perpetual inventory",
+ cost_center="Main - TCP1",
+ warehouse="Stores - TCP1",
+ expense_account="_Test Account Cost for Goods Sold - TCP1",
+ currency="USD",
+ conversion_rate=70,
+ )
pr = create_purchase_receipt(pi.name)
pr.conversion_rate = 80
@@ -1345,19 +1267,16 @@
# Get exchnage gain and loss account
exchange_gain_loss_account = frappe.db.get_value(
- 'Company', pr.company, 'exchange_gain_loss_account'
+ "Company", pr.company, "exchange_gain_loss_account"
)
# fetching the latest GL Entry with exchange gain and loss account account
amount = frappe.db.get_value(
- 'GL Entry',
- {
- 'account': exchange_gain_loss_account,
- 'voucher_no': pr.name
- },
- 'credit'
+ "GL Entry", {"account": exchange_gain_loss_account, "voucher_no": pr.name}, "credit"
)
- discrepancy_caused_by_exchange_rate_diff = abs(pi.items[0].base_net_amount - pr.items[0].base_net_amount)
+ discrepancy_caused_by_exchange_rate_diff = abs(
+ pi.items[0].base_net_amount - pr.items[0].base_net_amount
+ )
self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount)
@@ -1379,7 +1298,7 @@
po = create_purchase_order(qty=10, rate=100, do_not_save=1)
create_payment_terms_template()
- po.payment_terms_template = 'Test Receivable Template'
+ po.payment_terms_template = "Test Receivable Template"
po.submit()
pr = make_pr_against_po(po.name, received_qty=10)
@@ -1406,7 +1325,9 @@
account = "Stock Received But Not Billed - TCP1"
make_item(item_code)
- se = make_stock_entry(item_code=item_code, from_warehouse=warehouse, qty=50, do_not_save=True, rate=0)
+ se = make_stock_entry(
+ item_code=item_code, from_warehouse=warehouse, qty=50, do_not_save=True, rate=0
+ )
se.items[0].allow_zero_valuation_rate = 1
se.save()
se.submit()
@@ -1427,93 +1348,112 @@
def get_sl_entries(voucher_type, voucher_no):
- return frappe.db.sql(""" select actual_qty, warehouse, stock_value_difference
+ return frappe.db.sql(
+ """ select actual_qty, warehouse, stock_value_difference
from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s
- order by posting_time desc""", (voucher_type, voucher_no), as_dict=1)
+ order by posting_time desc""",
+ (voucher_type, voucher_no),
+ as_dict=1,
+ )
+
def get_gl_entries(voucher_type, voucher_no):
- return frappe.db.sql("""select account, debit, credit, cost_center, is_cancelled
+ return frappe.db.sql(
+ """select account, debit, credit, cost_center, is_cancelled
from `tabGL Entry` where voucher_type=%s and voucher_no=%s
- order by account desc""", (voucher_type, voucher_no), as_dict=1)
+ order by account desc""",
+ (voucher_type, voucher_no),
+ as_dict=1,
+ )
+
def get_taxes(**args):
args = frappe._dict(args)
- return [{'account_head': '_Test Account Shipping Charges - TCP1',
- 'add_deduct_tax': 'Add',
- 'category': 'Valuation and Total',
- 'charge_type': 'Actual',
- 'cost_center': args.cost_center or 'Main - TCP1',
- 'description': 'Shipping Charges',
- 'doctype': 'Purchase Taxes and Charges',
- 'parentfield': 'taxes',
- 'rate': 100.0,
- 'tax_amount': 100.0},
- {'account_head': '_Test Account VAT - TCP1',
- 'add_deduct_tax': 'Add',
- 'category': 'Total',
- 'charge_type': 'Actual',
- 'cost_center': args.cost_center or 'Main - TCP1',
- 'description': 'VAT',
- 'doctype': 'Purchase Taxes and Charges',
- 'parentfield': 'taxes',
- 'rate': 120.0,
- 'tax_amount': 120.0},
- {'account_head': '_Test Account Customs Duty - TCP1',
- 'add_deduct_tax': 'Add',
- 'category': 'Valuation',
- 'charge_type': 'Actual',
- 'cost_center': args.cost_center or 'Main - TCP1',
- 'description': 'Customs Duty',
- 'doctype': 'Purchase Taxes and Charges',
- 'parentfield': 'taxes',
- 'rate': 150.0,
- 'tax_amount': 150.0}]
+ return [
+ {
+ "account_head": "_Test Account Shipping Charges - TCP1",
+ "add_deduct_tax": "Add",
+ "category": "Valuation and Total",
+ "charge_type": "Actual",
+ "cost_center": args.cost_center or "Main - TCP1",
+ "description": "Shipping Charges",
+ "doctype": "Purchase Taxes and Charges",
+ "parentfield": "taxes",
+ "rate": 100.0,
+ "tax_amount": 100.0,
+ },
+ {
+ "account_head": "_Test Account VAT - TCP1",
+ "add_deduct_tax": "Add",
+ "category": "Total",
+ "charge_type": "Actual",
+ "cost_center": args.cost_center or "Main - TCP1",
+ "description": "VAT",
+ "doctype": "Purchase Taxes and Charges",
+ "parentfield": "taxes",
+ "rate": 120.0,
+ "tax_amount": 120.0,
+ },
+ {
+ "account_head": "_Test Account Customs Duty - TCP1",
+ "add_deduct_tax": "Add",
+ "category": "Valuation",
+ "charge_type": "Actual",
+ "cost_center": args.cost_center or "Main - TCP1",
+ "description": "Customs Duty",
+ "doctype": "Purchase Taxes and Charges",
+ "parentfield": "taxes",
+ "rate": 150.0,
+ "tax_amount": 150.0,
+ },
+ ]
+
def get_items(**args):
args = frappe._dict(args)
- return [{
- "base_amount": 250.0,
- "conversion_factor": 1.0,
- "description": "_Test Item",
- "doctype": "Purchase Receipt Item",
- "item_code": "_Test Item",
- "item_name": "_Test Item",
- "parentfield": "items",
- "qty": 5.0,
- "rate": 50.0,
- "received_qty": 5.0,
- "rejected_qty": 0.0,
- "stock_uom": "_Test UOM",
- "uom": "_Test UOM",
- "warehouse": args.warehouse or "_Test Warehouse - _TC",
- "cost_center": args.cost_center or "Main - _TC"
- },
- {
- "base_amount": 250.0,
- "conversion_factor": 1.0,
- "description": "_Test Item Home Desktop 100",
- "doctype": "Purchase Receipt Item",
- "item_code": "_Test Item Home Desktop 100",
- "item_name": "_Test Item Home Desktop 100",
- "parentfield": "items",
- "qty": 5.0,
- "rate": 50.0,
- "received_qty": 5.0,
- "rejected_qty": 0.0,
- "stock_uom": "_Test UOM",
- "uom": "_Test UOM",
- "warehouse": args.warehouse or "_Test Warehouse 1 - _TC",
- "cost_center": args.cost_center or "Main - _TC"
- }]
+ return [
+ {
+ "base_amount": 250.0,
+ "conversion_factor": 1.0,
+ "description": "_Test Item",
+ "doctype": "Purchase Receipt Item",
+ "item_code": "_Test Item",
+ "item_name": "_Test Item",
+ "parentfield": "items",
+ "qty": 5.0,
+ "rate": 50.0,
+ "received_qty": 5.0,
+ "rejected_qty": 0.0,
+ "stock_uom": "_Test UOM",
+ "uom": "_Test UOM",
+ "warehouse": args.warehouse or "_Test Warehouse - _TC",
+ "cost_center": args.cost_center or "Main - _TC",
+ },
+ {
+ "base_amount": 250.0,
+ "conversion_factor": 1.0,
+ "description": "_Test Item Home Desktop 100",
+ "doctype": "Purchase Receipt Item",
+ "item_code": "_Test Item Home Desktop 100",
+ "item_name": "_Test Item Home Desktop 100",
+ "parentfield": "items",
+ "qty": 5.0,
+ "rate": 50.0,
+ "received_qty": 5.0,
+ "rejected_qty": 0.0,
+ "stock_uom": "_Test UOM",
+ "uom": "_Test UOM",
+ "warehouse": args.warehouse or "_Test Warehouse 1 - _TC",
+ "cost_center": args.cost_center or "Main - _TC",
+ },
+ ]
+
def make_purchase_receipt(**args):
- if not frappe.db.exists('Location', 'Test Location'):
- frappe.get_doc({
- 'doctype': 'Location',
- 'location_name': 'Test Location'
- }).insert()
+ if not frappe.db.exists("Location", "Test Location"):
+ frappe.get_doc({"doctype": "Location", "location_name": "Test Location"}).insert()
frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1)
pr = frappe.new_doc("Purchase Receipt")
@@ -1537,28 +1477,34 @@
item_code = args.item or args.item_code or "_Test Item"
uom = args.uom or frappe.db.get_value("Item", item_code, "stock_uom") or "_Test UOM"
- pr.append("items", {
- "item_code": item_code,
- "warehouse": args.warehouse or "_Test Warehouse - _TC",
- "qty": qty,
- "received_qty": received_qty,
- "rejected_qty": rejected_qty,
- "rejected_warehouse": args.rejected_warehouse or "_Test Rejected Warehouse - _TC" if rejected_qty != 0 else "",
- "rate": args.rate if args.rate != None else 50,
- "conversion_factor": args.conversion_factor or 1.0,
- "stock_qty": flt(qty) * (flt(args.conversion_factor) or 1.0),
- "serial_no": args.serial_no,
- "batch_no": args.batch_no,
- "stock_uom": args.stock_uom or "_Test UOM",
- "uom": uom,
- "cost_center": args.cost_center or frappe.get_cached_value('Company', pr.company, 'cost_center'),
- "asset_location": args.location or "Test Location"
- })
+ pr.append(
+ "items",
+ {
+ "item_code": item_code,
+ "warehouse": args.warehouse or "_Test Warehouse - _TC",
+ "qty": qty,
+ "received_qty": received_qty,
+ "rejected_qty": rejected_qty,
+ "rejected_warehouse": args.rejected_warehouse or "_Test Rejected Warehouse - _TC"
+ if rejected_qty != 0
+ else "",
+ "rate": args.rate if args.rate != None else 50,
+ "conversion_factor": args.conversion_factor or 1.0,
+ "stock_qty": flt(qty) * (flt(args.conversion_factor) or 1.0),
+ "serial_no": args.serial_no,
+ "batch_no": args.batch_no,
+ "stock_uom": args.stock_uom or "_Test UOM",
+ "uom": uom,
+ "cost_center": args.cost_center
+ or frappe.get_cached_value("Company", pr.company, "cost_center"),
+ "asset_location": args.location or "Test Location",
+ },
+ )
if args.get_multiple_items:
pr.items = []
- company_cost_center = frappe.get_cached_value('Company', pr.company, 'cost_center')
+ company_cost_center = frappe.get_cached_value("Company", pr.company, "cost_center")
cost_center = args.cost_center or company_cost_center
for item in get_items(warehouse=args.warehouse, cost_center=cost_center):
@@ -1574,33 +1520,44 @@
pr.submit()
return pr
+
def create_subcontracted_item(**args):
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
args = frappe._dict(args)
- if not frappe.db.exists('Item', args.item_code):
- make_item(args.item_code, {
- 'is_stock_item': 1,
- 'is_sub_contracted_item': 1,
- 'has_batch_no': args.get("has_batch_no") or 0
- })
+ if not frappe.db.exists("Item", args.item_code):
+ make_item(
+ args.item_code,
+ {
+ "is_stock_item": 1,
+ "is_sub_contracted_item": 1,
+ "has_batch_no": args.get("has_batch_no") or 0,
+ },
+ )
if not args.raw_materials:
- if not frappe.db.exists('Item', "Test Extra Item 1"):
- make_item("Test Extra Item 1", {
- 'is_stock_item': 1,
- })
+ if not frappe.db.exists("Item", "Test Extra Item 1"):
+ make_item(
+ "Test Extra Item 1",
+ {
+ "is_stock_item": 1,
+ },
+ )
- if not frappe.db.exists('Item', "Test Extra Item 2"):
- make_item("Test Extra Item 2", {
- 'is_stock_item': 1,
- })
+ if not frappe.db.exists("Item", "Test Extra Item 2"):
+ make_item(
+ "Test Extra Item 2",
+ {
+ "is_stock_item": 1,
+ },
+ )
- args.raw_materials = ['_Test FG Item', 'Test Extra Item 1']
+ args.raw_materials = ["_Test FG Item", "Test Extra Item 1"]
- if not frappe.db.get_value('BOM', {'item': args.item_code}, 'name'):
- make_bom(item = args.item_code, raw_materials = args.get("raw_materials"))
+ if not frappe.db.get_value("BOM", {"item": args.item_code}, "name"):
+ make_bom(item=args.item_code, raw_materials=args.get("raw_materials"))
+
test_dependencies = ["BOM", "Item Price", "Location"]
-test_records = frappe.get_test_records('Purchase Receipt')
+test_records = frappe.get_test_records("Purchase Receipt")
diff --git a/erpnext/stock/doctype/putaway_rule/putaway_rule.py b/erpnext/stock/doctype/putaway_rule/putaway_rule.py
index 4e472a9..623fbde 100644
--- a/erpnext/stock/doctype/putaway_rule/putaway_rule.py
+++ b/erpnext/stock/doctype/putaway_rule/putaway_rule.py
@@ -24,11 +24,16 @@
self.set_stock_capacity()
def validate_duplicate_rule(self):
- existing_rule = frappe.db.exists("Putaway Rule", {"item_code": self.item_code, "warehouse": self.warehouse})
+ existing_rule = frappe.db.exists(
+ "Putaway Rule", {"item_code": self.item_code, "warehouse": self.warehouse}
+ )
if existing_rule and existing_rule != self.name:
- frappe.throw(_("Putaway Rule already exists for Item {0} in Warehouse {1}.")
- .format(frappe.bold(self.item_code), frappe.bold(self.warehouse)),
- title=_("Duplicate"))
+ frappe.throw(
+ _("Putaway Rule already exists for Item {0} in Warehouse {1}.").format(
+ frappe.bold(self.item_code), frappe.bold(self.warehouse)
+ ),
+ title=_("Duplicate"),
+ )
def validate_priority(self):
if self.priority < 1:
@@ -37,18 +42,24 @@
def validate_warehouse_and_company(self):
company = frappe.db.get_value("Warehouse", self.warehouse, "company")
if company != self.company:
- frappe.throw(_("Warehouse {0} does not belong to Company {1}.")
- .format(frappe.bold(self.warehouse), frappe.bold(self.company)),
- title=_("Invalid Warehouse"))
+ frappe.throw(
+ _("Warehouse {0} does not belong to Company {1}.").format(
+ frappe.bold(self.warehouse), frappe.bold(self.company)
+ ),
+ title=_("Invalid Warehouse"),
+ )
def validate_capacity(self):
stock_uom = frappe.db.get_value("Item", self.item_code, "stock_uom")
balance_qty = get_stock_balance(self.item_code, self.warehouse, nowdate())
if flt(self.stock_capacity) < flt(balance_qty):
- frappe.throw(_("Warehouse Capacity for Item '{0}' must be greater than the existing stock level of {1} {2}.")
- .format(self.item_code, frappe.bold(balance_qty), stock_uom),
- title=_("Insufficient Capacity"))
+ frappe.throw(
+ _(
+ "Warehouse Capacity for Item '{0}' must be greater than the existing stock level of {1} {2}."
+ ).format(self.item_code, frappe.bold(balance_qty), stock_uom),
+ title=_("Insufficient Capacity"),
+ )
if not self.capacity:
frappe.throw(_("Capacity must be greater than 0"), title=_("Invalid"))
@@ -56,23 +67,26 @@
def set_stock_capacity(self):
self.stock_capacity = (flt(self.conversion_factor) or 1) * flt(self.capacity)
+
@frappe.whitelist()
def get_available_putaway_capacity(rule):
- stock_capacity, item_code, warehouse = frappe.db.get_value("Putaway Rule", rule,
- ["stock_capacity", "item_code", "warehouse"])
+ stock_capacity, item_code, warehouse = frappe.db.get_value(
+ "Putaway Rule", rule, ["stock_capacity", "item_code", "warehouse"]
+ )
balance_qty = get_stock_balance(item_code, warehouse, nowdate())
free_space = flt(stock_capacity) - flt(balance_qty)
return free_space if free_space > 0 else 0
+
@frappe.whitelist()
def apply_putaway_rule(doctype, items, company, sync=None, purpose=None):
- """ Applies Putaway Rule on line items.
+ """Applies Putaway Rule on line items.
- items: List of Purchase Receipt/Stock Entry Items
- company: Company in the Purchase Receipt/Stock Entry
- doctype: Doctype to apply rule on
- purpose: Purpose of Stock Entry
- sync (optional): Sync with client side only for client side calls
+ items: List of Purchase Receipt/Stock Entry Items
+ company: Company in the Purchase Receipt/Stock Entry
+ doctype: Doctype to apply rule on
+ purpose: Purpose of Stock Entry
+ sync (optional): Sync with client side only for client side calls
"""
if isinstance(items, str):
items = json.loads(items)
@@ -89,16 +103,18 @@
item.conversion_factor = flt(item.conversion_factor) or 1.0
pending_qty, item_code = flt(item.qty), item.item_code
pending_stock_qty = flt(item.transfer_qty) if doctype == "Stock Entry" else flt(item.stock_qty)
- uom_must_be_whole_number = frappe.db.get_value('UOM', item.uom, 'must_be_whole_number')
+ uom_must_be_whole_number = frappe.db.get_value("UOM", item.uom, "must_be_whole_number")
if not pending_qty or not item_code:
updated_table = add_row(item, pending_qty, source_warehouse or item.warehouse, updated_table)
continue
- at_capacity, rules = get_ordered_putaway_rules(item_code, company, source_warehouse=source_warehouse)
+ at_capacity, rules = get_ordered_putaway_rules(
+ item_code, company, source_warehouse=source_warehouse
+ )
if not rules:
- warehouse = source_warehouse or item.get('warehouse')
+ warehouse = source_warehouse or item.get("warehouse")
if at_capacity:
# rules available, but no free space
items_not_accomodated.append([item_code, pending_qty])
@@ -117,23 +133,28 @@
for rule in item_wise_rules[key]:
if pending_stock_qty > 0 and rule.free_space:
- stock_qty_to_allocate = flt(rule.free_space) if pending_stock_qty >= flt(rule.free_space) else pending_stock_qty
+ stock_qty_to_allocate = (
+ flt(rule.free_space) if pending_stock_qty >= flt(rule.free_space) else pending_stock_qty
+ )
qty_to_allocate = stock_qty_to_allocate / item.conversion_factor
if uom_must_be_whole_number:
qty_to_allocate = floor(qty_to_allocate)
stock_qty_to_allocate = qty_to_allocate * item.conversion_factor
- if not qty_to_allocate: break
+ if not qty_to_allocate:
+ break
- updated_table = add_row(item, qty_to_allocate, rule.warehouse, updated_table,
- rule.name, serial_nos=serial_nos)
+ updated_table = add_row(
+ item, qty_to_allocate, rule.warehouse, updated_table, rule.name, serial_nos=serial_nos
+ )
pending_stock_qty -= stock_qty_to_allocate
pending_qty -= qty_to_allocate
rule["free_space"] -= stock_qty_to_allocate
- if not pending_stock_qty > 0: break
+ if not pending_stock_qty > 0:
+ break
# if pending qty after applying all rules, add row without warehouse
if pending_stock_qty > 0:
@@ -146,13 +167,14 @@
items[:] = updated_table
frappe.msgprint(_("Applied putaway rules."), alert=True)
- if sync and json.loads(sync): # sync with client side
+ if sync and json.loads(sync): # sync with client side
return items
-def _items_changed(old, new, doctype: str) -> bool:
- """ Check if any items changed by application of putaway rules.
- If not, changing item table can have side effects since `name` items also changes.
+def _items_changed(old, new, doctype: str) -> bool:
+ """Check if any items changed by application of putaway rules.
+
+ If not, changing item table can have side effects since `name` items also changes.
"""
if len(old) != len(new):
return True
@@ -161,13 +183,22 @@
if doctype == "Stock Entry":
compare_keys = ("item_code", "t_warehouse", "transfer_qty", "serial_no")
- sort_key = lambda item: (item.item_code, cstr(item.t_warehouse), # noqa
- flt(item.transfer_qty), cstr(item.serial_no))
+ sort_key = lambda item: ( # noqa
+ item.item_code,
+ cstr(item.t_warehouse),
+ flt(item.transfer_qty),
+ cstr(item.serial_no),
+ )
else:
# purchase receipt / invoice
compare_keys = ("item_code", "warehouse", "stock_qty", "received_qty", "serial_no")
- sort_key = lambda item: (item.item_code, cstr(item.warehouse), # noqa
- flt(item.stock_qty), flt(item.received_qty), cstr(item.serial_no))
+ sort_key = lambda item: ( # noqa
+ item.item_code,
+ cstr(item.warehouse),
+ flt(item.stock_qty),
+ flt(item.received_qty),
+ cstr(item.serial_no),
+ )
old_sorted = sorted(old, key=sort_key)
new_sorted = sorted(new, key=sort_key)
@@ -182,18 +213,16 @@
def get_ordered_putaway_rules(item_code, company, source_warehouse=None):
"""Returns an ordered list of putaway rules to apply on an item."""
- filters = {
- "item_code": item_code,
- "company": company,
- "disable": 0
- }
+ filters = {"item_code": item_code, "company": company, "disable": 0}
if source_warehouse:
filters.update({"warehouse": ["!=", source_warehouse]})
- rules = frappe.get_all("Putaway Rule",
+ rules = frappe.get_all(
+ "Putaway Rule",
fields=["name", "item_code", "stock_capacity", "priority", "warehouse"],
filters=filters,
- order_by="priority asc, capacity desc")
+ order_by="priority asc, capacity desc",
+ )
if not rules:
return False, None
@@ -211,10 +240,11 @@
# then there is not enough space left in any rule
return True, None
- vacant_rules = sorted(vacant_rules, key = lambda i: (i['priority'], -i['free_space']))
+ vacant_rules = sorted(vacant_rules, key=lambda i: (i["priority"], -i["free_space"]))
return False, vacant_rules
+
def add_row(item, to_allocate, warehouse, updated_table, rule=None, serial_nos=None):
new_updated_table_row = copy.deepcopy(item)
new_updated_table_row.idx = 1 if not updated_table else cint(updated_table[-1].idx) + 1
@@ -223,7 +253,9 @@
if item.doctype == "Stock Entry Detail":
new_updated_table_row.t_warehouse = warehouse
- new_updated_table_row.transfer_qty = flt(to_allocate) * flt(new_updated_table_row.conversion_factor)
+ new_updated_table_row.transfer_qty = flt(to_allocate) * flt(
+ new_updated_table_row.conversion_factor
+ )
else:
new_updated_table_row.stock_qty = flt(to_allocate) * flt(new_updated_table_row.conversion_factor)
new_updated_table_row.warehouse = warehouse
@@ -238,6 +270,7 @@
updated_table.append(new_updated_table_row)
return updated_table
+
def show_unassigned_items_message(items_not_accomodated):
msg = _("The following Items, having Putaway Rules, could not be accomodated:") + "<br><br>"
formatted_item_rows = ""
@@ -247,7 +280,9 @@
formatted_item_rows += """
<td>{0}</td>
<td>{1}</td>
- </tr>""".format(item_link, frappe.bold(entry[1]))
+ </tr>""".format(
+ item_link, frappe.bold(entry[1])
+ )
msg += """
<table class="table">
@@ -257,13 +292,17 @@
</thead>
{2}
</table>
- """.format(_("Item"), _("Unassigned Qty"), formatted_item_rows)
+ """.format(
+ _("Item"), _("Unassigned Qty"), formatted_item_rows
+ )
frappe.msgprint(msg, title=_("Insufficient Capacity"), is_minimizable=True, wide=True)
+
def get_serial_nos_to_allocate(serial_nos, to_allocate):
if serial_nos:
- allocated_serial_nos = serial_nos[0: cint(to_allocate)]
- serial_nos[:] = serial_nos[cint(to_allocate):] # pop out allocated serial nos and modify list
+ allocated_serial_nos = serial_nos[0 : cint(to_allocate)]
+ serial_nos[:] = serial_nos[cint(to_allocate) :] # pop out allocated serial nos and modify list
return "\n".join(allocated_serial_nos) if allocated_serial_nos else ""
- else: return ""
+ else:
+ return ""
diff --git a/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py b/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py
index 0ec812c..ab0ca10 100644
--- a/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py
+++ b/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py
@@ -15,12 +15,9 @@
class TestPutawayRule(FrappeTestCase):
def setUp(self):
if not frappe.db.exists("Item", "_Rice"):
- make_item("_Rice", {
- 'is_stock_item': 1,
- 'has_batch_no' : 1,
- 'create_new_batch': 1,
- 'stock_uom': 'Kg'
- })
+ make_item(
+ "_Rice", {"is_stock_item": 1, "has_batch_no": 1, "create_new_batch": 1, "stock_uom": "Kg"}
+ )
if not frappe.db.exists("Warehouse", {"warehouse_name": "Rack 1"}):
create_warehouse("Rack 1")
@@ -36,10 +33,10 @@
new_uom.save()
def assertUnchangedItemsOnResave(self, doc):
- """ Check if same items remain even after reapplication of rules.
+ """Check if same items remain even after reapplication of rules.
- This is required since some business logic like subcontracting
- depends on `name` of items to be same if item isn't changed.
+ This is required since some business logic like subcontracting
+ depends on `name` of items to be same if item isn't changed.
"""
doc.reload()
old_items = {d.name for d in doc.items}
@@ -49,13 +46,14 @@
def test_putaway_rules_priority(self):
"""Test if rule is applied by priority, irrespective of free space."""
- rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=200,
- uom="Kg")
- rule_2 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_2, capacity=300,
- uom="Kg", priority=2)
+ rule_1 = create_putaway_rule(
+ item_code="_Rice", warehouse=self.warehouse_1, capacity=200, uom="Kg"
+ )
+ rule_2 = create_putaway_rule(
+ item_code="_Rice", warehouse=self.warehouse_2, capacity=300, uom="Kg", priority=2
+ )
- pr = make_purchase_receipt(item_code="_Rice", qty=300, apply_putaway_rule=1,
- do_not_submit=1)
+ pr = make_purchase_receipt(item_code="_Rice", qty=300, apply_putaway_rule=1, do_not_submit=1)
self.assertEqual(len(pr.items), 2)
self.assertEqual(pr.items[0].qty, 200)
self.assertEqual(pr.items[0].warehouse, self.warehouse_1)
@@ -71,16 +69,19 @@
def test_putaway_rules_with_same_priority(self):
"""Test if rule with more free space is applied,
among two rules with same priority and capacity."""
- rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=500,
- uom="Kg")
- rule_2 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_2, capacity=500,
- uom="Kg")
+ rule_1 = create_putaway_rule(
+ item_code="_Rice", warehouse=self.warehouse_1, capacity=500, uom="Kg"
+ )
+ rule_2 = create_putaway_rule(
+ item_code="_Rice", warehouse=self.warehouse_2, capacity=500, uom="Kg"
+ )
# out of 500 kg capacity, occupy 100 kg in warehouse_1
- stock_receipt = make_stock_entry(item_code="_Rice", target=self.warehouse_1, qty=100, basic_rate=50)
+ stock_receipt = make_stock_entry(
+ item_code="_Rice", target=self.warehouse_1, qty=100, basic_rate=50
+ )
- pr = make_purchase_receipt(item_code="_Rice", qty=700, apply_putaway_rule=1,
- do_not_submit=1)
+ pr = make_purchase_receipt(item_code="_Rice", qty=700, apply_putaway_rule=1, do_not_submit=1)
self.assertEqual(len(pr.items), 2)
self.assertEqual(pr.items[0].qty, 500)
# warehouse_2 has 500 kg free space, it is given priority
@@ -96,13 +97,14 @@
def test_putaway_rules_with_insufficient_capacity(self):
"""Test if qty exceeding capacity, is handled."""
- rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=100,
- uom="Kg")
- rule_2 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_2, capacity=200,
- uom="Kg")
+ rule_1 = create_putaway_rule(
+ item_code="_Rice", warehouse=self.warehouse_1, capacity=100, uom="Kg"
+ )
+ rule_2 = create_putaway_rule(
+ item_code="_Rice", warehouse=self.warehouse_2, capacity=200, uom="Kg"
+ )
- pr = make_purchase_receipt(item_code="_Rice", qty=350, apply_putaway_rule=1,
- do_not_submit=1)
+ pr = make_purchase_receipt(item_code="_Rice", qty=350, apply_putaway_rule=1, do_not_submit=1)
self.assertEqual(len(pr.items), 2)
self.assertEqual(pr.items[0].qty, 200)
self.assertEqual(pr.items[0].warehouse, self.warehouse_2)
@@ -118,24 +120,32 @@
"""Test rules applied on uom other than stock uom."""
item = frappe.get_doc("Item", "_Rice")
if not frappe.db.get_value("UOM Conversion Detail", {"parent": "_Rice", "uom": "Bag"}):
- item.append("uoms", {
- "uom": "Bag",
- "conversion_factor": 1000
- })
+ item.append("uoms", {"uom": "Bag", "conversion_factor": 1000})
item.save()
- rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=3,
- uom="Bag")
+ rule_1 = create_putaway_rule(
+ item_code="_Rice", warehouse=self.warehouse_1, capacity=3, uom="Bag"
+ )
self.assertEqual(rule_1.stock_capacity, 3000)
- rule_2 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_2, capacity=4,
- uom="Bag")
+ rule_2 = create_putaway_rule(
+ item_code="_Rice", warehouse=self.warehouse_2, capacity=4, uom="Bag"
+ )
self.assertEqual(rule_2.stock_capacity, 4000)
# populate 'Rack 1' with 1 Bag, making the free space 2 Bags
- stock_receipt = make_stock_entry(item_code="_Rice", target=self.warehouse_1, qty=1000, basic_rate=50)
+ stock_receipt = make_stock_entry(
+ item_code="_Rice", target=self.warehouse_1, qty=1000, basic_rate=50
+ )
- pr = make_purchase_receipt(item_code="_Rice", qty=6, uom="Bag", stock_uom="Kg",
- conversion_factor=1000, apply_putaway_rule=1, do_not_submit=1)
+ pr = make_purchase_receipt(
+ item_code="_Rice",
+ qty=6,
+ uom="Bag",
+ stock_uom="Kg",
+ conversion_factor=1000,
+ apply_putaway_rule=1,
+ do_not_submit=1,
+ )
self.assertEqual(len(pr.items), 2)
self.assertEqual(pr.items[0].qty, 4)
self.assertEqual(pr.items[0].warehouse, self.warehouse_2)
@@ -151,25 +161,30 @@
"""Test if whole UOMs are handled."""
item = frappe.get_doc("Item", "_Rice")
if not frappe.db.get_value("UOM Conversion Detail", {"parent": "_Rice", "uom": "Bag"}):
- item.append("uoms", {
- "uom": "Bag",
- "conversion_factor": 1000
- })
+ item.append("uoms", {"uom": "Bag", "conversion_factor": 1000})
item.save()
frappe.db.set_value("UOM", "Bag", "must_be_whole_number", 1)
# Putaway Rule in different UOM
- rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=1,
- uom="Bag")
+ rule_1 = create_putaway_rule(
+ item_code="_Rice", warehouse=self.warehouse_1, capacity=1, uom="Bag"
+ )
self.assertEqual(rule_1.stock_capacity, 1000)
# Putaway Rule in Stock UOM
rule_2 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_2, capacity=500)
self.assertEqual(rule_2.stock_capacity, 500)
# total capacity is 1500 Kg
- pr = make_purchase_receipt(item_code="_Rice", qty=2, uom="Bag", stock_uom="Kg",
- conversion_factor=1000, apply_putaway_rule=1, do_not_submit=1)
+ pr = make_purchase_receipt(
+ item_code="_Rice",
+ qty=2,
+ uom="Bag",
+ stock_uom="Kg",
+ conversion_factor=1000,
+ apply_putaway_rule=1,
+ do_not_submit=1,
+ )
self.assertEqual(len(pr.items), 1)
self.assertEqual(pr.items[0].qty, 1)
self.assertEqual(pr.items[0].warehouse, self.warehouse_1)
@@ -184,23 +199,26 @@
def test_putaway_rules_with_reoccurring_item(self):
"""Test rules on same item entered multiple times with different rate."""
- rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=200,
- uom="Kg")
+ rule_1 = create_putaway_rule(
+ item_code="_Rice", warehouse=self.warehouse_1, capacity=200, uom="Kg"
+ )
# total capacity is 200 Kg
- pr = make_purchase_receipt(item_code="_Rice", qty=100, apply_putaway_rule=1,
- do_not_submit=1)
- pr.append("items", {
- "item_code": "_Rice",
- "warehouse": "_Test Warehouse - _TC",
- "qty": 200,
- "uom": "Kg",
- "stock_uom": "Kg",
- "stock_qty": 200,
- "received_qty": 200,
- "rate": 100,
- "conversion_factor": 1.0,
- }) # same item entered again in PR but with different rate
+ pr = make_purchase_receipt(item_code="_Rice", qty=100, apply_putaway_rule=1, do_not_submit=1)
+ pr.append(
+ "items",
+ {
+ "item_code": "_Rice",
+ "warehouse": "_Test Warehouse - _TC",
+ "qty": 200,
+ "uom": "Kg",
+ "stock_uom": "Kg",
+ "stock_qty": 200,
+ "received_qty": 200,
+ "rate": 100,
+ "conversion_factor": 1.0,
+ },
+ ) # same item entered again in PR but with different rate
pr.save()
self.assertEqual(len(pr.items), 2)
self.assertEqual(pr.items[0].qty, 100)
@@ -208,7 +226,7 @@
self.assertEqual(pr.items[0].putaway_rule, rule_1.name)
# same rule applied to second item row
# with previous assignment considered
- self.assertEqual(pr.items[1].qty, 100) # 100 unassigned in second row from 200
+ self.assertEqual(pr.items[1].qty, 100) # 100 unassigned in second row from 200
self.assertEqual(pr.items[1].warehouse, self.warehouse_1)
self.assertEqual(pr.items[1].putaway_rule, rule_1.name)
@@ -219,13 +237,13 @@
def test_validate_over_receipt_in_warehouse(self):
"""Test if overreceipt is blocked in the presence of putaway rules."""
- rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=200,
- uom="Kg")
+ rule_1 = create_putaway_rule(
+ item_code="_Rice", warehouse=self.warehouse_1, capacity=200, uom="Kg"
+ )
- pr = make_purchase_receipt(item_code="_Rice", qty=300, apply_putaway_rule=1,
- do_not_submit=1)
+ pr = make_purchase_receipt(item_code="_Rice", qty=300, apply_putaway_rule=1, do_not_submit=1)
self.assertEqual(len(pr.items), 1)
- self.assertEqual(pr.items[0].qty, 200) # 100 is unassigned fro 300 Kg
+ self.assertEqual(pr.items[0].qty, 200) # 100 is unassigned fro 300 Kg
self.assertEqual(pr.items[0].warehouse, self.warehouse_1)
self.assertEqual(pr.items[0].putaway_rule, rule_1.name)
@@ -240,21 +258,29 @@
def test_putaway_rule_on_stock_entry_material_transfer(self):
"""Test if source warehouse is considered while applying rules."""
- rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=200,
- uom="Kg") # higher priority
- rule_2 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_2, capacity=100,
- uom="Kg", priority=2)
+ rule_1 = create_putaway_rule(
+ item_code="_Rice", warehouse=self.warehouse_1, capacity=200, uom="Kg"
+ ) # higher priority
+ rule_2 = create_putaway_rule(
+ item_code="_Rice", warehouse=self.warehouse_2, capacity=100, uom="Kg", priority=2
+ )
- stock_entry = make_stock_entry(item_code="_Rice", source=self.warehouse_1, qty=200,
- target="_Test Warehouse - _TC", purpose="Material Transfer",
- apply_putaway_rule=1, do_not_submit=1)
+ stock_entry = make_stock_entry(
+ item_code="_Rice",
+ source=self.warehouse_1,
+ qty=200,
+ target="_Test Warehouse - _TC",
+ purpose="Material Transfer",
+ apply_putaway_rule=1,
+ do_not_submit=1,
+ )
stock_entry_item = stock_entry.get("items")[0]
# since source warehouse is Rack 1, rule 1 (for Rack 1) will be avoided
# even though it has more free space and higher priority
self.assertEqual(stock_entry_item.t_warehouse, self.warehouse_2)
- self.assertEqual(stock_entry_item.qty, 100) # unassigned 100 out of 200 Kg
+ self.assertEqual(stock_entry_item.qty, 100) # unassigned 100 out of 200 Kg
self.assertEqual(stock_entry_item.putaway_rule, rule_2.name)
self.assertUnchangedItemsOnResave(stock_entry)
@@ -265,37 +291,48 @@
def test_putaway_rule_on_stock_entry_material_transfer_reoccuring_item(self):
"""Test if reoccuring item is correctly considered."""
- rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=300,
- uom="Kg")
- rule_2 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_2, capacity=600,
- uom="Kg", priority=2)
+ rule_1 = create_putaway_rule(
+ item_code="_Rice", warehouse=self.warehouse_1, capacity=300, uom="Kg"
+ )
+ rule_2 = create_putaway_rule(
+ item_code="_Rice", warehouse=self.warehouse_2, capacity=600, uom="Kg", priority=2
+ )
# create SE with first row having source warehouse as Rack 2
- stock_entry = make_stock_entry(item_code="_Rice", source=self.warehouse_2, qty=200,
- target="_Test Warehouse - _TC", purpose="Material Transfer",
- apply_putaway_rule=1, do_not_submit=1)
+ stock_entry = make_stock_entry(
+ item_code="_Rice",
+ source=self.warehouse_2,
+ qty=200,
+ target="_Test Warehouse - _TC",
+ purpose="Material Transfer",
+ apply_putaway_rule=1,
+ do_not_submit=1,
+ )
# Add rows with source warehouse as Rack 1
- stock_entry.extend("items", [
- {
- "item_code": "_Rice",
- "s_warehouse": self.warehouse_1,
- "t_warehouse": "_Test Warehouse - _TC",
- "qty": 100,
- "basic_rate": 50,
- "conversion_factor": 1.0,
- "transfer_qty": 100
- },
- {
- "item_code": "_Rice",
- "s_warehouse": self.warehouse_1,
- "t_warehouse": "_Test Warehouse - _TC",
- "qty": 200,
- "basic_rate": 60,
- "conversion_factor": 1.0,
- "transfer_qty": 200
- }
- ])
+ stock_entry.extend(
+ "items",
+ [
+ {
+ "item_code": "_Rice",
+ "s_warehouse": self.warehouse_1,
+ "t_warehouse": "_Test Warehouse - _TC",
+ "qty": 100,
+ "basic_rate": 50,
+ "conversion_factor": 1.0,
+ "transfer_qty": 100,
+ },
+ {
+ "item_code": "_Rice",
+ "s_warehouse": self.warehouse_1,
+ "t_warehouse": "_Test Warehouse - _TC",
+ "qty": 200,
+ "basic_rate": 60,
+ "conversion_factor": 1.0,
+ "transfer_qty": 200,
+ },
+ ],
+ )
stock_entry.save()
@@ -323,19 +360,24 @@
def test_putaway_rule_on_stock_entry_material_transfer_batch_serial_item(self):
"""Test if batch and serial items are split correctly."""
if not frappe.db.exists("Item", "Water Bottle"):
- make_item("Water Bottle", {
- "is_stock_item": 1,
- "has_batch_no" : 1,
- "create_new_batch": 1,
- "has_serial_no": 1,
- "serial_no_series": "BOTTL-.####",
- "stock_uom": "Nos"
- })
+ make_item(
+ "Water Bottle",
+ {
+ "is_stock_item": 1,
+ "has_batch_no": 1,
+ "create_new_batch": 1,
+ "has_serial_no": 1,
+ "serial_no_series": "BOTTL-.####",
+ "stock_uom": "Nos",
+ },
+ )
- rule_1 = create_putaway_rule(item_code="Water Bottle", warehouse=self.warehouse_1, capacity=3,
- uom="Nos")
- rule_2 = create_putaway_rule(item_code="Water Bottle", warehouse=self.warehouse_2, capacity=2,
- uom="Nos")
+ rule_1 = create_putaway_rule(
+ item_code="Water Bottle", warehouse=self.warehouse_1, capacity=3, uom="Nos"
+ )
+ rule_2 = create_putaway_rule(
+ item_code="Water Bottle", warehouse=self.warehouse_2, capacity=2, uom="Nos"
+ )
make_new_batch(batch_id="BOTTL-BATCH-1", item_code="Water Bottle")
@@ -344,12 +386,20 @@
pr.save()
pr.submit()
- serial_nos = frappe.get_list("Serial No", filters={"purchase_document_no": pr.name, "status": "Active"})
+ serial_nos = frappe.get_list(
+ "Serial No", filters={"purchase_document_no": pr.name, "status": "Active"}
+ )
serial_nos = [d.name for d in serial_nos]
- stock_entry = make_stock_entry(item_code="Water Bottle", source="_Test Warehouse - _TC", qty=5,
- target="Finished Goods - _TC", purpose="Material Transfer",
- apply_putaway_rule=1, do_not_save=1)
+ stock_entry = make_stock_entry(
+ item_code="Water Bottle",
+ source="_Test Warehouse - _TC",
+ qty=5,
+ target="Finished Goods - _TC",
+ purpose="Material Transfer",
+ apply_putaway_rule=1,
+ do_not_save=1,
+ )
stock_entry.items[0].batch_no = "BOTTL-BATCH-1"
stock_entry.items[0].serial_no = "\n".join(serial_nos)
stock_entry.save()
@@ -375,14 +425,21 @@
def test_putaway_rule_on_stock_entry_material_receipt(self):
"""Test if rules are applied in Stock Entry of type Receipt."""
- rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=200,
- uom="Kg") # more capacity
- rule_2 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_2, capacity=100,
- uom="Kg")
+ rule_1 = create_putaway_rule(
+ item_code="_Rice", warehouse=self.warehouse_1, capacity=200, uom="Kg"
+ ) # more capacity
+ rule_2 = create_putaway_rule(
+ item_code="_Rice", warehouse=self.warehouse_2, capacity=100, uom="Kg"
+ )
- stock_entry = make_stock_entry(item_code="_Rice", qty=100,
- target="_Test Warehouse - _TC", purpose="Material Receipt",
- apply_putaway_rule=1, do_not_submit=1)
+ stock_entry = make_stock_entry(
+ item_code="_Rice",
+ qty=100,
+ target="_Test Warehouse - _TC",
+ purpose="Material Receipt",
+ apply_putaway_rule=1,
+ do_not_submit=1,
+ )
stock_entry_item = stock_entry.get("items")[0]
@@ -400,8 +457,7 @@
from erpnext.stock.dashboard.warehouse_capacity_dashboard import get_data
item = "_Rice"
- rule = create_putaway_rule(item_code=item, warehouse=self.warehouse_1, capacity=500,
- uom="Kg")
+ rule = create_putaway_rule(item_code=item, warehouse=self.warehouse_1, capacity=500, uom="Kg")
capacities = get_data(warehouse=self.warehouse_1)
for capacity in capacities:
@@ -411,6 +467,7 @@
get_data(warehouse=self.warehouse_1)
rule.delete()
+
def create_putaway_rule(**args):
args = frappe._dict(args)
putaway = frappe.new_doc("Putaway Rule")
@@ -423,7 +480,9 @@
putaway.capacity = args.capacity or 1
putaway.stock_uom = frappe.db.get_value("Item", putaway.item_code, "stock_uom")
putaway.uom = args.uom or putaway.stock_uom
- putaway.conversion_factor = get_conversion_factor(putaway.item_code, putaway.uom)['conversion_factor']
+ putaway.conversion_factor = get_conversion_factor(putaway.item_code, putaway.uom)[
+ "conversion_factor"
+ ]
if not args.do_not_save:
putaway.save()
diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
index 4e3b80a..331d3e8 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
@@ -18,8 +18,8 @@
if not self.readings and self.item_code:
self.get_item_specification_details()
- if self.inspection_type=="In Process" and self.reference_type=="Job Card":
- item_qi_template = frappe.db.get_value("Item", self.item_code, 'quality_inspection_template')
+ if self.inspection_type == "In Process" and self.reference_type == "Job Card":
+ item_qi_template = frappe.db.get_value("Item", self.item_code, "quality_inspection_template")
parameters = get_template_details(item_qi_template)
for reading in self.readings:
for d in parameters:
@@ -33,26 +33,28 @@
@frappe.whitelist()
def get_item_specification_details(self):
if not self.quality_inspection_template:
- self.quality_inspection_template = frappe.db.get_value('Item',
- self.item_code, 'quality_inspection_template')
+ self.quality_inspection_template = frappe.db.get_value(
+ "Item", self.item_code, "quality_inspection_template"
+ )
- if not self.quality_inspection_template: return
+ if not self.quality_inspection_template:
+ return
- self.set('readings', [])
+ self.set("readings", [])
parameters = get_template_details(self.quality_inspection_template)
for d in parameters:
- child = self.append('readings', {})
+ child = self.append("readings", {})
child.update(d)
child.status = "Accepted"
@frappe.whitelist()
def get_quality_inspection_template(self):
- template = ''
+ template = ""
if self.bom_no:
- template = frappe.db.get_value('BOM', self.bom_no, 'quality_inspection_template')
+ template = frappe.db.get_value("BOM", self.bom_no, "quality_inspection_template")
if not template:
- template = frappe.db.get_value('BOM', self.item_code, 'quality_inspection_template')
+ template = frappe.db.get_value("BOM", self.item_code, "quality_inspection_template")
self.quality_inspection_template = template
self.get_item_specification_details()
@@ -66,21 +68,25 @@
def update_qc_reference(self):
quality_inspection = self.name if self.docstatus == 1 else ""
- if self.reference_type == 'Job Card':
+ if self.reference_type == "Job Card":
if self.reference_name:
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE `tab{doctype}`
SET quality_inspection = %s, modified = %s
WHERE name = %s and production_item = %s
- """.format(doctype=self.reference_type),
- (quality_inspection, self.modified, self.reference_name, self.item_code))
+ """.format(
+ doctype=self.reference_type
+ ),
+ (quality_inspection, self.modified, self.reference_name, self.item_code),
+ )
else:
args = [quality_inspection, self.modified, self.reference_name, self.item_code]
- doctype = self.reference_type + ' Item'
+ doctype = self.reference_type + " Item"
- if self.reference_type == 'Stock Entry':
- doctype = 'Stock Entry Detail'
+ if self.reference_type == "Stock Entry":
+ doctype = "Stock Entry Detail"
if self.reference_type and self.reference_name:
conditions = ""
@@ -88,11 +94,12 @@
conditions += " and t1.batch_no = %s"
args.append(self.batch_no)
- if self.docstatus == 2: # if cancel, then remove qi link wherever same name
+ if self.docstatus == 2: # if cancel, then remove qi link wherever same name
conditions += " and t1.quality_inspection = %s"
args.append(self.name)
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE
`tab{child_doc}` t1, `tab{parent_doc}` t2
SET
@@ -102,12 +109,15 @@
and t1.item_code = %s
and t1.parent = t2.name
{conditions}
- """.format(parent_doc=self.reference_type, child_doc=doctype, conditions=conditions),
- args)
+ """.format(
+ parent_doc=self.reference_type, child_doc=doctype, conditions=conditions
+ ),
+ args,
+ )
def inspect_and_set_status(self):
for reading in self.readings:
- if not reading.manual_inspection: # dont auto set status if manual
+ if not reading.manual_inspection: # dont auto set status if manual
if reading.formula_based_criteria:
self.set_status_based_on_acceptance_formula(reading)
else:
@@ -129,13 +139,16 @@
reading_value = reading.get("reading_" + str(i))
if reading_value is not None and reading_value.strip():
result = flt(reading.get("min_value")) <= flt(reading_value) <= flt(reading.get("max_value"))
- if not result: return False
+ if not result:
+ return False
return True
def set_status_based_on_acceptance_formula(self, reading):
if not reading.acceptance_formula:
- frappe.throw(_("Row #{0}: Acceptance Criteria Formula is required.").format(reading.idx),
- title=_("Missing Formula"))
+ frappe.throw(
+ _("Row #{0}: Acceptance Criteria Formula is required.").format(reading.idx),
+ title=_("Missing Formula"),
+ )
condition = reading.acceptance_formula
data = self.get_formula_evaluation_data(reading)
@@ -145,12 +158,17 @@
reading.status = "Accepted" if result else "Rejected"
except NameError as e:
field = frappe.bold(e.args[0].split()[1])
- frappe.throw(_("Row #{0}: {1} is not a valid reading field. Please refer to the field description.")
- .format(reading.idx, field),
- title=_("Invalid Formula"))
+ frappe.throw(
+ _("Row #{0}: {1} is not a valid reading field. Please refer to the field description.").format(
+ reading.idx, field
+ ),
+ title=_("Invalid Formula"),
+ )
except Exception:
- frappe.throw(_("Row #{0}: Acceptance Criteria Formula is incorrect.").format(reading.idx),
- title=_("Invalid Formula"))
+ frappe.throw(
+ _("Row #{0}: Acceptance Criteria Formula is incorrect.").format(reading.idx),
+ title=_("Invalid Formula"),
+ )
def get_formula_evaluation_data(self, reading):
data = {}
@@ -168,6 +186,7 @@
def calculate_mean(self, reading):
"""Calculate mean of all non-empty readings."""
from statistics import mean
+
readings_list = []
for i in range(1, 11):
@@ -178,65 +197,90 @@
actual_mean = mean(readings_list) if readings_list else 0
return actual_mean
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def item_query(doctype, txt, searchfield, start, page_len, filters):
if filters.get("from"):
from frappe.desk.reportview import get_match_cond
+
mcond = get_match_cond(filters["from"])
cond, qi_condition = "", "and (quality_inspection is null or quality_inspection = '')"
if filters.get("parent"):
- if filters.get('from') in ['Purchase Invoice Item', 'Purchase Receipt Item']\
- and filters.get("inspection_type") != "In Process":
+ if (
+ filters.get("from") in ["Purchase Invoice Item", "Purchase Receipt Item"]
+ and filters.get("inspection_type") != "In Process"
+ ):
cond = """and item_code in (select name from `tabItem` where
inspection_required_before_purchase = 1)"""
- elif filters.get('from') in ['Sales Invoice Item', 'Delivery Note Item']\
- and filters.get("inspection_type") != "In Process":
+ elif (
+ filters.get("from") in ["Sales Invoice Item", "Delivery Note Item"]
+ and filters.get("inspection_type") != "In Process"
+ ):
cond = """and item_code in (select name from `tabItem` where
inspection_required_before_delivery = 1)"""
- elif filters.get('from') == 'Stock Entry Detail':
+ elif filters.get("from") == "Stock Entry Detail":
cond = """and s_warehouse is null"""
- if filters.get('from') in ['Supplier Quotation Item']:
+ if filters.get("from") in ["Supplier Quotation Item"]:
qi_condition = ""
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
SELECT item_code
FROM `tab{doc}`
WHERE parent=%(parent)s and docstatus < 2 and item_code like %(txt)s
{qi_condition} {cond} {mcond}
ORDER BY item_code limit {start}, {page_len}
- """.format(doc=filters.get('from'),
- cond = cond, mcond = mcond, start = start,
- page_len = page_len, qi_condition = qi_condition),
- {'parent': filters.get('parent'), 'txt': "%%%s%%" % txt})
+ """.format(
+ doc=filters.get("from"),
+ cond=cond,
+ mcond=mcond,
+ start=start,
+ page_len=page_len,
+ qi_condition=qi_condition,
+ ),
+ {"parent": filters.get("parent"), "txt": "%%%s%%" % txt},
+ )
elif filters.get("reference_name"):
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
SELECT production_item
FROM `tab{doc}`
WHERE name = %(reference_name)s and docstatus < 2 and production_item like %(txt)s
{qi_condition} {cond} {mcond}
ORDER BY production_item
LIMIT {start}, {page_len}
- """.format(doc=filters.get("from"),
- cond = cond, mcond = mcond, start = start,
- page_len = page_len, qi_condition = qi_condition),
- {'reference_name': filters.get('reference_name'), 'txt': "%%%s%%" % txt})
+ """.format(
+ doc=filters.get("from"),
+ cond=cond,
+ mcond=mcond,
+ start=start,
+ page_len=page_len,
+ qi_condition=qi_condition,
+ ),
+ {"reference_name": filters.get("reference_name"), "txt": "%%%s%%" % txt},
+ )
+
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def quality_inspection_query(doctype, txt, searchfield, start, page_len, filters):
- return frappe.get_all('Quality Inspection',
+ return frappe.get_all(
+ "Quality Inspection",
limit_start=start,
limit_page_length=page_len,
- filters = {
- 'docstatus': 1,
- 'name': ('like', '%%%s%%' % txt),
- 'item_code': filters.get("item_code"),
- 'reference_name': ('in', [filters.get("reference_name", ''), ''])
- }, as_list=1)
+ filters={
+ "docstatus": 1,
+ "name": ("like", "%%%s%%" % txt),
+ "item_code": filters.get("item_code"),
+ "reference_name": ("in", [filters.get("reference_name", ""), ""]),
+ },
+ as_list=1,
+ )
+
@frappe.whitelist()
def make_quality_inspection(source_name, target_doc=None):
@@ -244,19 +288,18 @@
doc.inspected_by = frappe.session.user
doc.get_quality_inspection_template()
- doc = get_mapped_doc("BOM", source_name, {
- 'BOM': {
- "doctype": "Quality Inspection",
- "validation": {
- "docstatus": ["=", 1]
- },
- "field_map": {
- "name": "bom_no",
- "item": "item_code",
- "stock_uom": "uom",
- "stock_qty": "qty"
- },
- }
- }, target_doc, postprocess)
+ doc = get_mapped_doc(
+ "BOM",
+ source_name,
+ {
+ "BOM": {
+ "doctype": "Quality Inspection",
+ "validation": {"docstatus": ["=", 1]},
+ "field_map": {"name": "bom_no", "item": "item_code", "stock_uom": "uom", "stock_qty": "qty"},
+ }
+ },
+ target_doc,
+ postprocess,
+ )
return doc
diff --git a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py
index 601ca05..144f138 100644
--- a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py
+++ b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py
@@ -22,16 +22,11 @@
def setUp(self):
super().setUp()
create_item("_Test Item with QA")
- frappe.db.set_value(
- "Item", "_Test Item with QA", "inspection_required_before_delivery", 1
- )
+ frappe.db.set_value("Item", "_Test Item with QA", "inspection_required_before_delivery", 1)
def test_qa_for_delivery(self):
make_stock_entry(
- item_code="_Test Item with QA",
- target="_Test Warehouse - _TC",
- qty=1,
- basic_rate=100
+ item_code="_Test Item with QA", target="_Test Warehouse - _TC", qty=1, basic_rate=100
)
dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True)
@@ -71,21 +66,18 @@
"specification": "Iron Content", # numeric reading
"min_value": 0.1,
"max_value": 0.9,
- "reading_1": "0.4"
+ "reading_1": "0.4",
},
{
"specification": "Particle Inspection Needed", # non-numeric reading
"numeric": 0,
"value": "Yes",
- "reading_value": "Yes"
- }
+ "reading_value": "Yes",
+ },
]
qa = create_quality_inspection(
- reference_type="Delivery Note",
- reference_name=dn.name,
- readings=readings,
- do_not_save=True
+ reference_type="Delivery Note", reference_name=dn.name, readings=readings, do_not_save=True
)
qa.save()
@@ -104,13 +96,13 @@
"specification": "Iron Content", # numeric reading
"formula_based_criteria": 1,
"acceptance_formula": "reading_1 > 0.35 and reading_1 < 0.50",
- "reading_1": "0.4"
+ "reading_1": "0.4",
},
{
"specification": "Calcium Content", # numeric reading
"formula_based_criteria": 1,
"acceptance_formula": "reading_1 > 0.20 and reading_1 < 0.50",
- "reading_1": "0.7"
+ "reading_1": "0.7",
},
{
"specification": "Mg Content", # numeric reading
@@ -118,22 +110,19 @@
"acceptance_formula": "mean < 0.9",
"reading_1": "0.5",
"reading_2": "0.7",
- "reading_3": "random text" # check if random string input causes issues
+ "reading_3": "random text", # check if random string input causes issues
},
{
"specification": "Calcium Content", # non-numeric reading
"formula_based_criteria": 1,
"numeric": 0,
"acceptance_formula": "reading_value in ('Grade A', 'Grade B', 'Grade C')",
- "reading_value": "Grade B"
- }
+ "reading_value": "Grade B",
+ },
]
qa = create_quality_inspection(
- reference_type="Delivery Note",
- reference_name=dn.name,
- readings=readings,
- do_not_save=True
+ reference_type="Delivery Note", reference_name=dn.name, readings=readings, do_not_save=True
)
qa.save()
@@ -167,32 +156,26 @@
qty=1,
basic_rate=100,
inspection_required=True,
- do_not_submit=True
+ do_not_submit=True,
)
readings = [
- {
- "specification": "Iron Content",
- "min_value": 0.1,
- "max_value": 0.9,
- "reading_1": "0.4"
- }
+ {"specification": "Iron Content", "min_value": 0.1, "max_value": 0.9, "reading_1": "0.4"}
]
qa = create_quality_inspection(
- reference_type="Stock Entry",
- reference_name=se.name,
- readings=readings,
- status="Rejected"
+ reference_type="Stock Entry", reference_name=se.name, readings=readings, status="Rejected"
)
frappe.db.set_value("Stock Settings", None, "action_if_quality_inspection_is_rejected", "Stop")
se.reload()
- self.assertRaises(QualityInspectionRejectedError, se.submit) # when blocked in Stock settings, block rejected QI
+ self.assertRaises(
+ QualityInspectionRejectedError, se.submit
+ ) # when blocked in Stock settings, block rejected QI
frappe.db.set_value("Stock Settings", None, "action_if_quality_inspection_is_rejected", "Warn")
se.reload()
- se.submit() # when allowed in Stock settings, allow rejected QI
+ se.submit() # when allowed in Stock settings, allow rejected QI
# teardown
qa.reload()
@@ -201,6 +184,7 @@
se.cancel()
frappe.db.set_value("Stock Settings", None, "action_if_quality_inspection_is_rejected", "Stop")
+
def create_quality_inspection(**args):
args = frappe._dict(args)
qa = frappe.new_doc("Quality Inspection")
@@ -238,8 +222,6 @@
def create_quality_inspection_parameter(parameter):
if not frappe.db.exists("Quality Inspection Parameter", parameter):
- frappe.get_doc({
- "doctype": "Quality Inspection Parameter",
- "parameter": parameter,
- "description": parameter
- }).insert()
+ frappe.get_doc(
+ {"doctype": "Quality Inspection Parameter", "parameter": parameter, "description": parameter}
+ ).insert()
diff --git a/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py b/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py
index 7f8c871..9b8f5d6 100644
--- a/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py
+++ b/erpnext/stock/doctype/quality_inspection_template/quality_inspection_template.py
@@ -9,11 +9,22 @@
class QualityInspectionTemplate(Document):
pass
-def get_template_details(template):
- if not template: return []
- return frappe.get_all('Item Quality Inspection Parameter',
- fields=["specification", "value", "acceptance_formula",
- "numeric", "formula_based_criteria", "min_value", "max_value"],
- filters={'parenttype': 'Quality Inspection Template', 'parent': template},
- order_by="idx")
+def get_template_details(template):
+ if not template:
+ return []
+
+ return frappe.get_all(
+ "Item Quality Inspection Parameter",
+ fields=[
+ "specification",
+ "value",
+ "acceptance_formula",
+ "numeric",
+ "formula_based_criteria",
+ "min_value",
+ "max_value",
+ ],
+ filters={"parenttype": "Quality Inspection Template", "parent": template},
+ order_by="idx",
+ )
diff --git a/erpnext/stock/doctype/quick_stock_balance/quick_stock_balance.py b/erpnext/stock/doctype/quick_stock_balance/quick_stock_balance.py
index 7a0f5d0..846be0b 100644
--- a/erpnext/stock/doctype/quick_stock_balance/quick_stock_balance.py
+++ b/erpnext/stock/doctype/quick_stock_balance/quick_stock_balance.py
@@ -12,24 +12,25 @@
class QuickStockBalance(Document):
pass
+
@frappe.whitelist()
def get_stock_item_details(warehouse, date, item=None, barcode=None):
out = {}
if barcode:
out["item"] = frappe.db.get_value(
- "Item Barcode", filters={"barcode": barcode}, fieldname=["parent"])
+ "Item Barcode", filters={"barcode": barcode}, fieldname=["parent"]
+ )
if not out["item"]:
- frappe.throw(
- _("Invalid Barcode. There is no Item attached to this barcode."))
+ frappe.throw(_("Invalid Barcode. There is no Item attached to this barcode."))
else:
out["item"] = item
- barcodes = frappe.db.get_values("Item Barcode", filters={"parent": out["item"]},
- fieldname=["barcode"])
+ barcodes = frappe.db.get_values(
+ "Item Barcode", filters={"parent": out["item"]}, fieldname=["barcode"]
+ )
out["barcodes"] = [x[0] for x in barcodes]
out["qty"] = get_stock_balance(out["item"], warehouse, date)
out["value"] = get_stock_value_on(warehouse, date, out["item"])
- out["image"] = frappe.db.get_value("Item",
- filters={"name": out["item"]}, fieldname=["image"])
+ out["image"] = frappe.db.get_value("Item", filters={"name": out["item"]}, fieldname=["image"])
return out
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
index f8ec784..ec1d140 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
@@ -23,7 +23,7 @@
self.set_company()
def reset_field_values(self):
- if self.based_on == 'Transaction':
+ if self.based_on == "Transaction":
self.item_code = None
self.warehouse = None
@@ -38,20 +38,20 @@
def set_status(self, status=None, write=True):
status = status or self.status
if not status:
- self.status = 'Queued'
+ self.status = "Queued"
else:
self.status = status
if write:
- self.db_set('status', self.status)
+ self.db_set("status", self.status)
def on_submit(self):
"""During tests reposts are executed immediately.
Exceptions:
- 1. "Repost Item Valuation" document has self.flags.dont_run_in_test
- 2. global flag frappe.flags.dont_execute_stock_reposts is set
+ 1. "Repost Item Valuation" document has self.flags.dont_run_in_test
+ 2. global flag frappe.flags.dont_execute_stock_reposts is set
- These flags are useful for asserting real time behaviour like quantity updates.
+ These flags are useful for asserting real time behaviour like quantity updates.
"""
if not frappe.flags.in_test:
@@ -63,14 +63,14 @@
@frappe.whitelist()
def restart_reposting(self):
- self.set_status('Queued', write=False)
+ self.set_status("Queued", write=False)
self.current_index = 0
self.distinct_item_and_warehouse = None
self.items_to_be_repost = None
self.db_update()
def deduplicate_similar_repost(self):
- """ Deduplicate similar reposts based on item-warehouse-posting combination."""
+ """Deduplicate similar reposts based on item-warehouse-posting combination."""
if self.based_on != "Item and Warehouse":
return
@@ -82,7 +82,8 @@
"posting_time": self.posting_time,
}
- frappe.db.sql("""
+ frappe.db.sql(
+ """
update `tabRepost Item Valuation`
set status = 'Skipped'
WHERE item_code = %(item_code)s
@@ -93,9 +94,10 @@
and status = 'Queued'
and based_on = 'Item and Warehouse'
""",
- filters
+ filters,
)
+
def on_doctype_update():
frappe.db.add_index("Repost Item Valuation", ["warehouse", "item_code"], "item_warehouse")
@@ -105,14 +107,14 @@
if not frappe.db.exists("Repost Item Valuation", doc.name):
return
- doc.set_status('In Progress')
+ doc.set_status("In Progress")
if not frappe.flags.in_test:
frappe.db.commit()
repost_sl_entries(doc)
repost_gl_entries(doc)
- doc.set_status('Completed')
+ doc.set_status("Completed")
except (Exception, JobTimeoutException):
frappe.db.rollback()
@@ -122,32 +124,47 @@
message = frappe.message_log.pop()
if traceback:
message += "<br>" + "Traceback: <br>" + traceback
- frappe.db.set_value(doc.doctype, doc.name, 'error_log', message)
+ frappe.db.set_value(doc.doctype, doc.name, "error_log", message)
notify_error_to_stock_managers(doc, message)
- doc.set_status('Failed')
+ doc.set_status("Failed")
raise
finally:
if not frappe.flags.in_test:
frappe.db.commit()
+
def repost_sl_entries(doc):
- if doc.based_on == 'Transaction':
- repost_future_sle(voucher_type=doc.voucher_type, voucher_no=doc.voucher_no,
- allow_negative_stock=doc.allow_negative_stock, via_landed_cost_voucher=doc.via_landed_cost_voucher, doc=doc)
+ if doc.based_on == "Transaction":
+ repost_future_sle(
+ voucher_type=doc.voucher_type,
+ voucher_no=doc.voucher_no,
+ allow_negative_stock=doc.allow_negative_stock,
+ via_landed_cost_voucher=doc.via_landed_cost_voucher,
+ doc=doc,
+ )
else:
- repost_future_sle(args=[frappe._dict({
- "item_code": doc.item_code,
- "warehouse": doc.warehouse,
- "posting_date": doc.posting_date,
- "posting_time": doc.posting_time
- })], allow_negative_stock=doc.allow_negative_stock, via_landed_cost_voucher=doc.via_landed_cost_voucher)
+ repost_future_sle(
+ args=[
+ frappe._dict(
+ {
+ "item_code": doc.item_code,
+ "warehouse": doc.warehouse,
+ "posting_date": doc.posting_date,
+ "posting_time": doc.posting_time,
+ }
+ )
+ ],
+ allow_negative_stock=doc.allow_negative_stock,
+ via_landed_cost_voucher=doc.via_landed_cost_voucher,
+ )
+
def repost_gl_entries(doc):
if not cint(erpnext.is_perpetual_inventory_enabled(doc.company)):
return
- if doc.based_on == 'Transaction':
+ if doc.based_on == "Transaction":
ref_doc = frappe.get_doc(doc.voucher_type, doc.voucher_no)
doc_items, doc_warehouses = ref_doc.get_items_and_warehouses()
@@ -161,8 +178,14 @@
items = [doc.item_code]
warehouses = [doc.warehouse]
- update_gl_entries_after(doc.posting_date, doc.posting_time,
- for_warehouses=warehouses, for_items=items, company=doc.company)
+ update_gl_entries_after(
+ doc.posting_date,
+ doc.posting_time,
+ for_warehouses=warehouses,
+ for_items=items,
+ company=doc.company,
+ )
+
def notify_error_to_stock_managers(doc, traceback):
recipients = get_users_with_role("Stock Manager")
@@ -170,13 +193,20 @@
get_users_with_role("System Manager")
subject = _("Error while reposting item valuation")
- message = (_("Hi,") + "<br>"
- + _("An error has been appeared while reposting item valuation via {0}")
- .format(get_link_to_form(doc.doctype, doc.name)) + "<br>"
- + _("Please check the error message and take necessary actions to fix the error and then restart the reposting again.")
+ message = (
+ _("Hi,")
+ + "<br>"
+ + _("An error has been appeared while reposting item valuation via {0}").format(
+ get_link_to_form(doc.doctype, doc.name)
+ )
+ + "<br>"
+ + _(
+ "Please check the error message and take necessary actions to fix the error and then restart the reposting again."
+ )
)
frappe.sendmail(recipients=recipients, subject=subject, message=message)
+
def repost_entries():
if not in_configured_timeslot():
return
@@ -184,8 +214,8 @@
riv_entries = get_repost_item_valuation_entries()
for row in riv_entries:
- doc = frappe.get_doc('Repost Item Valuation', row.name)
- if doc.status in ('Queued', 'In Progress'):
+ doc = frappe.get_doc("Repost Item Valuation", row.name)
+ if doc.status in ("Queued", "In Progress"):
repost(doc)
doc.deduplicate_similar_repost()
@@ -193,14 +223,19 @@
if riv_entries:
return
- for d in frappe.get_all('Company', filters= {'enable_perpetual_inventory': 1}):
+ for d in frappe.get_all("Company", filters={"enable_perpetual_inventory": 1}):
check_if_stock_and_account_balance_synced(today(), d.name)
+
def get_repost_item_valuation_entries():
- return frappe.db.sql(""" SELECT name from `tabRepost Item Valuation`
+ return frappe.db.sql(
+ """ SELECT name from `tabRepost Item Valuation`
WHERE status in ('Queued', 'In Progress') and creation <= %s and docstatus = 1
ORDER BY timestamp(posting_date, posting_time) asc, creation asc
- """, now(), as_dict=1)
+ """,
+ now(),
+ as_dict=1,
+ )
def in_configured_timeslot(repost_settings=None, current_time=None):
diff --git a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py
index 78b432d..f3bebad 100644
--- a/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py
+++ b/erpnext/stock/doctype/repost_item_valuation/test_repost_item_valuation.py
@@ -153,7 +153,7 @@
posting_date=today,
posting_time="00:01:00",
)
- riv.flags.dont_run_in_test = True # keep it queued
+ riv.flags.dont_run_in_test = True # keep it queued
riv.submit()
stock_settings = frappe.get_doc("Stock Settings")
diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index 2808c21..316c897 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.py
+++ b/erpnext/stock/doctype/serial_no/serial_no.py
@@ -24,16 +24,45 @@
from erpnext.stock.get_item_details import get_reserved_qty_for_so
-class SerialNoCannotCreateDirectError(ValidationError): pass
-class SerialNoCannotCannotChangeError(ValidationError): pass
-class SerialNoNotRequiredError(ValidationError): pass
-class SerialNoRequiredError(ValidationError): pass
-class SerialNoQtyError(ValidationError): pass
-class SerialNoItemError(ValidationError): pass
-class SerialNoWarehouseError(ValidationError): pass
-class SerialNoBatchError(ValidationError): pass
-class SerialNoNotExistsError(ValidationError): pass
-class SerialNoDuplicateError(ValidationError): pass
+class SerialNoCannotCreateDirectError(ValidationError):
+ pass
+
+
+class SerialNoCannotCannotChangeError(ValidationError):
+ pass
+
+
+class SerialNoNotRequiredError(ValidationError):
+ pass
+
+
+class SerialNoRequiredError(ValidationError):
+ pass
+
+
+class SerialNoQtyError(ValidationError):
+ pass
+
+
+class SerialNoItemError(ValidationError):
+ pass
+
+
+class SerialNoWarehouseError(ValidationError):
+ pass
+
+
+class SerialNoBatchError(ValidationError):
+ pass
+
+
+class SerialNoNotExistsError(ValidationError):
+ pass
+
+
+class SerialNoDuplicateError(ValidationError):
+ pass
+
class SerialNo(StockController):
def __init__(self, *args, **kwargs):
@@ -42,7 +71,12 @@
def validate(self):
if self.get("__islocal") and self.warehouse and not self.via_stock_ledger:
- frappe.throw(_("New Serial No cannot have Warehouse. Warehouse must be set by Stock Entry or Purchase Receipt"), SerialNoCannotCreateDirectError)
+ frappe.throw(
+ _(
+ "New Serial No cannot have Warehouse. Warehouse must be set by Stock Entry or Purchase Receipt"
+ ),
+ SerialNoCannotCreateDirectError,
+ )
self.set_maintenance_status()
self.validate_warehouse()
@@ -77,22 +111,21 @@
def validate_warehouse(self):
if not self.get("__islocal"):
- item_code, warehouse = frappe.db.get_value("Serial No",
- self.name, ["item_code", "warehouse"])
+ item_code, warehouse = frappe.db.get_value("Serial No", self.name, ["item_code", "warehouse"])
if not self.via_stock_ledger and item_code != self.item_code:
- frappe.throw(_("Item Code cannot be changed for Serial No."),
- SerialNoCannotCannotChangeError)
+ frappe.throw(_("Item Code cannot be changed for Serial No."), SerialNoCannotCannotChangeError)
if not self.via_stock_ledger and warehouse != self.warehouse:
- frappe.throw(_("Warehouse cannot be changed for Serial No."),
- SerialNoCannotCannotChangeError)
+ frappe.throw(_("Warehouse cannot be changed for Serial No."), SerialNoCannotCannotChangeError)
def validate_item(self):
"""
- Validate whether serial no is required for this item
+ Validate whether serial no is required for this item
"""
item = frappe.get_cached_doc("Item", self.item_code)
- if item.has_serial_no!=1:
- frappe.throw(_("Item {0} is not setup for Serial Nos. Check Item master").format(self.item_code))
+ if item.has_serial_no != 1:
+ frappe.throw(
+ _("Item {0} is not setup for Serial Nos. Check Item master").format(self.item_code)
+ )
self.item_group = item.item_group
self.description = item.description
@@ -108,17 +141,24 @@
self.purchase_time = purchase_sle.posting_time
self.purchase_rate = purchase_sle.incoming_rate
if purchase_sle.voucher_type in ("Purchase Receipt", "Purchase Invoice"):
- self.supplier, self.supplier_name = \
- frappe.db.get_value(purchase_sle.voucher_type, purchase_sle.voucher_no,
- ["supplier", "supplier_name"])
+ self.supplier, self.supplier_name = frappe.db.get_value(
+ purchase_sle.voucher_type, purchase_sle.voucher_no, ["supplier", "supplier_name"]
+ )
# If sales return entry
- if self.purchase_document_type == 'Delivery Note':
+ if self.purchase_document_type == "Delivery Note":
self.sales_invoice = None
else:
- for fieldname in ("purchase_document_type", "purchase_document_no",
- "purchase_date", "purchase_time", "purchase_rate", "supplier", "supplier_name"):
- self.set(fieldname, None)
+ for fieldname in (
+ "purchase_document_type",
+ "purchase_document_no",
+ "purchase_date",
+ "purchase_time",
+ "purchase_rate",
+ "supplier",
+ "supplier_name",
+ ):
+ self.set(fieldname, None)
def set_sales_details(self, delivery_sle):
if delivery_sle:
@@ -126,18 +166,25 @@
self.delivery_document_no = delivery_sle.voucher_no
self.delivery_date = delivery_sle.posting_date
self.delivery_time = delivery_sle.posting_time
- if delivery_sle.voucher_type in ("Delivery Note", "Sales Invoice"):
- self.customer, self.customer_name = \
- frappe.db.get_value(delivery_sle.voucher_type, delivery_sle.voucher_no,
- ["customer", "customer_name"])
+ if delivery_sle.voucher_type in ("Delivery Note", "Sales Invoice"):
+ self.customer, self.customer_name = frappe.db.get_value(
+ delivery_sle.voucher_type, delivery_sle.voucher_no, ["customer", "customer_name"]
+ )
if self.warranty_period:
- self.warranty_expiry_date = add_days(cstr(delivery_sle.posting_date),
- cint(self.warranty_period))
+ self.warranty_expiry_date = add_days(
+ cstr(delivery_sle.posting_date), cint(self.warranty_period)
+ )
else:
- for fieldname in ("delivery_document_type", "delivery_document_no",
- "delivery_date", "delivery_time", "customer", "customer_name",
- "warranty_expiry_date"):
- self.set(fieldname, None)
+ for fieldname in (
+ "delivery_document_type",
+ "delivery_document_no",
+ "delivery_date",
+ "delivery_time",
+ "customer",
+ "customer_name",
+ "warranty_expiry_date",
+ ):
+ self.set(fieldname, None)
def get_last_sle(self, serial_no=None):
entries = {}
@@ -159,7 +206,8 @@
if not serial_no:
serial_no = self.name
- for sle in frappe.db.sql("""
+ for sle in frappe.db.sql(
+ """
SELECT voucher_type, voucher_no,
posting_date, posting_time, incoming_rate, actual_qty, serial_no
FROM
@@ -175,25 +223,30 @@
ORDER BY
posting_date desc, posting_time desc, creation desc""",
(
- self.item_code, self.company,
+ self.item_code,
+ self.company,
serial_no,
- serial_no+'\n%',
- '%\n'+serial_no,
- '%\n'+serial_no+'\n%'
+ serial_no + "\n%",
+ "%\n" + serial_no,
+ "%\n" + serial_no + "\n%",
),
- as_dict=1):
- if serial_no.upper() in get_serial_nos(sle.serial_no):
- if cint(sle.actual_qty) > 0:
- sle_dict.setdefault("incoming", []).append(sle)
- else:
- sle_dict.setdefault("outgoing", []).append(sle)
+ as_dict=1,
+ ):
+ if serial_no.upper() in get_serial_nos(sle.serial_no):
+ if cint(sle.actual_qty) > 0:
+ sle_dict.setdefault("incoming", []).append(sle)
+ else:
+ sle_dict.setdefault("outgoing", []).append(sle)
return sle_dict
def on_trash(self):
- sl_entries = frappe.db.sql("""select serial_no from `tabStock Ledger Entry`
+ sl_entries = frappe.db.sql(
+ """select serial_no from `tabStock Ledger Entry`
where serial_no like %s and item_code=%s and is_cancelled=0""",
- ("%%%s%%" % self.name, self.item_code), as_dict=True)
+ ("%%%s%%" % self.name, self.item_code),
+ as_dict=True,
+ )
# Find the exact match
sle_exists = False
@@ -203,7 +256,9 @@
break
if sle_exists:
- frappe.throw(_("Cannot delete Serial No {0}, as it is used in stock transactions").format(self.name))
+ frappe.throw(
+ _("Cannot delete Serial No {0}, as it is used in stock transactions").format(self.name)
+ )
def update_serial_no_reference(self, serial_no=None):
last_sle = self.get_last_sle(serial_no)
@@ -212,57 +267,95 @@
self.set_maintenance_status()
self.set_status()
+
def process_serial_no(sle):
item_det = get_item_details(sle.item_code)
validate_serial_no(sle, item_det)
update_serial_nos(sle, item_det)
+
def validate_serial_no(sle, item_det):
serial_nos = get_serial_nos(sle.serial_no) if sle.serial_no else []
validate_material_transfer_entry(sle)
- if item_det.has_serial_no==0:
+ if item_det.has_serial_no == 0:
if serial_nos:
- frappe.throw(_("Item {0} is not setup for Serial Nos. Column must be blank").format(sle.item_code),
- SerialNoNotRequiredError)
+ frappe.throw(
+ _("Item {0} is not setup for Serial Nos. Column must be blank").format(sle.item_code),
+ SerialNoNotRequiredError,
+ )
elif not sle.is_cancelled:
if serial_nos:
if cint(sle.actual_qty) != flt(sle.actual_qty):
- frappe.throw(_("Serial No {0} quantity {1} cannot be a fraction").format(sle.item_code, sle.actual_qty))
+ frappe.throw(
+ _("Serial No {0} quantity {1} cannot be a fraction").format(sle.item_code, sle.actual_qty)
+ )
if len(serial_nos) and len(serial_nos) != abs(cint(sle.actual_qty)):
- frappe.throw(_("{0} Serial Numbers required for Item {1}. You have provided {2}.").format(abs(sle.actual_qty), sle.item_code, len(serial_nos)),
- SerialNoQtyError)
+ frappe.throw(
+ _("{0} Serial Numbers required for Item {1}. You have provided {2}.").format(
+ abs(sle.actual_qty), sle.item_code, len(serial_nos)
+ ),
+ SerialNoQtyError,
+ )
if len(serial_nos) != len(set(serial_nos)):
- frappe.throw(_("Duplicate Serial No entered for Item {0}").format(sle.item_code), SerialNoDuplicateError)
+ frappe.throw(
+ _("Duplicate Serial No entered for Item {0}").format(sle.item_code), SerialNoDuplicateError
+ )
for serial_no in serial_nos:
if frappe.db.exists("Serial No", serial_no):
- sr = frappe.db.get_value("Serial No", serial_no, ["name", "item_code", "batch_no", "sales_order",
- "delivery_document_no", "delivery_document_type", "warehouse", "purchase_document_type",
- "purchase_document_no", "company", "status"], as_dict=1)
+ sr = frappe.db.get_value(
+ "Serial No",
+ serial_no,
+ [
+ "name",
+ "item_code",
+ "batch_no",
+ "sales_order",
+ "delivery_document_no",
+ "delivery_document_type",
+ "warehouse",
+ "purchase_document_type",
+ "purchase_document_no",
+ "company",
+ "status",
+ ],
+ as_dict=1,
+ )
- if sr.item_code!=sle.item_code:
+ if sr.item_code != sle.item_code:
if not allow_serial_nos_with_different_item(serial_no, sle):
- frappe.throw(_("Serial No {0} does not belong to Item {1}").format(serial_no,
- sle.item_code), SerialNoItemError)
+ frappe.throw(
+ _("Serial No {0} does not belong to Item {1}").format(serial_no, sle.item_code),
+ SerialNoItemError,
+ )
if cint(sle.actual_qty) > 0 and has_serial_no_exists(sr, sle):
doc_name = frappe.bold(get_link_to_form(sr.purchase_document_type, sr.purchase_document_no))
- frappe.throw(_("Serial No {0} has already been received in the {1} #{2}")
- .format(frappe.bold(serial_no), sr.purchase_document_type, doc_name), SerialNoDuplicateError)
+ frappe.throw(
+ _("Serial No {0} has already been received in the {1} #{2}").format(
+ frappe.bold(serial_no), sr.purchase_document_type, doc_name
+ ),
+ SerialNoDuplicateError,
+ )
- if (sr.delivery_document_no and sle.voucher_type not in ['Stock Entry', 'Stock Reconciliation']
- and sle.voucher_type == sr.delivery_document_type):
- return_against = frappe.db.get_value(sle.voucher_type, sle.voucher_no, 'return_against')
+ if (
+ sr.delivery_document_no
+ and sle.voucher_type not in ["Stock Entry", "Stock Reconciliation"]
+ and sle.voucher_type == sr.delivery_document_type
+ ):
+ return_against = frappe.db.get_value(sle.voucher_type, sle.voucher_no, "return_against")
if return_against and return_against != sr.delivery_document_no:
frappe.throw(_("Serial no {0} has been already returned").format(sr.name))
if cint(sle.actual_qty) < 0:
- if sr.warehouse!=sle.warehouse:
- frappe.throw(_("Serial No {0} does not belong to Warehouse {1}").format(serial_no,
- sle.warehouse), SerialNoWarehouseError)
+ if sr.warehouse != sle.warehouse:
+ frappe.throw(
+ _("Serial No {0} does not belong to Warehouse {1}").format(serial_no, sle.warehouse),
+ SerialNoWarehouseError,
+ )
if not sr.purchase_document_no:
frappe.throw(_("Serial No {0} not in stock").format(serial_no), SerialNoNotExistsError)
@@ -270,66 +363,100 @@
if sle.voucher_type in ("Delivery Note", "Sales Invoice"):
if sr.batch_no and sr.batch_no != sle.batch_no:
- frappe.throw(_("Serial No {0} does not belong to Batch {1}").format(serial_no,
- sle.batch_no), SerialNoBatchError)
+ frappe.throw(
+ _("Serial No {0} does not belong to Batch {1}").format(serial_no, sle.batch_no),
+ SerialNoBatchError,
+ )
if not sle.is_cancelled and not sr.warehouse:
- frappe.throw(_("Serial No {0} does not belong to any Warehouse")
- .format(serial_no), SerialNoWarehouseError)
+ frappe.throw(
+ _("Serial No {0} does not belong to any Warehouse").format(serial_no),
+ SerialNoWarehouseError,
+ )
# if Sales Order reference in Serial No validate the Delivery Note or Invoice is against the same
if sr.sales_order:
if sle.voucher_type == "Sales Invoice":
- if not frappe.db.exists("Sales Invoice Item", {"parent": sle.voucher_no,
- "item_code": sle.item_code, "sales_order": sr.sales_order}):
+ if not frappe.db.exists(
+ "Sales Invoice Item",
+ {"parent": sle.voucher_no, "item_code": sle.item_code, "sales_order": sr.sales_order},
+ ):
frappe.throw(
- _("Cannot deliver Serial No {0} of item {1} as it is reserved to fullfill Sales Order {2}")
- .format(sr.name, sle.item_code, sr.sales_order)
+ _(
+ "Cannot deliver Serial No {0} of item {1} as it is reserved to fullfill Sales Order {2}"
+ ).format(sr.name, sle.item_code, sr.sales_order)
)
elif sle.voucher_type == "Delivery Note":
- if not frappe.db.exists("Delivery Note Item", {"parent": sle.voucher_no,
- "item_code": sle.item_code, "against_sales_order": sr.sales_order}):
- invoice = frappe.db.get_value("Delivery Note Item", {"parent": sle.voucher_no,
- "item_code": sle.item_code}, "against_sales_invoice")
- if not invoice or frappe.db.exists("Sales Invoice Item",
- {"parent": invoice, "item_code": sle.item_code,
- "sales_order": sr.sales_order}):
+ if not frappe.db.exists(
+ "Delivery Note Item",
+ {
+ "parent": sle.voucher_no,
+ "item_code": sle.item_code,
+ "against_sales_order": sr.sales_order,
+ },
+ ):
+ invoice = frappe.db.get_value(
+ "Delivery Note Item",
+ {"parent": sle.voucher_no, "item_code": sle.item_code},
+ "against_sales_invoice",
+ )
+ if not invoice or frappe.db.exists(
+ "Sales Invoice Item",
+ {"parent": invoice, "item_code": sle.item_code, "sales_order": sr.sales_order},
+ ):
frappe.throw(
- _("Cannot deliver Serial No {0} of item {1} as it is reserved to fullfill Sales Order {2}")
- .format(sr.name, sle.item_code, sr.sales_order)
+ _(
+ "Cannot deliver Serial No {0} of item {1} as it is reserved to fullfill Sales Order {2}"
+ ).format(sr.name, sle.item_code, sr.sales_order)
)
# if Sales Order reference in Delivery Note or Invoice validate SO reservations for item
if sle.voucher_type == "Sales Invoice":
- sales_order = frappe.db.get_value("Sales Invoice Item", {"parent": sle.voucher_no,
- "item_code": sle.item_code}, "sales_order")
+ sales_order = frappe.db.get_value(
+ "Sales Invoice Item",
+ {"parent": sle.voucher_no, "item_code": sle.item_code},
+ "sales_order",
+ )
if sales_order and get_reserved_qty_for_so(sales_order, sle.item_code):
validate_so_serial_no(sr, sales_order)
elif sle.voucher_type == "Delivery Note":
- sales_order = frappe.get_value("Delivery Note Item", {"parent": sle.voucher_no,
- "item_code": sle.item_code}, "against_sales_order")
+ sales_order = frappe.get_value(
+ "Delivery Note Item",
+ {"parent": sle.voucher_no, "item_code": sle.item_code},
+ "against_sales_order",
+ )
if sales_order and get_reserved_qty_for_so(sales_order, sle.item_code):
validate_so_serial_no(sr, sales_order)
else:
- sales_invoice = frappe.get_value("Delivery Note Item", {"parent": sle.voucher_no,
- "item_code": sle.item_code}, "against_sales_invoice")
+ sales_invoice = frappe.get_value(
+ "Delivery Note Item",
+ {"parent": sle.voucher_no, "item_code": sle.item_code},
+ "against_sales_invoice",
+ )
if sales_invoice:
- sales_order = frappe.db.get_value("Sales Invoice Item", {
- "parent": sales_invoice, "item_code": sle.item_code}, "sales_order")
+ sales_order = frappe.db.get_value(
+ "Sales Invoice Item",
+ {"parent": sales_invoice, "item_code": sle.item_code},
+ "sales_order",
+ )
if sales_order and get_reserved_qty_for_so(sales_order, sle.item_code):
validate_so_serial_no(sr, sales_order)
elif cint(sle.actual_qty) < 0:
# transfer out
frappe.throw(_("Serial No {0} not in stock").format(serial_no), SerialNoNotExistsError)
elif cint(sle.actual_qty) < 0 or not item_det.serial_no_series:
- frappe.throw(_("Serial Nos Required for Serialized Item {0}").format(sle.item_code),
- SerialNoRequiredError)
+ frappe.throw(
+ _("Serial Nos Required for Serialized Item {0}").format(sle.item_code), SerialNoRequiredError
+ )
elif serial_nos:
# SLE is being cancelled and has serial nos
for serial_no in serial_nos:
check_serial_no_validity_on_cancel(serial_no, sle)
+
def check_serial_no_validity_on_cancel(serial_no, sle):
- sr = frappe.db.get_value("Serial No", serial_no, ["name", "warehouse", "company", "status"], as_dict=1)
+ sr = frappe.db.get_value(
+ "Serial No", serial_no, ["name", "warehouse", "company", "status"], as_dict=1
+ )
sr_link = frappe.utils.get_link_to_form("Serial No", serial_no)
doc_link = frappe.utils.get_link_to_form(sle.voucher_type, sle.voucher_no)
actual_qty = cint(sle.actual_qty)
@@ -339,57 +466,65 @@
if sr and (actual_qty < 0 or is_stock_reco) and (sr.warehouse and sr.warehouse != sle.warehouse):
# receipt(inward) is being cancelled
msg = _("Cannot cancel {0} {1} as Serial No {2} does not belong to the warehouse {3}").format(
- sle.voucher_type, doc_link, sr_link, frappe.bold(sle.warehouse))
+ sle.voucher_type, doc_link, sr_link, frappe.bold(sle.warehouse)
+ )
elif sr and actual_qty > 0 and not is_stock_reco:
# delivery is being cancelled, check for warehouse.
if sr.warehouse:
# serial no is active in another warehouse/company.
msg = _("Cannot cancel {0} {1} as Serial No {2} is active in warehouse {3}").format(
- sle.voucher_type, doc_link, sr_link, frappe.bold(sr.warehouse))
+ sle.voucher_type, doc_link, sr_link, frappe.bold(sr.warehouse)
+ )
elif sr.company != sle.company and sr.status == "Delivered":
# serial no is inactive (allowed) or delivered from another company (block).
msg = _("Cannot cancel {0} {1} as Serial No {2} does not belong to the company {3}").format(
- sle.voucher_type, doc_link, sr_link, frappe.bold(sle.company))
+ sle.voucher_type, doc_link, sr_link, frappe.bold(sle.company)
+ )
if msg:
frappe.throw(msg, title=_("Cannot cancel"))
-def validate_material_transfer_entry(sle_doc):
- sle_doc.update({
- "skip_update_serial_no": False,
- "skip_serial_no_validaiton": False
- })
- if (sle_doc.voucher_type == "Stock Entry" and not sle_doc.is_cancelled and
- frappe.get_cached_value("Stock Entry", sle_doc.voucher_no, "purpose") == "Material Transfer"):
+def validate_material_transfer_entry(sle_doc):
+ sle_doc.update({"skip_update_serial_no": False, "skip_serial_no_validaiton": False})
+
+ if (
+ sle_doc.voucher_type == "Stock Entry"
+ and not sle_doc.is_cancelled
+ and frappe.get_cached_value("Stock Entry", sle_doc.voucher_no, "purpose") == "Material Transfer"
+ ):
if sle_doc.actual_qty < 0:
sle_doc.skip_update_serial_no = True
else:
sle_doc.skip_serial_no_validaiton = True
-def validate_so_serial_no(sr, sales_order):
- if not sr.sales_order or sr.sales_order!= sales_order:
- msg = (_("Sales Order {0} has reservation for the item {1}, you can only deliver reserved {1} against {0}.")
- .format(sales_order, sr.item_code))
- frappe.throw(_("""{0} Serial No {1} cannot be delivered""")
- .format(msg, sr.name))
+def validate_so_serial_no(sr, sales_order):
+ if not sr.sales_order or sr.sales_order != sales_order:
+ msg = _(
+ "Sales Order {0} has reservation for the item {1}, you can only deliver reserved {1} against {0}."
+ ).format(sales_order, sr.item_code)
+
+ frappe.throw(_("""{0} Serial No {1} cannot be delivered""").format(msg, sr.name))
+
def has_serial_no_exists(sn, sle):
- if (sn.warehouse and not sle.skip_serial_no_validaiton
- and sle.voucher_type != 'Stock Reconciliation'):
+ if (
+ sn.warehouse and not sle.skip_serial_no_validaiton and sle.voucher_type != "Stock Reconciliation"
+ ):
return True
if sn.company != sle.company:
return False
+
def allow_serial_nos_with_different_item(sle_serial_no, sle):
"""
- Allows same serial nos for raw materials and finished goods
- in Manufacture / Repack type Stock Entry
+ Allows same serial nos for raw materials and finished goods
+ in Manufacture / Repack type Stock Entry
"""
allow_serial_nos = False
- if sle.voucher_type=="Stock Entry" and cint(sle.actual_qty) > 0:
+ if sle.voucher_type == "Stock Entry" and cint(sle.actual_qty) > 0:
stock_entry = frappe.get_cached_doc("Stock Entry", sle.voucher_no)
if stock_entry.purpose in ("Repack", "Manufacture"):
for d in stock_entry.get("items"):
@@ -400,16 +535,24 @@
return allow_serial_nos
+
def update_serial_nos(sle, item_det):
- if sle.skip_update_serial_no: return
- if not sle.is_cancelled and not sle.serial_no and cint(sle.actual_qty) > 0 \
- and item_det.has_serial_no == 1 and item_det.serial_no_series:
+ if sle.skip_update_serial_no:
+ return
+ if (
+ not sle.is_cancelled
+ and not sle.serial_no
+ and cint(sle.actual_qty) > 0
+ and item_det.has_serial_no == 1
+ and item_det.serial_no_series
+ ):
serial_nos = get_auto_serial_nos(item_det.serial_no_series, sle.actual_qty)
sle.db_set("serial_no", serial_nos)
validate_serial_no(sle, item_det)
if sle.serial_no:
auto_make_serial_nos(sle)
+
def get_auto_serial_nos(serial_no_series, qty):
serial_nos = []
for i in range(cint(qty)):
@@ -417,22 +560,24 @@
return "\n".join(serial_nos)
+
def get_new_serial_number(series):
sr_no = make_autoname(series, "Serial No")
if frappe.db.exists("Serial No", sr_no):
sr_no = get_new_serial_number(series)
return sr_no
+
def auto_make_serial_nos(args):
- serial_nos = get_serial_nos(args.get('serial_no'))
+ serial_nos = get_serial_nos(args.get("serial_no"))
created_numbers = []
- voucher_type = args.get('voucher_type')
- item_code = args.get('item_code')
+ voucher_type = args.get("voucher_type")
+ item_code = args.get("item_code")
for serial_no in serial_nos:
is_new = False
if frappe.db.exists("Serial No", serial_no):
sr = frappe.get_cached_doc("Serial No", serial_no)
- elif args.get('actual_qty', 0) > 0:
+ elif args.get("actual_qty", 0) > 0:
sr = frappe.new_doc("Serial No")
is_new = True
@@ -440,7 +585,7 @@
if is_new:
created_numbers.append(sr.name)
- form_links = list(map(lambda d: get_link_to_form('Serial No', d), created_numbers))
+ form_links = list(map(lambda d: get_link_to_form("Serial No", d), created_numbers))
# Setting up tranlated title field for all cases
singular_title = _("Serial Number Created")
@@ -452,29 +597,41 @@
if len(form_links) == 1:
frappe.msgprint(_("Serial No {0} Created").format(form_links[0]), singular_title)
elif len(form_links) > 0:
- message = _("The following serial numbers were created: <br><br> {0}").format(get_items_html(form_links, item_code))
+ message = _("The following serial numbers were created: <br><br> {0}").format(
+ get_items_html(form_links, item_code)
+ )
frappe.msgprint(message, multiple_title)
+
def get_items_html(serial_nos, item_code):
- body = ', '.join(serial_nos)
- return '''<details><summary>
+ body = ", ".join(serial_nos)
+ return """<details><summary>
<b>{0}:</b> {1} Serial Numbers <span class="caret"></span>
</summary>
<div class="small">{2}</div></details>
- '''.format(item_code, len(serial_nos), body)
+ """.format(
+ item_code, len(serial_nos), body
+ )
def get_item_details(item_code):
- return frappe.db.sql("""select name, has_batch_no, docstatus,
+ return frappe.db.sql(
+ """select name, has_batch_no, docstatus,
is_stock_item, has_serial_no, serial_no_series
- from tabItem where name=%s""", item_code, as_dict=True)[0]
+ from tabItem where name=%s""",
+ item_code,
+ as_dict=True,
+ )[0]
+
def get_serial_nos(serial_no):
if isinstance(serial_no, list):
return serial_no
- return [s.strip() for s in cstr(serial_no).strip().upper().replace(',', '\n').split('\n')
- if s.strip()]
+ return [
+ s.strip() for s in cstr(serial_no).strip().upper().replace(",", "\n").split("\n") if s.strip()
+ ]
+
def clean_serial_no_string(serial_no: str) -> str:
if not serial_no:
@@ -483,20 +640,23 @@
serial_no_list = get_serial_nos(serial_no)
return "\n".join(serial_no_list)
+
def update_args_for_serial_no(serial_no_doc, serial_no, args, is_new=False):
for field in ["item_code", "work_order", "company", "batch_no", "supplier", "location"]:
if args.get(field):
serial_no_doc.set(field, args.get(field))
serial_no_doc.via_stock_ledger = args.get("via_stock_ledger") or True
- serial_no_doc.warehouse = (args.get("warehouse")
- if args.get("actual_qty", 0) > 0 else None)
+ serial_no_doc.warehouse = args.get("warehouse") if args.get("actual_qty", 0) > 0 else None
if is_new:
serial_no_doc.serial_no = serial_no
- if (serial_no_doc.sales_order and args.get("voucher_type") == "Stock Entry"
- and not args.get("actual_qty", 0) > 0):
+ if (
+ serial_no_doc.sales_order
+ and args.get("voucher_type") == "Stock Entry"
+ and not args.get("actual_qty", 0) > 0
+ ):
serial_no_doc.sales_order = None
serial_no_doc.validate_item()
@@ -509,19 +669,27 @@
return serial_no_doc
-def update_serial_nos_after_submit(controller, parentfield):
- stock_ledger_entries = frappe.db.sql("""select voucher_detail_no, serial_no, actual_qty, warehouse
- from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s""",
- (controller.doctype, controller.name), as_dict=True)
- if not stock_ledger_entries: return
+def update_serial_nos_after_submit(controller, parentfield):
+ stock_ledger_entries = frappe.db.sql(
+ """select voucher_detail_no, serial_no, actual_qty, warehouse
+ from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s""",
+ (controller.doctype, controller.name),
+ as_dict=True,
+ )
+
+ if not stock_ledger_entries:
+ return
for d in controller.get(parentfield):
if d.serial_no:
continue
- update_rejected_serial_nos = True if (controller.doctype in ("Purchase Receipt", "Purchase Invoice")
- and d.rejected_qty) else False
+ update_rejected_serial_nos = (
+ True
+ if (controller.doctype in ("Purchase Receipt", "Purchase Invoice") and d.rejected_qty)
+ else False
+ )
accepted_serial_nos_updated = False
if controller.doctype == "Stock Entry":
@@ -532,58 +700,73 @@
qty = d.stock_qty
else:
warehouse = d.warehouse
- qty = (d.qty if controller.doctype == "Stock Reconciliation"
- else d.stock_qty)
+ qty = d.qty if controller.doctype == "Stock Reconciliation" else d.stock_qty
for sle in stock_ledger_entries:
- if sle.voucher_detail_no==d.name:
- if not accepted_serial_nos_updated and qty and abs(sle.actual_qty) == abs(qty) \
- and sle.warehouse == warehouse and sle.serial_no != d.serial_no:
- d.serial_no = sle.serial_no
- frappe.db.set_value(d.doctype, d.name, "serial_no", sle.serial_no)
- accepted_serial_nos_updated = True
- if not update_rejected_serial_nos:
- break
- elif update_rejected_serial_nos and abs(sle.actual_qty)==d.rejected_qty \
- and sle.warehouse == d.rejected_warehouse and sle.serial_no != d.rejected_serial_no:
- d.rejected_serial_no = sle.serial_no
- frappe.db.set_value(d.doctype, d.name, "rejected_serial_no", sle.serial_no)
- update_rejected_serial_nos = False
- if accepted_serial_nos_updated:
- break
+ if sle.voucher_detail_no == d.name:
+ if (
+ not accepted_serial_nos_updated
+ and qty
+ and abs(sle.actual_qty) == abs(qty)
+ and sle.warehouse == warehouse
+ and sle.serial_no != d.serial_no
+ ):
+ d.serial_no = sle.serial_no
+ frappe.db.set_value(d.doctype, d.name, "serial_no", sle.serial_no)
+ accepted_serial_nos_updated = True
+ if not update_rejected_serial_nos:
+ break
+ elif (
+ update_rejected_serial_nos
+ and abs(sle.actual_qty) == d.rejected_qty
+ and sle.warehouse == d.rejected_warehouse
+ and sle.serial_no != d.rejected_serial_no
+ ):
+ d.rejected_serial_no = sle.serial_no
+ frappe.db.set_value(d.doctype, d.name, "rejected_serial_no", sle.serial_no)
+ update_rejected_serial_nos = False
+ if accepted_serial_nos_updated:
+ break
+
def update_maintenance_status():
- serial_nos = frappe.db.sql('''select name from `tabSerial No` where (amc_expiry_date<%s or
- warranty_expiry_date<%s) and maintenance_status not in ('Out of Warranty', 'Out of AMC')''',
- (nowdate(), nowdate()))
+ serial_nos = frappe.db.sql(
+ """select name from `tabSerial No` where (amc_expiry_date<%s or
+ warranty_expiry_date<%s) and maintenance_status not in ('Out of Warranty', 'Out of AMC')""",
+ (nowdate(), nowdate()),
+ )
for serial_no in serial_nos:
doc = frappe.get_doc("Serial No", serial_no[0])
doc.set_maintenance_status()
- frappe.db.set_value('Serial No', doc.name, 'maintenance_status', doc.maintenance_status)
+ frappe.db.set_value("Serial No", doc.name, "maintenance_status", doc.maintenance_status)
+
def get_delivery_note_serial_no(item_code, qty, delivery_note):
- serial_nos = ''
- dn_serial_nos = frappe.db.sql_list(""" select name from `tabSerial No`
+ serial_nos = ""
+ dn_serial_nos = frappe.db.sql_list(
+ """ select name from `tabSerial No`
where item_code = %(item_code)s and delivery_document_no = %(delivery_note)s
- and sales_invoice is null limit {0}""".format(cint(qty)), {
- 'item_code': item_code,
- 'delivery_note': delivery_note
- })
+ and sales_invoice is null limit {0}""".format(
+ cint(qty)
+ ),
+ {"item_code": item_code, "delivery_note": delivery_note},
+ )
- if dn_serial_nos and len(dn_serial_nos)>0:
- serial_nos = '\n'.join(dn_serial_nos)
+ if dn_serial_nos and len(dn_serial_nos) > 0:
+ serial_nos = "\n".join(dn_serial_nos)
return serial_nos
+
@frappe.whitelist()
def auto_fetch_serial_number(
- qty: float,
- item_code: str,
- warehouse: str,
- posting_date: Optional[str] = None,
- batch_nos: Optional[Union[str, List[str]]] = None,
- for_doctype: Optional[str] = None,
- exclude_sr_nos: Optional[List[str]] = None
- ) -> List[str]:
+ qty: float,
+ item_code: str,
+ warehouse: str,
+ posting_date: Optional[str] = None,
+ batch_nos: Optional[Union[str, List[str]]] = None,
+ for_doctype: Optional[str] = None,
+ exclude_sr_nos: Optional[List[str]] = None,
+) -> List[str]:
filters = frappe._dict({"item_code": item_code, "warehouse": warehouse})
@@ -604,24 +787,26 @@
filters.expiry_date = posting_date
serial_numbers = []
- if for_doctype == 'POS Invoice':
+ if for_doctype == "POS Invoice":
exclude_sr_nos.extend(get_pos_reserved_serial_nos(filters))
serial_numbers = fetch_serial_numbers(filters, qty, do_not_include=exclude_sr_nos)
- return sorted([d.get('name') for d in serial_numbers])
+ return sorted([d.get("name") for d in serial_numbers])
+
def get_delivered_serial_nos(serial_nos):
- '''
+ """
Returns serial numbers that delivered from the list of serial numbers
- '''
+ """
from frappe.query_builder.functions import Coalesce
SerialNo = frappe.qb.DocType("Serial No")
serial_nos = get_serial_nos(serial_nos)
- query = frappe.qb.select(SerialNo.name).from_(SerialNo).where(
- (SerialNo.name.isin(serial_nos))
- & (Coalesce(SerialNo.delivery_document_type, "") != "")
+ query = (
+ frappe.qb.select(SerialNo.name)
+ .from_(SerialNo)
+ .where((SerialNo.name.isin(serial_nos)) & (Coalesce(SerialNo.delivery_document_type, "") != ""))
)
result = query.run()
@@ -629,6 +814,7 @@
delivered_serial_nos = [row[0] for row in result]
return delivered_serial_nos
+
@frappe.whitelist()
def get_pos_reserved_serial_nos(filters):
if isinstance(filters, str):
@@ -636,21 +822,19 @@
POSInvoice = frappe.qb.DocType("POS Invoice")
POSInvoiceItem = frappe.qb.DocType("POS Invoice Item")
- query = frappe.qb.from_(
- POSInvoice
- ).from_(
- POSInvoiceItem
- ).select(
- POSInvoice.is_return,
- POSInvoiceItem.serial_no
- ).where(
- (POSInvoice.name == POSInvoiceItem.parent)
- & (POSInvoice.docstatus == 1)
- & (POSInvoiceItem.docstatus == 1)
- & (POSInvoiceItem.item_code == filters.get('item_code'))
- & (POSInvoiceItem.warehouse == filters.get('warehouse'))
- & (POSInvoiceItem.serial_no.isnotnull())
- & (POSInvoiceItem.serial_no != '')
+ query = (
+ frappe.qb.from_(POSInvoice)
+ .from_(POSInvoiceItem)
+ .select(POSInvoice.is_return, POSInvoiceItem.serial_no)
+ .where(
+ (POSInvoice.name == POSInvoiceItem.parent)
+ & (POSInvoice.docstatus == 1)
+ & (POSInvoiceItem.docstatus == 1)
+ & (POSInvoiceItem.item_code == filters.get("item_code"))
+ & (POSInvoiceItem.warehouse == filters.get("warehouse"))
+ & (POSInvoiceItem.serial_no.isnotnull())
+ & (POSInvoiceItem.serial_no != "")
+ )
)
pos_transacted_sr_nos = query.run(as_dict=True)
@@ -668,6 +852,7 @@
return reserved_sr_nos
+
def fetch_serial_numbers(filters, qty, do_not_include=None):
if do_not_include is None:
do_not_include = []
@@ -677,17 +862,16 @@
serial_no = frappe.qb.DocType("Serial No")
query = (
- frappe.qb
- .from_(serial_no)
- .select(serial_no.name)
- .where(
- (serial_no.item_code == filters["item_code"])
- & (serial_no.warehouse == filters["warehouse"])
- & (Coalesce(serial_no.sales_invoice, "") == "")
- & (Coalesce(serial_no.delivery_document_no, "") == "")
- )
- .orderby(serial_no.creation)
- .limit(qty or 1)
+ frappe.qb.from_(serial_no)
+ .select(serial_no.name)
+ .where(
+ (serial_no.item_code == filters["item_code"])
+ & (serial_no.warehouse == filters["warehouse"])
+ & (Coalesce(serial_no.sales_invoice, "") == "")
+ & (Coalesce(serial_no.delivery_document_no, "") == "")
+ )
+ .orderby(serial_no.creation)
+ .limit(qty or 1)
)
if do_not_include:
@@ -698,8 +882,9 @@
if expiry_date:
batch = frappe.qb.DocType("Batch")
- query = (query
- .left_join(batch).on(serial_no.batch_no == batch.name)
+ query = (
+ query.left_join(batch)
+ .on(serial_no.batch_no == batch.name)
.where(Coalesce(batch.expiry_date, "4000-12-31") >= expiry_date)
)
diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.py b/erpnext/stock/doctype/serial_no/test_serial_no.py
index 7df0a56..68623fb 100644
--- a/erpnext/stock/doctype/serial_no/test_serial_no.py
+++ b/erpnext/stock/doctype/serial_no/test_serial_no.py
@@ -18,12 +18,10 @@
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
test_dependencies = ["Item"]
-test_records = frappe.get_test_records('Serial No')
-
+test_records = frappe.get_test_records("Serial No")
class TestSerialNo(FrappeTestCase):
-
def tearDown(self):
frappe.db.rollback()
@@ -48,7 +46,9 @@
se = make_serialized_item(target_warehouse="_Test Warehouse - _TC")
serial_nos = get_serial_nos(se.get("items")[0].serial_no)
- dn = create_delivery_note(item_code="_Test Serialized Item With Series", qty=1, serial_no=serial_nos[0])
+ dn = create_delivery_note(
+ item_code="_Test Serialized Item With Series", qty=1, serial_no=serial_nos[0]
+ )
serial_no = frappe.get_doc("Serial No", serial_nos[0])
@@ -60,8 +60,13 @@
self.assertEqual(serial_no.delivery_document_no, dn.name)
wh = create_warehouse("_Test Warehouse", company="_Test Company 1")
- pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1, serial_no=serial_nos[0],
- company="_Test Company 1", warehouse=wh)
+ pr = make_purchase_receipt(
+ item_code="_Test Serialized Item With Series",
+ qty=1,
+ serial_no=serial_nos[0],
+ company="_Test Company 1",
+ warehouse=wh,
+ )
serial_no.reload()
@@ -74,9 +79,9 @@
def test_inter_company_transfer_intermediate_cancellation(self):
"""
- Receive into and Deliver Serial No from one company.
- Then Receive into and Deliver from second company.
- Try to cancel intermediate receipts/deliveries to test if it is blocked.
+ Receive into and Deliver Serial No from one company.
+ Then Receive into and Deliver from second company.
+ Try to cancel intermediate receipts/deliveries to test if it is blocked.
"""
se = make_serialized_item(target_warehouse="_Test Warehouse - _TC")
serial_nos = get_serial_nos(se.get("items")[0].serial_no)
@@ -89,8 +94,9 @@
self.assertEqual(sn_doc.warehouse, "_Test Warehouse - _TC")
self.assertEqual(sn_doc.purchase_document_no, se.name)
- dn = create_delivery_note(item_code="_Test Serialized Item With Series",
- qty=1, serial_no=serial_nos[0])
+ dn = create_delivery_note(
+ item_code="_Test Serialized Item With Series", qty=1, serial_no=serial_nos[0]
+ )
sn_doc.reload()
# check Serial No details after delivery from **first** company
self.assertEqual(sn_doc.status, "Delivered")
@@ -104,8 +110,13 @@
# receive serial no in second company
wh = create_warehouse("_Test Warehouse", company="_Test Company 1")
- pr = make_purchase_receipt(item_code="_Test Serialized Item With Series",
- qty=1, serial_no=serial_nos[0], company="_Test Company 1", warehouse=wh)
+ pr = make_purchase_receipt(
+ item_code="_Test Serialized Item With Series",
+ qty=1,
+ serial_no=serial_nos[0],
+ company="_Test Company 1",
+ warehouse=wh,
+ )
sn_doc.reload()
self.assertEqual(sn_doc.warehouse, wh)
@@ -114,8 +125,13 @@
self.assertRaises(frappe.ValidationError, dn.cancel)
# deliver from second company
- dn_2 = create_delivery_note(item_code="_Test Serialized Item With Series",
- qty=1, serial_no=serial_nos[0], company="_Test Company 1", warehouse=wh)
+ dn_2 = create_delivery_note(
+ item_code="_Test Serialized Item With Series",
+ qty=1,
+ serial_no=serial_nos[0],
+ company="_Test Company 1",
+ warehouse=wh,
+ )
sn_doc.reload()
# check Serial No details after delivery from **second** company
@@ -131,9 +147,9 @@
def test_inter_company_transfer_fallback_on_cancel(self):
"""
- Test Serial No state changes on cancellation.
- If Delivery cancelled, it should fall back on last Receipt in the same company.
- If Receipt is cancelled, it should be Inactive in the same company.
+ Test Serial No state changes on cancellation.
+ If Delivery cancelled, it should fall back on last Receipt in the same company.
+ If Receipt is cancelled, it should be Inactive in the same company.
"""
# Receipt in **first** company
se = make_serialized_item(target_warehouse="_Test Warehouse - _TC")
@@ -141,17 +157,28 @@
sn_doc = frappe.get_doc("Serial No", serial_nos[0])
# Delivery from first company
- dn = create_delivery_note(item_code="_Test Serialized Item With Series",
- qty=1, serial_no=serial_nos[0])
+ dn = create_delivery_note(
+ item_code="_Test Serialized Item With Series", qty=1, serial_no=serial_nos[0]
+ )
# Receipt in **second** company
wh = create_warehouse("_Test Warehouse", company="_Test Company 1")
- pr = make_purchase_receipt(item_code="_Test Serialized Item With Series",
- qty=1, serial_no=serial_nos[0], company="_Test Company 1", warehouse=wh)
+ pr = make_purchase_receipt(
+ item_code="_Test Serialized Item With Series",
+ qty=1,
+ serial_no=serial_nos[0],
+ company="_Test Company 1",
+ warehouse=wh,
+ )
# Delivery from second company
- dn_2 = create_delivery_note(item_code="_Test Serialized Item With Series",
- qty=1, serial_no=serial_nos[0], company="_Test Company 1", warehouse=wh)
+ dn_2 = create_delivery_note(
+ item_code="_Test Serialized Item With Series",
+ qty=1,
+ serial_no=serial_nos[0],
+ company="_Test Company 1",
+ warehouse=wh,
+ )
sn_doc.reload()
self.assertEqual(sn_doc.status, "Delivered")
@@ -184,12 +211,11 @@
def test_auto_creation_of_serial_no(self):
"""
- Test if auto created Serial No excludes existing serial numbers
+ Test if auto created Serial No excludes existing serial numbers
"""
- item_code = make_item("_Test Auto Serial Item ", {
- "has_serial_no": 1,
- "serial_no_series": "XYZ.###"
- }).item_code
+ item_code = make_item(
+ "_Test Auto Serial Item ", {"has_serial_no": 1, "serial_no_series": "XYZ.###"}
+ ).item_code
# Reserve XYZ005
pr_1 = make_purchase_receipt(item_code=item_code, qty=1, serial_no="XYZ005")
@@ -203,7 +229,7 @@
def test_serial_no_sanitation(self):
"Test if Serial No input is sanitised before entering the DB."
item_code = "_Test Serialized Item"
- test_records = frappe.get_test_records('Stock Entry')
+ test_records = frappe.get_test_records("Stock Entry")
se = frappe.copy_doc(test_records[0])
se.get("items")[0].item_code = item_code
@@ -217,37 +243,43 @@
self.assertEqual(se.get("items")[0].serial_no, "_TS1\n_TS2\n_TS3\n_TS4 - 2021")
def test_correct_serial_no_incoming_rate(self):
- """ Check correct consumption rate based on serial no record.
- """
+ """Check correct consumption rate based on serial no record."""
item_code = "_Test Serialized Item"
warehouse = "_Test Warehouse - _TC"
serial_nos = ["LOWVALUATION", "HIGHVALUATION"]
- in1 = make_stock_entry(item_code=item_code, to_warehouse=warehouse, qty=1, rate=42,
- serial_no=serial_nos[0])
- in2 = make_stock_entry(item_code=item_code, to_warehouse=warehouse, qty=1, rate=113,
- serial_no=serial_nos[1])
+ in1 = make_stock_entry(
+ item_code=item_code, to_warehouse=warehouse, qty=1, rate=42, serial_no=serial_nos[0]
+ )
+ in2 = make_stock_entry(
+ item_code=item_code, to_warehouse=warehouse, qty=1, rate=113, serial_no=serial_nos[1]
+ )
- out = create_delivery_note(item_code=item_code, qty=1, serial_no=serial_nos[0], do_not_submit=True)
+ out = create_delivery_note(
+ item_code=item_code, qty=1, serial_no=serial_nos[0], do_not_submit=True
+ )
# change serial no
out.items[0].serial_no = serial_nos[1]
out.save()
out.submit()
- value_diff = frappe.db.get_value("Stock Ledger Entry",
- {"voucher_no": out.name, "voucher_type": "Delivery Note"},
- "stock_value_difference"
- )
+ value_diff = frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"voucher_no": out.name, "voucher_type": "Delivery Note"},
+ "stock_value_difference",
+ )
self.assertEqual(value_diff, -113)
def test_auto_fetch(self):
- item_code = make_item(properties={
- "has_serial_no": 1,
- "has_batch_no": 1,
- "create_new_batch": 1,
- "serial_no_series": "TEST.#######"
- }).name
+ item_code = make_item(
+ properties={
+ "has_serial_no": 1,
+ "has_batch_no": 1,
+ "create_new_batch": 1,
+ "serial_no_series": "TEST.#######",
+ }
+ ).name
warehouse = "_Test Warehouse - _TC"
in1 = make_stock_entry(item_code=item_code, to_warehouse=warehouse, qty=5)
@@ -260,8 +292,8 @@
batch2 = in2.items[0].batch_no
batch_wise_serials = {
- batch1 : get_serial_nos(in1.items[0].serial_no),
- batch2: get_serial_nos(in2.items[0].serial_no)
+ batch1: get_serial_nos(in1.items[0].serial_no),
+ batch2: get_serial_nos(in2.items[0].serial_no),
}
# Test FIFO
@@ -270,12 +302,15 @@
# partial FIFO
partial_fetch = auto_fetch_serial_number(2, item_code, warehouse)
- self.assertTrue(set(partial_fetch).issubset(set(first_fetch)),
- msg=f"{partial_fetch} should be subset of {first_fetch}")
+ self.assertTrue(
+ set(partial_fetch).issubset(set(first_fetch)),
+ msg=f"{partial_fetch} should be subset of {first_fetch}",
+ )
# exclusion
- remaining = auto_fetch_serial_number(3, item_code, warehouse,
- exclude_sr_nos=json.dumps(partial_fetch))
+ remaining = auto_fetch_serial_number(
+ 3, item_code, warehouse, exclude_sr_nos=json.dumps(partial_fetch)
+ )
self.assertEqual(sorted(remaining + partial_fetch), first_fetch)
# batchwise
@@ -288,10 +323,14 @@
# multi batch
all_serials = [sr for sr_list in batch_wise_serials.values() for sr in sr_list]
- fetched_serials = auto_fetch_serial_number(10, item_code, warehouse, batch_nos=list(batch_wise_serials.keys()))
+ fetched_serials = auto_fetch_serial_number(
+ 10, item_code, warehouse, batch_nos=list(batch_wise_serials.keys())
+ )
self.assertEqual(sorted(all_serials), fetched_serials)
# expiry date
frappe.db.set_value("Batch", batch1, "expiry_date", "1980-01-01")
- non_expired_serials = auto_fetch_serial_number(5, item_code, warehouse, posting_date="2021-01-01", batch_nos=batch1)
+ non_expired_serials = auto_fetch_serial_number(
+ 5, item_code, warehouse, posting_date="2021-01-01", batch_nos=batch1
+ )
self.assertEqual(non_expired_serials, [])
diff --git a/erpnext/stock/doctype/shipment/shipment.py b/erpnext/stock/doctype/shipment/shipment.py
index 666de57..42a67f4 100644
--- a/erpnext/stock/doctype/shipment/shipment.py
+++ b/erpnext/stock/doctype/shipment/shipment.py
@@ -17,22 +17,22 @@
self.validate_pickup_time()
self.set_value_of_goods()
if self.docstatus == 0:
- self.status = 'Draft'
+ self.status = "Draft"
def on_submit(self):
if not self.shipment_parcel:
- frappe.throw(_('Please enter Shipment Parcel information'))
+ frappe.throw(_("Please enter Shipment Parcel information"))
if self.value_of_goods == 0:
- frappe.throw(_('Value of goods cannot be 0'))
- self.db_set('status', 'Submitted')
+ frappe.throw(_("Value of goods cannot be 0"))
+ self.db_set("status", "Submitted")
def on_cancel(self):
- self.db_set('status', 'Cancelled')
+ self.db_set("status", "Cancelled")
def validate_weight(self):
for parcel in self.shipment_parcel:
if flt(parcel.weight) <= 0:
- frappe.throw(_('Parcel weight cannot be 0'))
+ frappe.throw(_("Parcel weight cannot be 0"))
def validate_pickup_time(self):
if self.pickup_from and self.pickup_to and get_time(self.pickup_to) < get_time(self.pickup_from):
@@ -44,26 +44,34 @@
value_of_goods += flt(entry.get("grand_total"))
self.value_of_goods = value_of_goods if value_of_goods else self.value_of_goods
+
@frappe.whitelist()
def get_address_name(ref_doctype, docname):
# Return address name
return get_party_shipping_address(ref_doctype, docname)
+
@frappe.whitelist()
def get_contact_name(ref_doctype, docname):
# Return address name
return get_default_contact(ref_doctype, docname)
+
@frappe.whitelist()
def get_company_contact(user):
- contact = frappe.db.get_value('User', user, [
- 'first_name',
- 'last_name',
- 'email',
- 'phone',
- 'mobile_no',
- 'gender',
- ], as_dict=1)
+ contact = frappe.db.get_value(
+ "User",
+ user,
+ [
+ "first_name",
+ "last_name",
+ "email",
+ "phone",
+ "mobile_no",
+ "gender",
+ ],
+ as_dict=1,
+ )
if not contact.phone:
contact.phone = contact.mobile_no
return contact
diff --git a/erpnext/stock/doctype/shipment/test_shipment.py b/erpnext/stock/doctype/shipment/test_shipment.py
index 317abb6..ae97e7a 100644
--- a/erpnext/stock/doctype/shipment/test_shipment.py
+++ b/erpnext/stock/doctype/shipment/test_shipment.py
@@ -13,13 +13,14 @@
def test_shipment_from_delivery_note(self):
delivery_note = create_test_delivery_note()
delivery_note.submit()
- shipment = create_test_shipment([ delivery_note ])
+ shipment = create_test_shipment([delivery_note])
shipment.submit()
second_shipment = make_shipment(delivery_note.name)
self.assertEqual(second_shipment.value_of_goods, delivery_note.grand_total)
self.assertEqual(len(second_shipment.shipment_delivery_note), 1)
self.assertEqual(second_shipment.shipment_delivery_note[0].delivery_note, delivery_note.name)
+
def create_test_delivery_note():
company = get_shipment_company()
customer = get_shipment_customer()
@@ -30,25 +31,26 @@
delivery_note = frappe.new_doc("Delivery Note")
delivery_note.company = company.name
delivery_note.posting_date = posting_date.strftime("%Y-%m-%d")
- delivery_note.posting_time = '10:00'
+ delivery_note.posting_time = "10:00"
delivery_note.customer = customer.name
- delivery_note.append('items',
+ delivery_note.append(
+ "items",
{
"item_code": item.name,
"item_name": item.item_name,
- "description": 'Test delivery note for shipment',
+ "description": "Test delivery note for shipment",
"qty": 5,
- "uom": 'Nos',
- "warehouse": 'Stores - _TC',
+ "uom": "Nos",
+ "warehouse": "Stores - _TC",
"rate": item.standard_rate,
- "cost_center": 'Main - _TC'
- }
+ "cost_center": "Main - _TC",
+ },
)
delivery_note.insert()
return delivery_note
-def create_test_shipment(delivery_notes = None):
+def create_test_shipment(delivery_notes=None):
company = get_shipment_company()
company_address = get_shipment_company_address(company.name)
customer = get_shipment_customer()
@@ -57,45 +59,35 @@
posting_date = date.today() + timedelta(days=5)
shipment = frappe.new_doc("Shipment")
- shipment.pickup_from_type = 'Company'
+ shipment.pickup_from_type = "Company"
shipment.pickup_company = company.name
shipment.pickup_address_name = company_address.name
- shipment.delivery_to_type = 'Customer'
+ shipment.delivery_to_type = "Customer"
shipment.delivery_customer = customer.name
shipment.delivery_address_name = customer_address.name
shipment.delivery_contact_name = customer_contact.name
- shipment.pallets = 'No'
- shipment.shipment_type = 'Goods'
+ shipment.pallets = "No"
+ shipment.shipment_type = "Goods"
shipment.value_of_goods = 1000
- shipment.pickup_type = 'Pickup'
+ shipment.pickup_type = "Pickup"
shipment.pickup_date = posting_date.strftime("%Y-%m-%d")
- shipment.pickup_from = '09:00'
- shipment.pickup_to = '17:00'
- shipment.description_of_content = 'unit test entry'
+ shipment.pickup_from = "09:00"
+ shipment.pickup_to = "17:00"
+ shipment.description_of_content = "unit test entry"
for delivery_note in delivery_notes:
- shipment.append('shipment_delivery_note',
- {
- "delivery_note": delivery_note.name
- }
- )
- shipment.append('shipment_parcel',
- {
- "length": 5,
- "width": 5,
- "height": 5,
- "weight": 5,
- "count": 5
- }
+ shipment.append("shipment_delivery_note", {"delivery_note": delivery_note.name})
+ shipment.append(
+ "shipment_parcel", {"length": 5, "width": 5, "height": 5, "weight": 5, "count": 5}
)
shipment.insert()
return shipment
def get_shipment_customer_contact(customer_name):
- contact_fname = 'Customer Shipment'
- contact_lname = 'Testing'
- customer_name = contact_fname + ' ' + contact_lname
- contacts = frappe.get_all("Contact", fields=["name"], filters = {"name": customer_name})
+ contact_fname = "Customer Shipment"
+ contact_lname = "Testing"
+ customer_name = contact_fname + " " + contact_lname
+ contacts = frappe.get_all("Contact", fields=["name"], filters={"name": customer_name})
if len(contacts):
return contacts[0]
else:
@@ -103,104 +95,106 @@
def get_shipment_customer_address(customer_name):
- address_title = customer_name + ' address 123'
- customer_address = frappe.get_all("Address", fields=["name"], filters = {"address_title": address_title})
+ address_title = customer_name + " address 123"
+ customer_address = frappe.get_all(
+ "Address", fields=["name"], filters={"address_title": address_title}
+ )
if len(customer_address):
return customer_address[0]
else:
return create_shipment_address(address_title, customer_name, 81929)
+
def get_shipment_customer():
- customer_name = 'Shipment Customer'
- customer = frappe.get_all("Customer", fields=["name"], filters = {"name": customer_name})
+ customer_name = "Shipment Customer"
+ customer = frappe.get_all("Customer", fields=["name"], filters={"name": customer_name})
if len(customer):
return customer[0]
else:
return create_shipment_customer(customer_name)
+
def get_shipment_company_address(company_name):
- address_title = company_name + ' address 123'
- addresses = frappe.get_all("Address", fields=["name"], filters = {"address_title": address_title})
+ address_title = company_name + " address 123"
+ addresses = frappe.get_all("Address", fields=["name"], filters={"address_title": address_title})
if len(addresses):
return addresses[0]
else:
return create_shipment_address(address_title, company_name, 80331)
+
def get_shipment_company():
return frappe.get_doc("Company", "_Test Company")
+
def get_shipment_item(company_name):
- item_name = 'Testing Shipment item'
- items = frappe.get_all("Item",
+ item_name = "Testing Shipment item"
+ items = frappe.get_all(
+ "Item",
fields=["name", "item_name", "item_code", "standard_rate"],
- filters = {"item_name": item_name}
+ filters={"item_name": item_name},
)
if len(items):
return items[0]
else:
return create_shipment_item(item_name, company_name)
+
def create_shipment_address(address_title, company_name, postal_code):
address = frappe.new_doc("Address")
address.address_title = address_title
- address.address_type = 'Shipping'
- address.address_line1 = company_name + ' address line 1'
- address.city = 'Random City'
+ address.address_type = "Shipping"
+ address.address_line1 = company_name + " address line 1"
+ address.city = "Random City"
address.postal_code = postal_code
- address.country = 'Germany'
+ address.country = "Germany"
address.insert()
return address
def create_customer_contact(fname, lname):
customer = frappe.new_doc("Contact")
- customer.customer_name = fname + ' ' + lname
+ customer.customer_name = fname + " " + lname
customer.first_name = fname
customer.last_name = lname
customer.is_primary_contact = 1
customer.is_billing_contact = 1
- customer.append('email_ids',
- {
- 'email_id': 'randomme@email.com',
- 'is_primary': 1
- }
+ customer.append("email_ids", {"email_id": "randomme@email.com", "is_primary": 1})
+ customer.append(
+ "phone_nos", {"phone": "123123123", "is_primary_phone": 1, "is_primary_mobile_no": 1}
)
- customer.append('phone_nos',
- {
- 'phone': '123123123',
- 'is_primary_phone': 1,
- 'is_primary_mobile_no': 1
- }
- )
- customer.status = 'Passive'
+ customer.status = "Passive"
customer.insert()
return customer
+
def create_shipment_customer(customer_name):
customer = frappe.new_doc("Customer")
customer.customer_name = customer_name
- customer.customer_type = 'Company'
- customer.customer_group = 'All Customer Groups'
- customer.territory = 'All Territories'
- customer.gst_category = 'Unregistered'
+ customer.customer_type = "Company"
+ customer.customer_group = "All Customer Groups"
+ customer.territory = "All Territories"
+ customer.gst_category = "Unregistered"
customer.insert()
return customer
+
def create_material_receipt(item, company):
posting_date = date.today()
stock = frappe.new_doc("Stock Entry")
stock.company = company
- stock.stock_entry_type = 'Material Receipt'
+ stock.stock_entry_type = "Material Receipt"
stock.posting_date = posting_date.strftime("%Y-%m-%d")
- stock.append('items',
+ stock.append(
+ "items",
{
- "t_warehouse": 'Stores - _TC',
+ "t_warehouse": "Stores - _TC",
"item_code": item.name,
"qty": 5,
- "uom": 'Nos',
+ "uom": "Nos",
"basic_rate": item.standard_rate,
- "cost_center": 'Main - _TC'
- }
+ "cost_center": "Main - _TC",
+ },
)
stock.insert()
stock.submit()
@@ -210,14 +204,9 @@
item = frappe.new_doc("Item")
item.item_name = item_name
item.item_code = item_name
- item.item_group = 'All Item Groups'
- item.stock_uom = 'Nos'
+ item.item_group = "All Item Groups"
+ item.stock_uom = "Nos"
item.standard_rate = 50
- item.append('item_defaults',
- {
- "company": company_name,
- "default_warehouse": 'Stores - _TC'
- }
- )
+ item.append("item_defaults", {"company": company_name, "default_warehouse": "Stores - _TC"})
item.insert()
return item
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 99cf4de..bc54f7f 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -38,20 +38,28 @@
class FinishedGoodError(frappe.ValidationError):
pass
+
+
class IncorrectValuationRateError(frappe.ValidationError):
pass
+
+
class DuplicateEntryForWorkOrderError(frappe.ValidationError):
pass
+
+
class OperationsNotCompleteError(frappe.ValidationError):
pass
+
+
class MaxSampleAlreadyRetainedError(frappe.ValidationError):
pass
+
from erpnext.controllers.stock_controller import StockController
-form_grid_templates = {
- "items": "templates/form_grid/stock_entry_grid.html"
-}
+form_grid_templates = {"items": "templates/form_grid/stock_entry_grid.html"}
+
class StockEntry(StockController):
def get_feed(self):
@@ -63,16 +71,18 @@
def before_validate(self):
from erpnext.stock.doctype.putaway_rule.putaway_rule import apply_putaway_rule
- apply_rule = self.apply_putaway_rule and (self.purpose in ["Material Transfer", "Material Receipt"])
+
+ apply_rule = self.apply_putaway_rule and (
+ self.purpose in ["Material Transfer", "Material Receipt"]
+ )
if self.get("items") and apply_rule:
- apply_putaway_rule(self.doctype, self.get("items"), self.company,
- purpose=self.purpose)
+ apply_putaway_rule(self.doctype, self.get("items"), self.company, purpose=self.purpose)
def validate(self):
self.pro_doc = frappe._dict()
if self.work_order:
- self.pro_doc = frappe.get_doc('Work Order', self.work_order)
+ self.pro_doc = frappe.get_doc("Work Order", self.work_order)
self.validate_posting_time()
self.validate_purpose()
@@ -103,10 +113,10 @@
if not self.from_bom:
self.fg_completed_qty = 0.0
- if self._action == 'submit':
- self.make_batches('t_warehouse')
+ if self._action == "submit":
+ self.make_batches("t_warehouse")
else:
- set_batch_nos(self, 's_warehouse')
+ set_batch_nos(self, "s_warehouse")
self.validate_serialized_batch()
self.set_actual_qty()
@@ -138,10 +148,10 @@
if self.work_order and self.purpose == "Manufacture":
self.update_so_in_serial_number()
- if self.purpose == 'Material Transfer' and self.add_to_transit:
- self.set_material_request_transfer_status('In Transit')
- if self.purpose == 'Material Transfer' and self.outgoing_stock_entry:
- self.set_material_request_transfer_status('Completed')
+ if self.purpose == "Material Transfer" and self.add_to_transit:
+ self.set_material_request_transfer_status("In Transit")
+ if self.purpose == "Material Transfer" and self.outgoing_stock_entry:
+ self.set_material_request_transfer_status("Completed")
def on_cancel(self):
self.update_purchase_order_supplied_items()
@@ -152,7 +162,7 @@
self.update_work_order()
self.update_stock_ledger()
- self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation')
+ self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation")
self.make_gl_entries_on_cancel()
self.repost_future_sle_and_gle()
@@ -162,15 +172,16 @@
self.delete_auto_created_batches()
self.delete_linked_stock_entry()
- if self.purpose == 'Material Transfer' and self.add_to_transit:
- self.set_material_request_transfer_status('Not Started')
- if self.purpose == 'Material Transfer' and self.outgoing_stock_entry:
- self.set_material_request_transfer_status('In Transit')
+ if self.purpose == "Material Transfer" and self.add_to_transit:
+ self.set_material_request_transfer_status("Not Started")
+ if self.purpose == "Material Transfer" and self.outgoing_stock_entry:
+ self.set_material_request_transfer_status("In Transit")
def set_job_card_data(self):
if self.job_card and not self.work_order:
- data = frappe.db.get_value('Job Card',
- self.job_card, ['for_quantity', 'work_order', 'bom_no'], as_dict=1)
+ data = frappe.db.get_value(
+ "Job Card", self.job_card, ["for_quantity", "work_order", "bom_no"], as_dict=1
+ )
self.fg_completed_qty = data.for_quantity
self.work_order = data.work_order
self.from_bom = 1
@@ -178,25 +189,37 @@
def validate_work_order_status(self):
pro_doc = frappe.get_doc("Work Order", self.work_order)
- if pro_doc.status == 'Completed':
+ if pro_doc.status == "Completed":
frappe.throw(_("Cannot cancel transaction for Completed Work Order."))
def validate_purpose(self):
- valid_purposes = ["Material Issue", "Material Receipt", "Material Transfer",
- "Material Transfer for Manufacture", "Manufacture", "Repack", "Send to Subcontractor",
- "Material Consumption for Manufacture"]
+ valid_purposes = [
+ "Material Issue",
+ "Material Receipt",
+ "Material Transfer",
+ "Material Transfer for Manufacture",
+ "Manufacture",
+ "Repack",
+ "Send to Subcontractor",
+ "Material Consumption for Manufacture",
+ ]
if self.purpose not in valid_purposes:
frappe.throw(_("Purpose must be one of {0}").format(comma_or(valid_purposes)))
- if self.job_card and self.purpose not in ['Material Transfer for Manufacture', 'Repack']:
- frappe.throw(_("For job card {0}, you can only make the 'Material Transfer for Manufacture' type stock entry")
- .format(self.job_card))
+ if self.job_card and self.purpose not in ["Material Transfer for Manufacture", "Repack"]:
+ frappe.throw(
+ _(
+ "For job card {0}, you can only make the 'Material Transfer for Manufacture' type stock entry"
+ ).format(self.job_card)
+ )
def delete_linked_stock_entry(self):
if self.purpose == "Send to Warehouse":
- for d in frappe.get_all("Stock Entry", filters={"docstatus": 0,
- "outgoing_stock_entry": self.name, "purpose": "Receive at Warehouse"}):
+ for d in frappe.get_all(
+ "Stock Entry",
+ filters={"docstatus": 0, "outgoing_stock_entry": self.name, "purpose": "Receive at Warehouse"},
+ ):
frappe.delete_doc("Stock Entry", d.name)
def set_transfer_qty(self):
@@ -205,80 +228,117 @@
frappe.throw(_("Row {0}: Qty is mandatory").format(item.idx))
if not flt(item.conversion_factor):
frappe.throw(_("Row {0}: UOM Conversion Factor is mandatory").format(item.idx))
- item.transfer_qty = flt(flt(item.qty) * flt(item.conversion_factor),
- self.precision("transfer_qty", item))
+ item.transfer_qty = flt(
+ flt(item.qty) * flt(item.conversion_factor), self.precision("transfer_qty", item)
+ )
def update_cost_in_project(self):
- if (self.work_order and not frappe.db.get_value("Work Order",
- self.work_order, "update_consumed_material_cost_in_project")):
+ if self.work_order and not frappe.db.get_value(
+ "Work Order", self.work_order, "update_consumed_material_cost_in_project"
+ ):
return
if self.project:
- amount = frappe.db.sql(""" select ifnull(sum(sed.amount), 0)
+ amount = frappe.db.sql(
+ """ select ifnull(sum(sed.amount), 0)
from
`tabStock Entry` se, `tabStock Entry Detail` sed
where
se.docstatus = 1 and se.project = %s and sed.parent = se.name
- and (sed.t_warehouse is null or sed.t_warehouse = '')""", self.project, as_list=1)
+ and (sed.t_warehouse is null or sed.t_warehouse = '')""",
+ self.project,
+ as_list=1,
+ )
amount = amount[0][0] if amount else 0
- additional_costs = frappe.db.sql(""" select ifnull(sum(sed.base_amount), 0)
+ additional_costs = frappe.db.sql(
+ """ select ifnull(sum(sed.base_amount), 0)
from
`tabStock Entry` se, `tabLanded Cost Taxes and Charges` sed
where
se.docstatus = 1 and se.project = %s and sed.parent = se.name
- and se.purpose = 'Manufacture'""", self.project, as_list=1)
+ and se.purpose = 'Manufacture'""",
+ self.project,
+ as_list=1,
+ )
additional_cost_amt = additional_costs[0][0] if additional_costs else 0
amount += additional_cost_amt
- frappe.db.set_value('Project', self.project, 'total_consumed_material_cost', amount)
+ frappe.db.set_value("Project", self.project, "total_consumed_material_cost", amount)
def validate_item(self):
stock_items = self.get_stock_items()
serialized_items = self.get_serialized_items()
for item in self.get("items"):
if flt(item.qty) and flt(item.qty) < 0:
- frappe.throw(_("Row {0}: The item {1}, quantity must be positive number")
- .format(item.idx, frappe.bold(item.item_code)))
+ frappe.throw(
+ _("Row {0}: The item {1}, quantity must be positive number").format(
+ item.idx, frappe.bold(item.item_code)
+ )
+ )
if item.item_code not in stock_items:
frappe.throw(_("{0} is not a stock Item").format(item.item_code))
- item_details = self.get_item_details(frappe._dict(
- {"item_code": item.item_code, "company": self.company,
- "project": self.project, "uom": item.uom, 's_warehouse': item.s_warehouse}),
- for_update=True)
+ item_details = self.get_item_details(
+ frappe._dict(
+ {
+ "item_code": item.item_code,
+ "company": self.company,
+ "project": self.project,
+ "uom": item.uom,
+ "s_warehouse": item.s_warehouse,
+ }
+ ),
+ for_update=True,
+ )
- for f in ("uom", "stock_uom", "description", "item_name", "expense_account",
- "cost_center", "conversion_factor"):
- if f == "stock_uom" or not item.get(f):
- item.set(f, item_details.get(f))
- if f == 'conversion_factor' and item.uom == item_details.get('stock_uom'):
- item.set(f, item_details.get(f))
+ for f in (
+ "uom",
+ "stock_uom",
+ "description",
+ "item_name",
+ "expense_account",
+ "cost_center",
+ "conversion_factor",
+ ):
+ if f == "stock_uom" or not item.get(f):
+ item.set(f, item_details.get(f))
+ if f == "conversion_factor" and item.uom == item_details.get("stock_uom"):
+ item.set(f, item_details.get(f))
if not item.transfer_qty and item.qty:
- item.transfer_qty = flt(flt(item.qty) * flt(item.conversion_factor),
- self.precision("transfer_qty", item))
+ item.transfer_qty = flt(
+ flt(item.qty) * flt(item.conversion_factor), self.precision("transfer_qty", item)
+ )
- if (self.purpose in ("Material Transfer", "Material Transfer for Manufacture")
+ if (
+ self.purpose in ("Material Transfer", "Material Transfer for Manufacture")
and not item.serial_no
- and item.item_code in serialized_items):
- frappe.throw(_("Row #{0}: Please specify Serial No for Item {1}").format(item.idx, item.item_code),
- frappe.MandatoryError)
+ and item.item_code in serialized_items
+ ):
+ frappe.throw(
+ _("Row #{0}: Please specify Serial No for Item {1}").format(item.idx, item.item_code),
+ frappe.MandatoryError,
+ )
def validate_qty(self):
manufacture_purpose = ["Manufacture", "Material Consumption for Manufacture"]
if self.purpose in manufacture_purpose and self.work_order:
- if not frappe.get_value('Work Order', self.work_order, 'skip_transfer'):
+ if not frappe.get_value("Work Order", self.work_order, "skip_transfer"):
item_code = []
for item in self.items:
- if cstr(item.t_warehouse) == '':
- req_items = frappe.get_all('Work Order Item',
- filters={'parent': self.work_order, 'item_code': item.item_code}, fields=["item_code"])
+ if cstr(item.t_warehouse) == "":
+ req_items = frappe.get_all(
+ "Work Order Item",
+ filters={"parent": self.work_order, "item_code": item.item_code},
+ fields=["item_code"],
+ )
- transferred_materials = frappe.db.sql("""
+ transferred_materials = frappe.db.sql(
+ """
select
sum(qty) as qty
from `tabStock Entry` se,`tabStock Entry Detail` sed
@@ -286,7 +346,10 @@
se.name = sed.parent and se.docstatus=1 and
(se.purpose='Material Transfer for Manufacture' or se.purpose='Manufacture')
and sed.item_code=%s and se.work_order= %s and ifnull(sed.t_warehouse, '') != ''
- """, (item.item_code, self.work_order), as_dict=1)
+ """,
+ (item.item_code, self.work_order),
+ as_dict=1,
+ )
stock_qty = flt(item.qty)
trans_qty = flt(transferred_materials[0].qty)
@@ -304,8 +367,11 @@
for item_code, qty_list in item_wise_qty.items():
total = flt(sum(qty_list), frappe.get_precision("Stock Entry Detail", "qty"))
if self.fg_completed_qty != total:
- frappe.throw(_("The finished product {0} quantity {1} and For Quantity {2} cannot be different")
- .format(frappe.bold(item_code), frappe.bold(total), frappe.bold(self.fg_completed_qty)))
+ frappe.throw(
+ _("The finished product {0} quantity {1} and For Quantity {2} cannot be different").format(
+ frappe.bold(item_code), frappe.bold(total), frappe.bold(self.fg_completed_qty)
+ )
+ )
def validate_difference_account(self):
if not cint(erpnext.is_perpetual_inventory_enabled(self.company)):
@@ -313,33 +379,53 @@
for d in self.get("items"):
if not d.expense_account:
- frappe.throw(_("Please enter <b>Difference Account</b> or set default <b>Stock Adjustment Account</b> for company {0}")
- .format(frappe.bold(self.company)))
+ frappe.throw(
+ _(
+ "Please enter <b>Difference Account</b> or set default <b>Stock Adjustment Account</b> for company {0}"
+ ).format(frappe.bold(self.company))
+ )
- elif self.is_opening == "Yes" and frappe.db.get_value("Account", d.expense_account, "report_type") == "Profit and Loss":
- frappe.throw(_("Difference Account must be a Asset/Liability type account, since this Stock Entry is an Opening Entry"), OpeningEntryAccountError)
+ elif (
+ self.is_opening == "Yes"
+ and frappe.db.get_value("Account", d.expense_account, "report_type") == "Profit and Loss"
+ ):
+ frappe.throw(
+ _(
+ "Difference Account must be a Asset/Liability type account, since this Stock Entry is an Opening Entry"
+ ),
+ OpeningEntryAccountError,
+ )
def validate_warehouse(self):
"""perform various (sometimes conditional) validations on warehouse"""
- source_mandatory = ["Material Issue", "Material Transfer", "Send to Subcontractor", "Material Transfer for Manufacture",
- "Material Consumption for Manufacture"]
+ source_mandatory = [
+ "Material Issue",
+ "Material Transfer",
+ "Send to Subcontractor",
+ "Material Transfer for Manufacture",
+ "Material Consumption for Manufacture",
+ ]
- target_mandatory = ["Material Receipt", "Material Transfer", "Send to Subcontractor",
- "Material Transfer for Manufacture"]
+ target_mandatory = [
+ "Material Receipt",
+ "Material Transfer",
+ "Send to Subcontractor",
+ "Material Transfer for Manufacture",
+ ]
validate_for_manufacture = any([d.bom_no for d in self.get("items")])
if self.purpose in source_mandatory and self.purpose not in target_mandatory:
self.to_warehouse = None
- for d in self.get('items'):
+ for d in self.get("items"):
d.t_warehouse = None
elif self.purpose in target_mandatory and self.purpose not in source_mandatory:
self.from_warehouse = None
- for d in self.get('items'):
+ for d in self.get("items"):
d.s_warehouse = None
- for d in self.get('items'):
+ for d in self.get("items"):
if not d.s_warehouse and not d.t_warehouse:
d.s_warehouse = self.from_warehouse
d.t_warehouse = self.to_warehouse
@@ -356,7 +442,6 @@
else:
frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
-
if self.purpose == "Manufacture":
if validate_for_manufacture:
if d.is_finished_item or d.is_scrap_item or d.is_process_loss:
@@ -368,18 +453,26 @@
if not d.s_warehouse:
frappe.throw(_("Source warehouse is mandatory for row {0}").format(d.idx))
- if cstr(d.s_warehouse) == cstr(d.t_warehouse) and not self.purpose == "Material Transfer for Manufacture":
+ if (
+ cstr(d.s_warehouse) == cstr(d.t_warehouse)
+ and not self.purpose == "Material Transfer for Manufacture"
+ ):
frappe.throw(_("Source and target warehouse cannot be same for row {0}").format(d.idx))
if not (d.s_warehouse or d.t_warehouse):
frappe.throw(_("Atleast one warehouse is mandatory"))
def validate_work_order(self):
- if self.purpose in ("Manufacture", "Material Transfer for Manufacture", "Material Consumption for Manufacture"):
+ if self.purpose in (
+ "Manufacture",
+ "Material Transfer for Manufacture",
+ "Material Consumption for Manufacture",
+ ):
# check if work order is entered
- if (self.purpose=="Manufacture" or self.purpose=="Material Consumption for Manufacture") \
- and self.work_order:
+ if (
+ self.purpose == "Manufacture" or self.purpose == "Material Consumption for Manufacture"
+ ) and self.work_order:
if not self.fg_completed_qty:
frappe.throw(_("For Quantity (Manufactured Qty) is mandatory"))
self.check_if_operations_completed()
@@ -390,40 +483,66 @@
def check_if_operations_completed(self):
"""Check if Time Sheets are completed against before manufacturing to capture operating costs."""
prod_order = frappe.get_doc("Work Order", self.work_order)
- allowance_percentage = flt(frappe.db.get_single_value("Manufacturing Settings",
- "overproduction_percentage_for_work_order"))
+ allowance_percentage = flt(
+ frappe.db.get_single_value("Manufacturing Settings", "overproduction_percentage_for_work_order")
+ )
for d in prod_order.get("operations"):
total_completed_qty = flt(self.fg_completed_qty) + flt(prod_order.produced_qty)
- completed_qty = d.completed_qty + (allowance_percentage/100 * d.completed_qty)
+ completed_qty = d.completed_qty + (allowance_percentage / 100 * d.completed_qty)
if total_completed_qty > flt(completed_qty):
- job_card = frappe.db.get_value('Job Card', {'operation_id': d.name}, 'name')
+ job_card = frappe.db.get_value("Job Card", {"operation_id": d.name}, "name")
if not job_card:
- frappe.throw(_("Work Order {0}: Job Card not found for the operation {1}")
- .format(self.work_order, d.operation))
+ frappe.throw(
+ _("Work Order {0}: Job Card not found for the operation {1}").format(
+ self.work_order, d.operation
+ )
+ )
- work_order_link = frappe.utils.get_link_to_form('Work Order', self.work_order)
- job_card_link = frappe.utils.get_link_to_form('Job Card', job_card)
- frappe.throw(_("Row #{0}: Operation {1} is not completed for {2} qty of finished goods in Work Order {3}. Please update operation status via Job Card {4}.")
- .format(d.idx, frappe.bold(d.operation), frappe.bold(total_completed_qty), work_order_link, job_card_link), OperationsNotCompleteError)
+ work_order_link = frappe.utils.get_link_to_form("Work Order", self.work_order)
+ job_card_link = frappe.utils.get_link_to_form("Job Card", job_card)
+ frappe.throw(
+ _(
+ "Row #{0}: Operation {1} is not completed for {2} qty of finished goods in Work Order {3}. Please update operation status via Job Card {4}."
+ ).format(
+ d.idx,
+ frappe.bold(d.operation),
+ frappe.bold(total_completed_qty),
+ work_order_link,
+ job_card_link,
+ ),
+ OperationsNotCompleteError,
+ )
def check_duplicate_entry_for_work_order(self):
- other_ste = [t[0] for t in frappe.db.get_values("Stock Entry", {
- "work_order": self.work_order,
- "purpose": self.purpose,
- "docstatus": ["!=", 2],
- "name": ["!=", self.name]
- }, "name")]
+ other_ste = [
+ t[0]
+ for t in frappe.db.get_values(
+ "Stock Entry",
+ {
+ "work_order": self.work_order,
+ "purpose": self.purpose,
+ "docstatus": ["!=", 2],
+ "name": ["!=", self.name],
+ },
+ "name",
+ )
+ ]
if other_ste:
- production_item, qty = frappe.db.get_value("Work Order",
- self.work_order, ["production_item", "qty"])
+ production_item, qty = frappe.db.get_value(
+ "Work Order", self.work_order, ["production_item", "qty"]
+ )
args = other_ste + [production_item]
- fg_qty_already_entered = frappe.db.sql("""select sum(transfer_qty)
+ fg_qty_already_entered = frappe.db.sql(
+ """select sum(transfer_qty)
from `tabStock Entry Detail`
where parent in (%s)
and item_code = %s
- and ifnull(s_warehouse,'')='' """ % (", ".join(["%s" * len(other_ste)]), "%s"), args)[0][0]
+ and ifnull(s_warehouse,'')='' """
+ % (", ".join(["%s" * len(other_ste)]), "%s"),
+ args,
+ )[0][0]
if fg_qty_already_entered and fg_qty_already_entered >= qty:
frappe.throw(
_("Stock Entries already created for Work Order {0}: {1}").format(
@@ -435,34 +554,57 @@
def set_actual_qty(self):
from erpnext.stock.stock_ledger import is_negative_stock_allowed
- for d in self.get('items'):
+ for d in self.get("items"):
allow_negative_stock = is_negative_stock_allowed(item_code=d.item_code)
- previous_sle = get_previous_sle({
- "item_code": d.item_code,
- "warehouse": d.s_warehouse or d.t_warehouse,
- "posting_date": self.posting_date,
- "posting_time": self.posting_time
- })
+ previous_sle = get_previous_sle(
+ {
+ "item_code": d.item_code,
+ "warehouse": d.s_warehouse or d.t_warehouse,
+ "posting_date": self.posting_date,
+ "posting_time": self.posting_time,
+ }
+ )
# get actual stock at source warehouse
d.actual_qty = previous_sle.get("qty_after_transaction") or 0
# validate qty during submit
- if d.docstatus==1 and d.s_warehouse and not allow_negative_stock and flt(d.actual_qty, d.precision("actual_qty")) < flt(d.transfer_qty, d.precision("actual_qty")):
- frappe.throw(_("Row {0}: Quantity not available for {4} in warehouse {1} at posting time of the entry ({2} {3})").format(d.idx,
- frappe.bold(d.s_warehouse), formatdate(self.posting_date),
- format_time(self.posting_time), frappe.bold(d.item_code))
- + '<br><br>' + _("Available quantity is {0}, you need {1}").format(frappe.bold(d.actual_qty),
- frappe.bold(d.transfer_qty)),
- NegativeStockError, title=_('Insufficient Stock'))
+ if (
+ d.docstatus == 1
+ and d.s_warehouse
+ and not allow_negative_stock
+ and flt(d.actual_qty, d.precision("actual_qty"))
+ < flt(d.transfer_qty, d.precision("actual_qty"))
+ ):
+ frappe.throw(
+ _(
+ "Row {0}: Quantity not available for {4} in warehouse {1} at posting time of the entry ({2} {3})"
+ ).format(
+ d.idx,
+ frappe.bold(d.s_warehouse),
+ formatdate(self.posting_date),
+ format_time(self.posting_time),
+ frappe.bold(d.item_code),
+ )
+ + "<br><br>"
+ + _("Available quantity is {0}, you need {1}").format(
+ frappe.bold(d.actual_qty), frappe.bold(d.transfer_qty)
+ ),
+ NegativeStockError,
+ title=_("Insufficient Stock"),
+ )
def set_serial_nos(self, work_order):
- previous_se = frappe.db.get_value("Stock Entry", {"work_order": work_order,
- "purpose": "Material Transfer for Manufacture"}, "name")
+ previous_se = frappe.db.get_value(
+ "Stock Entry",
+ {"work_order": work_order, "purpose": "Material Transfer for Manufacture"},
+ "name",
+ )
- for d in self.get('items'):
- transferred_serial_no = frappe.db.get_value("Stock Entry Detail",{"parent": previous_se,
- "item_code": d.item_code}, "serial_no")
+ for d in self.get("items"):
+ transferred_serial_no = frappe.db.get_value(
+ "Stock Entry Detail", {"parent": previous_se, "item_code": d.item_code}, "serial_no"
+ )
if transferred_serial_no:
d.serial_no = transferred_serial_no
@@ -470,8 +612,8 @@
@frappe.whitelist()
def get_stock_and_rate(self):
"""
- Updates rate and availability of all the items.
- Called from Update Rate and Availability button.
+ Updates rate and availability of all the items.
+ Called from Update Rate and Availability button.
"""
self.set_work_order_details()
self.set_transfer_qty()
@@ -488,38 +630,52 @@
def set_basic_rate(self, reset_outgoing_rate=True, raise_error_if_no_rate=True):
"""
- Set rate for outgoing, scrapped and finished items
+ Set rate for outgoing, scrapped and finished items
"""
# Set rate for outgoing items
- outgoing_items_cost = self.set_rate_for_outgoing_items(reset_outgoing_rate, raise_error_if_no_rate)
- finished_item_qty = sum(d.transfer_qty for d in self.items if d.is_finished_item or d.is_process_loss)
+ outgoing_items_cost = self.set_rate_for_outgoing_items(
+ reset_outgoing_rate, raise_error_if_no_rate
+ )
+ finished_item_qty = sum(
+ d.transfer_qty for d in self.items if d.is_finished_item or d.is_process_loss
+ )
# Set basic rate for incoming items
- for d in self.get('items'):
- if d.s_warehouse or d.set_basic_rate_manually: continue
+ for d in self.get("items"):
+ if d.s_warehouse or d.set_basic_rate_manually:
+ continue
if d.allow_zero_valuation_rate:
d.basic_rate = 0.0
elif d.is_finished_item:
if self.purpose == "Manufacture":
- d.basic_rate = self.get_basic_rate_for_manufactured_item(finished_item_qty, outgoing_items_cost)
+ d.basic_rate = self.get_basic_rate_for_manufactured_item(
+ finished_item_qty, outgoing_items_cost
+ )
elif self.purpose == "Repack":
d.basic_rate = self.get_basic_rate_for_repacked_items(d.transfer_qty, outgoing_items_cost)
if not d.basic_rate and not d.allow_zero_valuation_rate:
- d.basic_rate = get_valuation_rate(d.item_code, d.t_warehouse,
- self.doctype, self.name, d.allow_zero_valuation_rate,
- currency=erpnext.get_company_currency(self.company), company=self.company,
- raise_error_if_no_rate=raise_error_if_no_rate, batch_no=d.batch_no)
+ d.basic_rate = get_valuation_rate(
+ d.item_code,
+ d.t_warehouse,
+ self.doctype,
+ self.name,
+ d.allow_zero_valuation_rate,
+ currency=erpnext.get_company_currency(self.company),
+ company=self.company,
+ raise_error_if_no_rate=raise_error_if_no_rate,
+ batch_no=d.batch_no,
+ )
d.basic_rate = flt(d.basic_rate, d.precision("basic_rate"))
if d.is_process_loss:
- d.basic_rate = flt(0.)
+ d.basic_rate = flt(0.0)
d.basic_amount = flt(flt(d.transfer_qty) * flt(d.basic_rate), d.precision("basic_amount"))
def set_rate_for_outgoing_items(self, reset_outgoing_rate=True, raise_error_if_no_rate=True):
outgoing_items_cost = 0.0
- for d in self.get('items'):
+ for d in self.get("items"):
if d.s_warehouse:
if reset_outgoing_rate:
args = self.get_args_for_incoming_rate(d)
@@ -534,19 +690,21 @@
return outgoing_items_cost
def get_args_for_incoming_rate(self, item):
- return frappe._dict({
- "item_code": item.item_code,
- "warehouse": item.s_warehouse or item.t_warehouse,
- "posting_date": self.posting_date,
- "posting_time": self.posting_time,
- "qty": item.s_warehouse and -1*flt(item.transfer_qty) or flt(item.transfer_qty),
- "serial_no": item.serial_no,
- "batch_no": item.batch_no,
- "voucher_type": self.doctype,
- "voucher_no": self.name,
- "company": self.company,
- "allow_zero_valuation": item.allow_zero_valuation_rate,
- })
+ return frappe._dict(
+ {
+ "item_code": item.item_code,
+ "warehouse": item.s_warehouse or item.t_warehouse,
+ "posting_date": self.posting_date,
+ "posting_time": self.posting_time,
+ "qty": item.s_warehouse and -1 * flt(item.transfer_qty) or flt(item.transfer_qty),
+ "serial_no": item.serial_no,
+ "batch_no": item.batch_no,
+ "voucher_type": self.doctype,
+ "voucher_no": self.name,
+ "company": self.company,
+ "allow_zero_valuation": item.allow_zero_valuation_rate,
+ }
+ )
def get_basic_rate_for_repacked_items(self, finished_item_qty, outgoing_items_cost):
finished_items = [d.item_code for d in self.get("items") if d.is_finished_item]
@@ -562,9 +720,11 @@
scrap_items_cost = sum([flt(d.basic_amount) for d in self.get("items") if d.is_scrap_item])
# Get raw materials cost from BOM if multiple material consumption entries
- if not outgoing_items_cost and frappe.db.get_single_value("Manufacturing Settings", "material_consumption", cache=True):
+ if not outgoing_items_cost and frappe.db.get_single_value(
+ "Manufacturing Settings", "material_consumption", cache=True
+ ):
bom_items = self.get_bom_raw_materials(finished_item_qty)
- outgoing_items_cost = sum([flt(row.qty)*flt(row.rate) for row in bom_items.values()])
+ outgoing_items_cost = sum([flt(row.qty) * flt(row.rate) for row in bom_items.values()])
return flt((outgoing_items_cost - scrap_items_cost) / finished_item_qty)
@@ -596,8 +756,10 @@
for d in self.get("items"):
if d.transfer_qty:
d.amount = flt(flt(d.basic_amount) + flt(d.additional_cost), d.precision("amount"))
- d.valuation_rate = flt(flt(d.basic_rate) + (flt(d.additional_cost) / flt(d.transfer_qty)),
- d.precision("valuation_rate"))
+ d.valuation_rate = flt(
+ flt(d.basic_rate) + (flt(d.additional_cost) / flt(d.transfer_qty)),
+ d.precision("valuation_rate"),
+ )
def set_total_incoming_outgoing_value(self):
self.total_incoming_value = self.total_outgoing_value = 0.0
@@ -611,92 +773,120 @@
def set_total_amount(self):
self.total_amount = None
- if self.purpose not in ['Manufacture', 'Repack']:
+ if self.purpose not in ["Manufacture", "Repack"]:
self.total_amount = sum([flt(item.amount) for item in self.get("items")])
def set_stock_entry_type(self):
if self.purpose:
- self.stock_entry_type = frappe.get_cached_value('Stock Entry Type',
- {'purpose': self.purpose}, 'name')
+ self.stock_entry_type = frappe.get_cached_value(
+ "Stock Entry Type", {"purpose": self.purpose}, "name"
+ )
def set_purpose_for_stock_entry(self):
if self.stock_entry_type and not self.purpose:
- self.purpose = frappe.get_cached_value('Stock Entry Type',
- self.stock_entry_type, 'purpose')
+ self.purpose = frappe.get_cached_value("Stock Entry Type", self.stock_entry_type, "purpose")
def validate_duplicate_serial_no(self):
warehouse_wise_serial_nos = {}
# In case of repack the source and target serial nos could be same
- for warehouse in ['s_warehouse', 't_warehouse']:
+ for warehouse in ["s_warehouse", "t_warehouse"]:
serial_nos = []
for row in self.items:
- if not (row.serial_no and row.get(warehouse)): continue
+ if not (row.serial_no and row.get(warehouse)):
+ continue
for sn in get_serial_nos(row.serial_no):
if sn in serial_nos:
- frappe.throw(_('The serial no {0} has added multiple times in the stock entry {1}')
- .format(frappe.bold(sn), self.name))
+ frappe.throw(
+ _("The serial no {0} has added multiple times in the stock entry {1}").format(
+ frappe.bold(sn), self.name
+ )
+ )
serial_nos.append(sn)
def validate_purchase_order(self):
"""Throw exception if more raw material is transferred against Purchase Order than in
the raw materials supplied table"""
- backflush_raw_materials_based_on = frappe.db.get_single_value("Buying Settings",
- "backflush_raw_materials_of_subcontract_based_on")
+ backflush_raw_materials_based_on = frappe.db.get_single_value(
+ "Buying Settings", "backflush_raw_materials_of_subcontract_based_on"
+ )
- qty_allowance = flt(frappe.db.get_single_value("Buying Settings",
- "over_transfer_allowance"))
+ qty_allowance = flt(frappe.db.get_single_value("Buying Settings", "over_transfer_allowance"))
- if not (self.purpose == "Send to Subcontractor" and self.purchase_order): return
+ if not (self.purpose == "Send to Subcontractor" and self.purchase_order):
+ return
- if (backflush_raw_materials_based_on == 'BOM'):
+ if backflush_raw_materials_based_on == "BOM":
purchase_order = frappe.get_doc("Purchase Order", self.purchase_order)
for se_item in self.items:
item_code = se_item.original_item or se_item.item_code
precision = cint(frappe.db.get_default("float_precision")) or 3
- required_qty = sum([flt(d.required_qty) for d in purchase_order.supplied_items \
- if d.rm_item_code == item_code])
+ required_qty = sum(
+ [flt(d.required_qty) for d in purchase_order.supplied_items if d.rm_item_code == item_code]
+ )
- total_allowed = required_qty + (required_qty * (qty_allowance/100))
+ total_allowed = required_qty + (required_qty * (qty_allowance / 100))
if not required_qty:
- bom_no = frappe.db.get_value("Purchase Order Item",
+ bom_no = frappe.db.get_value(
+ "Purchase Order Item",
{"parent": self.purchase_order, "item_code": se_item.subcontracted_item},
- "bom")
+ "bom",
+ )
if se_item.allow_alternative_item:
- original_item_code = frappe.get_value("Item Alternative", {"alternative_item_code": item_code}, "item_code")
+ original_item_code = frappe.get_value(
+ "Item Alternative", {"alternative_item_code": item_code}, "item_code"
+ )
- required_qty = sum([flt(d.required_qty) for d in purchase_order.supplied_items \
- if d.rm_item_code == original_item_code])
+ required_qty = sum(
+ [
+ flt(d.required_qty)
+ for d in purchase_order.supplied_items
+ if d.rm_item_code == original_item_code
+ ]
+ )
- total_allowed = required_qty + (required_qty * (qty_allowance/100))
+ total_allowed = required_qty + (required_qty * (qty_allowance / 100))
if not required_qty:
- frappe.throw(_("Item {0} not found in 'Raw Materials Supplied' table in Purchase Order {1}")
- .format(se_item.item_code, self.purchase_order))
- total_supplied = frappe.db.sql("""select sum(transfer_qty)
+ frappe.throw(
+ _("Item {0} not found in 'Raw Materials Supplied' table in Purchase Order {1}").format(
+ se_item.item_code, self.purchase_order
+ )
+ )
+ total_supplied = frappe.db.sql(
+ """select sum(transfer_qty)
from `tabStock Entry Detail`, `tabStock Entry`
where `tabStock Entry`.purchase_order = %s
and `tabStock Entry`.docstatus = 1
and `tabStock Entry Detail`.item_code = %s
and `tabStock Entry Detail`.parent = `tabStock Entry`.name""",
- (self.purchase_order, se_item.item_code))[0][0]
+ (self.purchase_order, se_item.item_code),
+ )[0][0]
if flt(total_supplied, precision) > flt(total_allowed, precision):
- frappe.throw(_("Row {0}# Item {1} cannot be transferred more than {2} against Purchase Order {3}")
- .format(se_item.idx, se_item.item_code, total_allowed, self.purchase_order))
+ frappe.throw(
+ _("Row {0}# Item {1} cannot be transferred more than {2} against Purchase Order {3}").format(
+ se_item.idx, se_item.item_code, total_allowed, self.purchase_order
+ )
+ )
elif backflush_raw_materials_based_on == "Material Transferred for Subcontract":
for row in self.items:
if not row.subcontracted_item:
- frappe.throw(_("Row {0}: Subcontracted Item is mandatory for the raw material {1}")
- .format(row.idx, frappe.bold(row.item_code)))
+ frappe.throw(
+ _("Row {0}: Subcontracted Item is mandatory for the raw material {1}").format(
+ row.idx, frappe.bold(row.item_code)
+ )
+ )
elif not row.po_detail:
filters = {
- "parent": self.purchase_order, "docstatus": 1,
- "rm_item_code": row.item_code, "main_item_code": row.subcontracted_item
+ "parent": self.purchase_order,
+ "docstatus": 1,
+ "rm_item_code": row.item_code,
+ "main_item_code": row.subcontracted_item,
}
po_detail = frappe.db.get_value("Purchase Order Item Supplied", filters, "name")
@@ -704,7 +894,7 @@
row.db_set("po_detail", po_detail)
def validate_bom(self):
- for d in self.get('items'):
+ for d in self.get("items"):
if d.bom_no and d.is_finished_item:
item_code = d.original_item or d.item_code
validate_bom_no(item_code, d.bom_no)
@@ -722,7 +912,7 @@
for d in self.items:
if d.t_warehouse and not d.s_warehouse:
- if self.purpose=="Repack" or d.item_code == finished_item:
+ if self.purpose == "Repack" or d.item_code == finished_item:
d.is_finished_item = 1
else:
d.is_scrap_item = 1
@@ -741,19 +931,17 @@
def validate_finished_goods(self):
"""
- 1. Check if FG exists (mfg, repack)
- 2. Check if Multiple FG Items are present (mfg)
- 3. Check FG Item and Qty against WO if present (mfg)
+ 1. Check if FG exists (mfg, repack)
+ 2. Check if Multiple FG Items are present (mfg)
+ 3. Check FG Item and Qty against WO if present (mfg)
"""
production_item, wo_qty, finished_items = None, 0, []
- wo_details = frappe.db.get_value(
- "Work Order", self.work_order, ["production_item", "qty"]
- )
+ wo_details = frappe.db.get_value("Work Order", self.work_order, ["production_item", "qty"])
if wo_details:
production_item, wo_qty = wo_details
- for d in self.get('items'):
+ for d in self.get("items"):
if d.is_finished_item:
if not self.work_order:
# Independent MFG Entry/ Repack Entry, no WO to match against
@@ -761,12 +949,16 @@
continue
if d.item_code != production_item:
- frappe.throw(_("Finished Item {0} does not match with Work Order {1}")
- .format(d.item_code, self.work_order)
+ frappe.throw(
+ _("Finished Item {0} does not match with Work Order {1}").format(
+ d.item_code, self.work_order
+ )
)
elif flt(d.transfer_qty) > flt(self.fg_completed_qty):
- frappe.throw(_("Quantity in row {0} ({1}) must be same as manufactured quantity {2}")
- .format(d.idx, d.transfer_qty, self.fg_completed_qty)
+ frappe.throw(
+ _("Quantity in row {0} ({1}) must be same as manufactured quantity {2}").format(
+ d.idx, d.transfer_qty, self.fg_completed_qty
+ )
)
finished_items.append(d.item_code)
@@ -774,28 +966,31 @@
if not finished_items:
frappe.throw(
msg=_("There must be atleast 1 Finished Good in this Stock Entry").format(self.name),
- title=_("Missing Finished Good"), exc=FinishedGoodError
+ title=_("Missing Finished Good"),
+ exc=FinishedGoodError,
)
if self.purpose == "Manufacture":
if len(set(finished_items)) > 1:
frappe.throw(
msg=_("Multiple items cannot be marked as finished item"),
- title=_("Note"), exc=FinishedGoodError
+ title=_("Note"),
+ exc=FinishedGoodError,
)
allowance_percentage = flt(
frappe.db.get_single_value(
- "Manufacturing Settings","overproduction_percentage_for_work_order"
+ "Manufacturing Settings", "overproduction_percentage_for_work_order"
)
)
- allowed_qty = wo_qty + ((allowance_percentage/100) * wo_qty)
+ allowed_qty = wo_qty + ((allowance_percentage / 100) * wo_qty)
# No work order could mean independent Manufacture entry, if so skip validation
if self.work_order and self.fg_completed_qty > allowed_qty:
frappe.throw(
- _("For quantity {0} should not be greater than work order quantity {1}")
- .format(flt(self.fg_completed_qty), wo_qty)
+ _("For quantity {0} should not be greater than work order quantity {1}").format(
+ flt(self.fg_completed_qty), wo_qty
+ )
)
def update_stock_ledger(self):
@@ -817,35 +1012,38 @@
def get_finished_item_row(self):
finished_item_row = None
if self.purpose in ("Manufacture", "Repack"):
- for d in self.get('items'):
+ for d in self.get("items"):
if d.is_finished_item:
finished_item_row = d
return finished_item_row
def get_sle_for_source_warehouse(self, sl_entries, finished_item_row):
- for d in self.get('items'):
+ for d in self.get("items"):
if cstr(d.s_warehouse):
- sle = self.get_sl_entries(d, {
- "warehouse": cstr(d.s_warehouse),
- "actual_qty": -flt(d.transfer_qty),
- "incoming_rate": 0
- })
+ sle = self.get_sl_entries(
+ d, {"warehouse": cstr(d.s_warehouse), "actual_qty": -flt(d.transfer_qty), "incoming_rate": 0}
+ )
if cstr(d.t_warehouse):
sle.dependant_sle_voucher_detail_no = d.name
- elif finished_item_row and (finished_item_row.item_code != d.item_code or finished_item_row.t_warehouse != d.s_warehouse):
+ elif finished_item_row and (
+ finished_item_row.item_code != d.item_code or finished_item_row.t_warehouse != d.s_warehouse
+ ):
sle.dependant_sle_voucher_detail_no = finished_item_row.name
sl_entries.append(sle)
def get_sle_for_target_warehouse(self, sl_entries, finished_item_row):
- for d in self.get('items'):
+ for d in self.get("items"):
if cstr(d.t_warehouse):
- sle = self.get_sl_entries(d, {
- "warehouse": cstr(d.t_warehouse),
- "actual_qty": flt(d.transfer_qty),
- "incoming_rate": flt(d.valuation_rate)
- })
+ sle = self.get_sl_entries(
+ d,
+ {
+ "warehouse": cstr(d.t_warehouse),
+ "actual_qty": flt(d.transfer_qty),
+ "incoming_rate": flt(d.valuation_rate),
+ },
+ )
if cstr(d.s_warehouse) or (finished_item_row and d.name == finished_item_row.name):
sle.recalculate_rate = 1
@@ -875,40 +1073,55 @@
continue
item_account_wise_additional_cost.setdefault((d.item_code, d.name), {})
- item_account_wise_additional_cost[(d.item_code, d.name)].setdefault(t.expense_account, {
- "amount": 0.0,
- "base_amount": 0.0
- })
+ item_account_wise_additional_cost[(d.item_code, d.name)].setdefault(
+ t.expense_account, {"amount": 0.0, "base_amount": 0.0}
+ )
multiply_based_on = d.basic_amount if total_basic_amount else d.qty
- item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account]["amount"] += \
+ item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account]["amount"] += (
flt(t.amount * multiply_based_on) / divide_based_on
+ )
- item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account]["base_amount"] += \
+ item_account_wise_additional_cost[(d.item_code, d.name)][t.expense_account]["base_amount"] += (
flt(t.base_amount * multiply_based_on) / divide_based_on
+ )
if item_account_wise_additional_cost:
for d in self.get("items"):
- for account, amount in item_account_wise_additional_cost.get((d.item_code, d.name), {}).items():
- if not amount: continue
+ for account, amount in item_account_wise_additional_cost.get(
+ (d.item_code, d.name), {}
+ ).items():
+ if not amount:
+ continue
- gl_entries.append(self.get_gl_dict({
- "account": account,
- "against": d.expense_account,
- "cost_center": d.cost_center,
- "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
- "credit_in_account_currency": flt(amount["amount"]),
- "credit": flt(amount["base_amount"])
- }, item=d))
+ gl_entries.append(
+ self.get_gl_dict(
+ {
+ "account": account,
+ "against": d.expense_account,
+ "cost_center": d.cost_center,
+ "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
+ "credit_in_account_currency": flt(amount["amount"]),
+ "credit": flt(amount["base_amount"]),
+ },
+ item=d,
+ )
+ )
- gl_entries.append(self.get_gl_dict({
- "account": d.expense_account,
- "against": account,
- "cost_center": d.cost_center,
- "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
- "credit": -1 * amount['base_amount'] # put it as negative credit instead of debit purposefully
- }, item=d))
+ gl_entries.append(
+ self.get_gl_dict(
+ {
+ "account": d.expense_account,
+ "against": account,
+ "cost_center": d.cost_center,
+ "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
+ "credit": -1
+ * amount["base_amount"], # put it as negative credit instead of debit purposefully
+ },
+ item=d,
+ )
+ )
return process_gl_map(gl_entries)
@@ -917,11 +1130,13 @@
if flt(pro_doc.docstatus) != 1:
frappe.throw(_("Work Order {0} must be submitted").format(self.work_order))
- if pro_doc.status == 'Stopped':
- frappe.throw(_("Transaction not allowed against stopped Work Order {0}").format(self.work_order))
+ if pro_doc.status == "Stopped":
+ frappe.throw(
+ _("Transaction not allowed against stopped Work Order {0}").format(self.work_order)
+ )
if self.job_card:
- job_doc = frappe.get_doc('Job Card', self.job_card)
+ job_doc = frappe.get_doc("Job Card", self.job_card)
job_doc.set_transferred_qty(update_status=True)
job_doc.set_transferred_qty_in_job_card(self)
@@ -941,73 +1156,95 @@
@frappe.whitelist()
def get_item_details(self, args=None, for_update=False):
- item = frappe.db.sql("""select i.name, i.stock_uom, i.description, i.image, i.item_name, i.item_group,
+ item = frappe.db.sql(
+ """select i.name, i.stock_uom, i.description, i.image, i.item_name, i.item_group,
i.has_batch_no, i.sample_quantity, i.has_serial_no, i.allow_alternative_item,
id.expense_account, id.buying_cost_center
from `tabItem` i LEFT JOIN `tabItem Default` id ON i.name=id.parent and id.company=%s
where i.name=%s
and i.disabled=0
and (i.end_of_life is null or i.end_of_life='0000-00-00' or i.end_of_life > %s)""",
- (self.company, args.get('item_code'), nowdate()), as_dict = 1)
+ (self.company, args.get("item_code"), nowdate()),
+ as_dict=1,
+ )
if not item:
- frappe.throw(_("Item {0} is not active or end of life has been reached").format(args.get("item_code")))
+ frappe.throw(
+ _("Item {0} is not active or end of life has been reached").format(args.get("item_code"))
+ )
item = item[0]
item_group_defaults = get_item_group_defaults(item.name, self.company)
brand_defaults = get_brand_defaults(item.name, self.company)
- ret = frappe._dict({
- 'uom' : item.stock_uom,
- 'stock_uom' : item.stock_uom,
- 'description' : item.description,
- 'image' : item.image,
- 'item_name' : item.item_name,
- 'cost_center' : get_default_cost_center(args, item, item_group_defaults, brand_defaults, self.company),
- 'qty' : args.get("qty"),
- 'transfer_qty' : args.get('qty'),
- 'conversion_factor' : 1,
- 'batch_no' : '',
- 'actual_qty' : 0,
- 'basic_rate' : 0,
- 'serial_no' : '',
- 'has_serial_no' : item.has_serial_no,
- 'has_batch_no' : item.has_batch_no,
- 'sample_quantity' : item.sample_quantity,
- 'expense_account' : item.expense_account
- })
+ ret = frappe._dict(
+ {
+ "uom": item.stock_uom,
+ "stock_uom": item.stock_uom,
+ "description": item.description,
+ "image": item.image,
+ "item_name": item.item_name,
+ "cost_center": get_default_cost_center(
+ args, item, item_group_defaults, brand_defaults, self.company
+ ),
+ "qty": args.get("qty"),
+ "transfer_qty": args.get("qty"),
+ "conversion_factor": 1,
+ "batch_no": "",
+ "actual_qty": 0,
+ "basic_rate": 0,
+ "serial_no": "",
+ "has_serial_no": item.has_serial_no,
+ "has_batch_no": item.has_batch_no,
+ "sample_quantity": item.sample_quantity,
+ "expense_account": item.expense_account,
+ }
+ )
- if self.purpose == 'Send to Subcontractor':
+ if self.purpose == "Send to Subcontractor":
ret["allow_alternative_item"] = item.allow_alternative_item
# update uom
if args.get("uom") and for_update:
- ret.update(get_uom_details(args.get('item_code'), args.get('uom'), args.get('qty')))
+ ret.update(get_uom_details(args.get("item_code"), args.get("uom"), args.get("qty")))
- if self.purpose == 'Material Issue':
- ret["expense_account"] = (item.get("expense_account") or
- item_group_defaults.get("expense_account") or
- frappe.get_cached_value('Company', self.company, "default_expense_account"))
+ if self.purpose == "Material Issue":
+ ret["expense_account"] = (
+ item.get("expense_account")
+ or item_group_defaults.get("expense_account")
+ or frappe.get_cached_value("Company", self.company, "default_expense_account")
+ )
- for company_field, field in {'stock_adjustment_account': 'expense_account',
- 'cost_center': 'cost_center'}.items():
+ for company_field, field in {
+ "stock_adjustment_account": "expense_account",
+ "cost_center": "cost_center",
+ }.items():
if not ret.get(field):
- ret[field] = frappe.get_cached_value('Company', self.company, company_field)
+ ret[field] = frappe.get_cached_value("Company", self.company, company_field)
- args['posting_date'] = self.posting_date
- args['posting_time'] = self.posting_time
+ args["posting_date"] = self.posting_date
+ args["posting_time"] = self.posting_time
- stock_and_rate = get_warehouse_details(args) if args.get('warehouse') else {}
+ stock_and_rate = get_warehouse_details(args) if args.get("warehouse") else {}
ret.update(stock_and_rate)
# automatically select batch for outgoing item
- if (args.get('s_warehouse', None) and args.get('qty') and
- ret.get('has_batch_no') and not args.get('batch_no')):
- args.batch_no = get_batch_no(args['item_code'], args['s_warehouse'], args['qty'])
+ if (
+ args.get("s_warehouse", None)
+ and args.get("qty")
+ and ret.get("has_batch_no")
+ and not args.get("batch_no")
+ ):
+ args.batch_no = get_batch_no(args["item_code"], args["s_warehouse"], args["qty"])
- if self.purpose == "Send to Subcontractor" and self.get("purchase_order") and args.get('item_code'):
- subcontract_items = frappe.get_all("Purchase Order Item Supplied",
- {"parent": self.purchase_order, "rm_item_code": args.get('item_code')}, "main_item_code")
+ if (
+ self.purpose == "Send to Subcontractor" and self.get("purchase_order") and args.get("item_code")
+ ):
+ subcontract_items = frappe.get_all(
+ "Purchase Order Item Supplied",
+ {"parent": self.purchase_order, "rm_item_code": args.get("item_code")},
+ "main_item_code",
+ )
if subcontract_items and len(subcontract_items) == 1:
ret["subcontracted_item"] = subcontract_items[0].main_item_code
@@ -1018,46 +1255,57 @@
def set_items_for_stock_in(self):
self.items = []
- if self.outgoing_stock_entry and self.purpose == 'Material Transfer':
- doc = frappe.get_doc('Stock Entry', self.outgoing_stock_entry)
+ if self.outgoing_stock_entry and self.purpose == "Material Transfer":
+ doc = frappe.get_doc("Stock Entry", self.outgoing_stock_entry)
if doc.per_transferred == 100:
- frappe.throw(_("Goods are already received against the outward entry {0}")
- .format(doc.name))
+ frappe.throw(_("Goods are already received against the outward entry {0}").format(doc.name))
for d in doc.items:
- self.append('items', {
- 's_warehouse': d.t_warehouse,
- 'item_code': d.item_code,
- 'qty': d.qty,
- 'uom': d.uom,
- 'against_stock_entry': d.parent,
- 'ste_detail': d.name,
- 'stock_uom': d.stock_uom,
- 'conversion_factor': d.conversion_factor,
- 'serial_no': d.serial_no,
- 'batch_no': d.batch_no
- })
+ self.append(
+ "items",
+ {
+ "s_warehouse": d.t_warehouse,
+ "item_code": d.item_code,
+ "qty": d.qty,
+ "uom": d.uom,
+ "against_stock_entry": d.parent,
+ "ste_detail": d.name,
+ "stock_uom": d.stock_uom,
+ "conversion_factor": d.conversion_factor,
+ "serial_no": d.serial_no,
+ "batch_no": d.batch_no,
+ },
+ )
@frappe.whitelist()
def get_items(self):
- self.set('items', [])
+ self.set("items", [])
self.validate_work_order()
if not self.posting_date or not self.posting_time:
frappe.throw(_("Posting date and posting time is mandatory"))
self.set_work_order_details()
- self.flags.backflush_based_on = frappe.db.get_single_value("Manufacturing Settings",
- "backflush_raw_materials_based_on")
+ self.flags.backflush_based_on = frappe.db.get_single_value(
+ "Manufacturing Settings", "backflush_raw_materials_based_on"
+ )
if self.bom_no:
- backflush_based_on = frappe.db.get_single_value("Manufacturing Settings",
- "backflush_raw_materials_based_on")
+ backflush_based_on = frappe.db.get_single_value(
+ "Manufacturing Settings", "backflush_raw_materials_based_on"
+ )
- if self.purpose in ["Material Issue", "Material Transfer", "Manufacture", "Repack",
- "Send to Subcontractor", "Material Transfer for Manufacture", "Material Consumption for Manufacture"]:
+ if self.purpose in [
+ "Material Issue",
+ "Material Transfer",
+ "Manufacture",
+ "Repack",
+ "Send to Subcontractor",
+ "Material Transfer for Manufacture",
+ "Material Consumption for Manufacture",
+ ]:
if self.work_order and self.purpose == "Material Transfer for Manufacture":
item_dict = self.get_pending_raw_materials(backflush_based_on)
@@ -1066,14 +1314,20 @@
item["to_warehouse"] = self.pro_doc.wip_warehouse
self.add_to_stock_entry_detail(item_dict)
- elif (self.work_order and (self.purpose == "Manufacture"
- or self.purpose == "Material Consumption for Manufacture") and not self.pro_doc.skip_transfer
- and self.flags.backflush_based_on == "Material Transferred for Manufacture"):
+ elif (
+ self.work_order
+ and (self.purpose == "Manufacture" or self.purpose == "Material Consumption for Manufacture")
+ and not self.pro_doc.skip_transfer
+ and self.flags.backflush_based_on == "Material Transferred for Manufacture"
+ ):
self.get_transfered_raw_materials()
- elif (self.work_order and (self.purpose == "Manufacture" or
- self.purpose == "Material Consumption for Manufacture") and self.flags.backflush_based_on== "BOM"
- and frappe.db.get_single_value("Manufacturing Settings", "material_consumption")== 1):
+ elif (
+ self.work_order
+ and (self.purpose == "Manufacture" or self.purpose == "Material Consumption for Manufacture")
+ and self.flags.backflush_based_on == "BOM"
+ and frappe.db.get_single_value("Manufacturing Settings", "material_consumption") == 1
+ ):
self.get_unconsumed_raw_materials()
else:
@@ -1082,31 +1336,36 @@
item_dict = self.get_bom_raw_materials(self.fg_completed_qty)
- #Get PO Supplied Items Details
+ # Get PO Supplied Items Details
if self.purchase_order and self.purpose == "Send to Subcontractor":
- #Get PO Supplied Items Details
- item_wh = frappe._dict(frappe.db.sql("""
+ # Get PO Supplied Items Details
+ item_wh = frappe._dict(
+ frappe.db.sql(
+ """
SELECT
rm_item_code, reserve_warehouse
FROM
`tabPurchase Order` po, `tabPurchase Order Item Supplied` poitemsup
WHERE
- po.name = poitemsup.parent and po.name = %s """,self.purchase_order))
+ po.name = poitemsup.parent and po.name = %s """,
+ self.purchase_order,
+ )
+ )
for item in item_dict.values():
if self.pro_doc and cint(self.pro_doc.from_wip_warehouse):
item["from_warehouse"] = self.pro_doc.wip_warehouse
- #Get Reserve Warehouse from PO
- if self.purchase_order and self.purpose=="Send to Subcontractor":
+ # Get Reserve Warehouse from PO
+ if self.purchase_order and self.purpose == "Send to Subcontractor":
item["from_warehouse"] = item_wh.get(item.item_code)
- item["to_warehouse"] = self.to_warehouse if self.purpose=="Send to Subcontractor" else ""
+ item["to_warehouse"] = self.to_warehouse if self.purpose == "Send to Subcontractor" else ""
self.add_to_stock_entry_detail(item_dict)
# fetch the serial_no of the first stock entry for the second stock entry
if self.work_order and self.purpose == "Manufacture":
self.set_serial_nos(self.work_order)
- work_order = frappe.get_doc('Work Order', self.work_order)
+ work_order = frappe.get_doc("Work Order", self.work_order)
add_additional_cost(self, work_order)
# add finished goods item
@@ -1123,7 +1382,7 @@
if self.purpose != "Send to Subcontractor" and self.purpose in ["Manufacture", "Repack"]:
scrap_item_dict = self.get_bom_scrap_material(self.fg_completed_qty)
for item in scrap_item_dict.values():
- item.idx = ''
+ item.idx = ""
if self.pro_doc and self.pro_doc.scrap_warehouse:
item["to_warehouse"] = self.pro_doc.scrap_warehouse
@@ -1136,7 +1395,7 @@
if self.work_order:
# common validations
if not self.pro_doc:
- self.pro_doc = frappe.get_doc('Work Order', self.work_order)
+ self.pro_doc = frappe.get_doc("Work Order", self.work_order)
if self.pro_doc:
self.bom_no = self.pro_doc.bom_no
@@ -1167,11 +1426,18 @@
"stock_uom": item.stock_uom,
"expense_account": item.get("expense_account"),
"cost_center": item.get("buying_cost_center"),
- "is_finished_item": 1
+ "is_finished_item": 1,
}
- if self.work_order and self.pro_doc.has_batch_no and cint(frappe.db.get_single_value('Manufacturing Settings',
- 'make_serial_no_batch_from_work_order', cache=True)):
+ if (
+ self.work_order
+ and self.pro_doc.has_batch_no
+ and cint(
+ frappe.db.get_single_value(
+ "Manufacturing Settings", "make_serial_no_batch_from_work_order", cache=True
+ )
+ )
+ ):
self.set_batchwise_finished_goods(args, item)
else:
self.add_finished_goods(args, item)
@@ -1180,12 +1446,12 @@
filters = {
"reference_name": self.pro_doc.name,
"reference_doctype": self.pro_doc.doctype,
- "qty_to_produce": (">", 0)
+ "qty_to_produce": (">", 0),
}
fields = ["qty_to_produce as qty", "produced_qty", "name"]
- data = frappe.get_all("Batch", filters = filters, fields = fields, order_by="creation asc")
+ data = frappe.get_all("Batch", filters=filters, fields=fields, order_by="creation asc")
if not data:
self.add_finished_goods(args, item)
@@ -1200,7 +1466,7 @@
if not batch_qty:
continue
- if qty <=0:
+ if qty <= 0:
break
fg_qty = batch_qty
@@ -1214,23 +1480,27 @@
self.add_finished_goods(args, item)
def add_finished_goods(self, args, item):
- self.add_to_stock_entry_detail({
- item.name: args
- }, bom_no = self.bom_no)
+ self.add_to_stock_entry_detail({item.name: args}, bom_no=self.bom_no)
def get_bom_raw_materials(self, qty):
from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict
# item dict = { item_code: {qty, description, stock_uom} }
- item_dict = get_bom_items_as_dict(self.bom_no, self.company, qty=qty,
- fetch_exploded = self.use_multi_level_bom, fetch_qty_in_stock_uom=False)
+ item_dict = get_bom_items_as_dict(
+ self.bom_no,
+ self.company,
+ qty=qty,
+ fetch_exploded=self.use_multi_level_bom,
+ fetch_qty_in_stock_uom=False,
+ )
- used_alternative_items = get_used_alternative_items(work_order = self.work_order)
+ used_alternative_items = get_used_alternative_items(work_order=self.work_order)
for item in item_dict.values():
# if source warehouse presents in BOM set from_warehouse as bom source_warehouse
if item["allow_alternative_item"]:
- item["allow_alternative_item"] = frappe.db.get_value('Work Order',
- self.work_order, "allow_alternative_item")
+ item["allow_alternative_item"] = frappe.db.get_value(
+ "Work Order", self.work_order, "allow_alternative_item"
+ )
item.from_warehouse = self.from_warehouse or item.source_warehouse or item.default_warehouse
if item.item_code in used_alternative_items:
@@ -1248,8 +1518,10 @@
from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict
# item dict = { item_code: {qty, description, stock_uom} }
- item_dict = get_bom_items_as_dict(self.bom_no, self.company, qty=qty,
- fetch_exploded = 0, fetch_scrap_items = 1) or {}
+ item_dict = (
+ get_bom_items_as_dict(self.bom_no, self.company, qty=qty, fetch_exploded=0, fetch_scrap_items=1)
+ or {}
+ )
for item in item_dict.values():
item.from_warehouse = ""
@@ -1263,16 +1535,18 @@
if not item_row:
item_row = frappe._dict({})
- item_row.update({
- 'uom': row.stock_uom,
- 'from_warehouse': '',
- 'qty': row.stock_qty + flt(item_row.stock_qty),
- 'converison_factor': 1,
- 'is_scrap_item': 1,
- 'item_name': row.item_name,
- 'description': row.description,
- 'allow_zero_valuation_rate': 1
- })
+ item_row.update(
+ {
+ "uom": row.stock_uom,
+ "from_warehouse": "",
+ "qty": row.stock_qty + flt(item_row.stock_qty),
+ "converison_factor": 1,
+ "is_scrap_item": 1,
+ "item_name": row.item_name,
+ "description": row.description,
+ "allow_zero_valuation_rate": 1,
+ }
+ )
item_dict[row.item_code] = item_row
@@ -1285,21 +1559,25 @@
if not self.pro_doc.operations:
return []
- job_card = frappe.qb.DocType('Job Card')
- job_card_scrap_item = frappe.qb.DocType('Job Card Scrap Item')
+ job_card = frappe.qb.DocType("Job Card")
+ job_card_scrap_item = frappe.qb.DocType("Job Card Scrap Item")
scrap_items = (
frappe.qb.from_(job_card)
.select(
- Sum(job_card_scrap_item.stock_qty).as_('stock_qty'),
- job_card_scrap_item.item_code, job_card_scrap_item.item_name,
- job_card_scrap_item.description, job_card_scrap_item.stock_uom)
+ Sum(job_card_scrap_item.stock_qty).as_("stock_qty"),
+ job_card_scrap_item.item_code,
+ job_card_scrap_item.item_name,
+ job_card_scrap_item.description,
+ job_card_scrap_item.stock_uom,
+ )
.join(job_card_scrap_item)
.on(job_card_scrap_item.parent == job_card.name)
.where(
(job_card_scrap_item.item_code.isnotnull())
& (job_card.work_order == self.work_order)
- & (job_card.docstatus == 1))
+ & (job_card.docstatus == 1)
+ )
.groupby(job_card_scrap_item.item_code)
).run(as_dict=1)
@@ -1313,7 +1591,7 @@
if used_scrap_items.get(row.item_code):
used_scrap_items[row.item_code] -= row.stock_qty
- if cint(frappe.get_cached_value('UOM', row.stock_uom, 'must_be_whole_number')):
+ if cint(frappe.get_cached_value("UOM", row.stock_uom, "must_be_whole_number")):
row.stock_qty = frappe.utils.ceil(row.stock_qty)
return scrap_items
@@ -1324,16 +1602,14 @@
def get_used_scrap_items(self):
used_scrap_items = defaultdict(float)
data = frappe.get_all(
- 'Stock Entry',
- fields = [
- '`tabStock Entry Detail`.`item_code`', '`tabStock Entry Detail`.`qty`'
+ "Stock Entry",
+ fields=["`tabStock Entry Detail`.`item_code`", "`tabStock Entry Detail`.`qty`"],
+ filters=[
+ ["Stock Entry", "work_order", "=", self.work_order],
+ ["Stock Entry Detail", "is_scrap_item", "=", 1],
+ ["Stock Entry", "docstatus", "=", 1],
+ ["Stock Entry", "purpose", "in", ["Repack", "Manufacture"]],
],
- filters = [
- ['Stock Entry', 'work_order', '=', self.work_order],
- ['Stock Entry Detail', 'is_scrap_item', '=', 1],
- ['Stock Entry', 'docstatus', '=', 1],
- ['Stock Entry', 'purpose', 'in', ['Repack', 'Manufacture']]
- ]
)
for row in data:
@@ -1343,10 +1619,11 @@
def get_unconsumed_raw_materials(self):
wo = frappe.get_doc("Work Order", self.work_order)
- wo_items = frappe.get_all('Work Order Item',
- filters={'parent': self.work_order},
- fields=["item_code", "source_warehouse", "required_qty", "consumed_qty", "transferred_qty"]
- )
+ wo_items = frappe.get_all(
+ "Work Order Item",
+ filters={"parent": self.work_order},
+ fields=["item_code", "source_warehouse", "required_qty", "consumed_qty", "transferred_qty"],
+ )
work_order_qty = wo.material_transferred_for_manufacturing or wo.qty
for item in wo_items:
@@ -1363,21 +1640,24 @@
qty = req_qty_each * flt(self.fg_completed_qty)
if qty > 0:
- self.add_to_stock_entry_detail({
- item.item_code: {
- "from_warehouse": wo.wip_warehouse or item.source_warehouse,
- "to_warehouse": "",
- "qty": qty,
- "item_name": item.item_name,
- "description": item.description,
- "stock_uom": item_account_details.stock_uom,
- "expense_account": item_account_details.get("expense_account"),
- "cost_center": item_account_details.get("buying_cost_center"),
+ self.add_to_stock_entry_detail(
+ {
+ item.item_code: {
+ "from_warehouse": wo.wip_warehouse or item.source_warehouse,
+ "to_warehouse": "",
+ "qty": qty,
+ "item_name": item.item_name,
+ "description": item.description,
+ "stock_uom": item_account_details.stock_uom,
+ "expense_account": item_account_details.get("expense_account"),
+ "cost_center": item_account_details.get("buying_cost_center"),
+ }
}
- })
+ )
def get_transfered_raw_materials(self):
- transferred_materials = frappe.db.sql("""
+ transferred_materials = frappe.db.sql(
+ """
select
item_name, original_item, item_code, sum(qty) as qty, sed.t_warehouse as warehouse,
description, stock_uom, expense_account, cost_center
@@ -1386,9 +1666,13 @@
se.name = sed.parent and se.docstatus=1 and se.purpose='Material Transfer for Manufacture'
and se.work_order= %s and ifnull(sed.t_warehouse, '') != ''
group by sed.item_code, sed.t_warehouse
- """, self.work_order, as_dict=1)
+ """,
+ self.work_order,
+ as_dict=1,
+ )
- materials_already_backflushed = frappe.db.sql("""
+ materials_already_backflushed = frappe.db.sql(
+ """
select
item_code, sed.s_warehouse as warehouse, sum(qty) as qty
from
@@ -1398,26 +1682,34 @@
and (se.purpose='Manufacture' or se.purpose='Material Consumption for Manufacture')
and se.work_order= %s and ifnull(sed.s_warehouse, '') != ''
group by sed.item_code, sed.s_warehouse
- """, self.work_order, as_dict=1)
+ """,
+ self.work_order,
+ as_dict=1,
+ )
- backflushed_materials= {}
+ backflushed_materials = {}
for d in materials_already_backflushed:
- backflushed_materials.setdefault(d.item_code,[]).append({d.warehouse: d.qty})
+ backflushed_materials.setdefault(d.item_code, []).append({d.warehouse: d.qty})
- po_qty = frappe.db.sql("""select qty, produced_qty, material_transferred_for_manufacturing from
- `tabWork Order` where name=%s""", self.work_order, as_dict=1)[0]
+ po_qty = frappe.db.sql(
+ """select qty, produced_qty, material_transferred_for_manufacturing from
+ `tabWork Order` where name=%s""",
+ self.work_order,
+ as_dict=1,
+ )[0]
manufacturing_qty = flt(po_qty.qty) or 1
produced_qty = flt(po_qty.produced_qty)
trans_qty = flt(po_qty.material_transferred_for_manufacturing) or 1
for item in transferred_materials:
- qty= item.qty
+ qty = item.qty
item_code = item.original_item or item.item_code
- req_items = frappe.get_all('Work Order Item',
- filters={'parent': self.work_order, 'item_code': item_code},
- fields=["required_qty", "consumed_qty"]
- )
+ req_items = frappe.get_all(
+ "Work Order Item",
+ filters={"parent": self.work_order, "item_code": item_code},
+ fields=["required_qty", "consumed_qty"],
+ )
req_qty = flt(req_items[0].required_qty) if req_items else flt(4)
req_qty_each = flt(req_qty / manufacturing_qty)
@@ -1425,23 +1717,23 @@
if trans_qty and manufacturing_qty > (produced_qty + flt(self.fg_completed_qty)):
if qty >= req_qty:
- qty = (req_qty/trans_qty) * flt(self.fg_completed_qty)
+ qty = (req_qty / trans_qty) * flt(self.fg_completed_qty)
else:
qty = qty - consumed_qty
- if self.purpose == 'Manufacture':
+ if self.purpose == "Manufacture":
# If Material Consumption is booked, must pull only remaining components to finish product
if consumed_qty != 0:
remaining_qty = consumed_qty - (produced_qty * req_qty_each)
exhaust_qty = req_qty_each * produced_qty
- if remaining_qty > exhaust_qty :
- if (remaining_qty/(req_qty_each * flt(self.fg_completed_qty))) >= 1:
- qty =0
+ if remaining_qty > exhaust_qty:
+ if (remaining_qty / (req_qty_each * flt(self.fg_completed_qty))) >= 1:
+ qty = 0
else:
qty = (req_qty_each * flt(self.fg_completed_qty)) - remaining_qty
else:
if self.flags.backflush_based_on == "Material Transferred for Manufacture":
- qty = (item.qty/trans_qty) * flt(self.fg_completed_qty)
+ qty = (item.qty / trans_qty) * flt(self.fg_completed_qty)
else:
qty = req_qty_each * flt(self.fg_completed_qty)
@@ -1449,45 +1741,51 @@
precision = frappe.get_precision("Stock Entry Detail", "qty")
for d in backflushed_materials.get(item.item_code):
if d.get(item.warehouse) > 0:
- if (qty > req_qty):
- qty = ((flt(qty, precision) - flt(d.get(item.warehouse), precision))
+ if qty > req_qty:
+ qty = (
+ (flt(qty, precision) - flt(d.get(item.warehouse), precision))
/ (flt(trans_qty, precision) - flt(produced_qty, precision))
) * flt(self.fg_completed_qty)
d[item.warehouse] -= qty
- if cint(frappe.get_cached_value('UOM', item.stock_uom, 'must_be_whole_number')):
+ if cint(frappe.get_cached_value("UOM", item.stock_uom, "must_be_whole_number")):
qty = frappe.utils.ceil(qty)
if qty > 0:
- self.add_to_stock_entry_detail({
- item.item_code: {
- "from_warehouse": item.warehouse,
- "to_warehouse": "",
- "qty": qty,
- "item_name": item.item_name,
- "description": item.description,
- "stock_uom": item.stock_uom,
- "expense_account": item.expense_account,
- "cost_center": item.buying_cost_center,
- "original_item": item.original_item
+ self.add_to_stock_entry_detail(
+ {
+ item.item_code: {
+ "from_warehouse": item.warehouse,
+ "to_warehouse": "",
+ "qty": qty,
+ "item_name": item.item_name,
+ "description": item.description,
+ "stock_uom": item.stock_uom,
+ "expense_account": item.expense_account,
+ "cost_center": item.buying_cost_center,
+ "original_item": item.original_item,
+ }
}
- })
+ )
def get_pending_raw_materials(self, backflush_based_on=None):
"""
- issue (item quantity) that is pending to issue or desire to transfer,
- whichever is less
+ issue (item quantity) that is pending to issue or desire to transfer,
+ whichever is less
"""
item_dict = self.get_pro_order_required_items(backflush_based_on)
max_qty = flt(self.pro_doc.qty)
allow_overproduction = False
- overproduction_percentage = flt(frappe.db.get_single_value("Manufacturing Settings",
- "overproduction_percentage_for_work_order"))
+ overproduction_percentage = flt(
+ frappe.db.get_single_value("Manufacturing Settings", "overproduction_percentage_for_work_order")
+ )
- to_transfer_qty = flt(self.pro_doc.material_transferred_for_manufacturing) + flt(self.fg_completed_qty)
+ to_transfer_qty = flt(self.pro_doc.material_transferred_for_manufacturing) + flt(
+ self.fg_completed_qty
+ )
transfer_limit_qty = max_qty + ((max_qty * overproduction_percentage) / 100)
if transfer_limit_qty >= to_transfer_qty:
@@ -1497,9 +1795,11 @@
pending_to_issue = flt(item_details.required_qty) - flt(item_details.transferred_qty)
desire_to_transfer = flt(self.fg_completed_qty) * flt(item_details.required_qty) / max_qty
- if (desire_to_transfer <= pending_to_issue
+ if (
+ desire_to_transfer <= pending_to_issue
or (desire_to_transfer > 0 and backflush_based_on == "Material Transferred for Manufacture")
- or allow_overproduction):
+ or allow_overproduction
+ ):
item_dict[item]["qty"] = desire_to_transfer
elif pending_to_issue > 0:
item_dict[item]["qty"] = pending_to_issue
@@ -1520,7 +1820,7 @@
def get_pro_order_required_items(self, backflush_based_on=None):
"""
- Gets Work Order Required Items only if Stock Entry purpose is **Material Transferred for Manufacture**.
+ Gets Work Order Required Items only if Stock Entry purpose is **Material Transferred for Manufacture**.
"""
item_dict, job_card_items = frappe._dict(), []
work_order = frappe.get_doc("Work Order", self.work_order)
@@ -1539,7 +1839,9 @@
continue
transfer_pending = flt(d.required_qty) > flt(d.transferred_qty)
- can_transfer = transfer_pending or (backflush_based_on == "Material Transferred for Manufacture")
+ can_transfer = transfer_pending or (
+ backflush_based_on == "Material Transferred for Manufacture"
+ )
if not can_transfer:
continue
@@ -1550,11 +1852,7 @@
if consider_job_card:
job_card_item = frappe.db.get_value(
- "Job Card Item",
- {
- "item_code": d.item_code,
- "parent": self.get("job_card")
- }
+ "Job Card Item", {"item_code": d.item_code, "parent": self.get("job_card")}
)
item_row["job_card_item"] = job_card_item or None
@@ -1574,12 +1872,7 @@
return []
job_card_items = frappe.get_all(
- "Job Card Item",
- filters={
- "parent": job_card
- },
- fields=["item_code"],
- distinct=True
+ "Job Card Item", filters={"parent": job_card}, fields=["item_code"], distinct=True
)
return [d.item_code for d in job_card_items]
@@ -1588,60 +1881,87 @@
item_row = item_dict[d]
stock_uom = item_row.get("stock_uom") or frappe.db.get_value("Item", d, "stock_uom")
- se_child = self.append('items')
+ se_child = self.append("items")
se_child.s_warehouse = item_row.get("from_warehouse")
se_child.t_warehouse = item_row.get("to_warehouse")
- se_child.item_code = item_row.get('item_code') or cstr(d)
+ se_child.item_code = item_row.get("item_code") or cstr(d)
se_child.uom = item_row["uom"] if item_row.get("uom") else stock_uom
se_child.stock_uom = stock_uom
se_child.qty = flt(item_row["qty"], se_child.precision("qty"))
se_child.allow_alternative_item = item_row.get("allow_alternative_item", 0)
se_child.subcontracted_item = item_row.get("main_item_code")
- se_child.cost_center = (item_row.get("cost_center") or
- get_default_cost_center(item_row, company = self.company))
+ se_child.cost_center = item_row.get("cost_center") or get_default_cost_center(
+ item_row, company=self.company
+ )
se_child.is_finished_item = item_row.get("is_finished_item", 0)
se_child.is_scrap_item = item_row.get("is_scrap_item", 0)
se_child.is_process_loss = item_row.get("is_process_loss", 0)
- for field in ["idx", "po_detail", "original_item", "expense_account",
- "description", "item_name", "serial_no", "batch_no", "allow_zero_valuation_rate"]:
+ for field in [
+ "idx",
+ "po_detail",
+ "original_item",
+ "expense_account",
+ "description",
+ "item_name",
+ "serial_no",
+ "batch_no",
+ "allow_zero_valuation_rate",
+ ]:
if item_row.get(field):
se_child.set(field, item_row.get(field))
- if se_child.s_warehouse==None:
+ if se_child.s_warehouse == None:
se_child.s_warehouse = self.from_warehouse
- if se_child.t_warehouse==None:
+ if se_child.t_warehouse == None:
se_child.t_warehouse = self.to_warehouse
# in stock uom
se_child.conversion_factor = flt(item_row.get("conversion_factor")) or 1
- se_child.transfer_qty = flt(item_row["qty"]*se_child.conversion_factor, se_child.precision("qty"))
+ se_child.transfer_qty = flt(
+ item_row["qty"] * se_child.conversion_factor, se_child.precision("qty")
+ )
- se_child.bom_no = bom_no # to be assigned for finished item
+ se_child.bom_no = bom_no # to be assigned for finished item
se_child.job_card_item = item_row.get("job_card_item") if self.get("job_card") else None
def validate_with_material_request(self):
for item in self.get("items"):
material_request = item.material_request or None
material_request_item = item.material_request_item or None
- if self.purpose == 'Material Transfer' and self.outgoing_stock_entry:
- parent_se = frappe.get_value("Stock Entry Detail", item.ste_detail, ['material_request','material_request_item'],as_dict=True)
+ if self.purpose == "Material Transfer" and self.outgoing_stock_entry:
+ parent_se = frappe.get_value(
+ "Stock Entry Detail",
+ item.ste_detail,
+ ["material_request", "material_request_item"],
+ as_dict=True,
+ )
if parent_se:
material_request = parent_se.material_request
material_request_item = parent_se.material_request_item
if material_request:
- mreq_item = frappe.db.get_value("Material Request Item",
+ mreq_item = frappe.db.get_value(
+ "Material Request Item",
{"name": material_request_item, "parent": material_request},
- ["item_code", "warehouse", "idx"], as_dict=True)
+ ["item_code", "warehouse", "idx"],
+ as_dict=True,
+ )
if mreq_item.item_code != item.item_code:
- frappe.throw(_("Item for row {0} does not match Material Request").format(item.idx),
- frappe.MappingMismatchError)
+ frappe.throw(
+ _("Item for row {0} does not match Material Request").format(item.idx),
+ frappe.MappingMismatchError,
+ )
elif self.purpose == "Material Transfer" and self.add_to_transit:
continue
def validate_batch(self):
- if self.purpose in ["Material Transfer for Manufacture", "Manufacture", "Repack", "Send to Subcontractor"]:
+ if self.purpose in [
+ "Material Transfer for Manufacture",
+ "Manufacture",
+ "Repack",
+ "Send to Subcontractor",
+ ]:
for item in self.get("items"):
if item.batch_no:
disabled = frappe.db.get_value("Batch", item.batch_no, "disabled")
@@ -1649,30 +1969,34 @@
expiry_date = frappe.db.get_value("Batch", item.batch_no, "expiry_date")
if expiry_date:
if getdate(self.posting_date) > getdate(expiry_date):
- frappe.throw(_("Batch {0} of Item {1} has expired.")
- .format(item.batch_no, item.item_code))
+ frappe.throw(_("Batch {0} of Item {1} has expired.").format(item.batch_no, item.item_code))
else:
- frappe.throw(_("Batch {0} of Item {1} is disabled.")
- .format(item.batch_no, item.item_code))
+ frappe.throw(_("Batch {0} of Item {1} is disabled.").format(item.batch_no, item.item_code))
def update_purchase_order_supplied_items(self):
- if (self.purchase_order and
- (self.purpose in ['Send to Subcontractor', 'Material Transfer'] or self.is_return)):
+ if self.purchase_order and (
+ self.purpose in ["Send to Subcontractor", "Material Transfer"] or self.is_return
+ ):
- #Get PO Supplied Items Details
- item_wh = frappe._dict(frappe.db.sql("""
+ # Get PO Supplied Items Details
+ item_wh = frappe._dict(
+ frappe.db.sql(
+ """
select rm_item_code, reserve_warehouse
from `tabPurchase Order` po, `tabPurchase Order Item Supplied` poitemsup
where po.name = poitemsup.parent
- and po.name = %s""", self.purchase_order))
+ and po.name = %s""",
+ self.purchase_order,
+ )
+ )
supplied_items = get_supplied_items(self.purchase_order)
for name, item in supplied_items.items():
- frappe.db.set_value('Purchase Order Item Supplied', name, item)
+ frappe.db.set_value("Purchase Order Item Supplied", name, item)
- #Update reserved sub contracted quantity in bin based on Supplied Item Details and
+ # Update reserved sub contracted quantity in bin based on Supplied Item Details and
for d in self.get("items"):
- item_code = d.get('original_item') or d.get('item_code')
+ item_code = d.get("original_item") or d.get("item_code")
reserve_warehouse = item_wh.get(item_code)
if not (reserve_warehouse and item_code):
continue
@@ -1680,12 +2004,17 @@
stock_bin.update_reserved_qty_for_sub_contracting()
def update_so_in_serial_number(self):
- so_name, item_code = frappe.db.get_value("Work Order", self.work_order, ["sales_order", "production_item"])
+ so_name, item_code = frappe.db.get_value(
+ "Work Order", self.work_order, ["sales_order", "production_item"]
+ )
if so_name and item_code:
qty_to_reserve = get_reserved_qty_for_so(so_name, item_code)
if qty_to_reserve:
- reserved_qty = frappe.db.sql("""select count(name) from `tabSerial No` where item_code=%s and
- sales_order=%s""", (item_code, so_name))
+ reserved_qty = frappe.db.sql(
+ """select count(name) from `tabSerial No` where item_code=%s and
+ sales_order=%s""",
+ (item_code, so_name),
+ )
if reserved_qty and reserved_qty[0][0]:
qty_to_reserve -= reserved_qty[0][0]
if qty_to_reserve > 0:
@@ -1696,7 +2025,7 @@
for serial_no in serial_nos:
if qty_to_reserve > 0:
frappe.db.set_value("Serial No", serial_no, "sales_order", so_name)
- qty_to_reserve -=1
+ qty_to_reserve -= 1
def validate_reserved_serial_no_consumption(self):
for item in self.items:
@@ -1704,13 +2033,14 @@
for sr in get_serial_nos(item.serial_no):
sales_order = frappe.db.get_value("Serial No", sr, "sales_order")
if sales_order:
- msg = (_("(Serial No: {0}) cannot be consumed as it's reserverd to fullfill Sales Order {1}.")
- .format(sr, sales_order))
+ msg = _(
+ "(Serial No: {0}) cannot be consumed as it's reserverd to fullfill Sales Order {1}."
+ ).format(sr, sales_order)
frappe.throw(_("Item {0} {1}").format(item.item_code, msg))
def update_transferred_qty(self):
- if self.purpose == 'Material Transfer' and self.outgoing_stock_entry:
+ if self.purpose == "Material Transfer" and self.outgoing_stock_entry:
stock_entries = {}
stock_entries_child_list = []
for d in self.items:
@@ -1718,70 +2048,87 @@
continue
stock_entries_child_list.append(d.ste_detail)
- transferred_qty = frappe.get_all("Stock Entry Detail", fields = ["sum(qty) as qty"],
- filters = { 'against_stock_entry': d.against_stock_entry,
- 'ste_detail': d.ste_detail,'docstatus': 1})
+ transferred_qty = frappe.get_all(
+ "Stock Entry Detail",
+ fields=["sum(qty) as qty"],
+ filters={
+ "against_stock_entry": d.against_stock_entry,
+ "ste_detail": d.ste_detail,
+ "docstatus": 1,
+ },
+ )
- stock_entries[(d.against_stock_entry, d.ste_detail)] = (transferred_qty[0].qty
- if transferred_qty and transferred_qty[0] else 0.0) or 0.0
+ stock_entries[(d.against_stock_entry, d.ste_detail)] = (
+ transferred_qty[0].qty if transferred_qty and transferred_qty[0] else 0.0
+ ) or 0.0
- if not stock_entries: return None
+ if not stock_entries:
+ return None
- cond = ''
+ cond = ""
for data, transferred_qty in stock_entries.items():
cond += """ WHEN (parent = %s and name = %s) THEN %s
- """ %(frappe.db.escape(data[0]), frappe.db.escape(data[1]), transferred_qty)
+ """ % (
+ frappe.db.escape(data[0]),
+ frappe.db.escape(data[1]),
+ transferred_qty,
+ )
if stock_entries_child_list:
- frappe.db.sql(""" UPDATE `tabStock Entry Detail`
+ frappe.db.sql(
+ """ UPDATE `tabStock Entry Detail`
SET
transferred_qty = CASE {cond} END
WHERE
- name in ({ste_details}) """.format(cond=cond,
- ste_details = ','.join(['%s'] * len(stock_entries_child_list))),
- tuple(stock_entries_child_list))
+ name in ({ste_details}) """.format(
+ cond=cond, ste_details=",".join(["%s"] * len(stock_entries_child_list))
+ ),
+ tuple(stock_entries_child_list),
+ )
args = {
- 'source_dt': 'Stock Entry Detail',
- 'target_field': 'transferred_qty',
- 'target_ref_field': 'qty',
- 'target_dt': 'Stock Entry Detail',
- 'join_field': 'ste_detail',
- 'target_parent_dt': 'Stock Entry',
- 'target_parent_field': 'per_transferred',
- 'source_field': 'qty',
- 'percent_join_field': 'against_stock_entry'
+ "source_dt": "Stock Entry Detail",
+ "target_field": "transferred_qty",
+ "target_ref_field": "qty",
+ "target_dt": "Stock Entry Detail",
+ "join_field": "ste_detail",
+ "target_parent_dt": "Stock Entry",
+ "target_parent_field": "per_transferred",
+ "source_field": "qty",
+ "percent_join_field": "against_stock_entry",
}
self._update_percent_field_in_targets(args, update_modified=True)
def update_quality_inspection(self):
if self.inspection_required:
- reference_type = reference_name = ''
+ reference_type = reference_name = ""
if self.docstatus == 1:
reference_name = self.name
- reference_type = 'Stock Entry'
+ reference_type = "Stock Entry"
for d in self.items:
if d.quality_inspection:
- frappe.db.set_value("Quality Inspection", d.quality_inspection, {
- 'reference_type': reference_type,
- 'reference_name': reference_name
- })
+ frappe.db.set_value(
+ "Quality Inspection",
+ d.quality_inspection,
+ {"reference_type": reference_type, "reference_name": reference_name},
+ )
+
def set_material_request_transfer_status(self, status):
material_requests = []
if self.outgoing_stock_entry:
- parent_se = frappe.get_value("Stock Entry", self.outgoing_stock_entry, 'add_to_transit')
+ parent_se = frappe.get_value("Stock Entry", self.outgoing_stock_entry, "add_to_transit")
for item in self.items:
material_request = item.material_request or None
if self.purpose == "Material Transfer" and material_request not in material_requests:
if self.outgoing_stock_entry and parent_se:
- material_request = frappe.get_value("Stock Entry Detail", item.ste_detail, 'material_request')
+ material_request = frappe.get_value("Stock Entry Detail", item.ste_detail, "material_request")
if material_request and material_request not in material_requests:
material_requests.append(material_request)
- frappe.db.set_value('Material Request', material_request, 'transfer_status', status)
+ frappe.db.set_value("Material Request", material_request, "transfer_status", status)
def update_items_for_process_loss(self):
process_loss_dict = {}
@@ -1789,7 +2136,9 @@
if not d.is_process_loss:
continue
- scrap_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_scrap_warehouse")
+ scrap_warehouse = frappe.db.get_single_value(
+ "Manufacturing Settings", "default_scrap_warehouse"
+ )
if scrap_warehouse is not None:
d.t_warehouse = scrap_warehouse
d.is_scrap_item = 0
@@ -1814,14 +2163,22 @@
for row in self.items:
if row.is_finished_item and row.item_code == self.pro_doc.production_item:
if args.get("serial_no"):
- row.serial_no = '\n'.join(args["serial_no"][0: cint(row.qty)])
+ row.serial_no = "\n".join(args["serial_no"][0 : cint(row.qty)])
def get_serial_nos_for_fg(self, args):
- fields = ["`tabStock Entry`.`name`", "`tabStock Entry Detail`.`qty`",
- "`tabStock Entry Detail`.`serial_no`", "`tabStock Entry Detail`.`batch_no`"]
+ fields = [
+ "`tabStock Entry`.`name`",
+ "`tabStock Entry Detail`.`qty`",
+ "`tabStock Entry Detail`.`serial_no`",
+ "`tabStock Entry Detail`.`batch_no`",
+ ]
- filters = [["Stock Entry","work_order","=",self.work_order], ["Stock Entry","purpose","=","Manufacture"],
- ["Stock Entry","docstatus","=",1], ["Stock Entry Detail","item_code","=",self.pro_doc.production_item]]
+ filters = [
+ ["Stock Entry", "work_order", "=", self.work_order],
+ ["Stock Entry", "purpose", "=", "Manufacture"],
+ ["Stock Entry", "docstatus", "=", 1],
+ ["Stock Entry Detail", "item_code", "=", self.pro_doc.production_item],
+ ]
stock_entries = frappe.get_all("Stock Entry", fields=fields, filters=filters)
@@ -1836,85 +2193,98 @@
return sorted(list(set(get_serial_nos(self.pro_doc.serial_no)) - set(used_serial_nos)))
+
@frappe.whitelist()
def move_sample_to_retention_warehouse(company, items):
if isinstance(items, str):
items = json.loads(items)
- retention_warehouse = frappe.db.get_single_value('Stock Settings', 'sample_retention_warehouse')
+ retention_warehouse = frappe.db.get_single_value("Stock Settings", "sample_retention_warehouse")
stock_entry = frappe.new_doc("Stock Entry")
stock_entry.company = company
stock_entry.purpose = "Material Transfer"
stock_entry.set_stock_entry_type()
for item in items:
- if item.get('sample_quantity') and item.get('batch_no'):
- sample_quantity = validate_sample_quantity(item.get('item_code'), item.get('sample_quantity'),
- item.get('transfer_qty') or item.get('qty'), item.get('batch_no'))
+ if item.get("sample_quantity") and item.get("batch_no"):
+ sample_quantity = validate_sample_quantity(
+ item.get("item_code"),
+ item.get("sample_quantity"),
+ item.get("transfer_qty") or item.get("qty"),
+ item.get("batch_no"),
+ )
if sample_quantity:
- sample_serial_nos = ''
- if item.get('serial_no'):
- serial_nos = (item.get('serial_no')).split()
- if serial_nos and len(serial_nos) > item.get('sample_quantity'):
- serial_no_list = serial_nos[:-(len(serial_nos)-item.get('sample_quantity'))]
- sample_serial_nos = '\n'.join(serial_no_list)
+ sample_serial_nos = ""
+ if item.get("serial_no"):
+ serial_nos = (item.get("serial_no")).split()
+ if serial_nos and len(serial_nos) > item.get("sample_quantity"):
+ serial_no_list = serial_nos[: -(len(serial_nos) - item.get("sample_quantity"))]
+ sample_serial_nos = "\n".join(serial_no_list)
- stock_entry.append("items", {
- "item_code": item.get('item_code'),
- "s_warehouse": item.get('t_warehouse'),
- "t_warehouse": retention_warehouse,
- "qty": item.get('sample_quantity'),
- "basic_rate": item.get('valuation_rate'),
- 'uom': item.get('uom'),
- 'stock_uom': item.get('stock_uom'),
- "conversion_factor": 1.0,
- "serial_no": sample_serial_nos,
- 'batch_no': item.get('batch_no')
- })
- if stock_entry.get('items'):
+ stock_entry.append(
+ "items",
+ {
+ "item_code": item.get("item_code"),
+ "s_warehouse": item.get("t_warehouse"),
+ "t_warehouse": retention_warehouse,
+ "qty": item.get("sample_quantity"),
+ "basic_rate": item.get("valuation_rate"),
+ "uom": item.get("uom"),
+ "stock_uom": item.get("stock_uom"),
+ "conversion_factor": 1.0,
+ "serial_no": sample_serial_nos,
+ "batch_no": item.get("batch_no"),
+ },
+ )
+ if stock_entry.get("items"):
return stock_entry.as_dict()
+
@frappe.whitelist()
def make_stock_in_entry(source_name, target_doc=None):
-
def set_missing_values(source, target):
target.set_stock_entry_type()
def update_item(source_doc, target_doc, source_parent):
- target_doc.t_warehouse = ''
+ target_doc.t_warehouse = ""
- if source_doc.material_request_item and source_doc.material_request :
- add_to_transit = frappe.db.get_value('Stock Entry', source_name, 'add_to_transit')
+ if source_doc.material_request_item and source_doc.material_request:
+ add_to_transit = frappe.db.get_value("Stock Entry", source_name, "add_to_transit")
if add_to_transit:
- warehouse = frappe.get_value('Material Request Item', source_doc.material_request_item, 'warehouse')
+ warehouse = frappe.get_value(
+ "Material Request Item", source_doc.material_request_item, "warehouse"
+ )
target_doc.t_warehouse = warehouse
target_doc.s_warehouse = source_doc.t_warehouse
target_doc.qty = source_doc.qty - source_doc.transferred_qty
- doclist = get_mapped_doc("Stock Entry", source_name, {
- "Stock Entry": {
- "doctype": "Stock Entry",
- "field_map": {
- "name": "outgoing_stock_entry"
+ doclist = get_mapped_doc(
+ "Stock Entry",
+ source_name,
+ {
+ "Stock Entry": {
+ "doctype": "Stock Entry",
+ "field_map": {"name": "outgoing_stock_entry"},
+ "validation": {"docstatus": ["=", 1]},
},
- "validation": {
- "docstatus": ["=", 1]
- }
- },
- "Stock Entry Detail": {
- "doctype": "Stock Entry Detail",
- "field_map": {
- "name": "ste_detail",
- "parent": "against_stock_entry",
- "serial_no": "serial_no",
- "batch_no": "batch_no"
+ "Stock Entry Detail": {
+ "doctype": "Stock Entry Detail",
+ "field_map": {
+ "name": "ste_detail",
+ "parent": "against_stock_entry",
+ "serial_no": "serial_no",
+ "batch_no": "batch_no",
+ },
+ "postprocess": update_item,
+ "condition": lambda doc: flt(doc.qty) - flt(doc.transferred_qty) > 0.01,
},
- "postprocess": update_item,
- "condition": lambda doc: flt(doc.qty) - flt(doc.transferred_qty) > 0.01
},
- }, target_doc, set_missing_values)
+ target_doc,
+ set_missing_values,
+ )
return doclist
+
@frappe.whitelist()
def get_work_order_details(work_order, company):
work_order = frappe.get_doc("Work Order", work_order)
@@ -1926,9 +2296,10 @@
"use_multi_level_bom": work_order.use_multi_level_bom,
"wip_warehouse": work_order.wip_warehouse,
"fg_warehouse": work_order.fg_warehouse,
- "fg_completed_qty": pending_qty_to_produce
+ "fg_completed_qty": pending_qty_to_produce,
}
+
def get_operating_cost_per_unit(work_order=None, bom_no=None):
operating_cost_per_unit = 0
if work_order:
@@ -1947,54 +2318,78 @@
if bom.quantity:
operating_cost_per_unit = flt(bom.operating_cost) / flt(bom.quantity)
- if work_order and work_order.produced_qty and cint(frappe.db.get_single_value('Manufacturing Settings',
- 'add_corrective_operation_cost_in_finished_good_valuation')):
- operating_cost_per_unit += flt(work_order.corrective_operation_cost) / flt(work_order.produced_qty)
+ if (
+ work_order
+ and work_order.produced_qty
+ and cint(
+ frappe.db.get_single_value(
+ "Manufacturing Settings", "add_corrective_operation_cost_in_finished_good_valuation"
+ )
+ )
+ ):
+ operating_cost_per_unit += flt(work_order.corrective_operation_cost) / flt(
+ work_order.produced_qty
+ )
return operating_cost_per_unit
+
def get_used_alternative_items(purchase_order=None, work_order=None):
cond = ""
if purchase_order:
- cond = "and ste.purpose = 'Send to Subcontractor' and ste.purchase_order = '{0}'".format(purchase_order)
+ cond = "and ste.purpose = 'Send to Subcontractor' and ste.purchase_order = '{0}'".format(
+ purchase_order
+ )
elif work_order:
- cond = "and ste.purpose = 'Material Transfer for Manufacture' and ste.work_order = '{0}'".format(work_order)
+ cond = "and ste.purpose = 'Material Transfer for Manufacture' and ste.work_order = '{0}'".format(
+ work_order
+ )
- if not cond: return {}
+ if not cond:
+ return {}
used_alternative_items = {}
- data = frappe.db.sql(""" select sted.original_item, sted.uom, sted.conversion_factor,
+ data = frappe.db.sql(
+ """ select sted.original_item, sted.uom, sted.conversion_factor,
sted.item_code, sted.item_name, sted.conversion_factor,sted.stock_uom, sted.description
from
`tabStock Entry` ste, `tabStock Entry Detail` sted
where
sted.parent = ste.name and ste.docstatus = 1 and sted.original_item != sted.item_code
- {0} """.format(cond), as_dict=1)
+ {0} """.format(
+ cond
+ ),
+ as_dict=1,
+ )
for d in data:
used_alternative_items[d.original_item] = d
return used_alternative_items
+
def get_valuation_rate_for_finished_good_entry(work_order):
- work_order_qty = flt(frappe.get_cached_value("Work Order",
- work_order, 'material_transferred_for_manufacturing'))
+ work_order_qty = flt(
+ frappe.get_cached_value("Work Order", work_order, "material_transferred_for_manufacturing")
+ )
field = "(SUM(total_outgoing_value) / %s) as valuation_rate" % (work_order_qty)
- stock_data = frappe.get_all("Stock Entry",
- fields = field,
- filters = {
+ stock_data = frappe.get_all(
+ "Stock Entry",
+ fields=field,
+ filters={
"docstatus": 1,
"purpose": "Material Transfer for Manufacture",
- "work_order": work_order
- }
+ "work_order": work_order,
+ },
)
if stock_data:
return stock_data[0].valuation_rate
+
@frappe.whitelist()
def get_uom_details(item_code, uom, qty):
"""Returns dict `{"conversion_factor": [value], "transfer_qty": qty * [value]}`
@@ -2003,24 +2398,31 @@
conversion_factor = get_conversion_factor(item_code, uom).get("conversion_factor")
if not conversion_factor:
- frappe.msgprint(_("UOM coversion factor required for UOM: {0} in Item: {1}")
- .format(uom, item_code))
- ret = {'uom' : ''}
+ frappe.msgprint(
+ _("UOM coversion factor required for UOM: {0} in Item: {1}").format(uom, item_code)
+ )
+ ret = {"uom": ""}
else:
ret = {
- 'conversion_factor' : flt(conversion_factor),
- 'transfer_qty' : flt(qty) * flt(conversion_factor)
+ "conversion_factor": flt(conversion_factor),
+ "transfer_qty": flt(qty) * flt(conversion_factor),
}
return ret
+
@frappe.whitelist()
def get_expired_batch_items():
- return frappe.db.sql("""select b.item, sum(sle.actual_qty) as qty, sle.batch_no, sle.warehouse, sle.stock_uom\
+ return frappe.db.sql(
+ """select b.item, sum(sle.actual_qty) as qty, sle.batch_no, sle.warehouse, sle.stock_uom\
from `tabBatch` b, `tabStock Ledger Entry` sle
where b.expiry_date <= %s
and b.expiry_date is not NULL
and b.batch_id = sle.batch_no and sle.is_cancelled = 0
- group by sle.warehouse, sle.item_code, sle.batch_no""",(nowdate()), as_dict=1)
+ group by sle.warehouse, sle.item_code, sle.batch_no""",
+ (nowdate()),
+ as_dict=1,
+ )
+
@frappe.whitelist()
def get_warehouse_details(args):
@@ -2031,51 +2433,73 @@
ret = {}
if args.warehouse and args.item_code:
- args.update({
- "posting_date": args.posting_date,
- "posting_time": args.posting_time,
- })
+ args.update(
+ {
+ "posting_date": args.posting_date,
+ "posting_time": args.posting_time,
+ }
+ )
ret = {
- "actual_qty" : get_previous_sle(args).get("qty_after_transaction") or 0,
- "basic_rate" : get_incoming_rate(args)
+ "actual_qty": get_previous_sle(args).get("qty_after_transaction") or 0,
+ "basic_rate": get_incoming_rate(args),
}
return ret
+
@frappe.whitelist()
-def validate_sample_quantity(item_code, sample_quantity, qty, batch_no = None):
+def validate_sample_quantity(item_code, sample_quantity, qty, batch_no=None):
if cint(qty) < cint(sample_quantity):
- frappe.throw(_("Sample quantity {0} cannot be more than received quantity {1}").format(sample_quantity, qty))
- retention_warehouse = frappe.db.get_single_value('Stock Settings', 'sample_retention_warehouse')
+ frappe.throw(
+ _("Sample quantity {0} cannot be more than received quantity {1}").format(sample_quantity, qty)
+ )
+ retention_warehouse = frappe.db.get_single_value("Stock Settings", "sample_retention_warehouse")
retainted_qty = 0
if batch_no:
retainted_qty = get_batch_qty(batch_no, retention_warehouse, item_code)
- max_retain_qty = frappe.get_value('Item', item_code, 'sample_quantity')
+ max_retain_qty = frappe.get_value("Item", item_code, "sample_quantity")
if retainted_qty >= max_retain_qty:
- frappe.msgprint(_("Maximum Samples - {0} have already been retained for Batch {1} and Item {2} in Batch {3}.").
- format(retainted_qty, batch_no, item_code, batch_no), alert=True)
+ frappe.msgprint(
+ _(
+ "Maximum Samples - {0} have already been retained for Batch {1} and Item {2} in Batch {3}."
+ ).format(retainted_qty, batch_no, item_code, batch_no),
+ alert=True,
+ )
sample_quantity = 0
- qty_diff = max_retain_qty-retainted_qty
+ qty_diff = max_retain_qty - retainted_qty
if cint(sample_quantity) > cint(qty_diff):
- frappe.msgprint(_("Maximum Samples - {0} can be retained for Batch {1} and Item {2}.").
- format(max_retain_qty, batch_no, item_code), alert=True)
+ frappe.msgprint(
+ _("Maximum Samples - {0} can be retained for Batch {1} and Item {2}.").format(
+ max_retain_qty, batch_no, item_code
+ ),
+ alert=True,
+ )
sample_quantity = qty_diff
return sample_quantity
-def get_supplied_items(purchase_order):
- fields = ['`tabStock Entry Detail`.`transfer_qty`', '`tabStock Entry`.`is_return`',
- '`tabStock Entry Detail`.`po_detail`', '`tabStock Entry Detail`.`item_code`']
- filters = [['Stock Entry', 'docstatus', '=', 1], ['Stock Entry', 'purchase_order', '=', purchase_order]]
+def get_supplied_items(purchase_order):
+ fields = [
+ "`tabStock Entry Detail`.`transfer_qty`",
+ "`tabStock Entry`.`is_return`",
+ "`tabStock Entry Detail`.`po_detail`",
+ "`tabStock Entry Detail`.`item_code`",
+ ]
+
+ filters = [
+ ["Stock Entry", "docstatus", "=", 1],
+ ["Stock Entry", "purchase_order", "=", purchase_order],
+ ]
supplied_item_details = {}
- for row in frappe.get_all('Stock Entry', fields = fields, filters = filters):
+ for row in frappe.get_all("Stock Entry", fields=fields, filters=filters):
if not row.po_detail:
continue
key = row.po_detail
if key not in supplied_item_details:
- supplied_item_details.setdefault(key,
- frappe._dict({'supplied_qty': 0, 'returned_qty':0, 'total_supplied_qty':0}))
+ supplied_item_details.setdefault(
+ key, frappe._dict({"supplied_qty": 0, "returned_qty": 0, "total_supplied_qty": 0})
+ )
supplied_item = supplied_item_details[key]
@@ -2084,6 +2508,8 @@
else:
supplied_item.supplied_qty += row.transfer_qty
- supplied_item.total_supplied_qty = flt(supplied_item.supplied_qty) - flt(supplied_item.returned_qty)
+ supplied_item.total_supplied_qty = flt(supplied_item.supplied_qty) - flt(
+ supplied_item.returned_qty
+ )
return supplied_item_details
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry_utils.py b/erpnext/stock/doctype/stock_entry/stock_entry_utils.py
index 17266ad..b3df728 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry_utils.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry_utils.py
@@ -10,7 +10,7 @@
@frappe.whitelist()
def make_stock_entry(**args):
- '''Helper function to make a Stock Entry
+ """Helper function to make a Stock Entry
:item_code: Item to be moved
:qty: Qty to be moved
@@ -25,16 +25,16 @@
:purpose: Optional
:do_not_save: Optional flag
:do_not_submit: Optional flag
- '''
+ """
def process_serial_numbers(serial_nos_list):
serial_nos_list = [
- '\n'.join(serial_num['serial_no'] for serial_num in serial_nos_list if serial_num.serial_no)
+ "\n".join(serial_num["serial_no"] for serial_num in serial_nos_list if serial_num.serial_no)
]
- uniques = list(set(serial_nos_list[0].split('\n')))
+ uniques = list(set(serial_nos_list[0].split("\n")))
- return '\n'.join(uniques)
+ return "\n".join(uniques)
s = frappe.new_doc("Stock Entry")
args = frappe._dict(args)
@@ -60,7 +60,7 @@
s.apply_putaway_rule = args.apply_putaway_rule
if isinstance(args.qty, str):
- if '.' in args.qty:
+ if "." in args.qty:
args.qty = flt(args.qty)
else:
args.qty = cint(args.qty)
@@ -79,16 +79,16 @@
# company
if not args.company:
if args.source:
- args.company = frappe.db.get_value('Warehouse', args.source, 'company')
+ args.company = frappe.db.get_value("Warehouse", args.source, "company")
elif args.target:
- args.company = frappe.db.get_value('Warehouse', args.target, 'company')
+ args.company = frappe.db.get_value("Warehouse", args.target, "company")
# set vales from test
if frappe.flags.in_test:
if not args.company:
- args.company = '_Test Company'
+ args.company = "_Test Company"
if not args.item:
- args.item = '_Test Item'
+ args.item = "_Test Item"
s.company = args.company or erpnext.get_default_company()
s.purchase_receipt_no = args.purchase_receipt_no
@@ -96,40 +96,40 @@
s.sales_invoice_no = args.sales_invoice_no
s.is_opening = args.is_opening or "No"
if not args.cost_center:
- args.cost_center = frappe.get_value('Company', s.company, 'cost_center')
+ args.cost_center = frappe.get_value("Company", s.company, "cost_center")
if not args.expense_account and s.is_opening == "No":
- args.expense_account = frappe.get_value('Company', s.company, 'stock_adjustment_account')
+ args.expense_account = frappe.get_value("Company", s.company, "stock_adjustment_account")
# We can find out the serial number using the batch source document
serial_number = args.serial_no
if not args.serial_no and args.qty and args.batch_no:
serial_number_list = frappe.get_list(
- doctype='Stock Ledger Entry',
- fields=['serial_no'],
- filters={
- 'batch_no': args.batch_no,
- 'warehouse': args.from_warehouse
- }
+ doctype="Stock Ledger Entry",
+ fields=["serial_no"],
+ filters={"batch_no": args.batch_no, "warehouse": args.from_warehouse},
)
serial_number = process_serial_numbers(serial_number_list)
args.serial_no = serial_number
- s.append("items", {
- "item_code": args.item,
- "s_warehouse": args.source,
- "t_warehouse": args.target,
- "qty": args.qty,
- "basic_rate": args.rate or args.basic_rate,
- "conversion_factor": args.conversion_factor or 1.0,
- "transfer_qty": flt(args.qty) * (flt(args.conversion_factor) or 1.0),
- "serial_no": args.serial_no,
- 'batch_no': args.batch_no,
- 'cost_center': args.cost_center,
- 'expense_account': args.expense_account
- })
+ s.append(
+ "items",
+ {
+ "item_code": args.item,
+ "s_warehouse": args.source,
+ "t_warehouse": args.target,
+ "qty": args.qty,
+ "basic_rate": args.rate or args.basic_rate,
+ "conversion_factor": args.conversion_factor or 1.0,
+ "transfer_qty": flt(args.qty) * (flt(args.conversion_factor) or 1.0),
+ "serial_no": args.serial_no,
+ "batch_no": args.batch_no,
+ "cost_center": args.cost_center,
+ "expense_account": args.expense_account,
+ },
+ )
s.set_stock_entry_type()
if not args.do_not_save:
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index 54c0e43..aeedcd1 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -38,9 +38,14 @@
condition += "`{0}`=%s".format(key)
values.append(value)
- return frappe.db.sql("""select * from `tabStock Ledger Entry` %s
- order by timestamp(posting_date, posting_time) desc, creation desc limit 1"""% condition,
- values, as_dict=1)
+ return frappe.db.sql(
+ """select * from `tabStock Ledger Entry` %s
+ order by timestamp(posting_date, posting_time) desc, creation desc limit 1"""
+ % condition,
+ values,
+ as_dict=1,
+ )
+
class TestStockEntry(FrappeTestCase):
def tearDown(self):
@@ -53,36 +58,37 @@
item_code = "_Test Item 2"
warehouse = "_Test Warehouse - _TC"
- create_stock_reconciliation(item_code="_Test Item 2", warehouse="_Test Warehouse - _TC",
- qty=0, rate=100)
+ create_stock_reconciliation(
+ item_code="_Test Item 2", warehouse="_Test Warehouse - _TC", qty=0, rate=100
+ )
make_stock_entry(item_code=item_code, target=warehouse, qty=1, basic_rate=10)
- sle = get_sle(item_code = item_code, warehouse = warehouse)[0]
+ sle = get_sle(item_code=item_code, warehouse=warehouse)[0]
self.assertEqual([[1, 10]], frappe.safe_eval(sle.stock_queue))
# negative qty
make_stock_entry(item_code=item_code, source=warehouse, qty=2, basic_rate=10)
- sle = get_sle(item_code = item_code, warehouse = warehouse)[0]
+ sle = get_sle(item_code=item_code, warehouse=warehouse)[0]
self.assertEqual([[-1, 10]], frappe.safe_eval(sle.stock_queue))
# further negative
make_stock_entry(item_code=item_code, source=warehouse, qty=1)
- sle = get_sle(item_code = item_code, warehouse = warehouse)[0]
+ sle = get_sle(item_code=item_code, warehouse=warehouse)[0]
self.assertEqual([[-2, 10]], frappe.safe_eval(sle.stock_queue))
# move stock to positive
make_stock_entry(item_code=item_code, target=warehouse, qty=3, basic_rate=20)
- sle = get_sle(item_code = item_code, warehouse = warehouse)[0]
+ sle = get_sle(item_code=item_code, warehouse=warehouse)[0]
self.assertEqual([[1, 20]], frappe.safe_eval(sle.stock_queue))
# incoming entry with diff rate
make_stock_entry(item_code=item_code, target=warehouse, qty=1, basic_rate=30)
- sle = get_sle(item_code = item_code, warehouse = warehouse)[0]
+ sle = get_sle(item_code=item_code, warehouse=warehouse)[0]
- self.assertEqual([[1, 20],[1, 30]], frappe.safe_eval(sle.stock_queue))
+ self.assertEqual([[1, 20], [1, 30]], frappe.safe_eval(sle.stock_queue))
frappe.db.set_default("allow_negative_stock", 0)
@@ -92,37 +98,48 @@
self._test_auto_material_request("_Test Item", material_request_type="Transfer")
def test_auto_material_request_for_variant(self):
- fields = [{'field_name': 'reorder_levels'}]
+ fields = [{"field_name": "reorder_levels"}]
set_item_variant_settings(fields)
make_item_variant()
template = frappe.get_doc("Item", "_Test Variant Item")
if not template.reorder_levels:
- template.append('reorder_levels', {
- "material_request_type": "Purchase",
- "warehouse": "_Test Warehouse - _TC",
- "warehouse_reorder_level": 20,
- "warehouse_reorder_qty": 20
- })
+ template.append(
+ "reorder_levels",
+ {
+ "material_request_type": "Purchase",
+ "warehouse": "_Test Warehouse - _TC",
+ "warehouse_reorder_level": 20,
+ "warehouse_reorder_qty": 20,
+ },
+ )
template.save()
self._test_auto_material_request("_Test Variant Item-S")
def test_auto_material_request_for_warehouse_group(self):
- self._test_auto_material_request("_Test Item Warehouse Group Wise Reorder", warehouse="_Test Warehouse Group-C1 - _TC")
+ self._test_auto_material_request(
+ "_Test Item Warehouse Group Wise Reorder", warehouse="_Test Warehouse Group-C1 - _TC"
+ )
- def _test_auto_material_request(self, item_code, material_request_type="Purchase", warehouse="_Test Warehouse - _TC"):
+ def _test_auto_material_request(
+ self, item_code, material_request_type="Purchase", warehouse="_Test Warehouse - _TC"
+ ):
variant = frappe.get_doc("Item", item_code)
- projected_qty, actual_qty = frappe.db.get_value("Bin", {"item_code": item_code,
- "warehouse": warehouse}, ["projected_qty", "actual_qty"]) or [0, 0]
+ projected_qty, actual_qty = frappe.db.get_value(
+ "Bin", {"item_code": item_code, "warehouse": warehouse}, ["projected_qty", "actual_qty"]
+ ) or [0, 0]
# stock entry reqd for auto-reorder
- create_stock_reconciliation(item_code=item_code, warehouse=warehouse,
- qty = actual_qty + abs(projected_qty) + 10, rate=100)
+ create_stock_reconciliation(
+ item_code=item_code, warehouse=warehouse, qty=actual_qty + abs(projected_qty) + 10, rate=100
+ )
- projected_qty = frappe.db.get_value("Bin", {"item_code": item_code,
- "warehouse": warehouse}, "projected_qty") or 0
+ projected_qty = (
+ frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "projected_qty")
+ or 0
+ )
frappe.db.set_value("Stock Settings", None, "auto_indent", 1)
@@ -133,6 +150,7 @@
variant.save()
from erpnext.stock.reorder_item import reorder_item
+
mr_list = reorder_item()
frappe.db.set_value("Stock Settings", None, "auto_indent", 0)
@@ -145,65 +163,113 @@
self.assertTrue(item_code in items)
def test_material_receipt_gl_entry(self):
- company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
+ company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
- mr = make_stock_entry(item_code="_Test Item", target="Stores - TCP1", company= company,
- qty=50, basic_rate=100, expense_account="Stock Adjustment - TCP1")
+ mr = make_stock_entry(
+ item_code="_Test Item",
+ target="Stores - TCP1",
+ company=company,
+ qty=50,
+ basic_rate=100,
+ expense_account="Stock Adjustment - TCP1",
+ )
stock_in_hand_account = get_inventory_account(mr.company, mr.get("items")[0].t_warehouse)
- self.check_stock_ledger_entries("Stock Entry", mr.name,
- [["_Test Item", "Stores - TCP1", 50.0]])
+ self.check_stock_ledger_entries("Stock Entry", mr.name, [["_Test Item", "Stores - TCP1", 50.0]])
- self.check_gl_entries("Stock Entry", mr.name,
- sorted([
- [stock_in_hand_account, 5000.0, 0.0],
- ["Stock Adjustment - TCP1", 0.0, 5000.0]
- ])
+ self.check_gl_entries(
+ "Stock Entry",
+ mr.name,
+ sorted([[stock_in_hand_account, 5000.0, 0.0], ["Stock Adjustment - TCP1", 0.0, 5000.0]]),
)
mr.cancel()
- self.assertTrue(frappe.db.sql("""select * from `tabStock Ledger Entry`
- where voucher_type='Stock Entry' and voucher_no=%s""", mr.name))
+ self.assertTrue(
+ frappe.db.sql(
+ """select * from `tabStock Ledger Entry`
+ where voucher_type='Stock Entry' and voucher_no=%s""",
+ mr.name,
+ )
+ )
- self.assertTrue(frappe.db.sql("""select * from `tabGL Entry`
- where voucher_type='Stock Entry' and voucher_no=%s""", mr.name))
+ self.assertTrue(
+ frappe.db.sql(
+ """select * from `tabGL Entry`
+ where voucher_type='Stock Entry' and voucher_no=%s""",
+ mr.name,
+ )
+ )
def test_material_issue_gl_entry(self):
- company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
- make_stock_entry(item_code="_Test Item", target="Stores - TCP1", company= company,
- qty=50, basic_rate=100, expense_account="Stock Adjustment - TCP1")
+ company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
+ make_stock_entry(
+ item_code="_Test Item",
+ target="Stores - TCP1",
+ company=company,
+ qty=50,
+ basic_rate=100,
+ expense_account="Stock Adjustment - TCP1",
+ )
- mi = make_stock_entry(item_code="_Test Item", source="Stores - TCP1", company=company,
- qty=40, expense_account="Stock Adjustment - TCP1")
+ mi = make_stock_entry(
+ item_code="_Test Item",
+ source="Stores - TCP1",
+ company=company,
+ qty=40,
+ expense_account="Stock Adjustment - TCP1",
+ )
- self.check_stock_ledger_entries("Stock Entry", mi.name,
- [["_Test Item", "Stores - TCP1", -40.0]])
+ self.check_stock_ledger_entries("Stock Entry", mi.name, [["_Test Item", "Stores - TCP1", -40.0]])
stock_in_hand_account = get_inventory_account(mi.company, "Stores - TCP1")
- stock_value_diff = abs(frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Stock Entry",
- "voucher_no": mi.name}, "stock_value_difference"))
+ stock_value_diff = abs(
+ frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"voucher_type": "Stock Entry", "voucher_no": mi.name},
+ "stock_value_difference",
+ )
+ )
- self.check_gl_entries("Stock Entry", mi.name,
- sorted([
- [stock_in_hand_account, 0.0, stock_value_diff],
- ["Stock Adjustment - TCP1", stock_value_diff, 0.0]
- ])
+ self.check_gl_entries(
+ "Stock Entry",
+ mi.name,
+ sorted(
+ [
+ [stock_in_hand_account, 0.0, stock_value_diff],
+ ["Stock Adjustment - TCP1", stock_value_diff, 0.0],
+ ]
+ ),
)
mi.cancel()
def test_material_transfer_gl_entry(self):
- company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
+ company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
- item_code = 'Hand Sanitizer - 001'
- create_item(item_code =item_code, is_stock_item = 1,
- is_purchase_item=1, opening_stock=1000, valuation_rate=10, company=company, warehouse="Stores - TCP1")
+ item_code = "Hand Sanitizer - 001"
+ create_item(
+ item_code=item_code,
+ is_stock_item=1,
+ is_purchase_item=1,
+ opening_stock=1000,
+ valuation_rate=10,
+ company=company,
+ warehouse="Stores - TCP1",
+ )
- mtn = make_stock_entry(item_code=item_code, source="Stores - TCP1",
- target="Finished Goods - TCP1", qty=45, company=company)
+ mtn = make_stock_entry(
+ item_code=item_code,
+ source="Stores - TCP1",
+ target="Finished Goods - TCP1",
+ qty=45,
+ company=company,
+ )
- self.check_stock_ledger_entries("Stock Entry", mtn.name,
- [[item_code, "Stores - TCP1", -45.0], [item_code, "Finished Goods - TCP1", 45.0]])
+ self.check_stock_ledger_entries(
+ "Stock Entry",
+ mtn.name,
+ [[item_code, "Stores - TCP1", -45.0], [item_code, "Finished Goods - TCP1", 45.0]],
+ )
source_warehouse_account = get_inventory_account(mtn.company, mtn.get("items")[0].s_warehouse)
@@ -211,18 +277,33 @@
if source_warehouse_account == target_warehouse_account:
# no gl entry as both source and target warehouse has linked to same account.
- self.assertFalse(frappe.db.sql("""select * from `tabGL Entry`
- where voucher_type='Stock Entry' and voucher_no=%s""", mtn.name, as_dict=1))
+ self.assertFalse(
+ frappe.db.sql(
+ """select * from `tabGL Entry`
+ where voucher_type='Stock Entry' and voucher_no=%s""",
+ mtn.name,
+ as_dict=1,
+ )
+ )
else:
- stock_value_diff = abs(frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Stock Entry",
- "voucher_no": mtn.name, "warehouse": "Stores - TCP1"}, "stock_value_difference"))
+ stock_value_diff = abs(
+ frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"voucher_type": "Stock Entry", "voucher_no": mtn.name, "warehouse": "Stores - TCP1"},
+ "stock_value_difference",
+ )
+ )
- self.check_gl_entries("Stock Entry", mtn.name,
- sorted([
- [source_warehouse_account, 0.0, stock_value_diff],
- [target_warehouse_account, stock_value_diff, 0.0],
- ])
+ self.check_gl_entries(
+ "Stock Entry",
+ mtn.name,
+ sorted(
+ [
+ [source_warehouse_account, 0.0, stock_value_diff],
+ [target_warehouse_account, stock_value_diff, 0.0],
+ ]
+ ),
)
mtn.cancel()
@@ -239,20 +320,23 @@
repack.items[0].transfer_qty = 100.0
repack.items[1].qty = 50.0
- repack.append("items", {
- "conversion_factor": 1.0,
- "cost_center": "_Test Cost Center - _TC",
- "doctype": "Stock Entry Detail",
- "expense_account": "Stock Adjustment - _TC",
- "basic_rate": 150,
- "item_code": "_Test Item 2",
- "parentfield": "items",
- "qty": 50.0,
- "stock_uom": "_Test UOM",
- "t_warehouse": "_Test Warehouse - _TC",
- "transfer_qty": 50.0,
- "uom": "_Test UOM"
- })
+ repack.append(
+ "items",
+ {
+ "conversion_factor": 1.0,
+ "cost_center": "_Test Cost Center - _TC",
+ "doctype": "Stock Entry Detail",
+ "expense_account": "Stock Adjustment - _TC",
+ "basic_rate": 150,
+ "item_code": "_Test Item 2",
+ "parentfield": "items",
+ "qty": 50.0,
+ "stock_uom": "_Test UOM",
+ "t_warehouse": "_Test Warehouse - _TC",
+ "transfer_qty": 50.0,
+ "uom": "_Test UOM",
+ },
+ )
repack.set_stock_entry_type()
repack.insert()
@@ -265,12 +349,13 @@
# must raise error if 0 fg in repack entry
self.assertRaises(FinishedGoodError, repack.validate_finished_goods)
- repack.delete() # teardown
+ repack.delete() # teardown
def test_repack_no_change_in_valuation(self):
make_stock_entry(item_code="_Test Item", target="_Test Warehouse - _TC", qty=50, basic_rate=100)
- make_stock_entry(item_code="_Test Item Home Desktop 100", target="_Test Warehouse - _TC",
- qty=50, basic_rate=100)
+ make_stock_entry(
+ item_code="_Test Item Home Desktop 100", target="_Test Warehouse - _TC", qty=50, basic_rate=100
+ )
repack = frappe.copy_doc(test_records[3])
repack.posting_date = nowdate()
@@ -279,76 +364,113 @@
repack.insert()
repack.submit()
- self.check_stock_ledger_entries("Stock Entry", repack.name,
- [["_Test Item", "_Test Warehouse - _TC", -50.0],
- ["_Test Item Home Desktop 100", "_Test Warehouse - _TC", 1]])
+ self.check_stock_ledger_entries(
+ "Stock Entry",
+ repack.name,
+ [
+ ["_Test Item", "_Test Warehouse - _TC", -50.0],
+ ["_Test Item Home Desktop 100", "_Test Warehouse - _TC", 1],
+ ],
+ )
- gl_entries = frappe.db.sql("""select account, debit, credit
+ gl_entries = frappe.db.sql(
+ """select account, debit, credit
from `tabGL Entry` where voucher_type='Stock Entry' and voucher_no=%s
- order by account desc""", repack.name, as_dict=1)
+ order by account desc""",
+ repack.name,
+ as_dict=1,
+ )
self.assertFalse(gl_entries)
def test_repack_with_additional_costs(self):
- company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
+ company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
- make_stock_entry(item_code="_Test Item", target="Stores - TCP1", company= company,
- qty=50, basic_rate=100, expense_account="Stock Adjustment - TCP1")
+ make_stock_entry(
+ item_code="_Test Item",
+ target="Stores - TCP1",
+ company=company,
+ qty=50,
+ basic_rate=100,
+ expense_account="Stock Adjustment - TCP1",
+ )
-
- repack = make_stock_entry(company = company, purpose="Repack", do_not_save=True)
+ repack = make_stock_entry(company=company, purpose="Repack", do_not_save=True)
repack.posting_date = nowdate()
repack.posting_time = nowtime()
- expenses_included_in_valuation = frappe.get_value("Company", company, "expenses_included_in_valuation")
+ expenses_included_in_valuation = frappe.get_value(
+ "Company", company, "expenses_included_in_valuation"
+ )
items = get_multiple_items()
repack.items = []
for item in items:
repack.append("items", item)
- repack.set("additional_costs", [
- {
- "expense_account": expenses_included_in_valuation,
- "description": "Actual Operating Cost",
- "amount": 1000
- },
- {
- "expense_account": expenses_included_in_valuation,
- "description": "Additional Operating Cost",
- "amount": 200
- },
- ])
+ repack.set(
+ "additional_costs",
+ [
+ {
+ "expense_account": expenses_included_in_valuation,
+ "description": "Actual Operating Cost",
+ "amount": 1000,
+ },
+ {
+ "expense_account": expenses_included_in_valuation,
+ "description": "Additional Operating Cost",
+ "amount": 200,
+ },
+ ],
+ )
repack.set_stock_entry_type()
repack.insert()
repack.submit()
stock_in_hand_account = get_inventory_account(repack.company, repack.get("items")[1].t_warehouse)
- rm_stock_value_diff = abs(frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Stock Entry",
- "voucher_no": repack.name, "item_code": "_Test Item"}, "stock_value_difference"))
+ rm_stock_value_diff = abs(
+ frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"voucher_type": "Stock Entry", "voucher_no": repack.name, "item_code": "_Test Item"},
+ "stock_value_difference",
+ )
+ )
- fg_stock_value_diff = abs(frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Stock Entry",
- "voucher_no": repack.name, "item_code": "_Test Item Home Desktop 100"}, "stock_value_difference"))
+ fg_stock_value_diff = abs(
+ frappe.db.get_value(
+ "Stock Ledger Entry",
+ {
+ "voucher_type": "Stock Entry",
+ "voucher_no": repack.name,
+ "item_code": "_Test Item Home Desktop 100",
+ },
+ "stock_value_difference",
+ )
+ )
stock_value_diff = flt(fg_stock_value_diff - rm_stock_value_diff, 2)
self.assertEqual(stock_value_diff, 1200)
- self.check_gl_entries("Stock Entry", repack.name,
- sorted([
- [stock_in_hand_account, 1200, 0.0],
- ["Expenses Included In Valuation - TCP1", 0.0, 1200.0]
- ])
+ self.check_gl_entries(
+ "Stock Entry",
+ repack.name,
+ sorted(
+ [[stock_in_hand_account, 1200, 0.0], ["Expenses Included In Valuation - TCP1", 0.0, 1200.0]]
+ ),
)
def check_stock_ledger_entries(self, voucher_type, voucher_no, expected_sle):
expected_sle.sort(key=lambda x: x[1])
# check stock ledger entries
- sle = frappe.db.sql("""select item_code, warehouse, actual_qty
+ sle = frappe.db.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)
+ (voucher_type, voucher_no),
+ as_list=1,
+ )
self.assertTrue(sle)
sle.sort(key=lambda x: x[1])
@@ -360,9 +482,13 @@
def check_gl_entries(self, voucher_type, voucher_no, expected_gl_entries):
expected_gl_entries.sort(key=lambda x: x[0])
- gl_entries = frappe.db.sql("""select account, debit, credit
+ gl_entries = frappe.db.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_list=1)
+ order by account asc, debit asc""",
+ (voucher_type, voucher_no),
+ as_list=1,
+ )
self.assertTrue(gl_entries)
gl_entries.sort(key=lambda x: x[0])
@@ -463,7 +589,7 @@
def test_serial_item_error(self):
se, serial_nos = self.test_serial_by_series()
- if not frappe.db.exists('Serial No', 'ABCD'):
+ if not frappe.db.exists("Serial No", "ABCD"):
make_serialized_item(item_code="_Test Serialized Item", serial_no="ABCD\nEFGH")
se = frappe.copy_doc(test_records[0])
@@ -493,10 +619,14 @@
se.set_stock_entry_type()
se.insert()
se.submit()
- self.assertTrue(frappe.db.get_value("Serial No", serial_no, "warehouse"), "_Test Warehouse 1 - _TC")
+ self.assertTrue(
+ frappe.db.get_value("Serial No", serial_no, "warehouse"), "_Test Warehouse 1 - _TC"
+ )
se.cancel()
- self.assertTrue(frappe.db.get_value("Serial No", serial_no, "warehouse"), "_Test Warehouse - _TC")
+ self.assertTrue(
+ frappe.db.get_value("Serial No", serial_no, "warehouse"), "_Test Warehouse - _TC"
+ )
def test_serial_warehouse_error(self):
make_serialized_item(target_warehouse="_Test Warehouse 1 - _TC")
@@ -524,14 +654,16 @@
self.assertFalse(frappe.db.get_value("Serial No", serial_no, "warehouse"))
def test_warehouse_company_validation(self):
- company = frappe.db.get_value('Warehouse', '_Test Warehouse 2 - _TC1', 'company')
- frappe.get_doc("User", "test2@example.com")\
- .add_roles("Sales User", "Sales Manager", "Stock User", "Stock Manager")
+ company = frappe.db.get_value("Warehouse", "_Test Warehouse 2 - _TC1", "company")
+ frappe.get_doc("User", "test2@example.com").add_roles(
+ "Sales User", "Sales Manager", "Stock User", "Stock Manager"
+ )
frappe.set_user("test2@example.com")
from erpnext.stock.utils import InvalidWarehouseCompany
+
st1 = frappe.copy_doc(test_records[0])
- st1.get("items")[0].t_warehouse="_Test Warehouse 2 - _TC1"
+ st1.get("items")[0].t_warehouse = "_Test Warehouse 2 - _TC1"
st1.set_stock_entry_type()
st1.insert()
self.assertRaises(InvalidWarehouseCompany, st1.submit)
@@ -545,14 +677,15 @@
test_user.add_roles("Sales User", "Sales Manager", "Stock User")
test_user.remove_roles("Stock Manager", "System Manager")
- frappe.get_doc("User", "test2@example.com")\
- .add_roles("Sales User", "Sales Manager", "Stock User", "Stock Manager")
+ frappe.get_doc("User", "test2@example.com").add_roles(
+ "Sales User", "Sales Manager", "Stock User", "Stock Manager"
+ )
st1 = frappe.copy_doc(test_records[0])
st1.company = "_Test Company 1"
frappe.set_user("test@example.com")
- st1.get("items")[0].t_warehouse="_Test Warehouse 2 - _TC1"
+ st1.get("items")[0].t_warehouse = "_Test Warehouse 2 - _TC1"
self.assertRaises(frappe.PermissionError, st1.insert)
test_user.add_roles("System Manager")
@@ -560,7 +693,7 @@
frappe.set_user("test2@example.com")
st1 = frappe.copy_doc(test_records[0])
st1.company = "_Test Company 1"
- st1.get("items")[0].t_warehouse="_Test Warehouse 2 - _TC1"
+ st1.get("items")[0].t_warehouse = "_Test Warehouse 2 - _TC1"
st1.get("items")[0].expense_account = "Stock Adjustment - _TC1"
st1.get("items")[0].cost_center = "Main - _TC1"
st1.set_stock_entry_type()
@@ -574,14 +707,14 @@
remove_user_permission("Company", "_Test Company 1", "test2@example.com")
def test_freeze_stocks(self):
- frappe.db.set_value('Stock Settings', None,'stock_auth_role', '')
+ frappe.db.set_value("Stock Settings", None, "stock_auth_role", "")
# test freeze_stocks_upto
frappe.db.set_value("Stock Settings", None, "stock_frozen_upto", add_days(nowdate(), 5))
se = frappe.copy_doc(test_records[0]).insert()
self.assertRaises(StockFreezeError, se.submit)
- frappe.db.set_value("Stock Settings", None, "stock_frozen_upto", '')
+ frappe.db.set_value("Stock Settings", None, "stock_frozen_upto", "")
# test freeze_stocks_upto_days
frappe.db.set_value("Stock Settings", None, "stock_frozen_upto_days", -1)
@@ -597,20 +730,24 @@
from erpnext.manufacturing.doctype.work_order.work_order import (
make_stock_entry as _make_stock_entry,
)
- bom_no, bom_operation_cost = frappe.db.get_value("BOM", {"item": "_Test FG Item 2",
- "is_default": 1, "docstatus": 1}, ["name", "operating_cost"])
+
+ bom_no, bom_operation_cost = frappe.db.get_value(
+ "BOM", {"item": "_Test FG Item 2", "is_default": 1, "docstatus": 1}, ["name", "operating_cost"]
+ )
work_order = frappe.new_doc("Work Order")
- work_order.update({
- "company": "_Test Company",
- "fg_warehouse": "_Test Warehouse 1 - _TC",
- "production_item": "_Test FG Item 2",
- "bom_no": bom_no,
- "qty": 1.0,
- "stock_uom": "_Test UOM",
- "wip_warehouse": "_Test Warehouse - _TC",
- "additional_operating_cost": 1000
- })
+ work_order.update(
+ {
+ "company": "_Test Company",
+ "fg_warehouse": "_Test Warehouse 1 - _TC",
+ "production_item": "_Test FG Item 2",
+ "bom_no": bom_no,
+ "qty": 1.0,
+ "stock_uom": "_Test UOM",
+ "wip_warehouse": "_Test Warehouse - _TC",
+ "additional_operating_cost": 1000,
+ }
+ )
work_order.insert()
work_order.submit()
@@ -623,37 +760,41 @@
for d in stock_entry.get("items"):
if d.item_code != "_Test FG Item 2":
rm_cost += flt(d.amount)
- fg_cost = list(filter(lambda x: x.item_code=="_Test FG Item 2", stock_entry.get("items")))[0].amount
- self.assertEqual(fg_cost,
- flt(rm_cost + bom_operation_cost + work_order.additional_operating_cost, 2))
+ fg_cost = list(filter(lambda x: x.item_code == "_Test FG Item 2", stock_entry.get("items")))[
+ 0
+ ].amount
+ self.assertEqual(
+ fg_cost, flt(rm_cost + bom_operation_cost + work_order.additional_operating_cost, 2)
+ )
def test_work_order_manufacture_with_material_consumption(self):
from erpnext.manufacturing.doctype.work_order.work_order import (
make_stock_entry as _make_stock_entry,
)
+
frappe.db.set_value("Manufacturing Settings", None, "material_consumption", "1")
- bom_no = frappe.db.get_value("BOM", {"item": "_Test FG Item",
- "is_default": 1, "docstatus": 1})
+ bom_no = frappe.db.get_value("BOM", {"item": "_Test FG Item", "is_default": 1, "docstatus": 1})
work_order = frappe.new_doc("Work Order")
- work_order.update({
- "company": "_Test Company",
- "fg_warehouse": "_Test Warehouse 1 - _TC",
- "production_item": "_Test FG Item",
- "bom_no": bom_no,
- "qty": 1.0,
- "stock_uom": "_Test UOM",
- "wip_warehouse": "_Test Warehouse - _TC"
- })
+ work_order.update(
+ {
+ "company": "_Test Company",
+ "fg_warehouse": "_Test Warehouse 1 - _TC",
+ "production_item": "_Test FG Item",
+ "bom_no": bom_no,
+ "qty": 1.0,
+ "stock_uom": "_Test UOM",
+ "wip_warehouse": "_Test Warehouse - _TC",
+ }
+ )
work_order.insert()
work_order.submit()
- make_stock_entry(item_code="_Test Item",
- target="Stores - _TC", qty=10, basic_rate=5000.0)
- make_stock_entry(item_code="_Test Item Home Desktop 100",
- target="Stores - _TC", qty=10, basic_rate=1000.0)
-
+ make_stock_entry(item_code="_Test Item", target="Stores - _TC", qty=10, basic_rate=5000.0)
+ make_stock_entry(
+ item_code="_Test Item Home Desktop 100", target="Stores - _TC", qty=10, basic_rate=1000.0
+ )
s = frappe.get_doc(_make_stock_entry(work_order.name, "Material Transfer for Manufacture", 1))
for d in s.get("items"):
@@ -665,13 +806,12 @@
s = frappe.get_doc(_make_stock_entry(work_order.name, "Manufacture", 1))
s.save()
rm_cost = 0
- for d in s.get('items'):
+ for d in s.get("items"):
if d.s_warehouse:
rm_cost += d.amount
- fg_cost = list(filter(lambda x: x.item_code=="_Test FG Item", s.get("items")))[0].amount
+ fg_cost = list(filter(lambda x: x.item_code == "_Test FG Item", s.get("items")))[0].amount
scrap_cost = list(filter(lambda x: x.is_scrap_item, s.get("items")))[0].amount
- self.assertEqual(fg_cost,
- flt(rm_cost - scrap_cost, 2))
+ self.assertEqual(fg_cost, flt(rm_cost - scrap_cost, 2))
# When Stock Entry has only FG + Scrap
s.items.pop(0)
@@ -679,31 +819,34 @@
s.submit()
rm_cost = 0
- for d in s.get('items'):
+ for d in s.get("items"):
if d.s_warehouse:
rm_cost += d.amount
self.assertEqual(rm_cost, 0)
expected_fg_cost = s.get_basic_rate_for_manufactured_item(1)
- fg_cost = list(filter(lambda x: x.item_code=="_Test FG Item", s.get("items")))[0].amount
+ fg_cost = list(filter(lambda x: x.item_code == "_Test FG Item", s.get("items")))[0].amount
self.assertEqual(flt(fg_cost, 2), flt(expected_fg_cost, 2))
def test_variant_work_order(self):
- bom_no = frappe.db.get_value("BOM", {"item": "_Test Variant Item",
- "is_default": 1, "docstatus": 1})
+ bom_no = frappe.db.get_value(
+ "BOM", {"item": "_Test Variant Item", "is_default": 1, "docstatus": 1}
+ )
- make_item_variant() # make variant of _Test Variant Item if absent
+ make_item_variant() # make variant of _Test Variant Item if absent
work_order = frappe.new_doc("Work Order")
- work_order.update({
- "company": "_Test Company",
- "fg_warehouse": "_Test Warehouse 1 - _TC",
- "production_item": "_Test Variant Item-S",
- "bom_no": bom_no,
- "qty": 1.0,
- "stock_uom": "_Test UOM",
- "wip_warehouse": "_Test Warehouse - _TC",
- "skip_transfer": 1
- })
+ work_order.update(
+ {
+ "company": "_Test Company",
+ "fg_warehouse": "_Test Warehouse 1 - _TC",
+ "production_item": "_Test Variant Item-S",
+ "bom_no": bom_no,
+ "qty": 1.0,
+ "stock_uom": "_Test UOM",
+ "wip_warehouse": "_Test Warehouse - _TC",
+ "skip_transfer": 1,
+ }
+ )
work_order.insert()
work_order.submit()
@@ -717,19 +860,29 @@
s1 = make_serialized_item(target_warehouse="_Test Warehouse - _TC")
serial_nos = s1.get("items")[0].serial_no
- s2 = make_stock_entry(item_code="_Test Serialized Item With Series", source="_Test Warehouse - _TC",
- qty=2, basic_rate=100, purpose="Repack", serial_no=serial_nos, do_not_save=True)
+ s2 = make_stock_entry(
+ item_code="_Test Serialized Item With Series",
+ source="_Test Warehouse - _TC",
+ qty=2,
+ basic_rate=100,
+ purpose="Repack",
+ serial_no=serial_nos,
+ do_not_save=True,
+ )
- s2.append("items", {
- "item_code": "_Test Serialized Item",
- "t_warehouse": "_Test Warehouse - _TC",
- "qty": 2,
- "basic_rate": 120,
- "expense_account": "Stock Adjustment - _TC",
- "conversion_factor": 1.0,
- "cost_center": "_Test Cost Center - _TC",
- "serial_no": serial_nos
- })
+ s2.append(
+ "items",
+ {
+ "item_code": "_Test Serialized Item",
+ "t_warehouse": "_Test Warehouse - _TC",
+ "qty": 2,
+ "basic_rate": 120,
+ "expense_account": "Stock Adjustment - _TC",
+ "conversion_factor": 1.0,
+ "cost_center": "_Test Cost Center - _TC",
+ "serial_no": serial_nos,
+ },
+ )
s2.submit()
s2.cancel()
@@ -739,10 +892,15 @@
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
create_warehouse("Test Warehouse for Sample Retention")
- frappe.db.set_value("Stock Settings", None, "sample_retention_warehouse", "Test Warehouse for Sample Retention - _TC")
+ frappe.db.set_value(
+ "Stock Settings",
+ None,
+ "sample_retention_warehouse",
+ "Test Warehouse for Sample Retention - _TC",
+ )
test_item_code = "Retain Sample Item"
- if not frappe.db.exists('Item', test_item_code):
+ if not frappe.db.exists("Item", test_item_code):
item = frappe.new_doc("Item")
item.item_code = test_item_code
item.item_name = "Retain Sample Item"
@@ -758,44 +916,58 @@
receipt_entry = frappe.new_doc("Stock Entry")
receipt_entry.company = "_Test Company"
receipt_entry.purpose = "Material Receipt"
- receipt_entry.append("items", {
- "item_code": test_item_code,
- "t_warehouse": "_Test Warehouse - _TC",
- "qty": 40,
- "basic_rate": 12,
- "cost_center": "_Test Cost Center - _TC",
- "sample_quantity": 4
- })
+ receipt_entry.append(
+ "items",
+ {
+ "item_code": test_item_code,
+ "t_warehouse": "_Test Warehouse - _TC",
+ "qty": 40,
+ "basic_rate": 12,
+ "cost_center": "_Test Cost Center - _TC",
+ "sample_quantity": 4,
+ },
+ )
receipt_entry.set_stock_entry_type()
receipt_entry.insert()
receipt_entry.submit()
- retention_data = move_sample_to_retention_warehouse(receipt_entry.company, receipt_entry.get("items"))
+ retention_data = move_sample_to_retention_warehouse(
+ receipt_entry.company, receipt_entry.get("items")
+ )
retention_entry = frappe.new_doc("Stock Entry")
retention_entry.company = retention_data.company
retention_entry.purpose = retention_data.purpose
- retention_entry.append("items", {
- "item_code": test_item_code,
- "t_warehouse": "Test Warehouse for Sample Retention - _TC",
- "s_warehouse": "_Test Warehouse - _TC",
- "qty": 4,
- "basic_rate": 12,
- "cost_center": "_Test Cost Center - _TC",
- "batch_no": receipt_entry.get("items")[0].batch_no
- })
+ retention_entry.append(
+ "items",
+ {
+ "item_code": test_item_code,
+ "t_warehouse": "Test Warehouse for Sample Retention - _TC",
+ "s_warehouse": "_Test Warehouse - _TC",
+ "qty": 4,
+ "basic_rate": 12,
+ "cost_center": "_Test Cost Center - _TC",
+ "batch_no": receipt_entry.get("items")[0].batch_no,
+ },
+ )
retention_entry.set_stock_entry_type()
retention_entry.insert()
retention_entry.submit()
- qty_in_usable_warehouse = get_batch_qty(receipt_entry.get("items")[0].batch_no, "_Test Warehouse - _TC", "_Test Item")
- qty_in_retention_warehouse = get_batch_qty(receipt_entry.get("items")[0].batch_no, "Test Warehouse for Sample Retention - _TC", "_Test Item")
+ qty_in_usable_warehouse = get_batch_qty(
+ receipt_entry.get("items")[0].batch_no, "_Test Warehouse - _TC", "_Test Item"
+ )
+ qty_in_retention_warehouse = get_batch_qty(
+ receipt_entry.get("items")[0].batch_no,
+ "Test Warehouse for Sample Retention - _TC",
+ "_Test Item",
+ )
self.assertEqual(qty_in_usable_warehouse, 36)
self.assertEqual(qty_in_retention_warehouse, 4)
def test_quality_check(self):
item_code = "_Test Item For QC"
- if not frappe.db.exists('Item', item_code):
+ if not frappe.db.exists("Item", item_code):
create_item(item_code)
repack = frappe.copy_doc(test_records[3])
@@ -849,44 +1021,63 @@
# self.assertEqual(item_quantity.get(d.item_code), d.qty)
def test_customer_provided_parts_se(self):
- create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
- se = make_stock_entry(item_code='CUST-0987', purpose = 'Material Receipt',
- qty=4, to_warehouse = "_Test Warehouse - _TC")
+ create_item(
+ "CUST-0987", is_customer_provided_item=1, customer="_Test Customer", is_purchase_item=0
+ )
+ se = make_stock_entry(
+ item_code="CUST-0987", purpose="Material Receipt", qty=4, to_warehouse="_Test Warehouse - _TC"
+ )
self.assertEqual(se.get("items")[0].allow_zero_valuation_rate, 1)
self.assertEqual(se.get("items")[0].amount, 0)
def test_zero_incoming_rate(self):
- """ Make sure incoming rate of 0 is allowed while consuming.
+ """Make sure incoming rate of 0 is allowed while consuming.
- qty | rate | valuation rate
- 1 | 100 | 100
- 1 | 0 | 50
- -1 | 100 | 0
- -1 | 0 <--- assert this
+ qty | rate | valuation rate
+ 1 | 100 | 100
+ 1 | 0 | 50
+ -1 | 100 | 0
+ -1 | 0 <--- assert this
"""
item_code = "_TestZeroVal"
warehouse = "_Test Warehouse - _TC"
- create_item('_TestZeroVal')
+ create_item("_TestZeroVal")
_receipt = make_stock_entry(item_code=item_code, qty=1, to_warehouse=warehouse, rate=100)
- receipt2 = make_stock_entry(item_code=item_code, qty=1, to_warehouse=warehouse, rate=0, do_not_save=True)
+ receipt2 = make_stock_entry(
+ item_code=item_code, qty=1, to_warehouse=warehouse, rate=0, do_not_save=True
+ )
receipt2.items[0].allow_zero_valuation_rate = 1
receipt2.save()
receipt2.submit()
issue = make_stock_entry(item_code=item_code, qty=1, from_warehouse=warehouse)
- value_diff = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": issue.name, "voucher_type": "Stock Entry"}, "stock_value_difference")
+ value_diff = frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"voucher_no": issue.name, "voucher_type": "Stock Entry"},
+ "stock_value_difference",
+ )
self.assertEqual(value_diff, -100)
issue2 = make_stock_entry(item_code=item_code, qty=1, from_warehouse=warehouse)
- value_diff = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": issue2.name, "voucher_type": "Stock Entry"}, "stock_value_difference")
+ value_diff = frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"voucher_no": issue2.name, "voucher_type": "Stock Entry"},
+ "stock_value_difference",
+ )
self.assertEqual(value_diff, 0)
-
def test_gle_for_opening_stock_entry(self):
- mr = make_stock_entry(item_code="_Test Item", target="Stores - TCP1",
- company="_Test Company with perpetual inventory", qty=50, basic_rate=100,
- expense_account="Stock Adjustment - TCP1", is_opening="Yes", do_not_save=True)
+ mr = make_stock_entry(
+ item_code="_Test Item",
+ target="Stores - TCP1",
+ company="_Test Company with perpetual inventory",
+ qty=50,
+ basic_rate=100,
+ expense_account="Stock Adjustment - TCP1",
+ is_opening="Yes",
+ do_not_save=True,
+ )
self.assertRaises(OpeningEntryAccountError, mr.save)
@@ -895,52 +1086,61 @@
mr.save()
mr.submit()
- is_opening = frappe.db.get_value("GL Entry",
- filters={"voucher_type": "Stock Entry", "voucher_no": mr.name}, fieldname="is_opening")
+ is_opening = frappe.db.get_value(
+ "GL Entry",
+ filters={"voucher_type": "Stock Entry", "voucher_no": mr.name},
+ fieldname="is_opening",
+ )
self.assertEqual(is_opening, "Yes")
def test_total_basic_amount_zero(self):
- se = frappe.get_doc({"doctype":"Stock Entry",
- "purpose":"Material Receipt",
- "stock_entry_type":"Material Receipt",
- "posting_date": nowdate(),
- "company":"_Test Company with perpetual inventory",
- "items":[
- {
- "item_code":"_Test Item",
- "description":"_Test Item",
- "qty": 1,
- "basic_rate": 0,
- "uom":"Nos",
- "t_warehouse": "Stores - TCP1",
- "allow_zero_valuation_rate": 1,
- "cost_center": "Main - TCP1"
- },
- {
- "item_code":"_Test Item",
- "description":"_Test Item",
- "qty": 2,
- "basic_rate": 0,
- "uom":"Nos",
- "t_warehouse": "Stores - TCP1",
- "allow_zero_valuation_rate": 1,
- "cost_center": "Main - TCP1"
- },
- ],
- "additional_costs":[
- {"expense_account":"Miscellaneous Expenses - TCP1",
- "amount":100,
- "description": "miscellanous"
- }]
- })
+ se = frappe.get_doc(
+ {
+ "doctype": "Stock Entry",
+ "purpose": "Material Receipt",
+ "stock_entry_type": "Material Receipt",
+ "posting_date": nowdate(),
+ "company": "_Test Company with perpetual inventory",
+ "items": [
+ {
+ "item_code": "_Test Item",
+ "description": "_Test Item",
+ "qty": 1,
+ "basic_rate": 0,
+ "uom": "Nos",
+ "t_warehouse": "Stores - TCP1",
+ "allow_zero_valuation_rate": 1,
+ "cost_center": "Main - TCP1",
+ },
+ {
+ "item_code": "_Test Item",
+ "description": "_Test Item",
+ "qty": 2,
+ "basic_rate": 0,
+ "uom": "Nos",
+ "t_warehouse": "Stores - TCP1",
+ "allow_zero_valuation_rate": 1,
+ "cost_center": "Main - TCP1",
+ },
+ ],
+ "additional_costs": [
+ {
+ "expense_account": "Miscellaneous Expenses - TCP1",
+ "amount": 100,
+ "description": "miscellanous",
+ }
+ ],
+ }
+ )
se.insert()
se.submit()
- self.check_gl_entries("Stock Entry", se.name,
- sorted([
- ["Stock Adjustment - TCP1", 100.0, 0.0],
- ["Miscellaneous Expenses - TCP1", 0.0, 100.0]
- ])
+ self.check_gl_entries(
+ "Stock Entry",
+ se.name,
+ sorted(
+ [["Stock Adjustment - TCP1", 100.0, 0.0], ["Miscellaneous Expenses - TCP1", 0.0, 100.0]]
+ ),
)
def test_conversion_factor_change(self):
@@ -971,15 +1171,15 @@
def test_additional_cost_distribution_manufacture(self):
se = frappe.get_doc(
- doctype="Stock Entry",
- purpose="Manufacture",
- additional_costs=[frappe._dict(base_amount=100)],
- items=[
- frappe._dict(item_code="RM", basic_amount=10),
- frappe._dict(item_code="FG", basic_amount=20, t_warehouse="X", is_finished_item=1),
- frappe._dict(item_code="scrap", basic_amount=30, t_warehouse="X")
- ],
- )
+ doctype="Stock Entry",
+ purpose="Manufacture",
+ additional_costs=[frappe._dict(base_amount=100)],
+ items=[
+ frappe._dict(item_code="RM", basic_amount=10),
+ frappe._dict(item_code="FG", basic_amount=20, t_warehouse="X", is_finished_item=1),
+ frappe._dict(item_code="scrap", basic_amount=30, t_warehouse="X"),
+ ],
+ )
se.distribute_additional_costs()
@@ -988,14 +1188,14 @@
def test_additional_cost_distribution_non_manufacture(self):
se = frappe.get_doc(
- doctype="Stock Entry",
- purpose="Material Receipt",
- additional_costs=[frappe._dict(base_amount=100)],
- items=[
- frappe._dict(item_code="RECEIVED_1", basic_amount=20, t_warehouse="X"),
- frappe._dict(item_code="RECEIVED_2", basic_amount=30, t_warehouse="X")
- ],
- )
+ doctype="Stock Entry",
+ purpose="Material Receipt",
+ additional_costs=[frappe._dict(base_amount=100)],
+ items=[
+ frappe._dict(item_code="RECEIVED_1", basic_amount=20, t_warehouse="X"),
+ frappe._dict(item_code="RECEIVED_2", basic_amount=30, t_warehouse="X"),
+ ],
+ )
se.distribute_additional_costs()
@@ -1010,9 +1210,11 @@
stock_entry_type="Manufacture",
company="_Test Company",
items=[
- frappe._dict(item_code="_Test Item", qty=1, basic_rate=200, s_warehouse="_Test Warehouse - _TC"),
- frappe._dict(item_code="_Test FG Item", qty=4, t_warehouse="_Test Warehouse 1 - _TC")
- ]
+ frappe._dict(
+ item_code="_Test Item", qty=1, basic_rate=200, s_warehouse="_Test Warehouse - _TC"
+ ),
+ frappe._dict(item_code="_Test FG Item", qty=4, t_warehouse="_Test Warehouse 1 - _TC"),
+ ],
)
# SE must have atleast one FG
self.assertRaises(FinishedGoodError, se.save)
@@ -1027,47 +1229,49 @@
# Check if FG cost is calculated based on RM total cost
# RM total cost = 200, FG rate = 200/4(FG qty) = 50
- self.assertEqual(se.items[1].basic_rate, flt(se.items[0].basic_rate/4))
+ self.assertEqual(se.items[1].basic_rate, flt(se.items[0].basic_rate / 4))
self.assertEqual(se.value_difference, 0.0)
self.assertEqual(se.total_incoming_value, se.total_outgoing_value)
@change_settings("Stock Settings", {"allow_negative_stock": 0})
def test_future_negative_sle(self):
# Initialize item, batch, warehouse, opening qty
- item_code = '_Test Future Neg Item'
- batch_no = '_Test Future Neg Batch'
- warehouses = [
- '_Test Future Neg Warehouse Source',
- '_Test Future Neg Warehouse Destination'
- ]
+ item_code = "_Test Future Neg Item"
+ batch_no = "_Test Future Neg Batch"
+ warehouses = ["_Test Future Neg Warehouse Source", "_Test Future Neg Warehouse Destination"]
warehouse_names = initialize_records_for_future_negative_sle_test(
- item_code, batch_no, warehouses,
- opening_qty=2, posting_date='2021-07-01'
+ item_code, batch_no, warehouses, opening_qty=2, posting_date="2021-07-01"
)
# Executing an illegal sequence should raise an error
sequence_of_entries = [
- dict(item_code=item_code,
+ dict(
+ item_code=item_code,
qty=2,
from_warehouse=warehouse_names[0],
to_warehouse=warehouse_names[1],
batch_no=batch_no,
- posting_date='2021-07-03',
- purpose='Material Transfer'),
- dict(item_code=item_code,
+ posting_date="2021-07-03",
+ purpose="Material Transfer",
+ ),
+ dict(
+ item_code=item_code,
qty=2,
from_warehouse=warehouse_names[1],
to_warehouse=warehouse_names[0],
batch_no=batch_no,
- posting_date='2021-07-04',
- purpose='Material Transfer'),
- dict(item_code=item_code,
+ posting_date="2021-07-04",
+ purpose="Material Transfer",
+ ),
+ dict(
+ item_code=item_code,
qty=2,
from_warehouse=warehouse_names[0],
to_warehouse=warehouse_names[1],
batch_no=batch_no,
- posting_date='2021-07-02', # Illegal SE
- purpose='Material Transfer')
+ posting_date="2021-07-02", # Illegal SE
+ purpose="Material Transfer",
+ ),
]
self.assertRaises(NegativeStockError, create_stock_entries, sequence_of_entries)
@@ -1077,74 +1281,74 @@
from erpnext.stock.doctype.batch.test_batch import TestBatch
# Initialize item, batch, warehouse, opening qty
- item_code = '_Test MultiBatch Item'
+ item_code = "_Test MultiBatch Item"
TestBatch.make_batch_item(item_code)
- batch_nos = [] # store generate batches
- warehouse = '_Test Warehouse - _TC'
+ batch_nos = [] # store generate batches
+ warehouse = "_Test Warehouse - _TC"
se1 = make_stock_entry(
- item_code=item_code,
- qty=2,
- to_warehouse=warehouse,
- posting_date='2021-09-01',
- purpose='Material Receipt'
- )
+ item_code=item_code,
+ qty=2,
+ to_warehouse=warehouse,
+ posting_date="2021-09-01",
+ purpose="Material Receipt",
+ )
batch_nos.append(se1.items[0].batch_no)
se2 = make_stock_entry(
- item_code=item_code,
- qty=2,
- to_warehouse=warehouse,
- posting_date='2021-09-03',
- purpose='Material Receipt'
- )
+ item_code=item_code,
+ qty=2,
+ to_warehouse=warehouse,
+ posting_date="2021-09-03",
+ purpose="Material Receipt",
+ )
batch_nos.append(se2.items[0].batch_no)
with self.assertRaises(NegativeStockError) as nse:
- make_stock_entry(item_code=item_code,
+ make_stock_entry(
+ item_code=item_code,
qty=1,
from_warehouse=warehouse,
batch_no=batch_nos[1],
- posting_date='2021-09-02', # backdated consumption of 2nd batch
- purpose='Material Issue')
+ posting_date="2021-09-02", # backdated consumption of 2nd batch
+ purpose="Material Issue",
+ )
def test_multi_batch_value_diff(self):
- """ Test value difference on stock entry in case of multi-batch.
- | Stock entry | batch | qty | rate | value diff on SE |
- | --- | --- | --- | --- | --- |
- | receipt | A | 1 | 10 | 30 |
- | receipt | B | 1 | 20 | |
- | issue | A | -1 | 10 | -30 (to assert after submit) |
- | issue | B | -1 | 20 | |
+ """Test value difference on stock entry in case of multi-batch.
+ | Stock entry | batch | qty | rate | value diff on SE |
+ | --- | --- | --- | --- | --- |
+ | receipt | A | 1 | 10 | 30 |
+ | receipt | B | 1 | 20 | |
+ | issue | A | -1 | 10 | -30 (to assert after submit) |
+ | issue | B | -1 | 20 | |
"""
from erpnext.stock.doctype.batch.test_batch import TestBatch
batch_nos = []
- item_code = '_TestMultibatchFifo'
+ item_code = "_TestMultibatchFifo"
TestBatch.make_batch_item(item_code)
- warehouse = '_Test Warehouse - _TC'
+ warehouse = "_Test Warehouse - _TC"
receipt = make_stock_entry(
- item_code=item_code,
- qty=1,
- rate=10,
- to_warehouse=warehouse,
- purpose='Material Receipt',
- do_not_save=True
- )
- receipt.append("items", frappe.copy_doc(receipt.items[0], ignore_no_copy=False).update({"basic_rate": 20}) )
+ item_code=item_code,
+ qty=1,
+ rate=10,
+ to_warehouse=warehouse,
+ purpose="Material Receipt",
+ do_not_save=True,
+ )
+ receipt.append(
+ "items", frappe.copy_doc(receipt.items[0], ignore_no_copy=False).update({"basic_rate": 20})
+ )
receipt.save()
receipt.submit()
batch_nos.extend(row.batch_no for row in receipt.items)
self.assertEqual(receipt.value_difference, 30)
issue = make_stock_entry(
- item_code=item_code,
- qty=1,
- from_warehouse=warehouse,
- purpose='Material Issue',
- do_not_save=True
- )
+ item_code=item_code, qty=1, from_warehouse=warehouse, purpose="Material Issue", do_not_save=True
+ )
issue.append("items", frappe.copy_doc(issue.items[0], ignore_no_copy=False))
for row, batch_no in zip(issue.items, batch_nos):
row.batch_no = batch_no
@@ -1154,6 +1358,7 @@
issue.reload() # reload because reposting current voucher updates rate
self.assertEqual(issue.value_difference, -30)
+
def make_serialized_item(**args):
args = frappe._dict(args)
se = frappe.copy_doc(test_records[0])
@@ -1183,50 +1388,57 @@
se.submit()
return se
+
def get_qty_after_transaction(**args):
args = frappe._dict(args)
- last_sle = get_previous_sle({
- "item_code": args.item_code or "_Test Item",
- "warehouse": args.warehouse or "_Test Warehouse - _TC",
- "posting_date": args.posting_date or nowdate(),
- "posting_time": args.posting_time or nowtime()
- })
+ last_sle = get_previous_sle(
+ {
+ "item_code": args.item_code or "_Test Item",
+ "warehouse": args.warehouse or "_Test Warehouse - _TC",
+ "posting_date": args.posting_date or nowdate(),
+ "posting_time": args.posting_time or nowtime(),
+ }
+ )
return flt(last_sle.get("qty_after_transaction"))
+
def get_multiple_items():
return [
- {
- "conversion_factor": 1.0,
- "cost_center": "Main - TCP1",
- "doctype": "Stock Entry Detail",
- "expense_account": "Stock Adjustment - TCP1",
- "basic_rate": 100,
- "item_code": "_Test Item",
- "qty": 50.0,
- "s_warehouse": "Stores - TCP1",
- "stock_uom": "_Test UOM",
- "transfer_qty": 50.0,
- "uom": "_Test UOM"
- },
- {
- "conversion_factor": 1.0,
- "cost_center": "Main - TCP1",
- "doctype": "Stock Entry Detail",
- "expense_account": "Stock Adjustment - TCP1",
- "basic_rate": 5000,
- "item_code": "_Test Item Home Desktop 100",
- "qty": 1,
- "stock_uom": "_Test UOM",
- "t_warehouse": "Stores - TCP1",
- "transfer_qty": 1,
- "uom": "_Test UOM"
- }
- ]
+ {
+ "conversion_factor": 1.0,
+ "cost_center": "Main - TCP1",
+ "doctype": "Stock Entry Detail",
+ "expense_account": "Stock Adjustment - TCP1",
+ "basic_rate": 100,
+ "item_code": "_Test Item",
+ "qty": 50.0,
+ "s_warehouse": "Stores - TCP1",
+ "stock_uom": "_Test UOM",
+ "transfer_qty": 50.0,
+ "uom": "_Test UOM",
+ },
+ {
+ "conversion_factor": 1.0,
+ "cost_center": "Main - TCP1",
+ "doctype": "Stock Entry Detail",
+ "expense_account": "Stock Adjustment - TCP1",
+ "basic_rate": 5000,
+ "item_code": "_Test Item Home Desktop 100",
+ "qty": 1,
+ "stock_uom": "_Test UOM",
+ "t_warehouse": "Stores - TCP1",
+ "transfer_qty": 1,
+ "uom": "_Test UOM",
+ },
+ ]
-test_records = frappe.get_test_records('Stock Entry')
+
+test_records = frappe.get_test_records("Stock Entry")
+
def initialize_records_for_future_negative_sle_test(
- item_code, batch_no, warehouses, opening_qty, posting_date):
+ item_code, batch_no, warehouses, opening_qty, posting_date
+):
from erpnext.stock.doctype.batch.test_batch import TestBatch, make_new_batch
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
create_stock_reconciliation,
@@ -1237,9 +1449,9 @@
make_new_batch(item_code=item_code, batch_id=batch_no)
warehouse_names = [create_warehouse(w) for w in warehouses]
create_stock_reconciliation(
- purpose='Opening Stock',
+ purpose="Opening Stock",
posting_date=posting_date,
- posting_time='20:00:20',
+ posting_time="20:00:20",
item_code=item_code,
warehouse=warehouse_names[0],
valuation_rate=100,
diff --git a/erpnext/stock/doctype/stock_entry_type/stock_entry_type.py b/erpnext/stock/doctype/stock_entry_type/stock_entry_type.py
index efd97c0..7258cfb 100644
--- a/erpnext/stock/doctype/stock_entry_type/stock_entry_type.py
+++ b/erpnext/stock/doctype/stock_entry_type/stock_entry_type.py
@@ -8,5 +8,5 @@
class StockEntryType(Document):
def validate(self):
- if self.add_to_transit and self.purpose != 'Material Transfer':
+ if self.add_to_transit and self.purpose != "Material Transfer":
self.add_to_transit = 0
diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
index 2f59304..5c1da42 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
@@ -14,11 +14,17 @@
from erpnext.controllers.item_variant import ItemTemplateCannotHaveStock
-class StockFreezeError(frappe.ValidationError): pass
-class BackDatedStockTransaction(frappe.ValidationError): pass
+class StockFreezeError(frappe.ValidationError):
+ pass
+
+
+class BackDatedStockTransaction(frappe.ValidationError):
+ pass
+
exclude_from_linked_with = True
+
class StockLedgerEntry(Document):
def autoname(self):
"""
@@ -32,6 +38,7 @@
def validate(self):
self.flags.ignore_submit_comment = True
from erpnext.stock.utils import validate_disabled_warehouse, validate_warehouse_company
+
self.validate_mandatory()
self.validate_item()
self.validate_batch()
@@ -42,24 +49,29 @@
self.block_transactions_against_group_warehouse()
self.validate_with_last_transaction_posting_time()
-
def on_submit(self):
self.check_stock_frozen_date()
self.calculate_batch_qty()
if not self.get("via_landed_cost_voucher"):
from erpnext.stock.doctype.serial_no.serial_no import process_serial_no
+
process_serial_no(self)
def calculate_batch_qty(self):
if self.batch_no:
- batch_qty = frappe.db.get_value("Stock Ledger Entry",
- {"docstatus": 1, "batch_no": self.batch_no, "is_cancelled": 0},
- "sum(actual_qty)") or 0
+ batch_qty = (
+ frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"docstatus": 1, "batch_no": self.batch_no, "is_cancelled": 0},
+ "sum(actual_qty)",
+ )
+ or 0
+ )
frappe.db.set_value("Batch", self.batch_no, "batch_qty", batch_qty)
def validate_mandatory(self):
- mandatory = ['warehouse','posting_date','voucher_type','voucher_no','company']
+ mandatory = ["warehouse", "posting_date", "voucher_type", "voucher_no", "company"]
for k in mandatory:
if not self.get(k):
frappe.throw(_("{0} is required").format(self.meta.get_label(k)))
@@ -68,9 +80,13 @@
frappe.throw(_("Actual Qty is mandatory"))
def validate_item(self):
- item_det = frappe.db.sql("""select name, item_name, has_batch_no, docstatus,
+ item_det = frappe.db.sql(
+ """select name, item_name, has_batch_no, docstatus,
is_stock_item, has_variants, stock_uom, create_new_batch
- from tabItem where name=%s""", self.item_code, as_dict=True)
+ from tabItem where name=%s""",
+ self.item_code,
+ as_dict=True,
+ )
if not item_det:
frappe.throw(_("Item {0} not found").format(self.item_code))
@@ -82,39 +98,58 @@
# check if batch number is valid
if item_det.has_batch_no == 1:
- batch_item = self.item_code if self.item_code == item_det.item_name else self.item_code + ":" + item_det.item_name
+ batch_item = (
+ self.item_code
+ if self.item_code == item_det.item_name
+ else self.item_code + ":" + item_det.item_name
+ )
if not self.batch_no:
frappe.throw(_("Batch number is mandatory for Item {0}").format(batch_item))
- elif not frappe.db.get_value("Batch",{"item": self.item_code, "name": self.batch_no}):
- frappe.throw(_("{0} is not a valid Batch Number for Item {1}").format(self.batch_no, batch_item))
+ elif not frappe.db.get_value("Batch", {"item": self.item_code, "name": self.batch_no}):
+ frappe.throw(
+ _("{0} is not a valid Batch Number for Item {1}").format(self.batch_no, batch_item)
+ )
elif item_det.has_batch_no == 0 and self.batch_no and self.is_cancelled == 0:
frappe.throw(_("The Item {0} cannot have Batch").format(self.item_code))
if item_det.has_variants:
- frappe.throw(_("Stock cannot exist for Item {0} since has variants").format(self.item_code),
- ItemTemplateCannotHaveStock)
+ frappe.throw(
+ _("Stock cannot exist for Item {0} since has variants").format(self.item_code),
+ ItemTemplateCannotHaveStock,
+ )
self.stock_uom = item_det.stock_uom
def check_stock_frozen_date(self):
- stock_settings = frappe.get_cached_doc('Stock Settings')
+ stock_settings = frappe.get_cached_doc("Stock Settings")
if stock_settings.stock_frozen_upto:
- if (getdate(self.posting_date) <= getdate(stock_settings.stock_frozen_upto)
- and stock_settings.stock_auth_role not in frappe.get_roles()):
- frappe.throw(_("Stock transactions before {0} are frozen")
- .format(formatdate(stock_settings.stock_frozen_upto)), StockFreezeError)
+ if (
+ getdate(self.posting_date) <= getdate(stock_settings.stock_frozen_upto)
+ and stock_settings.stock_auth_role not in frappe.get_roles()
+ ):
+ frappe.throw(
+ _("Stock transactions before {0} are frozen").format(
+ formatdate(stock_settings.stock_frozen_upto)
+ ),
+ StockFreezeError,
+ )
stock_frozen_upto_days = cint(stock_settings.stock_frozen_upto_days)
if stock_frozen_upto_days:
- older_than_x_days_ago = (add_days(getdate(self.posting_date), stock_frozen_upto_days) <= date.today())
+ older_than_x_days_ago = (
+ add_days(getdate(self.posting_date), stock_frozen_upto_days) <= date.today()
+ )
if older_than_x_days_ago and stock_settings.stock_auth_role not in frappe.get_roles():
- frappe.throw(_("Not allowed to update stock transactions older than {0}").format(stock_frozen_upto_days), StockFreezeError)
+ frappe.throw(
+ _("Not allowed to update stock transactions older than {0}").format(stock_frozen_upto_days),
+ StockFreezeError,
+ )
def scrub_posting_time(self):
- if not self.posting_time or self.posting_time == '00:0':
- self.posting_time = '00:00'
+ if not self.posting_time or self.posting_time == "00:0":
+ self.posting_time = "00:00"
def validate_batch(self):
if self.batch_no and self.voucher_type != "Stock Entry":
@@ -128,43 +163,61 @@
self.fiscal_year = get_fiscal_year(self.posting_date, company=self.company)[0]
else:
from erpnext.accounts.utils import validate_fiscal_year
- validate_fiscal_year(self.posting_date, self.fiscal_year, self.company,
- self.meta.get_label("posting_date"), self)
+
+ validate_fiscal_year(
+ self.posting_date, self.fiscal_year, self.company, self.meta.get_label("posting_date"), self
+ )
def block_transactions_against_group_warehouse(self):
from erpnext.stock.utils import is_group_warehouse
+
is_group_warehouse(self.warehouse)
def validate_with_last_transaction_posting_time(self):
- authorized_role = frappe.db.get_single_value("Stock Settings", "role_allowed_to_create_edit_back_dated_transactions")
+ authorized_role = frappe.db.get_single_value(
+ "Stock Settings", "role_allowed_to_create_edit_back_dated_transactions"
+ )
if authorized_role:
authorized_users = get_users(authorized_role)
if authorized_users and frappe.session.user not in authorized_users:
- last_transaction_time = frappe.db.sql("""
+ last_transaction_time = frappe.db.sql(
+ """
select MAX(timestamp(posting_date, posting_time)) as posting_time
from `tabStock Ledger Entry`
where docstatus = 1 and is_cancelled = 0 and item_code = %s
- and warehouse = %s""", (self.item_code, self.warehouse))[0][0]
+ and warehouse = %s""",
+ (self.item_code, self.warehouse),
+ )[0][0]
- cur_doc_posting_datetime = "%s %s" % (self.posting_date, self.get("posting_time") or "00:00:00")
+ cur_doc_posting_datetime = "%s %s" % (
+ self.posting_date,
+ self.get("posting_time") or "00:00:00",
+ )
- if last_transaction_time and get_datetime(cur_doc_posting_datetime) < get_datetime(last_transaction_time):
- msg = _("Last Stock Transaction for item {0} under warehouse {1} was on {2}.").format(frappe.bold(self.item_code),
- frappe.bold(self.warehouse), frappe.bold(last_transaction_time))
+ if last_transaction_time and get_datetime(cur_doc_posting_datetime) < get_datetime(
+ last_transaction_time
+ ):
+ msg = _("Last Stock Transaction for item {0} under warehouse {1} was on {2}.").format(
+ frappe.bold(self.item_code), frappe.bold(self.warehouse), frappe.bold(last_transaction_time)
+ )
- msg += "<br><br>" + _("You are not authorized to make/edit Stock Transactions for Item {0} under warehouse {1} before this time.").format(
- frappe.bold(self.item_code), frappe.bold(self.warehouse))
+ msg += "<br><br>" + _(
+ "You are not authorized to make/edit Stock Transactions for Item {0} under warehouse {1} before this time."
+ ).format(frappe.bold(self.item_code), frappe.bold(self.warehouse))
msg += "<br><br>" + _("Please contact any of the following users to {} this transaction.")
msg += "<br>" + "<br>".join(authorized_users)
frappe.throw(msg, BackDatedStockTransaction, title=_("Backdated Stock Entry"))
+
def on_doctype_update():
- if not frappe.db.has_index('tabStock Ledger Entry', 'posting_sort_index'):
+ if not frappe.db.has_index("tabStock Ledger Entry", "posting_sort_index"):
frappe.db.commit()
- frappe.db.add_index("Stock Ledger Entry",
+ frappe.db.add_index(
+ "Stock Ledger Entry",
fields=["posting_date", "posting_time", "name"],
- index_name="posting_sort_index")
+ index_name="posting_sort_index",
+ )
frappe.db.add_index("Stock Ledger Entry", ["voucher_no", "voucher_type"])
frappe.db.add_index("Stock Ledger Entry", ["batch_no", "item_code", "warehouse"])
diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
index fc57995..42956a1 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
@@ -29,11 +29,17 @@
class TestStockLedgerEntry(FrappeTestCase):
def setUp(self):
items = create_items()
- reset('Stock Entry')
+ reset("Stock Entry")
# delete SLE and BINs for all items
- frappe.db.sql("delete from `tabStock Ledger Entry` where item_code in (%s)" % (', '.join(['%s']*len(items))), items)
- frappe.db.sql("delete from `tabBin` where item_code in (%s)" % (', '.join(['%s']*len(items))), items)
+ frappe.db.sql(
+ "delete from `tabStock Ledger Entry` where item_code in (%s)"
+ % (", ".join(["%s"] * len(items))),
+ items,
+ )
+ frappe.db.sql(
+ "delete from `tabBin` where item_code in (%s)" % (", ".join(["%s"] * len(items))), items
+ )
def test_item_cost_reposting(self):
company = "_Test Company"
@@ -45,9 +51,11 @@
qty=50,
rate=100,
company=company,
- expense_account = "Stock Adjustment - _TC" if frappe.get_all("Stock Ledger Entry") else "Temporary Opening - _TC",
- posting_date='2020-04-10',
- posting_time='14:00'
+ expense_account="Stock Adjustment - _TC"
+ if frappe.get_all("Stock Ledger Entry")
+ else "Temporary Opening - _TC",
+ posting_date="2020-04-10",
+ posting_time="14:00",
)
# _Test Item for Reposting at FG warehouse on 20-04-2020: Qty = 10, Rate = 200
@@ -57,9 +65,11 @@
qty=10,
rate=200,
company=company,
- expense_account="Stock Adjustment - _TC" if frappe.get_all("Stock Ledger Entry") else "Temporary Opening - _TC",
- posting_date='2020-04-20',
- posting_time='14:00'
+ expense_account="Stock Adjustment - _TC"
+ if frappe.get_all("Stock Ledger Entry")
+ else "Temporary Opening - _TC",
+ posting_date="2020-04-20",
+ posting_time="14:00",
)
# _Test Item for Reposting transferred from Stores to FG warehouse on 30-04-2020
@@ -69,28 +79,40 @@
target="Finished Goods - _TC",
company=company,
qty=10,
- expense_account="Stock Adjustment - _TC" if frappe.get_all("Stock Ledger Entry") else "Temporary Opening - _TC",
- posting_date='2020-04-30',
- posting_time='14:00'
+ expense_account="Stock Adjustment - _TC"
+ if frappe.get_all("Stock Ledger Entry")
+ else "Temporary Opening - _TC",
+ posting_date="2020-04-30",
+ posting_time="14:00",
)
- target_wh_sle = frappe.db.get_value('Stock Ledger Entry', {
- "item_code": "_Test Item for Reposting",
- "warehouse": "Finished Goods - _TC",
- "voucher_type": "Stock Entry",
- "voucher_no": se.name
- }, ["valuation_rate"], as_dict=1)
+ target_wh_sle = frappe.db.get_value(
+ "Stock Ledger Entry",
+ {
+ "item_code": "_Test Item for Reposting",
+ "warehouse": "Finished Goods - _TC",
+ "voucher_type": "Stock Entry",
+ "voucher_no": se.name,
+ },
+ ["valuation_rate"],
+ as_dict=1,
+ )
self.assertEqual(target_wh_sle.get("valuation_rate"), 150)
# Repack entry on 5-5-2020
- repack = create_repack_entry(company=company, posting_date='2020-05-05', posting_time='14:00')
+ repack = create_repack_entry(company=company, posting_date="2020-05-05", posting_time="14:00")
- finished_item_sle = frappe.db.get_value('Stock Ledger Entry', {
- "item_code": "_Test Finished Item for Reposting",
- "warehouse": "Finished Goods - _TC",
- "voucher_type": "Stock Entry",
- "voucher_no": repack.name
- }, ["incoming_rate", "valuation_rate"], as_dict=1)
+ finished_item_sle = frappe.db.get_value(
+ "Stock Ledger Entry",
+ {
+ "item_code": "_Test Finished Item for Reposting",
+ "warehouse": "Finished Goods - _TC",
+ "voucher_type": "Stock Entry",
+ "voucher_no": repack.name,
+ },
+ ["incoming_rate", "valuation_rate"],
+ as_dict=1,
+ )
self.assertEqual(finished_item_sle.get("incoming_rate"), 540)
self.assertEqual(finished_item_sle.get("valuation_rate"), 540)
@@ -101,29 +123,37 @@
qty=50,
rate=150,
company=company,
- expense_account ="Stock Adjustment - _TC" if frappe.get_all("Stock Ledger Entry") else "Temporary Opening - _TC",
- posting_date='2020-04-12',
- posting_time='14:00'
+ expense_account="Stock Adjustment - _TC"
+ if frappe.get_all("Stock Ledger Entry")
+ else "Temporary Opening - _TC",
+ posting_date="2020-04-12",
+ posting_time="14:00",
)
-
# Check valuation rate of finished goods warehouse after back-dated entry at Stores
- target_wh_sle = get_previous_sle({
- "item_code": "_Test Item for Reposting",
- "warehouse": "Finished Goods - _TC",
- "posting_date": '2020-04-30',
- "posting_time": '14:00'
- })
+ target_wh_sle = get_previous_sle(
+ {
+ "item_code": "_Test Item for Reposting",
+ "warehouse": "Finished Goods - _TC",
+ "posting_date": "2020-04-30",
+ "posting_time": "14:00",
+ }
+ )
self.assertEqual(target_wh_sle.get("incoming_rate"), 150)
self.assertEqual(target_wh_sle.get("valuation_rate"), 175)
# Check valuation rate of repacked item after back-dated entry at Stores
- finished_item_sle = frappe.db.get_value('Stock Ledger Entry', {
- "item_code": "_Test Finished Item for Reposting",
- "warehouse": "Finished Goods - _TC",
- "voucher_type": "Stock Entry",
- "voucher_no": repack.name
- }, ["incoming_rate", "valuation_rate"], as_dict=1)
+ finished_item_sle = frappe.db.get_value(
+ "Stock Ledger Entry",
+ {
+ "item_code": "_Test Finished Item for Reposting",
+ "warehouse": "Finished Goods - _TC",
+ "voucher_type": "Stock Entry",
+ "voucher_no": repack.name,
+ },
+ ["incoming_rate", "valuation_rate"],
+ as_dict=1,
+ )
self.assertEqual(finished_item_sle.get("incoming_rate"), 790)
self.assertEqual(finished_item_sle.get("valuation_rate"), 790)
@@ -133,76 +163,133 @@
self.assertEqual(repack.items[1].get("basic_rate"), 750)
def test_purchase_return_valuation_reposting(self):
- pr = make_purchase_receipt(company="_Test Company", posting_date='2020-04-10',
- warehouse="Stores - _TC", item_code="_Test Item for Reposting", qty=5, rate=100)
+ pr = make_purchase_receipt(
+ company="_Test Company",
+ posting_date="2020-04-10",
+ warehouse="Stores - _TC",
+ item_code="_Test Item for Reposting",
+ qty=5,
+ rate=100,
+ )
- return_pr = make_purchase_receipt(company="_Test Company", posting_date='2020-04-15',
- warehouse="Stores - _TC", item_code="_Test Item for Reposting", is_return=1, return_against=pr.name, qty=-2)
+ return_pr = make_purchase_receipt(
+ company="_Test Company",
+ posting_date="2020-04-15",
+ warehouse="Stores - _TC",
+ item_code="_Test Item for Reposting",
+ is_return=1,
+ return_against=pr.name,
+ qty=-2,
+ )
# check sle
- outgoing_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Purchase Receipt",
- "voucher_no": return_pr.name}, ["outgoing_rate", "stock_value_difference"])
+ outgoing_rate, stock_value_difference = frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"voucher_type": "Purchase Receipt", "voucher_no": return_pr.name},
+ ["outgoing_rate", "stock_value_difference"],
+ )
self.assertEqual(outgoing_rate, 100)
self.assertEqual(stock_value_difference, -200)
create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company)
- outgoing_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Purchase Receipt",
- "voucher_no": return_pr.name}, ["outgoing_rate", "stock_value_difference"])
+ outgoing_rate, stock_value_difference = frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"voucher_type": "Purchase Receipt", "voucher_no": return_pr.name},
+ ["outgoing_rate", "stock_value_difference"],
+ )
self.assertEqual(outgoing_rate, 110)
self.assertEqual(stock_value_difference, -220)
def test_sales_return_valuation_reposting(self):
company = "_Test Company"
- item_code="_Test Item for Reposting"
+ item_code = "_Test Item for Reposting"
# Purchase Return: Qty = 5, Rate = 100
- pr = make_purchase_receipt(company=company, posting_date='2020-04-10',
- warehouse="Stores - _TC", item_code=item_code, qty=5, rate=100)
+ pr = make_purchase_receipt(
+ company=company,
+ posting_date="2020-04-10",
+ warehouse="Stores - _TC",
+ item_code=item_code,
+ qty=5,
+ rate=100,
+ )
- #Delivery Note: Qty = 5, Rate = 150
- dn = create_delivery_note(item_code=item_code, qty=5, rate=150, warehouse="Stores - _TC",
- company=company, expense_account="Cost of Goods Sold - _TC", cost_center="Main - _TC")
+ # Delivery Note: Qty = 5, Rate = 150
+ dn = create_delivery_note(
+ item_code=item_code,
+ qty=5,
+ rate=150,
+ warehouse="Stores - _TC",
+ company=company,
+ expense_account="Cost of Goods Sold - _TC",
+ cost_center="Main - _TC",
+ )
# check outgoing_rate for DN
- outgoing_rate = abs(frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Delivery Note",
- "voucher_no": dn.name}, "stock_value_difference") / 5)
+ outgoing_rate = abs(
+ frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"voucher_type": "Delivery Note", "voucher_no": dn.name},
+ "stock_value_difference",
+ )
+ / 5
+ )
self.assertEqual(dn.items[0].incoming_rate, 100)
self.assertEqual(outgoing_rate, 100)
# Return Entry: Qty = -2, Rate = 150
- return_dn = create_delivery_note(is_return=1, return_against=dn.name, item_code=item_code, qty=-2, rate=150,
- company=company, warehouse="Stores - _TC", expense_account="Cost of Goods Sold - _TC", cost_center="Main - _TC")
+ return_dn = create_delivery_note(
+ is_return=1,
+ return_against=dn.name,
+ item_code=item_code,
+ qty=-2,
+ rate=150,
+ company=company,
+ warehouse="Stores - _TC",
+ expense_account="Cost of Goods Sold - _TC",
+ cost_center="Main - _TC",
+ )
# check incoming rate for Return entry
- incoming_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry",
+ incoming_rate, stock_value_difference = frappe.db.get_value(
+ "Stock Ledger Entry",
{"voucher_type": "Delivery Note", "voucher_no": return_dn.name},
- ["incoming_rate", "stock_value_difference"])
+ ["incoming_rate", "stock_value_difference"],
+ )
self.assertEqual(return_dn.items[0].incoming_rate, 100)
self.assertEqual(incoming_rate, 100)
self.assertEqual(stock_value_difference, 200)
- #-------------------------------
+ # -------------------------------
# Landed Cost Voucher to update the rate of incoming Purchase Return: Additional cost = 50
lcv = create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company)
# check outgoing_rate for DN after reposting
- outgoing_rate = abs(frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Delivery Note",
- "voucher_no": dn.name}, "stock_value_difference") / 5)
+ outgoing_rate = abs(
+ frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"voucher_type": "Delivery Note", "voucher_no": dn.name},
+ "stock_value_difference",
+ )
+ / 5
+ )
self.assertEqual(outgoing_rate, 110)
dn.reload()
self.assertEqual(dn.items[0].incoming_rate, 110)
# check incoming rate for Return entry after reposting
- incoming_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry",
+ incoming_rate, stock_value_difference = frappe.db.get_value(
+ "Stock Ledger Entry",
{"voucher_type": "Delivery Note", "voucher_no": return_dn.name},
- ["incoming_rate", "stock_value_difference"])
+ ["incoming_rate", "stock_value_difference"],
+ )
self.assertEqual(incoming_rate, 110)
self.assertEqual(stock_value_difference, 220)
@@ -218,55 +305,93 @@
def test_reposting_of_sales_return_for_packed_item(self):
company = "_Test Company"
- packed_item_code="_Test Item for Reposting"
+ packed_item_code = "_Test Item for Reposting"
bundled_item = "_Test Bundled Item for Reposting"
create_product_bundle_item(bundled_item, [[packed_item_code, 4]])
# Purchase Return: Qty = 50, Rate = 100
- pr = make_purchase_receipt(company=company, posting_date='2020-04-10',
- warehouse="Stores - _TC", item_code=packed_item_code, qty=50, rate=100)
+ pr = make_purchase_receipt(
+ company=company,
+ posting_date="2020-04-10",
+ warehouse="Stores - _TC",
+ item_code=packed_item_code,
+ qty=50,
+ rate=100,
+ )
- #Delivery Note: Qty = 5, Rate = 150
- dn = create_delivery_note(item_code=bundled_item, qty=5, rate=150, warehouse="Stores - _TC",
- company=company, expense_account="Cost of Goods Sold - _TC", cost_center="Main - _TC")
+ # Delivery Note: Qty = 5, Rate = 150
+ dn = create_delivery_note(
+ item_code=bundled_item,
+ qty=5,
+ rate=150,
+ warehouse="Stores - _TC",
+ company=company,
+ expense_account="Cost of Goods Sold - _TC",
+ cost_center="Main - _TC",
+ )
# check outgoing_rate for DN
- outgoing_rate = abs(frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Delivery Note",
- "voucher_no": dn.name}, "stock_value_difference") / 20)
+ outgoing_rate = abs(
+ frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"voucher_type": "Delivery Note", "voucher_no": dn.name},
+ "stock_value_difference",
+ )
+ / 20
+ )
self.assertEqual(dn.packed_items[0].incoming_rate, 100)
self.assertEqual(outgoing_rate, 100)
# Return Entry: Qty = -2, Rate = 150
- return_dn = create_delivery_note(is_return=1, return_against=dn.name, item_code=bundled_item, qty=-2, rate=150,
- company=company, warehouse="Stores - _TC", expense_account="Cost of Goods Sold - _TC", cost_center="Main - _TC")
+ return_dn = create_delivery_note(
+ is_return=1,
+ return_against=dn.name,
+ item_code=bundled_item,
+ qty=-2,
+ rate=150,
+ company=company,
+ warehouse="Stores - _TC",
+ expense_account="Cost of Goods Sold - _TC",
+ cost_center="Main - _TC",
+ )
# check incoming rate for Return entry
- incoming_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry",
+ incoming_rate, stock_value_difference = frappe.db.get_value(
+ "Stock Ledger Entry",
{"voucher_type": "Delivery Note", "voucher_no": return_dn.name},
- ["incoming_rate", "stock_value_difference"])
+ ["incoming_rate", "stock_value_difference"],
+ )
self.assertEqual(return_dn.packed_items[0].incoming_rate, 100)
self.assertEqual(incoming_rate, 100)
self.assertEqual(stock_value_difference, 800)
- #-------------------------------
+ # -------------------------------
# Landed Cost Voucher to update the rate of incoming Purchase Return: Additional cost = 50
lcv = create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company)
# check outgoing_rate for DN after reposting
- outgoing_rate = abs(frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Delivery Note",
- "voucher_no": dn.name}, "stock_value_difference") / 20)
+ outgoing_rate = abs(
+ frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"voucher_type": "Delivery Note", "voucher_no": dn.name},
+ "stock_value_difference",
+ )
+ / 20
+ )
self.assertEqual(outgoing_rate, 101)
dn.reload()
self.assertEqual(dn.packed_items[0].incoming_rate, 101)
# check incoming rate for Return entry after reposting
- incoming_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry",
+ incoming_rate, stock_value_difference = frappe.db.get_value(
+ "Stock Ledger Entry",
{"voucher_type": "Delivery Note", "voucher_no": return_dn.name},
- ["incoming_rate", "stock_value_difference"])
+ ["incoming_rate", "stock_value_difference"],
+ )
self.assertEqual(incoming_rate, 101)
self.assertEqual(stock_value_difference, 808)
@@ -284,20 +409,35 @@
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
company = "_Test Company"
- rm_item_code="_Test Item for Reposting"
+ rm_item_code = "_Test Item for Reposting"
subcontracted_item = "_Test Subcontracted Item for Reposting"
- frappe.db.set_value("Buying Settings", None, "backflush_raw_materials_of_subcontract_based_on", "BOM")
- make_bom(item = subcontracted_item, raw_materials =[rm_item_code], currency="INR")
+ frappe.db.set_value(
+ "Buying Settings", None, "backflush_raw_materials_of_subcontract_based_on", "BOM"
+ )
+ make_bom(item=subcontracted_item, raw_materials=[rm_item_code], currency="INR")
# Purchase raw materials on supplier warehouse: Qty = 50, Rate = 100
- pr = make_purchase_receipt(company=company, posting_date='2020-04-10',
- warehouse="Stores - _TC", item_code=rm_item_code, qty=10, rate=100)
+ pr = make_purchase_receipt(
+ company=company,
+ posting_date="2020-04-10",
+ warehouse="Stores - _TC",
+ item_code=rm_item_code,
+ qty=10,
+ rate=100,
+ )
# Purchase Receipt for subcontracted item
- pr1 = make_purchase_receipt(company=company, posting_date='2020-04-20',
- warehouse="Finished Goods - _TC", supplier_warehouse="Stores - _TC",
- item_code=subcontracted_item, qty=10, rate=20, is_subcontracted="Yes")
+ pr1 = make_purchase_receipt(
+ company=company,
+ posting_date="2020-04-20",
+ warehouse="Finished Goods - _TC",
+ supplier_warehouse="Stores - _TC",
+ item_code=subcontracted_item,
+ qty=10,
+ rate=20,
+ is_subcontracted="Yes",
+ )
self.assertEqual(pr1.items[0].valuation_rate, 120)
@@ -308,8 +448,11 @@
self.assertEqual(pr1.items[0].valuation_rate, 125)
# check outgoing_rate for DN after reposting
- incoming_rate = frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Purchase Receipt",
- "voucher_no": pr1.name, "item_code": subcontracted_item}, "incoming_rate")
+ incoming_rate = frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"voucher_type": "Purchase Receipt", "voucher_no": pr1.name, "item_code": subcontracted_item},
+ "incoming_rate",
+ )
self.assertEqual(incoming_rate, 125)
# cleanup data
@@ -319,8 +462,9 @@
def test_back_dated_entry_not_allowed(self):
# Back dated stock transactions are only allowed to stock managers
- frappe.db.set_value("Stock Settings", None,
- "role_allowed_to_create_edit_back_dated_transactions", "Stock Manager")
+ frappe.db.set_value(
+ "Stock Settings", None, "role_allowed_to_create_edit_back_dated_transactions", "Stock Manager"
+ )
# Set User with Stock User role but not Stock Manager
try:
@@ -331,8 +475,13 @@
frappe.set_user(user.name)
stock_entry_on_today = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100)
- back_dated_se_1 = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100,
- posting_date=add_days(today(), -1), do_not_submit=True)
+ back_dated_se_1 = make_stock_entry(
+ target="_Test Warehouse - _TC",
+ qty=10,
+ basic_rate=100,
+ posting_date=add_days(today(), -1),
+ do_not_submit=True,
+ )
# Block back-dated entry
self.assertRaises(BackDatedStockTransaction, back_dated_se_1.submit)
@@ -342,14 +491,17 @@
frappe.set_user(user.name)
# Back dated entry allowed to Stock Manager
- back_dated_se_2 = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100,
- posting_date=add_days(today(), -1))
+ back_dated_se_2 = make_stock_entry(
+ target="_Test Warehouse - _TC", qty=10, basic_rate=100, posting_date=add_days(today(), -1)
+ )
back_dated_se_2.cancel()
stock_entry_on_today.cancel()
finally:
- frappe.db.set_value("Stock Settings", None, "role_allowed_to_create_edit_back_dated_transactions", None)
+ frappe.db.set_value(
+ "Stock Settings", None, "role_allowed_to_create_edit_back_dated_transactions", None
+ )
frappe.set_user("Administrator")
user.remove_roles("Stock Manager")
@@ -359,13 +511,13 @@
# Incoming Entries for Stock Value check
pr_entry_list = [
(item, warehouses[0], batches[0], 1, 100),
- (item, warehouses[0], batches[1], 1, 50),
+ (item, warehouses[0], batches[1], 1, 50),
(item, warehouses[0], batches[0], 1, 150),
(item, warehouses[0], batches[1], 1, 100),
]
prs = create_purchase_receipt_entries_for_batchwise_item_valuation_test(pr_entry_list)
- sle_details = fetch_sle_details_for_doc_list(prs, ['stock_value'])
- sv_list = [d['stock_value'] for d in sle_details]
+ sle_details = fetch_sle_details_for_doc_list(prs, ["stock_value"])
+ sv_list = [d["stock_value"] for d in sle_details]
expected_sv = [100, 150, 300, 400]
self.assertEqual(expected_sv, sv_list, "Incorrect 'Stock Value' values")
@@ -374,29 +526,33 @@
(item, warehouses[0], batches[1], 1, 200),
(item, warehouses[0], batches[0], 1, 200),
(item, warehouses[0], batches[1], 1, 200),
- (item, warehouses[0], batches[0], 1, 200)
+ (item, warehouses[0], batches[0], 1, 200),
]
dns = create_delivery_note_entries_for_batchwise_item_valuation_test(dn_entry_list)
- sle_details = fetch_sle_details_for_doc_list(dns, ['stock_value_difference'])
- svd_list = [-1 * d['stock_value_difference'] for d in sle_details]
+ sle_details = fetch_sle_details_for_doc_list(dns, ["stock_value_difference"])
+ svd_list = [-1 * d["stock_value_difference"] for d in sle_details]
expected_incoming_rates = expected_abs_svd = [75, 125, 75, 125]
self.assertEqual(expected_abs_svd, svd_list, "Incorrect 'Stock Value Difference' values")
for dn, incoming_rate in zip(dns, expected_incoming_rates):
self.assertEqual(
- dn.items[0].incoming_rate, incoming_rate,
- "Incorrect 'Incoming Rate' values fetched for DN items"
+ dn.items[0].incoming_rate,
+ incoming_rate,
+ "Incorrect 'Incoming Rate' values fetched for DN items",
)
-
def assertSLEs(self, doc, expected_sles, sle_filters=None):
- """ Compare sorted SLEs, useful for vouchers that create multiple SLEs for same line"""
+ """Compare sorted SLEs, useful for vouchers that create multiple SLEs for same line"""
filters = {"voucher_no": doc.name, "voucher_type": doc.doctype, "is_cancelled": 0}
if sle_filters:
filters.update(sle_filters)
- sles = frappe.get_all("Stock Ledger Entry", fields=["*"], filters=filters,
- order_by="timestamp(posting_date, posting_time), creation")
+ sles = frappe.get_all(
+ "Stock Ledger Entry",
+ fields=["*"],
+ filters=filters,
+ order_by="timestamp(posting_date, posting_time), creation",
+ )
for exp_sle, act_sle in zip(expected_sles, sles):
for k, v in exp_sle.items():
@@ -409,13 +565,10 @@
self.assertEqual(v, act_value, msg=f"{k} doesn't match \n{exp_sle}\n{act_sle}")
-
def test_batchwise_item_valuation_stock_reco(self):
item, warehouses, batches = setup_item_valuation_test()
- state = {
- "stock_value" : 0.0,
- "qty": 0.0
- }
+ state = {"stock_value": 0.0, "qty": 0.0}
+
def update_invariants(exp_sles):
for sle in exp_sles:
state["stock_value"] += sle["stock_value_difference"]
@@ -423,33 +576,41 @@
sle["stock_value"] = state["stock_value"]
sle["qty_after_transaction"] = state["qty"]
- osr1 = create_stock_reconciliation(warehouse=warehouses[0], item_code=item, qty=10, rate=100, batch_no=batches[1])
+ osr1 = create_stock_reconciliation(
+ warehouse=warehouses[0], item_code=item, qty=10, rate=100, batch_no=batches[1]
+ )
expected_sles = [
{"actual_qty": 10, "stock_value_difference": 1000},
]
update_invariants(expected_sles)
self.assertSLEs(osr1, expected_sles)
- osr2 = create_stock_reconciliation(warehouse=warehouses[0], item_code=item, qty=13, rate=200, batch_no=batches[0])
+ osr2 = create_stock_reconciliation(
+ warehouse=warehouses[0], item_code=item, qty=13, rate=200, batch_no=batches[0]
+ )
expected_sles = [
- {"actual_qty": 13, "stock_value_difference": 200*13},
+ {"actual_qty": 13, "stock_value_difference": 200 * 13},
]
update_invariants(expected_sles)
self.assertSLEs(osr2, expected_sles)
- sr1 = create_stock_reconciliation(warehouse=warehouses[0], item_code=item, qty=5, rate=50, batch_no=batches[1])
+ sr1 = create_stock_reconciliation(
+ warehouse=warehouses[0], item_code=item, qty=5, rate=50, batch_no=batches[1]
+ )
expected_sles = [
{"actual_qty": -10, "stock_value_difference": -10 * 100},
- {"actual_qty": 5, "stock_value_difference": 250}
+ {"actual_qty": 5, "stock_value_difference": 250},
]
update_invariants(expected_sles)
self.assertSLEs(sr1, expected_sles)
- sr2 = create_stock_reconciliation(warehouse=warehouses[0], item_code=item, qty=20, rate=75, batch_no=batches[0])
+ sr2 = create_stock_reconciliation(
+ warehouse=warehouses[0], item_code=item, qty=20, rate=75, batch_no=batches[0]
+ )
expected_sles = [
{"actual_qty": -13, "stock_value_difference": -13 * 200},
- {"actual_qty": 20, "stock_value_difference": 20 * 75}
+ {"actual_qty": 20, "stock_value_difference": 20 * 75},
]
update_invariants(expected_sles)
self.assertSLEs(sr2, expected_sles)
@@ -459,108 +620,190 @@
source = warehouses[0]
target = warehouses[1]
- unrelated_batch = make_stock_entry(item_code=item_code, target=source, batch_no=batches[1],
- qty=5, rate=10)
- self.assertSLEs(unrelated_batch, [
- {"actual_qty": 5, "stock_value_difference": 10 * 5},
- ])
+ unrelated_batch = make_stock_entry(
+ item_code=item_code, target=source, batch_no=batches[1], qty=5, rate=10
+ )
+ self.assertSLEs(
+ unrelated_batch,
+ [
+ {"actual_qty": 5, "stock_value_difference": 10 * 5},
+ ],
+ )
- reciept = make_stock_entry(item_code=item_code, target=source, batch_no=batches[0], qty=5, rate=10)
- self.assertSLEs(reciept, [
- {"actual_qty": 5, "stock_value_difference": 10 * 5},
- ])
+ reciept = make_stock_entry(
+ item_code=item_code, target=source, batch_no=batches[0], qty=5, rate=10
+ )
+ self.assertSLEs(
+ reciept,
+ [
+ {"actual_qty": 5, "stock_value_difference": 10 * 5},
+ ],
+ )
- transfer = make_stock_entry(item_code=item_code, source=source, target=target, batch_no=batches[0], qty=5)
- self.assertSLEs(transfer, [
- {"actual_qty": -5, "stock_value_difference": -10 * 5, "warehouse": source},
- {"actual_qty": 5, "stock_value_difference": 10 * 5, "warehouse": target}
- ])
+ transfer = make_stock_entry(
+ item_code=item_code, source=source, target=target, batch_no=batches[0], qty=5
+ )
+ self.assertSLEs(
+ transfer,
+ [
+ {"actual_qty": -5, "stock_value_difference": -10 * 5, "warehouse": source},
+ {"actual_qty": 5, "stock_value_difference": 10 * 5, "warehouse": target},
+ ],
+ )
- backdated_receipt = make_stock_entry(item_code=item_code, target=source, batch_no=batches[0],
- qty=5, rate=20, posting_date=add_days(today(), -1))
- self.assertSLEs(backdated_receipt, [
- {"actual_qty": 5, "stock_value_difference": 20 * 5},
- ])
+ backdated_receipt = make_stock_entry(
+ item_code=item_code,
+ target=source,
+ batch_no=batches[0],
+ qty=5,
+ rate=20,
+ posting_date=add_days(today(), -1),
+ )
+ self.assertSLEs(
+ backdated_receipt,
+ [
+ {"actual_qty": 5, "stock_value_difference": 20 * 5},
+ ],
+ )
# check reposted average rate in *future* transfer
- self.assertSLEs(transfer, [
- {"actual_qty": -5, "stock_value_difference": -15 * 5, "warehouse": source, "stock_value": 15 * 5 + 10 * 5},
- {"actual_qty": 5, "stock_value_difference": 15 * 5, "warehouse": target, "stock_value": 15 * 5}
- ])
+ self.assertSLEs(
+ transfer,
+ [
+ {
+ "actual_qty": -5,
+ "stock_value_difference": -15 * 5,
+ "warehouse": source,
+ "stock_value": 15 * 5 + 10 * 5,
+ },
+ {
+ "actual_qty": 5,
+ "stock_value_difference": 15 * 5,
+ "warehouse": target,
+ "stock_value": 15 * 5,
+ },
+ ],
+ )
- transfer_unrelated = make_stock_entry(item_code=item_code, source=source,
- target=target, batch_no=batches[1], qty=5)
- self.assertSLEs(transfer_unrelated, [
- {"actual_qty": -5, "stock_value_difference": -10 * 5, "warehouse": source, "stock_value": 15 * 5},
- {"actual_qty": 5, "stock_value_difference": 10 * 5, "warehouse": target, "stock_value": 15 * 5 + 10 * 5}
- ])
+ transfer_unrelated = make_stock_entry(
+ item_code=item_code, source=source, target=target, batch_no=batches[1], qty=5
+ )
+ self.assertSLEs(
+ transfer_unrelated,
+ [
+ {
+ "actual_qty": -5,
+ "stock_value_difference": -10 * 5,
+ "warehouse": source,
+ "stock_value": 15 * 5,
+ },
+ {
+ "actual_qty": 5,
+ "stock_value_difference": 10 * 5,
+ "warehouse": target,
+ "stock_value": 15 * 5 + 10 * 5,
+ },
+ ],
+ )
def test_intermediate_average_batch_wise_valuation(self):
- """ A batch has moving average up until posting time,
+ """A batch has moving average up until posting time,
check if same is respected when backdated entry is inserted in middle"""
item_code, warehouses, batches = setup_item_valuation_test()
warehouse = warehouses[0]
batch = batches[0]
- yesterday = make_stock_entry(item_code=item_code, target=warehouse, batch_no=batch,
- qty=1, rate=10, posting_date=add_days(today(), -1))
- self.assertSLEs(yesterday, [
- {"actual_qty": 1, "stock_value_difference": 10},
- ])
+ yesterday = make_stock_entry(
+ item_code=item_code,
+ target=warehouse,
+ batch_no=batch,
+ qty=1,
+ rate=10,
+ posting_date=add_days(today(), -1),
+ )
+ self.assertSLEs(
+ yesterday,
+ [
+ {"actual_qty": 1, "stock_value_difference": 10},
+ ],
+ )
- tomorrow = make_stock_entry(item_code=item_code, target=warehouse, batch_no=batches[0],
- qty=1, rate=30, posting_date=add_days(today(), 1))
- self.assertSLEs(tomorrow, [
- {"actual_qty": 1, "stock_value_difference": 30},
- ])
+ tomorrow = make_stock_entry(
+ item_code=item_code,
+ target=warehouse,
+ batch_no=batches[0],
+ qty=1,
+ rate=30,
+ posting_date=add_days(today(), 1),
+ )
+ self.assertSLEs(
+ tomorrow,
+ [
+ {"actual_qty": 1, "stock_value_difference": 30},
+ ],
+ )
- create_today = make_stock_entry(item_code=item_code, target=warehouse, batch_no=batches[0],
- qty=1, rate=20)
- self.assertSLEs(create_today, [
- {"actual_qty": 1, "stock_value_difference": 20},
- ])
+ create_today = make_stock_entry(
+ item_code=item_code, target=warehouse, batch_no=batches[0], qty=1, rate=20
+ )
+ self.assertSLEs(
+ create_today,
+ [
+ {"actual_qty": 1, "stock_value_difference": 20},
+ ],
+ )
- consume_today = make_stock_entry(item_code=item_code, source=warehouse, batch_no=batches[0],
- qty=1)
- self.assertSLEs(consume_today, [
- {"actual_qty": -1, "stock_value_difference": -15},
- ])
+ consume_today = make_stock_entry(
+ item_code=item_code, source=warehouse, batch_no=batches[0], qty=1
+ )
+ self.assertSLEs(
+ consume_today,
+ [
+ {"actual_qty": -1, "stock_value_difference": -15},
+ ],
+ )
- consume_tomorrow = make_stock_entry(item_code=item_code, source=warehouse, batch_no=batches[0],
- qty=2, posting_date=add_days(today(), 2))
- self.assertSLEs(consume_tomorrow, [
- {"stock_value_difference": -(30 + 15), "stock_value": 0, "qty_after_transaction": 0},
- ])
+ consume_tomorrow = make_stock_entry(
+ item_code=item_code,
+ source=warehouse,
+ batch_no=batches[0],
+ qty=2,
+ posting_date=add_days(today(), 2),
+ )
+ self.assertSLEs(
+ consume_tomorrow,
+ [
+ {"stock_value_difference": -(30 + 15), "stock_value": 0, "qty_after_transaction": 0},
+ ],
+ )
def test_legacy_item_valuation_stock_entry(self):
columns = [
- 'stock_value_difference',
- 'stock_value',
- 'actual_qty',
- 'qty_after_transaction',
- 'stock_queue',
+ "stock_value_difference",
+ "stock_value",
+ "actual_qty",
+ "qty_after_transaction",
+ "stock_queue",
]
item, warehouses, batches = setup_item_valuation_test(use_batchwise_valuation=0)
def check_sle_details_against_expected(sle_details, expected_sle_details, detail, columns):
for i, (sle_vals, ex_sle_vals) in enumerate(zip(sle_details, expected_sle_details)):
for col, sle_val, ex_sle_val in zip(columns, sle_vals, ex_sle_vals):
- if col == 'stock_queue':
+ if col == "stock_queue":
sle_val = get_stock_value_from_q(sle_val)
ex_sle_val = get_stock_value_from_q(ex_sle_val)
self.assertEqual(
- sle_val, ex_sle_val,
- f"Incorrect {col} value on transaction #: {i} in {detail}"
+ sle_val, ex_sle_val, f"Incorrect {col} value on transaction #: {i} in {detail}"
)
# List used to defer assertions to prevent commits cause of error skipped rollback
details_list = []
-
# Test Material Receipt Entries
se_entry_list_mr = [
- (item, None, warehouses[0], batches[0], 1, 50, "2021-01-21"),
+ (item, None, warehouses[0], batches[0], 1, 50, "2021-01-21"),
(item, None, warehouses[0], batches[1], 1, 100, "2021-01-23"),
]
ses = create_stock_entry_entries_for_batchwise_item_valuation_test(
@@ -568,14 +811,10 @@
)
sle_details = fetch_sle_details_for_doc_list(ses, columns=columns, as_dict=0)
expected_sle_details = [
- (50.0, 50.0, 1.0, 1.0, '[[1.0, 50.0]]'),
- (100.0, 150.0, 1.0, 2.0, '[[1.0, 50.0], [1.0, 100.0]]'),
+ (50.0, 50.0, 1.0, 1.0, "[[1.0, 50.0]]"),
+ (100.0, 150.0, 1.0, 2.0, "[[1.0, 50.0], [1.0, 100.0]]"),
]
- details_list.append((
- sle_details, expected_sle_details,
- "Material Receipt Entries", columns
- ))
-
+ details_list.append((sle_details, expected_sle_details, "Material Receipt Entries", columns))
# Test Material Issue Entries
se_entry_list_mi = [
@@ -585,14 +824,8 @@
se_entry_list_mi, "Material Issue"
)
sle_details = fetch_sle_details_for_doc_list(ses, columns=columns, as_dict=0)
- expected_sle_details = [
- (-50.0, 100.0, -1.0, 1.0, '[[1, 100.0]]')
- ]
- details_list.append((
- sle_details, expected_sle_details,
- "Material Issue Entries", columns
- ))
-
+ expected_sle_details = [(-50.0, 100.0, -1.0, 1.0, "[[1, 100.0]]")]
+ details_list.append((sle_details, expected_sle_details, "Material Issue Entries", columns))
# Run assertions
for details in details_list:
@@ -602,10 +835,8 @@
item_code, warehouses, batches = setup_item_valuation_test(use_batchwise_valuation=0)
warehouse = warehouses[0]
- state = {
- "qty": 0.0,
- "stock_value": 0.0
- }
+ state = {"qty": 0.0, "stock_value": 0.0}
+
def update_invariants(exp_sles):
for sle in exp_sles:
state["stock_value"] += sle["stock_value_difference"]
@@ -614,59 +845,131 @@
sle["qty_after_transaction"] = state["qty"]
return exp_sles
- old1 = make_stock_entry(item_code=item_code, target=warehouse, batch_no=batches[0],
- qty=10, rate=10)
- self.assertSLEs(old1, update_invariants([
- {"actual_qty": 10, "stock_value_difference": 10*10, "stock_queue": [[10, 10]]},
- ]))
- old2 = make_stock_entry(item_code=item_code, target=warehouse, batch_no=batches[1],
- qty=10, rate=20)
- self.assertSLEs(old2, update_invariants([
- {"actual_qty": 10, "stock_value_difference": 10*20, "stock_queue": [[10, 10], [10, 20]]},
- ]))
- old3 = make_stock_entry(item_code=item_code, target=warehouse, batch_no=batches[0],
- qty=5, rate=15)
+ old1 = make_stock_entry(
+ item_code=item_code, target=warehouse, batch_no=batches[0], qty=10, rate=10
+ )
+ self.assertSLEs(
+ old1,
+ update_invariants(
+ [
+ {"actual_qty": 10, "stock_value_difference": 10 * 10, "stock_queue": [[10, 10]]},
+ ]
+ ),
+ )
+ old2 = make_stock_entry(
+ item_code=item_code, target=warehouse, batch_no=batches[1], qty=10, rate=20
+ )
+ self.assertSLEs(
+ old2,
+ update_invariants(
+ [
+ {"actual_qty": 10, "stock_value_difference": 10 * 20, "stock_queue": [[10, 10], [10, 20]]},
+ ]
+ ),
+ )
+ old3 = make_stock_entry(
+ item_code=item_code, target=warehouse, batch_no=batches[0], qty=5, rate=15
+ )
- self.assertSLEs(old3, update_invariants([
- {"actual_qty": 5, "stock_value_difference": 5*15, "stock_queue": [[10, 10], [10, 20], [5, 15]]},
- ]))
+ self.assertSLEs(
+ old3,
+ update_invariants(
+ [
+ {
+ "actual_qty": 5,
+ "stock_value_difference": 5 * 15,
+ "stock_queue": [[10, 10], [10, 20], [5, 15]],
+ },
+ ]
+ ),
+ )
new1 = make_stock_entry(item_code=item_code, target=warehouse, qty=10, rate=40)
batches.append(new1.items[0].batch_no)
# assert old queue remains
- self.assertSLEs(new1, update_invariants([
- {"actual_qty": 10, "stock_value_difference": 10*40, "stock_queue": [[10, 10], [10, 20], [5, 15]]},
- ]))
+ self.assertSLEs(
+ new1,
+ update_invariants(
+ [
+ {
+ "actual_qty": 10,
+ "stock_value_difference": 10 * 40,
+ "stock_queue": [[10, 10], [10, 20], [5, 15]],
+ },
+ ]
+ ),
+ )
new2 = make_stock_entry(item_code=item_code, target=warehouse, qty=10, rate=42)
batches.append(new2.items[0].batch_no)
- self.assertSLEs(new2, update_invariants([
- {"actual_qty": 10, "stock_value_difference": 10*42, "stock_queue": [[10, 10], [10, 20], [5, 15]]},
- ]))
+ self.assertSLEs(
+ new2,
+ update_invariants(
+ [
+ {
+ "actual_qty": 10,
+ "stock_value_difference": 10 * 42,
+ "stock_queue": [[10, 10], [10, 20], [5, 15]],
+ },
+ ]
+ ),
+ )
# consume old batch as per FIFO
- consume_old1 = make_stock_entry(item_code=item_code, source=warehouse, qty=15, batch_no=batches[0])
- self.assertSLEs(consume_old1, update_invariants([
- {"actual_qty": -15, "stock_value_difference": -10*10 - 5*20, "stock_queue": [[5, 20], [5, 15]]},
- ]))
+ consume_old1 = make_stock_entry(
+ item_code=item_code, source=warehouse, qty=15, batch_no=batches[0]
+ )
+ self.assertSLEs(
+ consume_old1,
+ update_invariants(
+ [
+ {
+ "actual_qty": -15,
+ "stock_value_difference": -10 * 10 - 5 * 20,
+ "stock_queue": [[5, 20], [5, 15]],
+ },
+ ]
+ ),
+ )
# consume new batch as per batch
- consume_new2 = make_stock_entry(item_code=item_code, source=warehouse, qty=10, batch_no=batches[-1])
- self.assertSLEs(consume_new2, update_invariants([
- {"actual_qty": -10, "stock_value_difference": -10*42, "stock_queue": [[5, 20], [5, 15]]},
- ]))
+ consume_new2 = make_stock_entry(
+ item_code=item_code, source=warehouse, qty=10, batch_no=batches[-1]
+ )
+ self.assertSLEs(
+ consume_new2,
+ update_invariants(
+ [
+ {"actual_qty": -10, "stock_value_difference": -10 * 42, "stock_queue": [[5, 20], [5, 15]]},
+ ]
+ ),
+ )
# finish all old batches
- consume_old2 = make_stock_entry(item_code=item_code, source=warehouse, qty=10, batch_no=batches[1])
- self.assertSLEs(consume_old2, update_invariants([
- {"actual_qty": -10, "stock_value_difference": -5*20 - 5*15, "stock_queue": []},
- ]))
+ consume_old2 = make_stock_entry(
+ item_code=item_code, source=warehouse, qty=10, batch_no=batches[1]
+ )
+ self.assertSLEs(
+ consume_old2,
+ update_invariants(
+ [
+ {"actual_qty": -10, "stock_value_difference": -5 * 20 - 5 * 15, "stock_queue": []},
+ ]
+ ),
+ )
# finish all new batches
- consume_new1 = make_stock_entry(item_code=item_code, source=warehouse, qty=10, batch_no=batches[-2])
- self.assertSLEs(consume_new1, update_invariants([
- {"actual_qty": -10, "stock_value_difference": -10*40, "stock_queue": []},
- ]))
+ consume_new1 = make_stock_entry(
+ item_code=item_code, source=warehouse, qty=10, batch_no=batches[-2]
+ )
+ self.assertSLEs(
+ consume_new1,
+ update_invariants(
+ [
+ {"actual_qty": -10, "stock_value_difference": -10 * 40, "stock_queue": []},
+ ]
+ ),
+ )
def test_fifo_dependent_consumption(self):
item = make_item("_TestFifoTransferRates")
@@ -686,12 +989,12 @@
expected_queues = []
for idx, rate in enumerate(rates, start=1):
- expected_queues.append(
- {"stock_queue": [[10, 10 * i] for i in range(1, idx + 1)]}
- )
+ expected_queues.append({"stock_queue": [[10, 10 * i] for i in range(1, idx + 1)]})
self.assertSLEs(receipt, expected_queues)
- transfer = make_stock_entry(item_code=item.name, source=source, target=target, qty=10, do_not_save=True, rate=10)
+ transfer = make_stock_entry(
+ item_code=item.name, source=source, target=target, qty=10, do_not_save=True, rate=10
+ )
for rate in rates[1:]:
row = frappe.copy_doc(transfer.items[0], ignore_no_copy=False)
transfer.append("items", row)
@@ -709,7 +1012,9 @@
rates = [10 * i for i in range(1, 5)]
- receipt = make_stock_entry(item_code=rm.name, target=warehouse, qty=10, do_not_save=True, rate=10)
+ receipt = make_stock_entry(
+ item_code=rm.name, target=warehouse, qty=10, do_not_save=True, rate=10
+ )
for rate in rates[1:]:
row = frappe.copy_doc(receipt.items[0], ignore_no_copy=False)
row.basic_rate = rate
@@ -718,26 +1023,30 @@
receipt.save()
receipt.submit()
- repack = make_stock_entry(item_code=rm.name, source=warehouse, qty=10,
- do_not_save=True, rate=10, purpose="Repack")
+ repack = make_stock_entry(
+ item_code=rm.name, source=warehouse, qty=10, do_not_save=True, rate=10, purpose="Repack"
+ )
for rate in rates[1:]:
row = frappe.copy_doc(repack.items[0], ignore_no_copy=False)
repack.append("items", row)
- repack.append("items", {
- "item_code": packed.name,
- "t_warehouse": warehouse,
- "qty": 1,
- "transfer_qty": 1,
- })
+ repack.append(
+ "items",
+ {
+ "item_code": packed.name,
+ "t_warehouse": warehouse,
+ "qty": 1,
+ "transfer_qty": 1,
+ },
+ )
repack.save()
repack.submit()
# same exact queue should be transferred
- self.assertSLEs(repack, [
- {"incoming_rate": sum(rates) * 10}
- ], sle_filters={"item_code": packed.name})
+ self.assertSLEs(
+ repack, [{"incoming_rate": sum(rates) * 10}], sle_filters={"item_code": packed.name}
+ )
def test_negative_fifo_valuation(self):
"""
@@ -750,19 +1059,14 @@
receipt = make_stock_entry(item_code=item, target=warehouse, qty=10, rate=10)
consume1 = make_stock_entry(item_code=item, source=warehouse, qty=15)
- self.assertSLEs(consume1, [
- {"stock_value": -5 * 10, "stock_queue": [[-5, 10]]}
- ])
+ self.assertSLEs(consume1, [{"stock_value": -5 * 10, "stock_queue": [[-5, 10]]}])
consume2 = make_stock_entry(item_code=item, source=warehouse, qty=5)
- self.assertSLEs(consume2, [
- {"stock_value": -10 * 10, "stock_queue": [[-10, 10]]}
- ])
+ self.assertSLEs(consume2, [{"stock_value": -10 * 10, "stock_queue": [[-10, 10]]}])
receipt2 = make_stock_entry(item_code=item, target=warehouse, qty=15, rate=15)
- self.assertSLEs(receipt2, [
- {"stock_queue": [[5, 15]], "stock_value_difference": 175}
- ])
+ self.assertSLEs(receipt2, [{"stock_queue": [[5, 15]], "stock_value_difference": 175}])
+
def create_repack_entry(**args):
args = frappe._dict(args)
@@ -771,51 +1075,63 @@
repack.company = args.company or "_Test Company"
repack.posting_date = args.posting_date
repack.set_posting_time = 1
- repack.append("items", {
- "item_code": "_Test Item for Reposting",
- "s_warehouse": "Stores - _TC",
- "qty": 5,
- "conversion_factor": 1,
- "expense_account": "Stock Adjustment - _TC",
- "cost_center": "Main - _TC"
- })
+ repack.append(
+ "items",
+ {
+ "item_code": "_Test Item for Reposting",
+ "s_warehouse": "Stores - _TC",
+ "qty": 5,
+ "conversion_factor": 1,
+ "expense_account": "Stock Adjustment - _TC",
+ "cost_center": "Main - _TC",
+ },
+ )
- repack.append("items", {
- "item_code": "_Test Finished Item for Reposting",
- "t_warehouse": "Finished Goods - _TC",
- "qty": 1,
- "conversion_factor": 1,
- "expense_account": "Stock Adjustment - _TC",
- "cost_center": "Main - _TC"
- })
+ repack.append(
+ "items",
+ {
+ "item_code": "_Test Finished Item for Reposting",
+ "t_warehouse": "Finished Goods - _TC",
+ "qty": 1,
+ "conversion_factor": 1,
+ "expense_account": "Stock Adjustment - _TC",
+ "cost_center": "Main - _TC",
+ },
+ )
- repack.append("additional_costs", {
- "expense_account": "Freight and Forwarding Charges - _TC",
- "description": "transport cost",
- "amount": 40
- })
+ repack.append(
+ "additional_costs",
+ {
+ "expense_account": "Freight and Forwarding Charges - _TC",
+ "description": "transport cost",
+ "amount": 40,
+ },
+ )
repack.save()
repack.submit()
return repack
+
def create_product_bundle_item(new_item_code, packed_items):
if not frappe.db.exists("Product Bundle", new_item_code):
item = frappe.new_doc("Product Bundle")
item.new_item_code = new_item_code
for d in packed_items:
- item.append("items", {
- "item_code": d[0],
- "qty": d[1]
- })
+ item.append("items", {"item_code": d[0], "qty": d[1]})
item.save()
+
def create_items():
- items = ["_Test Item for Reposting", "_Test Finished Item for Reposting",
- "_Test Subcontracted Item for Reposting", "_Test Bundled Item for Reposting"]
+ items = [
+ "_Test Item for Reposting",
+ "_Test Finished Item for Reposting",
+ "_Test Subcontracted Item for Reposting",
+ "_Test Bundled Item for Reposting",
+ ]
for d in items:
properties = {"valuation_method": "FIFO"}
if d == "_Test Bundled Item for Reposting":
@@ -827,7 +1143,10 @@
return items
-def setup_item_valuation_test(valuation_method="FIFO", suffix=None, use_batchwise_valuation=1, batches_list=['X', 'Y']):
+
+def setup_item_valuation_test(
+ valuation_method="FIFO", suffix=None, use_batchwise_valuation=1, batches_list=["X", "Y"]
+):
from erpnext.stock.doctype.batch.batch import make_batch
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
@@ -837,9 +1156,9 @@
item = make_item(
f"IV - Test Item {valuation_method} {suffix}",
- dict(valuation_method=valuation_method, has_batch_no=1, create_new_batch=1)
+ dict(valuation_method=valuation_method, has_batch_no=1, create_new_batch=1),
)
- warehouses = [create_warehouse(f"IV - Test Warehouse {i}") for i in ['J', 'K']]
+ warehouses = [create_warehouse(f"IV - Test Warehouse {i}") for i in ["J", "K"]]
batches = [f"IV - Test Batch {i} {valuation_method} {suffix}" for i in batches_list]
for i, batch_id in enumerate(batches):
@@ -847,11 +1166,9 @@
ubw = use_batchwise_valuation
if isinstance(use_batchwise_valuation, (list, tuple)):
ubw = use_batchwise_valuation[i]
- batch = frappe.get_doc(frappe._dict(
- doctype="Batch",
- batch_id=batch_id,
- item=item.item_code,
- use_batchwise_valuation=ubw
+ batch = frappe.get_doc(
+ frappe._dict(
+ doctype="Batch", batch_id=batch_id, item=item.item_code, use_batchwise_valuation=ubw
)
).insert()
batch.use_batchwise_valuation = ubw
@@ -859,8 +1176,10 @@
return item.item_code, warehouses, batches
+
def create_purchase_receipt_entries_for_batchwise_item_valuation_test(pr_entry_list):
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
+
prs = []
for item, warehouse, batch_no, qty, rate in pr_entry_list:
@@ -869,17 +1188,15 @@
return prs
+
def create_delivery_note_entries_for_batchwise_item_valuation_test(dn_entry_list):
from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
+
dns = []
for item, warehouse, batch_no, qty, rate in dn_entry_list:
so = make_sales_order(
- rate=rate,
- qty=qty,
- item=item,
- warehouse=warehouse,
- against_blanket_order=0
+ rate=rate, qty=qty, item=item, warehouse=warehouse, against_blanket_order=0
)
dn = make_delivery_note(so.name)
@@ -889,20 +1206,25 @@
dns.append(dn)
return dns
+
def fetch_sle_details_for_doc_list(doc_list, columns, as_dict=1):
- return frappe.db.sql(f"""
+ return frappe.db.sql(
+ f"""
SELECT { ', '.join(columns)}
FROM `tabStock Ledger Entry`
WHERE
voucher_no IN %(voucher_nos)s
and docstatus = 1
ORDER BY timestamp(posting_date, posting_time) ASC, CREATION ASC
- """, dict(
- voucher_nos=[doc.name for doc in doc_list]
- ), as_dict=as_dict)
+ """,
+ dict(voucher_nos=[doc.name for doc in doc_list]),
+ as_dict=as_dict,
+ )
+
def get_stock_value_from_q(q):
- return sum(r*q for r,q in json.loads(q))
+ return sum(r * q for r, q in json.loads(q))
+
def create_stock_entry_entries_for_batchwise_item_valuation_test(se_entry_list, purpose):
ses = []
@@ -913,23 +1235,17 @@
company="_Test Company",
batch_no=batch,
posting_date=posting_date,
- purpose=purpose
+ purpose=purpose,
)
if purpose == "Material Receipt":
- args.update(
- dict(to_warehouse=target, rate=rate)
- )
+ args.update(dict(to_warehouse=target, rate=rate))
elif purpose == "Material Issue":
- args.update(
- dict(from_warehouse=source)
- )
+ args.update(dict(from_warehouse=source))
elif purpose == "Material Transfer":
- args.update(
- dict(from_warehouse=source, to_warehouse=target)
- )
+ args.update(dict(from_warehouse=source, to_warehouse=target))
else:
raise ValueError(f"Invalid purpose: {purpose}")
@@ -937,6 +1253,7 @@
return ses
+
def get_unique_suffix():
# Used to isolate valuation sensitive
# tests to prevent future tests from failing.
@@ -944,7 +1261,6 @@
class TestDeferredNaming(FrappeTestCase):
-
@classmethod
def setUpClass(cls) -> None:
super().setUpClass()
@@ -957,10 +1273,22 @@
self.company = "_Test Company with perpetual inventory"
def tearDown(self) -> None:
- make_property_setter(doctype="GL Entry", for_doctype=True,
- property="autoname", value=self.gle_autoname, property_type="Data", fieldname=None)
- make_property_setter(doctype="Stock Ledger Entry", for_doctype=True,
- property="autoname", value=self.sle_autoname, property_type="Data", fieldname=None)
+ make_property_setter(
+ doctype="GL Entry",
+ for_doctype=True,
+ property="autoname",
+ value=self.gle_autoname,
+ property_type="Data",
+ fieldname=None,
+ )
+ make_property_setter(
+ doctype="Stock Ledger Entry",
+ for_doctype=True,
+ property="autoname",
+ value=self.sle_autoname,
+ property_type="Data",
+ fieldname=None,
+ )
# since deferred naming autocommits, commit all changes to avoid flake
frappe.db.commit() # nosemgrep
@@ -973,12 +1301,13 @@
return gle, sle
def test_deferred_naming(self):
- se = make_stock_entry(item_code=self.item, to_warehouse=self.warehouse,
- qty=10, rate=100, company=self.company)
+ se = make_stock_entry(
+ item_code=self.item, to_warehouse=self.warehouse, qty=10, rate=100, company=self.company
+ )
gle, sle = self.get_gle_sles(se)
rename_gle_sle_docs()
- renamed_gle, renamed_sle = self.get_gle_sles(se)
+ renamed_gle, renamed_sle = self.get_gle_sles(se)
self.assertFalse(gle & renamed_gle, msg="GLEs not renamed")
self.assertFalse(sle & renamed_sle, msg="SLEs not renamed")
@@ -987,15 +1316,22 @@
def test_hash_naming(self):
# disable naming series
for doctype in ("GL Entry", "Stock Ledger Entry"):
- make_property_setter(doctype=doctype, for_doctype=True,
- property="autoname", value="hash", property_type="Data", fieldname=None)
+ make_property_setter(
+ doctype=doctype,
+ for_doctype=True,
+ property="autoname",
+ value="hash",
+ property_type="Data",
+ fieldname=None,
+ )
- se = make_stock_entry(item_code=self.item, to_warehouse=self.warehouse,
- qty=10, rate=100, company=self.company)
+ se = make_stock_entry(
+ item_code=self.item, to_warehouse=self.warehouse, qty=10, rate=100, company=self.company
+ )
gle, sle = self.get_gle_sles(se)
rename_gle_sle_docs()
- renamed_gle, renamed_sle = self.get_gle_sles(se)
+ renamed_gle, renamed_sle = self.get_gle_sles(se)
self.assertEqual(gle, renamed_gle, msg="GLEs are renamed while using hash naming")
self.assertEqual(sle, renamed_sle, msg="SLEs are renamed while using hash naming")
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 82a8c37..07a8566 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -14,8 +14,13 @@
from erpnext.stock.utils import get_stock_balance
-class OpeningEntryAccountError(frappe.ValidationError): pass
-class EmptyStockReconciliationItemsError(frappe.ValidationError): pass
+class OpeningEntryAccountError(frappe.ValidationError):
+ pass
+
+
+class EmptyStockReconciliationItemsError(frappe.ValidationError):
+ pass
+
class StockReconciliation(StockController):
def __init__(self, *args, **kwargs):
@@ -24,9 +29,11 @@
def validate(self):
if not self.expense_account:
- self.expense_account = frappe.get_cached_value('Company', self.company, "stock_adjustment_account")
+ self.expense_account = frappe.get_cached_value(
+ "Company", self.company, "stock_adjustment_account"
+ )
if not self.cost_center:
- self.cost_center = frappe.get_cached_value('Company', self.company, "cost_center")
+ self.cost_center = frappe.get_cached_value("Company", self.company, "cost_center")
self.validate_posting_time()
self.remove_items_with_no_change()
self.validate_data()
@@ -37,8 +44,8 @@
self.set_total_qty_and_amount()
self.validate_putaway_capacity()
- if self._action=="submit":
- self.make_batches('warehouse')
+ if self._action == "submit":
+ self.make_batches("warehouse")
def on_submit(self):
self.update_stock_ledger()
@@ -46,10 +53,11 @@
self.repost_future_sle_and_gle()
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
+
update_serial_nos_after_submit(self, "items")
def on_cancel(self):
- self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation')
+ self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation")
self.make_sle_on_cancel()
self.make_gl_entries_on_cancel()
self.repost_future_sle_and_gle()
@@ -57,13 +65,17 @@
def remove_items_with_no_change(self):
"""Remove items if qty or rate is not changed"""
self.difference_amount = 0.0
- def _changed(item):
- item_dict = get_stock_balance_for(item.item_code, item.warehouse,
- self.posting_date, self.posting_time, batch_no=item.batch_no)
- if ((item.qty is None or item.qty==item_dict.get("qty")) and
- (item.valuation_rate is None or item.valuation_rate==item_dict.get("rate")) and
- (not item.serial_no or (item.serial_no == item_dict.get("serial_nos")) )):
+ def _changed(item):
+ item_dict = get_stock_balance_for(
+ item.item_code, item.warehouse, self.posting_date, self.posting_time, batch_no=item.batch_no
+ )
+
+ if (
+ (item.qty is None or item.qty == item_dict.get("qty"))
+ and (item.valuation_rate is None or item.valuation_rate == item_dict.get("rate"))
+ and (not item.serial_no or (item.serial_no == item_dict.get("serial_nos")))
+ ):
return False
else:
# set default as current rates
@@ -80,16 +92,20 @@
item.current_qty = item_dict.get("qty")
item.current_valuation_rate = item_dict.get("rate")
- self.difference_amount += (flt(item.qty, item.precision("qty")) * \
- flt(item.valuation_rate or item_dict.get("rate"), item.precision("valuation_rate")) \
- - flt(item_dict.get("qty"), item.precision("qty")) * flt(item_dict.get("rate"), item.precision("valuation_rate")))
+ self.difference_amount += flt(item.qty, item.precision("qty")) * flt(
+ item.valuation_rate or item_dict.get("rate"), item.precision("valuation_rate")
+ ) - flt(item_dict.get("qty"), item.precision("qty")) * flt(
+ item_dict.get("rate"), item.precision("valuation_rate")
+ )
return True
items = list(filter(lambda d: _changed(d), self.items))
if not items:
- frappe.throw(_("None of the items have any change in quantity or value."),
- EmptyStockReconciliationItemsError)
+ frappe.throw(
+ _("None of the items have any change in quantity or value."),
+ EmptyStockReconciliationItemsError,
+ )
elif len(items) != len(self.items):
self.items = items
@@ -99,7 +115,7 @@
def validate_data(self):
def _get_msg(row_num, msg):
- return _("Row # {0}:").format(row_num+1) + " " + msg
+ return _("Row # {0}:").format(row_num + 1) + " " + msg
self.validation_messages = []
item_warehouse_combinations = []
@@ -109,7 +125,7 @@
for row_num, row in enumerate(self.items):
# find duplicates
key = [row.item_code, row.warehouse]
- for field in ['serial_no', 'batch_no']:
+ for field in ["serial_no", "batch_no"]:
if row.get(field):
key.append(row.get(field))
@@ -126,32 +142,35 @@
# if both not specified
if row.qty in ["", None] and row.valuation_rate in ["", None]:
- self.validation_messages.append(_get_msg(row_num,
- _("Please specify either Quantity or Valuation Rate or both")))
+ self.validation_messages.append(
+ _get_msg(row_num, _("Please specify either Quantity or Valuation Rate or both"))
+ )
# do not allow negative quantity
if flt(row.qty) < 0:
- self.validation_messages.append(_get_msg(row_num,
- _("Negative Quantity is not allowed")))
+ self.validation_messages.append(_get_msg(row_num, _("Negative Quantity is not allowed")))
# do not allow negative valuation
if flt(row.valuation_rate) < 0:
- self.validation_messages.append(_get_msg(row_num,
- _("Negative Valuation Rate is not allowed")))
+ self.validation_messages.append(_get_msg(row_num, _("Negative Valuation Rate is not allowed")))
if row.qty and row.valuation_rate in ["", None]:
- row.valuation_rate = get_stock_balance(row.item_code, row.warehouse,
- self.posting_date, self.posting_time, with_valuation_rate=True)[1]
+ row.valuation_rate = get_stock_balance(
+ row.item_code, row.warehouse, self.posting_date, self.posting_time, with_valuation_rate=True
+ )[1]
if not row.valuation_rate:
# try if there is a buying price list in default currency
- buying_rate = frappe.db.get_value("Item Price", {"item_code": row.item_code,
- "buying": 1, "currency": default_currency}, "price_list_rate")
+ buying_rate = frappe.db.get_value(
+ "Item Price",
+ {"item_code": row.item_code, "buying": 1, "currency": default_currency},
+ "price_list_rate",
+ )
if buying_rate:
row.valuation_rate = buying_rate
else:
# get valuation rate from Item
- row.valuation_rate = frappe.get_value('Item', row.item_code, 'valuation_rate')
+ row.valuation_rate = frappe.get_value("Item", row.item_code, "valuation_rate")
# throw all validation messages
if self.validation_messages:
@@ -178,7 +197,9 @@
# item should not be serialized
if item.has_serial_no and not row.serial_no and not item.serial_no_series:
- raise frappe.ValidationError(_("Serial no(s) required for serialized item {0}").format(item_code))
+ raise frappe.ValidationError(
+ _("Serial no(s) required for serialized item {0}").format(item_code)
+ )
# item managed batch-wise not allowed
if item.has_batch_no and not row.batch_no and not item.create_new_batch:
@@ -191,8 +212,8 @@
self.validation_messages.append(_("Row #") + " " + ("%d: " % (row.idx)) + cstr(e))
def update_stock_ledger(self):
- """ find difference between current and expected entries
- and create stock ledger entries based on the difference"""
+ """find difference between current and expected entries
+ and create stock ledger entries based on the difference"""
from erpnext.stock.stock_ledger import get_previous_sle
sl_entries = []
@@ -208,15 +229,20 @@
self.get_sle_for_serialized_items(row, sl_entries)
else:
if row.serial_no or row.batch_no:
- frappe.throw(_("Row #{0}: Item {1} is not a Serialized/Batched Item. It cannot have a Serial No/Batch No against it.") \
- .format(row.idx, frappe.bold(row.item_code)))
+ frappe.throw(
+ _(
+ "Row #{0}: Item {1} is not a Serialized/Batched Item. It cannot have a Serial No/Batch No against it."
+ ).format(row.idx, frappe.bold(row.item_code))
+ )
- previous_sle = get_previous_sle({
- "item_code": row.item_code,
- "warehouse": row.warehouse,
- "posting_date": self.posting_date,
- "posting_time": self.posting_time
- })
+ previous_sle = get_previous_sle(
+ {
+ "item_code": row.item_code,
+ "warehouse": row.warehouse,
+ "posting_date": self.posting_date,
+ "posting_time": self.posting_time,
+ }
+ )
if previous_sle:
if row.qty in ("", None):
@@ -226,12 +252,16 @@
row.valuation_rate = previous_sle.get("valuation_rate", 0)
if row.qty and not row.valuation_rate and not row.allow_zero_valuation_rate:
- frappe.throw(_("Valuation Rate required for Item {0} at row {1}").format(row.item_code, row.idx))
+ frappe.throw(
+ _("Valuation Rate required for Item {0} at row {1}").format(row.item_code, row.idx)
+ )
- if ((previous_sle and row.qty == previous_sle.get("qty_after_transaction")
- and (row.valuation_rate == previous_sle.get("valuation_rate") or row.qty == 0))
- or (not previous_sle and not row.qty)):
- continue
+ if (
+ previous_sle
+ and row.qty == previous_sle.get("qty_after_transaction")
+ and (row.valuation_rate == previous_sle.get("valuation_rate") or row.qty == 0)
+ ) or (not previous_sle and not row.qty):
+ continue
sl_entries.append(self.get_sle_for_items(row))
@@ -253,21 +283,24 @@
serial_nos = get_serial_nos(row.serial_no)
-
# To issue existing serial nos
if row.current_qty and (row.current_serial_no or row.batch_no):
args = self.get_sle_for_items(row)
- args.update({
- 'actual_qty': -1 * row.current_qty,
- 'serial_no': row.current_serial_no,
- 'batch_no': row.batch_no,
- 'valuation_rate': row.current_valuation_rate
- })
+ args.update(
+ {
+ "actual_qty": -1 * row.current_qty,
+ "serial_no": row.current_serial_no,
+ "batch_no": row.batch_no,
+ "valuation_rate": row.current_valuation_rate,
+ }
+ )
if row.current_serial_no:
- args.update({
- 'qty_after_transaction': 0,
- })
+ args.update(
+ {
+ "qty_after_transaction": 0,
+ }
+ )
sl_entries.append(args)
@@ -275,42 +308,49 @@
for serial_no in serial_nos:
args = self.get_sle_for_items(row, [serial_no])
- previous_sle = get_previous_sle({
- "item_code": row.item_code,
- "posting_date": self.posting_date,
- "posting_time": self.posting_time,
- "serial_no": serial_no
- })
+ previous_sle = get_previous_sle(
+ {
+ "item_code": row.item_code,
+ "posting_date": self.posting_date,
+ "posting_time": self.posting_time,
+ "serial_no": serial_no,
+ }
+ )
if previous_sle and row.warehouse != previous_sle.get("warehouse"):
# If serial no exists in different warehouse
- warehouse = previous_sle.get("warehouse", '') or row.warehouse
+ warehouse = previous_sle.get("warehouse", "") or row.warehouse
if not qty_after_transaction:
- qty_after_transaction = get_stock_balance(row.item_code,
- warehouse, self.posting_date, self.posting_time)
+ qty_after_transaction = get_stock_balance(
+ row.item_code, warehouse, self.posting_date, self.posting_time
+ )
qty_after_transaction -= 1
new_args = args.copy()
- new_args.update({
- 'actual_qty': -1,
- 'qty_after_transaction': qty_after_transaction,
- 'warehouse': warehouse,
- 'valuation_rate': previous_sle.get("valuation_rate")
- })
+ new_args.update(
+ {
+ "actual_qty": -1,
+ "qty_after_transaction": qty_after_transaction,
+ "warehouse": warehouse,
+ "valuation_rate": previous_sle.get("valuation_rate"),
+ }
+ )
sl_entries.append(new_args)
if row.qty:
args = self.get_sle_for_items(row)
- args.update({
- 'actual_qty': row.qty,
- 'incoming_rate': row.valuation_rate,
- 'valuation_rate': row.valuation_rate
- })
+ args.update(
+ {
+ "actual_qty": row.qty,
+ "incoming_rate": row.valuation_rate,
+ "valuation_rate": row.valuation_rate,
+ }
+ )
sl_entries.append(args)
@@ -320,7 +360,8 @@
def update_valuation_rate_for_serial_no(self):
for d in self.items:
- if not d.serial_no: continue
+ if not d.serial_no:
+ continue
serial_nos = get_serial_nos(d.serial_no)
self.update_valuation_rate_for_serial_nos(d, serial_nos)
@@ -331,7 +372,7 @@
return
for d in serial_nos:
- frappe.db.set_value("Serial No", d, 'purchase_rate', valuation_rate)
+ frappe.db.set_value("Serial No", d, "purchase_rate", valuation_rate)
def get_sle_for_items(self, row, serial_nos=None):
"""Insert Stock Ledger Entries"""
@@ -339,22 +380,24 @@
if not serial_nos and row.serial_no:
serial_nos = get_serial_nos(row.serial_no)
- data = frappe._dict({
- "doctype": "Stock Ledger Entry",
- "item_code": row.item_code,
- "warehouse": row.warehouse,
- "posting_date": self.posting_date,
- "posting_time": self.posting_time,
- "voucher_type": self.doctype,
- "voucher_no": self.name,
- "voucher_detail_no": row.name,
- "company": self.company,
- "stock_uom": frappe.db.get_value("Item", row.item_code, "stock_uom"),
- "is_cancelled": 1 if self.docstatus == 2 else 0,
- "serial_no": '\n'.join(serial_nos) if serial_nos else '',
- "batch_no": row.batch_no,
- "valuation_rate": flt(row.valuation_rate, row.precision("valuation_rate"))
- })
+ data = frappe._dict(
+ {
+ "doctype": "Stock Ledger Entry",
+ "item_code": row.item_code,
+ "warehouse": row.warehouse,
+ "posting_date": self.posting_date,
+ "posting_time": self.posting_time,
+ "voucher_type": self.doctype,
+ "voucher_no": self.name,
+ "voucher_detail_no": row.name,
+ "company": self.company,
+ "stock_uom": frappe.db.get_value("Item", row.item_code, "stock_uom"),
+ "is_cancelled": 1 if self.docstatus == 2 else 0,
+ "serial_no": "\n".join(serial_nos) if serial_nos else "",
+ "batch_no": row.batch_no,
+ "valuation_rate": flt(row.valuation_rate, row.precision("valuation_rate")),
+ }
+ )
if not row.batch_no:
data.qty_after_transaction = flt(row.qty, row.precision("qty"))
@@ -382,7 +425,7 @@
for row in self.items:
if row.serial_no or row.batch_no or row.current_serial_no:
has_serial_no = True
- serial_nos = ''
+ serial_nos = ""
if row.current_serial_no:
serial_nos = get_serial_nos(row.current_serial_no)
@@ -395,10 +438,11 @@
sl_entries = self.merge_similar_item_serial_nos(sl_entries)
sl_entries.reverse()
- allow_negative_stock = cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock"))
+ allow_negative_stock = cint(
+ frappe.db.get_single_value("Stock Settings", "allow_negative_stock")
+ )
self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock)
-
def merge_similar_item_serial_nos(self, sl_entries):
# If user has put the same item in multiple row with different serial no
new_sl_entries = []
@@ -411,16 +455,16 @@
key = (d.item_code, d.warehouse)
if key not in merge_similar_entries:
- d.total_amount = (d.actual_qty * d.valuation_rate)
+ d.total_amount = d.actual_qty * d.valuation_rate
merge_similar_entries[key] = d
elif d.serial_no:
data = merge_similar_entries[key]
data.actual_qty += d.actual_qty
data.qty_after_transaction += d.qty_after_transaction
- data.total_amount += (d.actual_qty * d.valuation_rate)
+ data.total_amount += d.actual_qty * d.valuation_rate
data.valuation_rate = (data.total_amount) / data.actual_qty
- data.serial_no += '\n' + d.serial_no
+ data.serial_no += "\n" + d.serial_no
data.incoming_rate = (data.total_amount) / data.actual_qty
@@ -433,8 +477,9 @@
if not self.cost_center:
msgprint(_("Please enter Cost Center"), raise_exception=1)
- return super(StockReconciliation, self).get_gl_entries(warehouse_account,
- self.expense_account, self.cost_center)
+ return super(StockReconciliation, self).get_gl_entries(
+ warehouse_account, self.expense_account, self.cost_center
+ )
def validate_expense_account(self):
if not cint(erpnext.is_perpetual_inventory_enabled(self.company)):
@@ -442,29 +487,39 @@
if not self.expense_account:
frappe.throw(_("Please enter Expense Account"))
- elif self.purpose == "Opening Stock" or not frappe.db.sql("""select name from `tabStock Ledger Entry` limit 1"""):
+ elif self.purpose == "Opening Stock" or not frappe.db.sql(
+ """select name from `tabStock Ledger Entry` limit 1"""
+ ):
if frappe.db.get_value("Account", self.expense_account, "report_type") == "Profit and Loss":
- frappe.throw(_("Difference Account must be a Asset/Liability type account, since this Stock Reconciliation is an Opening Entry"), OpeningEntryAccountError)
+ frappe.throw(
+ _(
+ "Difference Account must be a Asset/Liability type account, since this Stock Reconciliation is an Opening Entry"
+ ),
+ OpeningEntryAccountError,
+ )
def set_zero_value_for_customer_provided_items(self):
changed_any_values = False
- for d in self.get('items'):
- is_customer_item = frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item')
+ for d in self.get("items"):
+ is_customer_item = frappe.db.get_value("Item", d.item_code, "is_customer_provided_item")
if is_customer_item and d.valuation_rate:
d.valuation_rate = 0.0
changed_any_values = True
if changed_any_values:
- msgprint(_("Valuation rate for customer provided items has been set to zero."),
- title=_("Note"), indicator="blue")
-
+ msgprint(
+ _("Valuation rate for customer provided items has been set to zero."),
+ title=_("Note"),
+ indicator="blue",
+ )
def set_total_qty_and_amount(self):
for d in self.get("items"):
d.amount = flt(d.qty, d.precision("qty")) * flt(d.valuation_rate, d.precision("valuation_rate"))
- d.current_amount = (flt(d.current_qty,
- d.precision("current_qty")) * flt(d.current_valuation_rate, d.precision("current_valuation_rate")))
+ d.current_amount = flt(d.current_qty, d.precision("current_qty")) * flt(
+ d.current_valuation_rate, d.precision("current_valuation_rate")
+ )
d.quantity_difference = flt(d.qty) - flt(d.current_qty)
d.amount_difference = flt(d.amount) - flt(d.current_amount)
@@ -476,25 +531,33 @@
def submit(self):
if len(self.items) > 100:
- msgprint(_("The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Reconciliation and revert to the Draft stage"))
- self.queue_action('submit', timeout=4600)
+ msgprint(
+ _(
+ "The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Reconciliation and revert to the Draft stage"
+ )
+ )
+ self.queue_action("submit", timeout=4600)
else:
self._submit()
def cancel(self):
if len(self.items) > 100:
- msgprint(_("The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Reconciliation and revert to the Submitted stage"))
- self.queue_action('cancel', timeout=2000)
+ msgprint(
+ _(
+ "The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Reconciliation and revert to the Submitted stage"
+ )
+ )
+ self.queue_action("cancel", timeout=2000)
else:
self._cancel()
+
@frappe.whitelist()
-def get_items(warehouse, posting_date, posting_time, company, item_code=None, ignore_empty_stock=False):
+def get_items(
+ warehouse, posting_date, posting_time, company, item_code=None, ignore_empty_stock=False
+):
ignore_empty_stock = cint(ignore_empty_stock)
- items = [frappe._dict({
- 'item_code': item_code,
- 'warehouse': warehouse
- })]
+ items = [frappe._dict({"item_code": item_code, "warehouse": warehouse})]
if not item_code:
items = get_items_for_stock_reco(warehouse, company)
@@ -504,8 +567,9 @@
for d in items:
if d.item_code in itemwise_batch_data:
- valuation_rate = get_stock_balance(d.item_code, d.warehouse,
- posting_date, posting_time, with_valuation_rate=True)[1]
+ valuation_rate = get_stock_balance(
+ d.item_code, d.warehouse, posting_date, posting_time, with_valuation_rate=True
+ )[1]
for row in itemwise_batch_data.get(d.item_code):
if ignore_empty_stock and not row.qty:
@@ -514,12 +578,22 @@
args = get_item_data(row, row.qty, valuation_rate)
res.append(args)
else:
- stock_bal = get_stock_balance(d.item_code, d.warehouse, posting_date, posting_time,
- with_valuation_rate=True , with_serial_no=cint(d.has_serial_no))
- qty, valuation_rate, serial_no = stock_bal[0], stock_bal[1], stock_bal[2] if cint(d.has_serial_no) else ''
+ stock_bal = get_stock_balance(
+ d.item_code,
+ d.warehouse,
+ posting_date,
+ posting_time,
+ with_valuation_rate=True,
+ with_serial_no=cint(d.has_serial_no),
+ )
+ qty, valuation_rate, serial_no = (
+ stock_bal[0],
+ stock_bal[1],
+ stock_bal[2] if cint(d.has_serial_no) else "",
+ )
if ignore_empty_stock and not stock_bal[0]:
- continue
+ continue
args = get_item_data(d, qty, valuation_rate, serial_no)
@@ -527,9 +601,11 @@
return res
+
def get_items_for_stock_reco(warehouse, company):
lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"])
- items = frappe.db.sql(f"""
+ items = frappe.db.sql(
+ f"""
select
i.name as item_code, i.item_name, bin.warehouse as warehouse, i.has_serial_no, i.has_batch_no
from
@@ -542,9 +618,12 @@
and exists(
select name from `tabWarehouse` where lft >= {lft} and rgt <= {rgt} and name = bin.warehouse
)
- """, as_dict=1)
+ """,
+ as_dict=1,
+ )
- items += frappe.db.sql("""
+ items += frappe.db.sql(
+ """
select
i.name as item_code, i.item_name, id.default_warehouse as warehouse, i.has_serial_no, i.has_batch_no
from
@@ -559,40 +638,50 @@
and IFNULL(i.disabled, 0) = 0
and id.company = %s
group by i.name
- """, (lft, rgt, company), as_dict=1)
+ """,
+ (lft, rgt, company),
+ as_dict=1,
+ )
# remove duplicates
# check if item-warehouse key extracted from each entry exists in set iw_keys
# and update iw_keys
iw_keys = set()
- items = [item for item in items if [(item.item_code, item.warehouse) not in iw_keys, iw_keys.add((item.item_code, item.warehouse))][0]]
+ items = [
+ item
+ for item in items
+ if [
+ (item.item_code, item.warehouse) not in iw_keys,
+ iw_keys.add((item.item_code, item.warehouse)),
+ ][0]
+ ]
return items
+
def get_item_data(row, qty, valuation_rate, serial_no=None):
return {
- 'item_code': row.item_code,
- 'warehouse': row.warehouse,
- 'qty': qty,
- 'item_name': row.item_name,
- 'valuation_rate': valuation_rate,
- 'current_qty': qty,
- 'current_valuation_rate': valuation_rate,
- 'current_serial_no': serial_no,
- 'serial_no': serial_no,
- 'batch_no': row.get('batch_no')
+ "item_code": row.item_code,
+ "warehouse": row.warehouse,
+ "qty": qty,
+ "item_name": row.item_name,
+ "valuation_rate": valuation_rate,
+ "current_qty": qty,
+ "current_valuation_rate": valuation_rate,
+ "current_serial_no": serial_no,
+ "serial_no": serial_no,
+ "batch_no": row.get("batch_no"),
}
+
def get_itemwise_batch(warehouse, posting_date, company, item_code=None):
from erpnext.stock.report.batch_wise_balance_history.batch_wise_balance_history import execute
+
itemwise_batch_data = {}
- filters = frappe._dict({
- 'warehouse': warehouse,
- 'from_date': posting_date,
- 'to_date': posting_date,
- 'company': company
- })
+ filters = frappe._dict(
+ {"warehouse": warehouse, "from_date": posting_date, "to_date": posting_date, "company": company}
+ )
if item_code:
filters.item_code = item_code
@@ -600,23 +689,28 @@
columns, data = execute(filters)
for row in data:
- itemwise_batch_data.setdefault(row[0], []).append(frappe._dict({
- 'item_code': row[0],
- 'warehouse': warehouse,
- 'qty': row[8],
- 'item_name': row[1],
- 'batch_no': row[4]
- }))
+ itemwise_batch_data.setdefault(row[0], []).append(
+ frappe._dict(
+ {
+ "item_code": row[0],
+ "warehouse": warehouse,
+ "qty": row[8],
+ "item_name": row[1],
+ "batch_no": row[4],
+ }
+ )
+ )
return itemwise_batch_data
-@frappe.whitelist()
-def get_stock_balance_for(item_code, warehouse,
- posting_date, posting_time, batch_no=None, with_valuation_rate= True):
- frappe.has_permission("Stock Reconciliation", "write", throw = True)
- item_dict = frappe.db.get_value("Item", item_code,
- ["has_serial_no", "has_batch_no"], as_dict=1)
+@frappe.whitelist()
+def get_stock_balance_for(
+ item_code, warehouse, posting_date, posting_time, batch_no=None, with_valuation_rate=True
+):
+ frappe.has_permission("Stock Reconciliation", "write", throw=True)
+
+ item_dict = frappe.db.get_value("Item", item_code, ["has_serial_no", "has_batch_no"], as_dict=1)
if not item_dict:
# In cases of data upload to Items table
@@ -625,8 +719,14 @@
serial_nos = ""
with_serial_no = True if item_dict.get("has_serial_no") else False
- data = get_stock_balance(item_code, warehouse, posting_date, posting_time,
- with_valuation_rate=with_valuation_rate, with_serial_no=with_serial_no)
+ data = get_stock_balance(
+ item_code,
+ warehouse,
+ posting_date,
+ posting_time,
+ with_valuation_rate=with_valuation_rate,
+ with_serial_no=with_serial_no,
+ )
if with_serial_no:
qty, rate, serial_nos = data
@@ -634,20 +734,20 @@
qty, rate = data
if item_dict.get("has_batch_no"):
- qty = get_batch_qty(batch_no, warehouse, posting_date=posting_date, posting_time=posting_time) or 0
+ qty = (
+ get_batch_qty(batch_no, warehouse, posting_date=posting_date, posting_time=posting_time) or 0
+ )
- return {
- 'qty': qty,
- 'rate': rate,
- 'serial_nos': serial_nos
- }
+ return {"qty": qty, "rate": rate, "serial_nos": serial_nos}
+
@frappe.whitelist()
def get_difference_account(purpose, company):
- if purpose == 'Stock Reconciliation':
+ if purpose == "Stock Reconciliation":
account = get_company_default(company, "stock_adjustment_account")
else:
- account = frappe.db.get_value('Account', {'is_group': 0,
- 'company': company, 'account_type': 'Temporary'}, 'name')
+ account = frappe.db.get_value(
+ "Account", {"is_group": 0, "company": company, "account_type": "Temporary"}, "name"
+ )
return account
diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
index e6b252e..46a3f2a 100644
--- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
@@ -32,7 +32,6 @@
def tearDown(self):
frappe.flags.dont_execute_stock_reposts = None
-
def test_reco_for_fifo(self):
self._test_reco_sle_gle("FIFO")
@@ -40,55 +39,72 @@
self._test_reco_sle_gle("Moving Average")
def _test_reco_sle_gle(self, valuation_method):
- se1, se2, se3 = insert_existing_sle(warehouse='Stores - TCP1')
- company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
+ se1, se2, se3 = insert_existing_sle(warehouse="Stores - TCP1")
+ company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company")
# [[qty, valuation_rate, posting_date,
- # posting_time, expected_stock_value, bin_qty, bin_valuation]]
+ # posting_time, expected_stock_value, bin_qty, bin_valuation]]
input_data = [
[50, 1000, "2012-12-26", "12:00"],
[25, 900, "2012-12-26", "12:00"],
["", 1000, "2012-12-20", "12:05"],
[20, "", "2012-12-26", "12:05"],
- [0, "", "2012-12-31", "12:10"]
+ [0, "", "2012-12-31", "12:10"],
]
for d in input_data:
set_valuation_method("_Test Item", valuation_method)
- last_sle = get_previous_sle({
- "item_code": "_Test Item",
- "warehouse": "Stores - TCP1",
- "posting_date": d[2],
- "posting_time": d[3]
- })
+ last_sle = get_previous_sle(
+ {
+ "item_code": "_Test Item",
+ "warehouse": "Stores - TCP1",
+ "posting_date": d[2],
+ "posting_time": d[3],
+ }
+ )
# submit stock reconciliation
- stock_reco = create_stock_reconciliation(qty=d[0], rate=d[1],
- posting_date=d[2], posting_time=d[3], warehouse="Stores - TCP1",
- company=company, expense_account = "Stock Adjustment - TCP1")
+ stock_reco = create_stock_reconciliation(
+ qty=d[0],
+ rate=d[1],
+ posting_date=d[2],
+ posting_time=d[3],
+ warehouse="Stores - TCP1",
+ company=company,
+ expense_account="Stock Adjustment - TCP1",
+ )
# check stock value
- sle = frappe.db.sql("""select * from `tabStock Ledger Entry`
- where voucher_type='Stock Reconciliation' and voucher_no=%s""", stock_reco.name, as_dict=1)
+ sle = frappe.db.sql(
+ """select * from `tabStock Ledger Entry`
+ where voucher_type='Stock Reconciliation' and voucher_no=%s""",
+ stock_reco.name,
+ as_dict=1,
+ )
qty_after_transaction = flt(d[0]) if d[0] != "" else flt(last_sle.get("qty_after_transaction"))
valuation_rate = flt(d[1]) if d[1] != "" else flt(last_sle.get("valuation_rate"))
- if qty_after_transaction == last_sle.get("qty_after_transaction") \
- and valuation_rate == last_sle.get("valuation_rate"):
- self.assertFalse(sle)
+ if qty_after_transaction == last_sle.get(
+ "qty_after_transaction"
+ ) and valuation_rate == last_sle.get("valuation_rate"):
+ self.assertFalse(sle)
else:
self.assertEqual(flt(sle[0].qty_after_transaction, 1), flt(qty_after_transaction, 1))
self.assertEqual(flt(sle[0].stock_value, 1), flt(qty_after_transaction * valuation_rate, 1))
# no gl entries
- self.assertTrue(frappe.db.get_value("Stock Ledger Entry",
- {"voucher_type": "Stock Reconciliation", "voucher_no": stock_reco.name}))
+ self.assertTrue(
+ frappe.db.get_value(
+ "Stock Ledger Entry", {"voucher_type": "Stock Reconciliation", "voucher_no": stock_reco.name}
+ )
+ )
- acc_bal, stock_bal, wh_list = get_stock_and_account_balance("Stock In Hand - TCP1",
- stock_reco.posting_date, stock_reco.company)
+ acc_bal, stock_bal, wh_list = get_stock_and_account_balance(
+ "Stock In Hand - TCP1", stock_reco.posting_date, stock_reco.company
+ )
self.assertEqual(flt(acc_bal, 1), flt(stock_bal, 1))
stock_reco.cancel()
@@ -98,18 +114,33 @@
se1.cancel()
def test_get_items(self):
- create_warehouse("_Test Warehouse Group 1",
- {"is_group": 1, "company": "_Test Company", "parent_warehouse": "All Warehouses - _TC"})
- create_warehouse("_Test Warehouse Ledger 1",
- {"is_group": 0, "parent_warehouse": "_Test Warehouse Group 1 - _TC", "company": "_Test Company"})
+ create_warehouse(
+ "_Test Warehouse Group 1",
+ {"is_group": 1, "company": "_Test Company", "parent_warehouse": "All Warehouses - _TC"},
+ )
+ create_warehouse(
+ "_Test Warehouse Ledger 1",
+ {
+ "is_group": 0,
+ "parent_warehouse": "_Test Warehouse Group 1 - _TC",
+ "company": "_Test Company",
+ },
+ )
- create_item("_Test Stock Reco Item", is_stock_item=1, valuation_rate=100,
- warehouse="_Test Warehouse Ledger 1 - _TC", opening_stock=100)
+ create_item(
+ "_Test Stock Reco Item",
+ is_stock_item=1,
+ valuation_rate=100,
+ warehouse="_Test Warehouse Ledger 1 - _TC",
+ opening_stock=100,
+ )
items = get_items("_Test Warehouse Group 1 - _TC", nowdate(), nowtime(), "_Test Company")
- self.assertEqual(["_Test Stock Reco Item", "_Test Warehouse Ledger 1 - _TC", 100],
- [items[0]["item_code"], items[0]["warehouse"], items[0]["qty"]])
+ self.assertEqual(
+ ["_Test Stock Reco Item", "_Test Warehouse Ledger 1 - _TC", 100],
+ [items[0]["item_code"], items[0]["warehouse"], items[0]["qty"]],
+ )
def test_stock_reco_for_serialized_item(self):
to_delete_records = []
@@ -119,8 +150,9 @@
serial_item_code = "Stock-Reco-Serial-Item-1"
serial_warehouse = "_Test Warehouse for Stock Reco1 - _TC"
- sr = create_stock_reconciliation(item_code=serial_item_code,
- warehouse = serial_warehouse, qty=5, rate=200)
+ sr = create_stock_reconciliation(
+ item_code=serial_item_code, warehouse=serial_warehouse, qty=5, rate=200
+ )
serial_nos = get_serial_nos(sr.items[0].serial_no)
self.assertEqual(len(serial_nos), 5)
@@ -130,7 +162,7 @@
"warehouse": serial_warehouse,
"posting_date": nowdate(),
"posting_time": nowtime(),
- "serial_no": sr.items[0].serial_no
+ "serial_no": sr.items[0].serial_no,
}
valuation_rate = get_incoming_rate(args)
@@ -138,8 +170,9 @@
to_delete_records.append(sr.name)
- sr = create_stock_reconciliation(item_code=serial_item_code,
- warehouse = serial_warehouse, qty=5, rate=300)
+ sr = create_stock_reconciliation(
+ item_code=serial_item_code, warehouse=serial_warehouse, qty=5, rate=300
+ )
serial_nos1 = get_serial_nos(sr.items[0].serial_no)
self.assertEqual(len(serial_nos1), 5)
@@ -149,7 +182,7 @@
"warehouse": serial_warehouse,
"posting_date": nowdate(),
"posting_time": nowtime(),
- "serial_no": sr.items[0].serial_no
+ "serial_no": sr.items[0].serial_no,
}
valuation_rate = get_incoming_rate(args)
@@ -162,7 +195,6 @@
stock_doc = frappe.get_doc("Stock Reconciliation", d)
stock_doc.cancel()
-
def test_stock_reco_for_merge_serialized_item(self):
to_delete_records = []
@@ -170,23 +202,34 @@
serial_item_code = "Stock-Reco-Serial-Item-2"
serial_warehouse = "_Test Warehouse for Stock Reco1 - _TC"
- sr = create_stock_reconciliation(item_code=serial_item_code, serial_no=random_string(6),
- warehouse = serial_warehouse, qty=1, rate=100, do_not_submit=True, purpose='Opening Stock')
+ sr = create_stock_reconciliation(
+ item_code=serial_item_code,
+ serial_no=random_string(6),
+ warehouse=serial_warehouse,
+ qty=1,
+ rate=100,
+ do_not_submit=True,
+ purpose="Opening Stock",
+ )
for i in range(3):
- sr.append('items', {
- 'item_code': serial_item_code,
- 'warehouse': serial_warehouse,
- 'qty': 1,
- 'valuation_rate': 100,
- 'serial_no': random_string(6)
- })
+ sr.append(
+ "items",
+ {
+ "item_code": serial_item_code,
+ "warehouse": serial_warehouse,
+ "qty": 1,
+ "valuation_rate": 100,
+ "serial_no": random_string(6),
+ },
+ )
sr.save()
sr.submit()
- sle_entries = frappe.get_all('Stock Ledger Entry', filters= {'voucher_no': sr.name},
- fields = ['name', 'incoming_rate'])
+ sle_entries = frappe.get_all(
+ "Stock Ledger Entry", filters={"voucher_no": sr.name}, fields=["name", "incoming_rate"]
+ )
self.assertEqual(len(sle_entries), 1)
self.assertEqual(sle_entries[0].incoming_rate, 100)
@@ -205,8 +248,9 @@
item_code = "Stock-Reco-batch-Item-1"
warehouse = "_Test Warehouse for Stock Reco2 - _TC"
- sr = create_stock_reconciliation(item_code=item_code,
- warehouse = warehouse, qty=5, rate=200, do_not_submit=1)
+ sr = create_stock_reconciliation(
+ item_code=item_code, warehouse=warehouse, qty=5, rate=200, do_not_submit=1
+ )
sr.save()
sr.submit()
@@ -214,8 +258,9 @@
self.assertTrue(batch_no)
to_delete_records.append(sr.name)
- sr1 = create_stock_reconciliation(item_code=item_code,
- warehouse = warehouse, qty=6, rate=300, batch_no=batch_no)
+ sr1 = create_stock_reconciliation(
+ item_code=item_code, warehouse=warehouse, qty=6, rate=300, batch_no=batch_no
+ )
args = {
"item_code": item_code,
@@ -229,9 +274,9 @@
self.assertEqual(valuation_rate, 300)
to_delete_records.append(sr1.name)
-
- sr2 = create_stock_reconciliation(item_code=item_code,
- warehouse = warehouse, qty=0, rate=0, batch_no=batch_no)
+ sr2 = create_stock_reconciliation(
+ item_code=item_code, warehouse=warehouse, qty=0, rate=0, batch_no=batch_no
+ )
stock_value = get_stock_value_on(warehouse, nowdate(), item_code)
self.assertEqual(stock_value, 0)
@@ -243,11 +288,12 @@
stock_doc.cancel()
def test_customer_provided_items(self):
- item_code = 'Stock-Reco-customer-Item-100'
- create_item(item_code, is_customer_provided_item = 1,
- customer = '_Test Customer', is_purchase_item = 0)
+ item_code = "Stock-Reco-customer-Item-100"
+ create_item(
+ item_code, is_customer_provided_item=1, customer="_Test Customer", is_purchase_item=0
+ )
- sr = create_stock_reconciliation(item_code = item_code, qty = 10, rate = 420)
+ sr = create_stock_reconciliation(item_code=item_code, qty=10, rate=420)
self.assertEqual(sr.get("items")[0].allow_zero_valuation_rate, 1)
self.assertEqual(sr.get("items")[0].valuation_rate, 0)
@@ -255,65 +301,79 @@
def test_backdated_stock_reco_qty_reposting(self):
"""
- Test if a backdated stock reco recalculates future qty until next reco.
- -------------------------------------------
- Var | Doc | Qty | Balance
- -------------------------------------------
- SR5 | Reco | 0 | 8 (posting date: today-4) [backdated]
- PR1 | PR | 10 | 18 (posting date: today-3)
- PR2 | PR | 1 | 19 (posting date: today-2)
- SR4 | Reco | 0 | 6 (posting date: today-1) [backdated]
- PR3 | PR | 1 | 7 (posting date: today) # can't post future PR
+ Test if a backdated stock reco recalculates future qty until next reco.
+ -------------------------------------------
+ Var | Doc | Qty | Balance
+ -------------------------------------------
+ SR5 | Reco | 0 | 8 (posting date: today-4) [backdated]
+ PR1 | PR | 10 | 18 (posting date: today-3)
+ PR2 | PR | 1 | 19 (posting date: today-2)
+ SR4 | Reco | 0 | 6 (posting date: today-1) [backdated]
+ PR3 | PR | 1 | 7 (posting date: today) # can't post future PR
"""
item_code = "Backdated-Reco-Item"
warehouse = "_Test Warehouse - _TC"
create_item(item_code)
- pr1 = make_purchase_receipt(item_code=item_code, warehouse=warehouse, qty=10, rate=100,
- posting_date=add_days(nowdate(), -3))
- pr2 = make_purchase_receipt(item_code=item_code, warehouse=warehouse, qty=1, rate=100,
- posting_date=add_days(nowdate(), -2))
- pr3 = make_purchase_receipt(item_code=item_code, warehouse=warehouse, qty=1, rate=100,
- posting_date=nowdate())
+ pr1 = make_purchase_receipt(
+ item_code=item_code, warehouse=warehouse, qty=10, rate=100, posting_date=add_days(nowdate(), -3)
+ )
+ pr2 = make_purchase_receipt(
+ item_code=item_code, warehouse=warehouse, qty=1, rate=100, posting_date=add_days(nowdate(), -2)
+ )
+ pr3 = make_purchase_receipt(
+ item_code=item_code, warehouse=warehouse, qty=1, rate=100, posting_date=nowdate()
+ )
- pr1_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": pr1.name, "is_cancelled": 0},
- "qty_after_transaction")
- pr3_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": pr3.name, "is_cancelled": 0},
- "qty_after_transaction")
+ pr1_balance = frappe.db.get_value(
+ "Stock Ledger Entry", {"voucher_no": pr1.name, "is_cancelled": 0}, "qty_after_transaction"
+ )
+ pr3_balance = frappe.db.get_value(
+ "Stock Ledger Entry", {"voucher_no": pr3.name, "is_cancelled": 0}, "qty_after_transaction"
+ )
self.assertEqual(pr1_balance, 10)
self.assertEqual(pr3_balance, 12)
# post backdated stock reco in between
- sr4 = create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=6, rate=100,
- posting_date=add_days(nowdate(), -1))
- pr3_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": pr3.name, "is_cancelled": 0},
- "qty_after_transaction")
+ sr4 = create_stock_reconciliation(
+ item_code=item_code, warehouse=warehouse, qty=6, rate=100, posting_date=add_days(nowdate(), -1)
+ )
+ pr3_balance = frappe.db.get_value(
+ "Stock Ledger Entry", {"voucher_no": pr3.name, "is_cancelled": 0}, "qty_after_transaction"
+ )
self.assertEqual(pr3_balance, 7)
# post backdated stock reco at the start
- sr5 = create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=8, rate=100,
- posting_date=add_days(nowdate(), -4))
- pr1_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": pr1.name, "is_cancelled": 0},
- "qty_after_transaction")
- pr2_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": pr2.name, "is_cancelled": 0},
- "qty_after_transaction")
- sr4_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": sr4.name, "is_cancelled": 0},
- "qty_after_transaction")
+ sr5 = create_stock_reconciliation(
+ item_code=item_code, warehouse=warehouse, qty=8, rate=100, posting_date=add_days(nowdate(), -4)
+ )
+ pr1_balance = frappe.db.get_value(
+ "Stock Ledger Entry", {"voucher_no": pr1.name, "is_cancelled": 0}, "qty_after_transaction"
+ )
+ pr2_balance = frappe.db.get_value(
+ "Stock Ledger Entry", {"voucher_no": pr2.name, "is_cancelled": 0}, "qty_after_transaction"
+ )
+ sr4_balance = frappe.db.get_value(
+ "Stock Ledger Entry", {"voucher_no": sr4.name, "is_cancelled": 0}, "qty_after_transaction"
+ )
self.assertEqual(pr1_balance, 18)
self.assertEqual(pr2_balance, 19)
- self.assertEqual(sr4_balance, 6) # check if future stock reco is unaffected
+ self.assertEqual(sr4_balance, 6) # check if future stock reco is unaffected
# cancel backdated stock reco and check future impact
sr5.cancel()
- pr1_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": pr1.name, "is_cancelled": 0},
- "qty_after_transaction")
- pr2_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": pr2.name, "is_cancelled": 0},
- "qty_after_transaction")
- sr4_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": sr4.name, "is_cancelled": 0},
- "qty_after_transaction")
+ pr1_balance = frappe.db.get_value(
+ "Stock Ledger Entry", {"voucher_no": pr1.name, "is_cancelled": 0}, "qty_after_transaction"
+ )
+ pr2_balance = frappe.db.get_value(
+ "Stock Ledger Entry", {"voucher_no": pr2.name, "is_cancelled": 0}, "qty_after_transaction"
+ )
+ sr4_balance = frappe.db.get_value(
+ "Stock Ledger Entry", {"voucher_no": sr4.name, "is_cancelled": 0}, "qty_after_transaction"
+ )
self.assertEqual(pr1_balance, 10)
self.assertEqual(pr2_balance, 11)
- self.assertEqual(sr4_balance, 6) # check if future stock reco is unaffected
+ self.assertEqual(sr4_balance, 6) # check if future stock reco is unaffected
# teardown
sr4.cancel()
@@ -324,13 +384,13 @@
@change_settings("Stock Settings", {"allow_negative_stock": 0})
def test_backdated_stock_reco_future_negative_stock(self):
"""
- Test if a backdated stock reco causes future negative stock and is blocked.
- -------------------------------------------
- Var | Doc | Qty | Balance
- -------------------------------------------
- PR1 | PR | 10 | 10 (posting date: today-2)
- SR3 | Reco | 0 | 1 (posting date: today-1) [backdated & blocked]
- DN2 | DN | -2 | 8(-1) (posting date: today)
+ Test if a backdated stock reco causes future negative stock and is blocked.
+ -------------------------------------------
+ Var | Doc | Qty | Balance
+ -------------------------------------------
+ PR1 | PR | 10 | 10 (posting date: today-2)
+ SR3 | Reco | 0 | 1 (posting date: today-1) [backdated & blocked]
+ DN2 | DN | -2 | 8(-1) (posting date: today)
"""
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
from erpnext.stock.stock_ledger import NegativeStockError
@@ -339,22 +399,31 @@
warehouse = "_Test Warehouse - _TC"
create_item(item_code)
+ pr1 = make_purchase_receipt(
+ item_code=item_code, warehouse=warehouse, qty=10, rate=100, posting_date=add_days(nowdate(), -2)
+ )
+ dn2 = create_delivery_note(
+ item_code=item_code, warehouse=warehouse, qty=2, rate=120, posting_date=nowdate()
+ )
- pr1 = make_purchase_receipt(item_code=item_code, warehouse=warehouse, qty=10, rate=100,
- posting_date=add_days(nowdate(), -2))
- dn2 = create_delivery_note(item_code=item_code, warehouse=warehouse, qty=2, rate=120,
- posting_date=nowdate())
-
- pr1_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": pr1.name, "is_cancelled": 0},
- "qty_after_transaction")
- dn2_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": dn2.name, "is_cancelled": 0},
- "qty_after_transaction")
+ pr1_balance = frappe.db.get_value(
+ "Stock Ledger Entry", {"voucher_no": pr1.name, "is_cancelled": 0}, "qty_after_transaction"
+ )
+ dn2_balance = frappe.db.get_value(
+ "Stock Ledger Entry", {"voucher_no": dn2.name, "is_cancelled": 0}, "qty_after_transaction"
+ )
self.assertEqual(pr1_balance, 10)
self.assertEqual(dn2_balance, 8)
# check if stock reco is blocked
- sr3 = create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=1, rate=100,
- posting_date=add_days(nowdate(), -1), do_not_submit=True)
+ sr3 = create_stock_reconciliation(
+ item_code=item_code,
+ warehouse=warehouse,
+ qty=1,
+ rate=100,
+ posting_date=add_days(nowdate(), -1),
+ do_not_submit=True,
+ )
self.assertRaises(NegativeStockError, sr3.submit)
# teardown
@@ -362,16 +431,15 @@
dn2.cancel()
pr1.cancel()
-
@change_settings("Stock Settings", {"allow_negative_stock": 0})
def test_backdated_stock_reco_cancellation_future_negative_stock(self):
"""
- Test if a backdated stock reco cancellation that causes future negative stock is blocked.
- -------------------------------------------
- Var | Doc | Qty | Balance
- -------------------------------------------
- SR | Reco | 100 | 100 (posting date: today-1) (shouldn't be cancelled after DN)
- DN | DN | 100 | 0 (posting date: today)
+ Test if a backdated stock reco cancellation that causes future negative stock is blocked.
+ -------------------------------------------
+ Var | Doc | Qty | Balance
+ -------------------------------------------
+ SR | Reco | 100 | 100 (posting date: today-1) (shouldn't be cancelled after DN)
+ DN | DN | 100 | 0 (posting date: today)
"""
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
from erpnext.stock.stock_ledger import NegativeStockError
@@ -380,15 +448,21 @@
warehouse = "_Test Warehouse - _TC"
create_item(item_code)
+ sr = create_stock_reconciliation(
+ item_code=item_code,
+ warehouse=warehouse,
+ qty=100,
+ rate=100,
+ posting_date=add_days(nowdate(), -1),
+ )
- sr = create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=100, rate=100,
- posting_date=add_days(nowdate(), -1))
+ dn = create_delivery_note(
+ item_code=item_code, warehouse=warehouse, qty=100, rate=120, posting_date=nowdate()
+ )
- dn = create_delivery_note(item_code=item_code, warehouse=warehouse, qty=100, rate=120,
- posting_date=nowdate())
-
- dn_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": dn.name, "is_cancelled": 0},
- "qty_after_transaction")
+ dn_balance = frappe.db.get_value(
+ "Stock Ledger Entry", {"voucher_no": dn.name, "is_cancelled": 0}, "qty_after_transaction"
+ )
self.assertEqual(dn_balance, 0)
# check if cancellation of stock reco is blocked
@@ -400,12 +474,12 @@
def test_intermediate_sr_bin_update(self):
"""Bin should show correct qty even for backdated entries.
- -------------------------------------------
- | creation | Var | Doc | Qty | balance qty
- -------------------------------------------
- | 1 | SR | Reco | 10 | 10 (posting date: today+10)
- | 3 | SR2 | Reco | 11 | 11 (posting date: today+11)
- | 2 | DN | DN | 5 | 6 <-- assert in BIN (posting date: today+12)
+ -------------------------------------------
+ | creation | Var | Doc | Qty | balance qty
+ -------------------------------------------
+ | 1 | SR | Reco | 10 | 10 (posting date: today+10)
+ | 3 | SR2 | Reco | 11 | 11 (posting date: today+11)
+ | 2 | DN | DN | 5 | 6 <-- assert in BIN (posting date: today+12)
"""
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
@@ -417,26 +491,33 @@
warehouse = "_Test Warehouse - _TC"
create_item(item_code)
- sr = create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=10, rate=100,
- posting_date=add_days(nowdate(), 10))
+ sr = create_stock_reconciliation(
+ item_code=item_code, warehouse=warehouse, qty=10, rate=100, posting_date=add_days(nowdate(), 10)
+ )
- dn = create_delivery_note(item_code=item_code, warehouse=warehouse, qty=5, rate=120,
- posting_date=add_days(nowdate(), 12))
- old_bin_qty = frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "actual_qty")
+ dn = create_delivery_note(
+ item_code=item_code, warehouse=warehouse, qty=5, rate=120, posting_date=add_days(nowdate(), 12)
+ )
+ old_bin_qty = frappe.db.get_value(
+ "Bin", {"item_code": item_code, "warehouse": warehouse}, "actual_qty"
+ )
- sr2 = create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=11, rate=100,
- posting_date=add_days(nowdate(), 11))
- new_bin_qty = frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "actual_qty")
+ sr2 = create_stock_reconciliation(
+ item_code=item_code, warehouse=warehouse, qty=11, rate=100, posting_date=add_days(nowdate(), 11)
+ )
+ new_bin_qty = frappe.db.get_value(
+ "Bin", {"item_code": item_code, "warehouse": warehouse}, "actual_qty"
+ )
self.assertEqual(old_bin_qty + 1, new_bin_qty)
frappe.db.rollback()
-
def test_valid_batch(self):
create_batch_item_with_batch("Testing Batch Item 1", "001")
create_batch_item_with_batch("Testing Batch Item 2", "002")
- sr = create_stock_reconciliation(item_code="Testing Batch Item 1", qty=1, rate=100, batch_no="002"
- , do_not_submit=True)
+ sr = create_stock_reconciliation(
+ item_code="Testing Batch Item 1", qty=1, rate=100, batch_no="002", do_not_submit=True
+ )
self.assertRaises(frappe.ValidationError, sr.submit)
def test_serial_no_cancellation(self):
@@ -458,15 +539,17 @@
serial_nos.pop()
new_serial_nos = "\n".join(serial_nos)
- sr = create_stock_reconciliation(item_code=item.name, warehouse=warehouse, serial_no=new_serial_nos, qty=9)
+ sr = create_stock_reconciliation(
+ item_code=item.name, warehouse=warehouse, serial_no=new_serial_nos, qty=9
+ )
sr.cancel()
- active_sr_no = frappe.get_all("Serial No",
- filters={"item_code": item_code, "warehouse": warehouse, "status": "Active"})
+ active_sr_no = frappe.get_all(
+ "Serial No", filters={"item_code": item_code, "warehouse": warehouse, "status": "Active"}
+ )
self.assertEqual(len(active_sr_no), 10)
-
def test_serial_no_creation_and_inactivation(self):
item = create_item("_TestItemCreatedWithStockReco", is_stock_item=1)
if not item.has_serial_no:
@@ -476,19 +559,27 @@
item_code = item.name
warehouse = "_Test Warehouse - _TC"
- sr = create_stock_reconciliation(item_code=item.name, warehouse=warehouse,
- serial_no="SR-CREATED-SR-NO", qty=1, do_not_submit=True, rate=100)
+ sr = create_stock_reconciliation(
+ item_code=item.name,
+ warehouse=warehouse,
+ serial_no="SR-CREATED-SR-NO",
+ qty=1,
+ do_not_submit=True,
+ rate=100,
+ )
sr.save()
self.assertEqual(cstr(sr.items[0].current_serial_no), "")
sr.submit()
- active_sr_no = frappe.get_all("Serial No",
- filters={"item_code": item_code, "warehouse": warehouse, "status": "Active"})
+ active_sr_no = frappe.get_all(
+ "Serial No", filters={"item_code": item_code, "warehouse": warehouse, "status": "Active"}
+ )
self.assertEqual(len(active_sr_no), 1)
sr.cancel()
- active_sr_no = frappe.get_all("Serial No",
- filters={"item_code": item_code, "warehouse": warehouse, "status": "Active"})
+ active_sr_no = frappe.get_all(
+ "Serial No", filters={"item_code": item_code, "warehouse": warehouse, "status": "Active"}
+ )
self.assertEqual(len(active_sr_no), 0)
@@ -499,32 +590,51 @@
batch_item_doc.create_new_batch = 1
batch_item_doc.save(ignore_permissions=True)
- if not frappe.db.exists('Batch', batch_id):
- b = frappe.new_doc('Batch')
+ if not frappe.db.exists("Batch", batch_id):
+ b = frappe.new_doc("Batch")
b.item = item_name
b.batch_id = batch_id
b.save()
+
def insert_existing_sle(warehouse):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
- se1 = make_stock_entry(posting_date="2012-12-15", posting_time="02:00", item_code="_Test Item",
- target=warehouse, qty=10, basic_rate=700)
+ se1 = make_stock_entry(
+ posting_date="2012-12-15",
+ posting_time="02:00",
+ item_code="_Test Item",
+ target=warehouse,
+ qty=10,
+ basic_rate=700,
+ )
- se2 = make_stock_entry(posting_date="2012-12-25", posting_time="03:00", item_code="_Test Item",
- source=warehouse, qty=15)
+ se2 = make_stock_entry(
+ posting_date="2012-12-25", posting_time="03:00", item_code="_Test Item", source=warehouse, qty=15
+ )
- se3 = make_stock_entry(posting_date="2013-01-05", posting_time="07:00", item_code="_Test Item",
- target=warehouse, qty=15, basic_rate=1200)
+ se3 = make_stock_entry(
+ posting_date="2013-01-05",
+ posting_time="07:00",
+ item_code="_Test Item",
+ target=warehouse,
+ qty=15,
+ basic_rate=1200,
+ )
return se1, se2, se3
-def create_batch_or_serial_no_items():
- create_warehouse("_Test Warehouse for Stock Reco1",
- {"is_group": 0, "parent_warehouse": "_Test Warehouse Group - _TC"})
- create_warehouse("_Test Warehouse for Stock Reco2",
- {"is_group": 0, "parent_warehouse": "_Test Warehouse Group - _TC"})
+def create_batch_or_serial_no_items():
+ create_warehouse(
+ "_Test Warehouse for Stock Reco1",
+ {"is_group": 0, "parent_warehouse": "_Test Warehouse Group - _TC"},
+ )
+
+ create_warehouse(
+ "_Test Warehouse for Stock Reco2",
+ {"is_group": 0, "parent_warehouse": "_Test Warehouse Group - _TC"},
+ )
serial_item_doc = create_item("Stock-Reco-Serial-Item-1", is_stock_item=1)
if not serial_item_doc.has_serial_no:
@@ -545,6 +655,7 @@
serial_item_doc.batch_number_series = "BASR.#####"
batch_item_doc.save(ignore_permissions=True)
+
def create_stock_reconciliation(**args):
args = frappe._dict(args)
sr = frappe.new_doc("Stock Reconciliation")
@@ -553,20 +664,26 @@
sr.posting_time = args.posting_time or nowtime()
sr.set_posting_time = 1
sr.company = args.company or "_Test Company"
- sr.expense_account = args.expense_account or \
- ("Stock Adjustment - _TC" if frappe.get_all("Stock Ledger Entry") else "Temporary Opening - _TC")
- sr.cost_center = args.cost_center \
- or frappe.get_cached_value("Company", sr.company, "cost_center") \
+ sr.expense_account = args.expense_account or (
+ "Stock Adjustment - _TC" if frappe.get_all("Stock Ledger Entry") else "Temporary Opening - _TC"
+ )
+ sr.cost_center = (
+ args.cost_center
+ or frappe.get_cached_value("Company", sr.company, "cost_center")
or "_Test Cost Center - _TC"
+ )
- sr.append("items", {
- "item_code": args.item_code or "_Test Item",
- "warehouse": args.warehouse or "_Test Warehouse - _TC",
- "qty": args.qty,
- "valuation_rate": args.rate,
- "serial_no": args.serial_no,
- "batch_no": args.batch_no
- })
+ sr.append(
+ "items",
+ {
+ "item_code": args.item_code or "_Test Item",
+ "warehouse": args.warehouse or "_Test Warehouse - _TC",
+ "qty": args.qty,
+ "valuation_rate": args.rate,
+ "serial_no": args.serial_no,
+ "batch_no": args.batch_no,
+ },
+ )
try:
if not args.do_not_submit:
@@ -575,6 +692,7 @@
pass
return sr
+
def set_valuation_method(item_code, valuation_method):
existing_valuation_method = get_valuation_method(item_code)
if valuation_method == existing_valuation_method:
@@ -582,11 +700,13 @@
frappe.db.set_value("Item", item_code, "valuation_method", valuation_method)
- for warehouse in frappe.get_all("Warehouse", filters={"company": "_Test Company"}, fields=["name", "is_group"]):
+ for warehouse in frappe.get_all(
+ "Warehouse", filters={"company": "_Test Company"}, fields=["name", "is_group"]
+ ):
if not warehouse.is_group:
- update_entries_after({
- "item_code": item_code,
- "warehouse": warehouse.name
- }, allow_negative_stock=1)
+ update_entries_after(
+ {"item_code": item_code, "warehouse": warehouse.name}, allow_negative_stock=1
+ )
+
test_dependencies = ["Item", "Warehouse"]
diff --git a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.py b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.py
index bab521d..e0c8ed1 100644
--- a/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.py
+++ b/erpnext/stock/doctype/stock_reposting_settings/stock_reposting_settings.py
@@ -6,8 +6,6 @@
class StockRepostingSettings(Document):
-
-
def validate(self):
self.set_minimum_reposting_time_slot()
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.py b/erpnext/stock/doctype/stock_settings/stock_settings.py
index c1293cb..e592a4b 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.py
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.py
@@ -16,24 +16,40 @@
class StockSettings(Document):
def validate(self):
- for key in ["item_naming_by", "item_group", "stock_uom",
- "allow_negative_stock", "default_warehouse", "set_qty_in_transactions_based_on_serial_no_input"]:
- frappe.db.set_default(key, self.get(key, ""))
+ for key in [
+ "item_naming_by",
+ "item_group",
+ "stock_uom",
+ "allow_negative_stock",
+ "default_warehouse",
+ "set_qty_in_transactions_based_on_serial_no_input",
+ ]:
+ frappe.db.set_default(key, self.get(key, ""))
from erpnext.setup.doctype.naming_series.naming_series import set_by_naming_series
- set_by_naming_series("Item", "item_code",
- self.get("item_naming_by")=="Naming Series", hide_name_field=True, make_mandatory=0)
+
+ set_by_naming_series(
+ "Item",
+ "item_code",
+ self.get("item_naming_by") == "Naming Series",
+ hide_name_field=True,
+ make_mandatory=0,
+ )
stock_frozen_limit = 356
submitted_stock_frozen = self.stock_frozen_upto_days or 0
if submitted_stock_frozen > stock_frozen_limit:
self.stock_frozen_upto_days = stock_frozen_limit
- frappe.msgprint (_("`Freeze Stocks Older Than` should be smaller than %d days.") %stock_frozen_limit)
+ frappe.msgprint(
+ _("`Freeze Stocks Older Than` should be smaller than %d days.") % stock_frozen_limit
+ )
# show/hide barcode field
for name in ["barcode", "barcodes", "scan_barcode"]:
- frappe.make_property_setter({'fieldname': name, 'property': 'hidden',
- 'value': 0 if self.show_barcode_field else 1}, validate_fields_for_doctype=False)
+ frappe.make_property_setter(
+ {"fieldname": name, "property": "hidden", "value": 0 if self.show_barcode_field else 1},
+ validate_fields_for_doctype=False,
+ )
self.validate_warehouses()
self.cant_change_valuation_method()
@@ -44,8 +60,12 @@
warehouse_fields = ["default_warehouse", "sample_retention_warehouse"]
for field in warehouse_fields:
if frappe.db.get_value("Warehouse", self.get(field), "is_group"):
- frappe.throw(_("Group Warehouses cannot be used in transactions. Please change the value of {0}") \
- .format(frappe.bold(self.meta.get_field(field).label)), title =_("Incorrect Warehouse"))
+ frappe.throw(
+ _("Group Warehouses cannot be used in transactions. Please change the value of {0}").format(
+ frappe.bold(self.meta.get_field(field).label)
+ ),
+ title=_("Incorrect Warehouse"),
+ )
def cant_change_valuation_method(self):
db_valuation_method = frappe.db.get_single_value("Stock Settings", "valuation_method")
@@ -53,38 +73,73 @@
if db_valuation_method and db_valuation_method != self.valuation_method:
# check if there are any stock ledger entries against items
# which does not have it's own valuation method
- sle = frappe.db.sql("""select name from `tabStock Ledger Entry` sle
+ sle = frappe.db.sql(
+ """select name from `tabStock Ledger Entry` sle
where exists(select name from tabItem
where name=sle.item_code and (valuation_method is null or valuation_method='')) limit 1
- """)
+ """
+ )
if sle:
- frappe.throw(_("Can't change the valuation method, as there are transactions against some items which do not have its own valuation method"))
+ frappe.throw(
+ _(
+ "Can't change the valuation method, as there are transactions against some items which do not have its own valuation method"
+ )
+ )
def validate_clean_description_html(self):
- if int(self.clean_description_html or 0) \
- and not int(self.db_get('clean_description_html') or 0):
+ if int(self.clean_description_html or 0) and not int(self.db_get("clean_description_html") or 0):
# changed to text
- frappe.enqueue('erpnext.stock.doctype.stock_settings.stock_settings.clean_all_descriptions', now=frappe.flags.in_test)
+ frappe.enqueue(
+ "erpnext.stock.doctype.stock_settings.stock_settings.clean_all_descriptions",
+ now=frappe.flags.in_test,
+ )
def validate_pending_reposts(self):
if self.stock_frozen_upto:
check_pending_reposting(self.stock_frozen_upto)
-
def on_update(self):
self.toggle_warehouse_field_for_inter_warehouse_transfer()
def toggle_warehouse_field_for_inter_warehouse_transfer(self):
- make_property_setter("Sales Invoice Item", "target_warehouse", "hidden", 1 - cint(self.allow_from_dn), "Check", validate_fields_for_doctype=False)
- make_property_setter("Delivery Note Item", "target_warehouse", "hidden", 1 - cint(self.allow_from_dn), "Check", validate_fields_for_doctype=False)
- make_property_setter("Purchase Invoice Item", "from_warehouse", "hidden", 1 - cint(self.allow_from_pr), "Check", validate_fields_for_doctype=False)
- make_property_setter("Purchase Receipt Item", "from_warehouse", "hidden", 1 - cint(self.allow_from_pr), "Check", validate_fields_for_doctype=False)
+ make_property_setter(
+ "Sales Invoice Item",
+ "target_warehouse",
+ "hidden",
+ 1 - cint(self.allow_from_dn),
+ "Check",
+ validate_fields_for_doctype=False,
+ )
+ make_property_setter(
+ "Delivery Note Item",
+ "target_warehouse",
+ "hidden",
+ 1 - cint(self.allow_from_dn),
+ "Check",
+ validate_fields_for_doctype=False,
+ )
+ make_property_setter(
+ "Purchase Invoice Item",
+ "from_warehouse",
+ "hidden",
+ 1 - cint(self.allow_from_pr),
+ "Check",
+ validate_fields_for_doctype=False,
+ )
+ make_property_setter(
+ "Purchase Receipt Item",
+ "from_warehouse",
+ "hidden",
+ 1 - cint(self.allow_from_pr),
+ "Check",
+ validate_fields_for_doctype=False,
+ )
def clean_all_descriptions():
- for item in frappe.get_all('Item', ['name', 'description']):
+ for item in frappe.get_all("Item", ["name", "description"]):
if item.description:
clean_description = clean_html(item.description)
if item.description != clean_description:
- frappe.db.set_value('Item', item.name, 'description', clean_description)
+ frappe.db.set_value("Item", item.name, "description", clean_description)
diff --git a/erpnext/stock/doctype/stock_settings/test_stock_settings.py b/erpnext/stock/doctype/stock_settings/test_stock_settings.py
index 1349671..974e163 100644
--- a/erpnext/stock/doctype/stock_settings/test_stock_settings.py
+++ b/erpnext/stock/doctype/stock_settings/test_stock_settings.py
@@ -13,35 +13,45 @@
frappe.db.set_value("Stock Settings", None, "clean_description_html", 0)
def test_settings(self):
- item = frappe.get_doc(dict(
- doctype = 'Item',
- item_code = 'Item for description test',
- item_group = 'Products',
- description = '<p><span style="font-size: 12px;">Drawing No. 07-xxx-PO132<br></span><span style="font-size: 12px;">1800 x 1685 x 750<br></span><span style="font-size: 12px;">All parts made of Marine Ply<br></span><span style="font-size: 12px;">Top w/ Corian dd<br></span><span style="font-size: 12px;">CO, CS, VIP Day Cabin</span></p>'
- )).insert()
+ item = frappe.get_doc(
+ dict(
+ doctype="Item",
+ item_code="Item for description test",
+ item_group="Products",
+ description='<p><span style="font-size: 12px;">Drawing No. 07-xxx-PO132<br></span><span style="font-size: 12px;">1800 x 1685 x 750<br></span><span style="font-size: 12px;">All parts made of Marine Ply<br></span><span style="font-size: 12px;">Top w/ Corian dd<br></span><span style="font-size: 12px;">CO, CS, VIP Day Cabin</span></p>',
+ )
+ ).insert()
- settings = frappe.get_single('Stock Settings')
+ settings = frappe.get_single("Stock Settings")
settings.clean_description_html = 1
settings.save()
item.reload()
- self.assertEqual(item.description, '<p>Drawing No. 07-xxx-PO132<br>1800 x 1685 x 750<br>All parts made of Marine Ply<br>Top w/ Corian dd<br>CO, CS, VIP Day Cabin</p>')
+ self.assertEqual(
+ item.description,
+ "<p>Drawing No. 07-xxx-PO132<br>1800 x 1685 x 750<br>All parts made of Marine Ply<br>Top w/ Corian dd<br>CO, CS, VIP Day Cabin</p>",
+ )
item.delete()
def test_clean_html(self):
- settings = frappe.get_single('Stock Settings')
+ settings = frappe.get_single("Stock Settings")
settings.clean_description_html = 1
settings.save()
- item = frappe.get_doc(dict(
- doctype = 'Item',
- item_code = 'Item for description test',
- item_group = 'Products',
- description = '<p><span style="font-size: 12px;">Drawing No. 07-xxx-PO132<br></span><span style="font-size: 12px;">1800 x 1685 x 750<br></span><span style="font-size: 12px;">All parts made of Marine Ply<br></span><span style="font-size: 12px;">Top w/ Corian dd<br></span><span style="font-size: 12px;">CO, CS, VIP Day Cabin</span></p>'
- )).insert()
+ item = frappe.get_doc(
+ dict(
+ doctype="Item",
+ item_code="Item for description test",
+ item_group="Products",
+ description='<p><span style="font-size: 12px;">Drawing No. 07-xxx-PO132<br></span><span style="font-size: 12px;">1800 x 1685 x 750<br></span><span style="font-size: 12px;">All parts made of Marine Ply<br></span><span style="font-size: 12px;">Top w/ Corian dd<br></span><span style="font-size: 12px;">CO, CS, VIP Day Cabin</span></p>',
+ )
+ ).insert()
- self.assertEqual(item.description, '<p>Drawing No. 07-xxx-PO132<br>1800 x 1685 x 750<br>All parts made of Marine Ply<br>Top w/ Corian dd<br>CO, CS, VIP Day Cabin</p>')
+ self.assertEqual(
+ item.description,
+ "<p>Drawing No. 07-xxx-PO132<br>1800 x 1685 x 750<br>All parts made of Marine Ply<br>Top w/ Corian dd<br>CO, CS, VIP Day Cabin</p>",
+ )
item.delete()
diff --git a/erpnext/stock/doctype/warehouse/test_warehouse.py b/erpnext/stock/doctype/warehouse/test_warehouse.py
index 08d7c99..1e9d01a 100644
--- a/erpnext/stock/doctype/warehouse/test_warehouse.py
+++ b/erpnext/stock/doctype/warehouse/test_warehouse.py
@@ -11,13 +11,14 @@
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from erpnext.stock.doctype.warehouse.warehouse import convert_to_group_or_ledger, get_children
-test_records = frappe.get_test_records('Warehouse')
+test_records = frappe.get_test_records("Warehouse")
+
class TestWarehouse(FrappeTestCase):
def setUp(self):
super().setUp()
- if not frappe.get_value('Item', '_Test Item'):
- make_test_records('Item')
+ if not frappe.get_value("Item", "_Test Item"):
+ make_test_records("Item")
def test_parent_warehouse(self):
parent_warehouse = frappe.get_doc("Warehouse", "_Test Warehouse Group - _TC")
@@ -26,8 +27,12 @@
def test_warehouse_hierarchy(self):
p_warehouse = frappe.get_doc("Warehouse", "_Test Warehouse Group - _TC")
- child_warehouses = frappe.db.sql("""select name, is_group, parent_warehouse from `tabWarehouse` wh
- where wh.lft > %s and wh.rgt < %s""", (p_warehouse.lft, p_warehouse.rgt), as_dict=1)
+ child_warehouses = frappe.db.sql(
+ """select name, is_group, parent_warehouse from `tabWarehouse` wh
+ where wh.lft > %s and wh.rgt < %s""",
+ (p_warehouse.lft, p_warehouse.rgt),
+ as_dict=1,
+ )
for child_warehouse in child_warehouses:
self.assertEqual(p_warehouse.name, child_warehouse.parent_warehouse)
@@ -36,13 +41,13 @@
def test_unlinking_warehouse_from_item_defaults(self):
company = "_Test Company"
- warehouse_names = [f'_Test Warehouse {i} for Unlinking' for i in range(2)]
+ warehouse_names = [f"_Test Warehouse {i} for Unlinking" for i in range(2)]
warehouse_ids = []
for warehouse in warehouse_names:
warehouse_id = create_warehouse(warehouse, company=company)
warehouse_ids.append(warehouse_id)
- item_names = [f'_Test Item {i} for Unlinking' for i in range(2)]
+ item_names = [f"_Test Item {i} for Unlinking" for i in range(2)]
for item, warehouse in zip(item_names, warehouse_ids):
create_item(item, warehouse=warehouse, company=company)
@@ -52,17 +57,14 @@
# Check Item existance
for item in item_names:
- self.assertTrue(
- bool(frappe.db.exists("Item", item)),
- f"{item} doesn't exist"
- )
+ self.assertTrue(bool(frappe.db.exists("Item", item)), f"{item} doesn't exist")
item_doc = frappe.get_doc("Item", item)
for item_default in item_doc.item_defaults:
self.assertNotIn(
item_default.default_warehouse,
warehouse_ids,
- f"{item} linked to {item_default.default_warehouse} in {warehouse_ids}."
+ f"{item} linked to {item_default.default_warehouse} in {warehouse_ids}.",
)
def test_group_non_group_conversion(self):
@@ -90,7 +92,7 @@
company = "_Test Company"
children = get_children("Warehouse", parent=company, company=company, is_root=True)
- self.assertTrue(any(wh['value'] == "_Test Warehouse - _TC" for wh in children))
+ self.assertTrue(any(wh["value"] == "_Test Warehouse - _TC" for wh in children))
def create_warehouse(warehouse_name, properties=None, company=None):
@@ -111,40 +113,46 @@
else:
return warehouse_id
+
def get_warehouse(**args):
args = frappe._dict(args)
- if(frappe.db.exists("Warehouse", args.warehouse_name + " - " + args.abbr)):
+ if frappe.db.exists("Warehouse", args.warehouse_name + " - " + args.abbr):
return frappe.get_doc("Warehouse", args.warehouse_name + " - " + args.abbr)
else:
- w = frappe.get_doc({
- "company": args.company or "_Test Company",
- "doctype": "Warehouse",
- "warehouse_name": args.warehouse_name,
- "is_group": 0,
- "account": get_warehouse_account(args.warehouse_name, args.company, args.abbr)
- })
+ w = frappe.get_doc(
+ {
+ "company": args.company or "_Test Company",
+ "doctype": "Warehouse",
+ "warehouse_name": args.warehouse_name,
+ "is_group": 0,
+ "account": get_warehouse_account(args.warehouse_name, args.company, args.abbr),
+ }
+ )
w.insert()
return w
+
def get_warehouse_account(warehouse_name, company, company_abbr=None):
if not company_abbr:
- company_abbr = frappe.get_cached_value("Company", company, 'abbr')
+ company_abbr = frappe.get_cached_value("Company", company, "abbr")
if not frappe.db.exists("Account", warehouse_name + " - " + company_abbr):
return create_account(
account_name=warehouse_name,
parent_account=get_group_stock_account(company, company_abbr),
- account_type='Stock',
- company=company)
+ account_type="Stock",
+ company=company,
+ )
else:
return warehouse_name + " - " + company_abbr
def get_group_stock_account(company, company_abbr=None):
- group_stock_account = frappe.db.get_value("Account",
- filters={'account_type': 'Stock', 'is_group': 1, 'company': company}, fieldname='name')
+ group_stock_account = frappe.db.get_value(
+ "Account", filters={"account_type": "Stock", "is_group": 1, "company": company}, fieldname="name"
+ )
if not group_stock_account:
if not company_abbr:
- company_abbr = frappe.get_cached_value("Company", company, 'abbr')
+ company_abbr = frappe.get_cached_value("Company", company, "abbr")
group_stock_account = "Current Assets - " + company_abbr
return group_stock_account
diff --git a/erpnext/stock/doctype/warehouse/warehouse.py b/erpnext/stock/doctype/warehouse/warehouse.py
index 4c7f41d..c892ba3 100644
--- a/erpnext/stock/doctype/warehouse/warehouse.py
+++ b/erpnext/stock/doctype/warehouse/warehouse.py
@@ -14,23 +14,25 @@
class Warehouse(NestedSet):
- nsm_parent_field = 'parent_warehouse'
+ nsm_parent_field = "parent_warehouse"
def autoname(self):
if self.company:
- suffix = " - " + frappe.get_cached_value('Company', self.company, "abbr")
+ suffix = " - " + frappe.get_cached_value("Company", self.company, "abbr")
if not self.warehouse_name.endswith(suffix):
self.name = self.warehouse_name + suffix
else:
self.name = self.warehouse_name
def onload(self):
- '''load account name for General Ledger Report'''
- if self.company and cint(frappe.db.get_value("Company", self.company, "enable_perpetual_inventory")):
+ """load account name for General Ledger Report"""
+ if self.company and cint(
+ frappe.db.get_value("Company", self.company, "enable_perpetual_inventory")
+ ):
account = self.account or get_warehouse_account(self)
if account:
- self.set_onload('account', account)
+ self.set_onload("account", account)
load_address_and_contact(self)
def on_update(self):
@@ -43,9 +45,19 @@
# delete bin
bins = frappe.get_all("Bin", fields="*", filters={"warehouse": self.name})
for d in bins:
- if d['actual_qty'] or d['reserved_qty'] or d['ordered_qty'] or \
- d['indented_qty'] or d['projected_qty'] or d['planned_qty']:
- throw(_("Warehouse {0} can not be deleted as quantity exists for Item {1}").format(self.name, d['item_code']))
+ if (
+ d["actual_qty"]
+ or d["reserved_qty"]
+ or d["ordered_qty"]
+ or d["indented_qty"]
+ or d["projected_qty"]
+ or d["planned_qty"]
+ ):
+ throw(
+ _("Warehouse {0} can not be deleted as quantity exists for Item {1}").format(
+ self.name, d["item_code"]
+ )
+ )
if self.check_if_sle_exists():
throw(_("Warehouse can not be deleted as stock ledger entry exists for this warehouse."))
@@ -90,23 +102,24 @@
def unlink_from_items(self):
frappe.db.set_value("Item Default", {"default_warehouse": self.name}, "default_warehouse", None)
+
@frappe.whitelist()
def get_children(doctype, parent=None, company=None, is_root=False):
if is_root:
parent = ""
- fields = ['name as value', 'is_group as expandable']
+ fields = ["name as value", "is_group as expandable"]
filters = [
- ['docstatus', '<', '2'],
- ['ifnull(`parent_warehouse`, "")', '=', parent],
- ['company', 'in', (company, None,'')]
+ ["docstatus", "<", "2"],
+ ['ifnull(`parent_warehouse`, "")', "=", parent],
+ ["company", "in", (company, None, "")],
]
- warehouses = frappe.get_list(doctype, fields=fields, filters=filters, order_by='name')
+ warehouses = frappe.get_list(doctype, fields=fields, filters=filters, order_by="name")
- company_currency = ''
+ company_currency = ""
if company:
- company_currency = frappe.get_cached_value('Company', company, 'default_currency')
+ company_currency = frappe.get_cached_value("Company", company, "default_currency")
warehouse_wise_value = get_warehouse_wise_stock_value(company)
@@ -117,14 +130,20 @@
wh["company_currency"] = company_currency
return warehouses
-def get_warehouse_wise_stock_value(company):
- warehouses = frappe.get_all('Warehouse',
- fields = ['name', 'parent_warehouse'], filters = {'company': company})
- parent_warehouse = {d.name : d.parent_warehouse for d in warehouses}
- filters = {'warehouse': ('in', [data.name for data in warehouses])}
- bin_data = frappe.get_all('Bin', fields = ['sum(stock_value) as stock_value', 'warehouse'],
- filters = filters, group_by = 'warehouse')
+def get_warehouse_wise_stock_value(company):
+ warehouses = frappe.get_all(
+ "Warehouse", fields=["name", "parent_warehouse"], filters={"company": company}
+ )
+ parent_warehouse = {d.name: d.parent_warehouse for d in warehouses}
+
+ filters = {"warehouse": ("in", [data.name for data in warehouses])}
+ bin_data = frappe.get_all(
+ "Bin",
+ fields=["sum(stock_value) as stock_value", "warehouse"],
+ filters=filters,
+ group_by="warehouse",
+ )
warehouse_wise_stock_value = defaultdict(float)
for row in bin_data:
@@ -132,23 +151,30 @@
continue
warehouse_wise_stock_value[row.warehouse] = row.stock_value
- update_value_in_parent_warehouse(warehouse_wise_stock_value,
- parent_warehouse, row.warehouse, row.stock_value)
+ update_value_in_parent_warehouse(
+ warehouse_wise_stock_value, parent_warehouse, row.warehouse, row.stock_value
+ )
return warehouse_wise_stock_value
-def update_value_in_parent_warehouse(warehouse_wise_stock_value, parent_warehouse_dict, warehouse, stock_value):
+
+def update_value_in_parent_warehouse(
+ warehouse_wise_stock_value, parent_warehouse_dict, warehouse, stock_value
+):
parent_warehouse = parent_warehouse_dict.get(warehouse)
if not parent_warehouse:
return
warehouse_wise_stock_value[parent_warehouse] += flt(stock_value)
- update_value_in_parent_warehouse(warehouse_wise_stock_value, parent_warehouse_dict,
- parent_warehouse, stock_value)
+ update_value_in_parent_warehouse(
+ warehouse_wise_stock_value, parent_warehouse_dict, parent_warehouse, stock_value
+ )
+
@frappe.whitelist()
def add_node():
from frappe.desk.treeview import make_tree_args
+
args = make_tree_args(**frappe.form_dict)
if cint(args.is_root):
@@ -156,33 +182,37 @@
frappe.get_doc(args).insert()
+
@frappe.whitelist()
def convert_to_group_or_ledger(docname=None):
if not docname:
docname = frappe.form_dict.docname
return frappe.get_doc("Warehouse", docname).convert_to_group_or_ledger()
+
def get_child_warehouses(warehouse):
from frappe.utils.nestedset import get_descendants_of
children = get_descendants_of("Warehouse", warehouse, ignore_permissions=True, order_by="lft")
- return children + [warehouse] # append self for backward compatibility
+ return children + [warehouse] # append self for backward compatibility
+
def get_warehouses_based_on_account(account, company=None):
warehouses = []
- for d in frappe.get_all("Warehouse", fields = ["name", "is_group"],
- filters = {"account": account}):
+ for d in frappe.get_all("Warehouse", fields=["name", "is_group"], filters={"account": account}):
if d.is_group:
warehouses.extend(get_child_warehouses(d.name))
else:
warehouses.append(d.name)
- if (not warehouses and company and
- frappe.get_cached_value("Company", company, "default_inventory_account") == account):
- warehouses = [d.name for d in frappe.get_all("Warehouse", filters={'is_group': 0})]
+ if (
+ not warehouses
+ and company
+ and frappe.get_cached_value("Company", company, "default_inventory_account") == account
+ ):
+ warehouses = [d.name for d in frappe.get_all("Warehouse", filters={"is_group": 0})]
if not warehouses:
- frappe.throw(_("Warehouse not found against the account {0}")
- .format(account))
+ frappe.throw(_("Warehouse not found against the account {0}").format(account))
return warehouses
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index 9bb41b9..f72588e 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -23,31 +23,38 @@
from erpnext.stock.doctype.item_manufacturer.item_manufacturer import get_item_manufacturer_part_no
from erpnext.stock.doctype.price_list.price_list import get_price_list_details
-sales_doctypes = ['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice', 'POS Invoice']
-purchase_doctypes = ['Material Request', 'Supplier Quotation', 'Purchase Order', 'Purchase Receipt', 'Purchase Invoice']
+sales_doctypes = ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"]
+purchase_doctypes = [
+ "Material Request",
+ "Supplier Quotation",
+ "Purchase Order",
+ "Purchase Receipt",
+ "Purchase Invoice",
+]
+
@frappe.whitelist()
def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=True):
"""
- args = {
- "item_code": "",
- "warehouse": None,
- "customer": "",
- "conversion_rate": 1.0,
- "selling_price_list": None,
- "price_list_currency": None,
- "plc_conversion_rate": 1.0,
- "doctype": "",
- "name": "",
- "supplier": None,
- "transaction_date": None,
- "conversion_rate": 1.0,
- "buying_price_list": None,
- "is_subcontracted": "Yes" / "No",
- "ignore_pricing_rule": 0/1
- "project": ""
- "set_warehouse": ""
- }
+ args = {
+ "item_code": "",
+ "warehouse": None,
+ "customer": "",
+ "conversion_rate": 1.0,
+ "selling_price_list": None,
+ "price_list_currency": None,
+ "plc_conversion_rate": 1.0,
+ "doctype": "",
+ "name": "",
+ "supplier": None,
+ "transaction_date": None,
+ "conversion_rate": 1.0,
+ "buying_price_list": None,
+ "is_subcontracted": "Yes" / "No",
+ "ignore_pricing_rule": 0/1
+ "project": ""
+ "set_warehouse": ""
+ }
"""
args = process_args(args)
@@ -61,16 +68,21 @@
if isinstance(doc, str):
doc = json.loads(doc)
- if doc and doc.get('doctype') == 'Purchase Invoice':
- args['bill_date'] = doc.get('bill_date')
+ if doc and doc.get("doctype") == "Purchase Invoice":
+ args["bill_date"] = doc.get("bill_date")
if doc:
- args['posting_date'] = doc.get('posting_date')
- args['transaction_date'] = doc.get('transaction_date')
+ args["posting_date"] = doc.get("posting_date")
+ args["transaction_date"] = doc.get("transaction_date")
get_item_tax_template(args, item, out)
- out["item_tax_rate"] = get_item_tax_map(args.company, args.get("item_tax_template") if out.get("item_tax_template") is None \
- else out.get("item_tax_template"), as_json=True)
+ out["item_tax_rate"] = get_item_tax_map(
+ args.company,
+ args.get("item_tax_template")
+ if out.get("item_tax_template") is None
+ else out.get("item_tax_template"),
+ as_json=True,
+ )
get_party_item_code(args, item, out)
@@ -83,12 +95,14 @@
if args.customer and cint(args.is_pos):
out.update(get_pos_profile_item_details(args.company, args, update_data=True))
- if (args.get("doctype") == "Material Request" and
- args.get("material_request_type") == "Material Transfer"):
+ if (
+ args.get("doctype") == "Material Request"
+ and args.get("material_request_type") == "Material Transfer"
+ ):
out.update(get_bin_details(args.item_code, args.get("from_warehouse")))
elif out.get("warehouse"):
- if doc and doc.get('doctype') == 'Purchase Order':
+ if doc and doc.get("doctype") == "Purchase Order":
# calculate company_total_stock only for po
bin_details = get_bin_details(args.item_code, out.warehouse, args.company)
else:
@@ -101,28 +115,27 @@
if args.get(key) is None:
args[key] = value
- data = get_pricing_rule_for_item(args, out.price_list_rate,
- doc, for_validate=for_validate)
+ data = get_pricing_rule_for_item(args, out.price_list_rate, doc, for_validate=for_validate)
out.update(data)
update_stock(args, out)
if args.transaction_date and item.lead_time_days:
- out.schedule_date = out.lead_time_date = add_days(args.transaction_date,
- item.lead_time_days)
+ out.schedule_date = out.lead_time_date = add_days(args.transaction_date, item.lead_time_days)
if args.get("is_subcontracted") == "Yes":
- out.bom = args.get('bom') or get_default_bom(args.item_code)
+ out.bom = args.get("bom") or get_default_bom(args.item_code)
get_gross_profit(out)
- if args.doctype == 'Material Request':
+ if args.doctype == "Material Request":
out.rate = args.rate or out.price_list_rate
out.amount = flt(args.qty) * flt(out.rate)
out = remove_standard_fields(out)
return out
+
def remove_standard_fields(details):
for key in child_table_fields + default_fields:
details.pop(key, None)
@@ -130,9 +143,14 @@
def update_stock(args, out):
- if (args.get("doctype") == "Delivery Note" or
- (args.get("doctype") == "Sales Invoice" and args.get('update_stock'))) \
- and out.warehouse and out.stock_qty > 0:
+ if (
+ (
+ args.get("doctype") == "Delivery Note"
+ or (args.get("doctype") == "Sales Invoice" and args.get("update_stock"))
+ )
+ and out.warehouse
+ and out.stock_qty > 0
+ ):
if out.has_batch_no and not args.get("batch_no"):
out.batch_no = get_batch_no(out.item_code, out.warehouse, out.qty)
@@ -140,9 +158,9 @@
if actual_batch_qty:
out.update(actual_batch_qty)
- if out.has_serial_no and args.get('batch_no'):
+ if out.has_serial_no and args.get("batch_no"):
reserved_so = get_so_reservation_for_item(args)
- out.batch_no = args.get('batch_no')
+ out.batch_no = args.get("batch_no")
out.serial_no = get_serial_no(out, args.serial_no, sales_order=reserved_so)
elif out.has_serial_no:
@@ -156,13 +174,14 @@
bundled_items = frappe.get_doc("Product Bundle", args.item_code)
for bundle_item in bundled_items.items:
- valuation_rate += \
- flt(get_valuation_rate(bundle_item.item_code, args.company, out.get("warehouse")).get("valuation_rate") \
- * bundle_item.qty)
+ valuation_rate += flt(
+ get_valuation_rate(bundle_item.item_code, args.company, out.get("warehouse")).get(
+ "valuation_rate"
+ )
+ * bundle_item.qty
+ )
- out.update({
- "valuation_rate": valuation_rate
- })
+ out.update({"valuation_rate": valuation_rate})
else:
out.update(get_valuation_rate(args.item_code, args.company, out.get("warehouse")))
@@ -185,11 +204,13 @@
set_transaction_type(args)
return args
+
def process_string_args(args):
if isinstance(args, str):
args = json.loads(args)
return args
+
@frappe.whitelist()
def get_item_code(barcode=None, serial_no=None):
if barcode:
@@ -209,6 +230,7 @@
throw(_("Please specify Company"))
from erpnext.stock.doctype.item.item import validate_end_of_life
+
validate_end_of_life(item.name, item.end_of_life, item.disabled)
if args.transaction_type == "selling" and cint(item.has_variants):
@@ -222,37 +244,37 @@
def get_basic_details(args, item, overwrite_warehouse=True):
"""
:param args: {
- "item_code": "",
- "warehouse": None,
- "customer": "",
- "conversion_rate": 1.0,
- "selling_price_list": None,
- "price_list_currency": None,
- "price_list_uom_dependant": None,
- "plc_conversion_rate": 1.0,
- "doctype": "",
- "name": "",
- "supplier": None,
- "transaction_date": None,
- "conversion_rate": 1.0,
- "buying_price_list": None,
- "is_subcontracted": "Yes" / "No",
- "ignore_pricing_rule": 0/1
- "project": "",
- barcode: "",
- serial_no: "",
- currency: "",
- update_stock: "",
- price_list: "",
- company: "",
- order_type: "",
- is_pos: "",
- project: "",
- qty: "",
- stock_qty: "",
- conversion_factor: "",
- against_blanket_order: 0/1
- }
+ "item_code": "",
+ "warehouse": None,
+ "customer": "",
+ "conversion_rate": 1.0,
+ "selling_price_list": None,
+ "price_list_currency": None,
+ "price_list_uom_dependant": None,
+ "plc_conversion_rate": 1.0,
+ "doctype": "",
+ "name": "",
+ "supplier": None,
+ "transaction_date": None,
+ "conversion_rate": 1.0,
+ "buying_price_list": None,
+ "is_subcontracted": "Yes" / "No",
+ "ignore_pricing_rule": 0/1
+ "project": "",
+ barcode: "",
+ serial_no: "",
+ currency: "",
+ update_stock: "",
+ price_list: "",
+ company: "",
+ order_type: "",
+ is_pos: "",
+ project: "",
+ qty: "",
+ stock_qty: "",
+ conversion_factor: "",
+ against_blanket_order: 0/1
+ }
:param item: `item_code` of Item object
:return: frappe._dict
"""
@@ -267,77 +289,98 @@
item_group_defaults = get_item_group_defaults(item.name, args.company)
brand_defaults = get_brand_defaults(item.name, args.company)
- defaults = frappe._dict({
- 'item_defaults': item_defaults,
- 'item_group_defaults': item_group_defaults,
- 'brand_defaults': brand_defaults
- })
+ defaults = frappe._dict(
+ {
+ "item_defaults": item_defaults,
+ "item_group_defaults": item_group_defaults,
+ "brand_defaults": brand_defaults,
+ }
+ )
warehouse = get_item_warehouse(item, args, overwrite_warehouse, defaults)
- if args.get('doctype') == "Material Request" and not args.get('material_request_type'):
- args['material_request_type'] = frappe.db.get_value('Material Request',
- args.get('name'), 'material_request_type', cache=True)
+ if args.get("doctype") == "Material Request" and not args.get("material_request_type"):
+ args["material_request_type"] = frappe.db.get_value(
+ "Material Request", args.get("name"), "material_request_type", cache=True
+ )
expense_account = None
- if args.get('doctype') == 'Purchase Invoice' and item.is_fixed_asset:
+ if args.get("doctype") == "Purchase Invoice" and item.is_fixed_asset:
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
- expense_account = get_asset_category_account(fieldname = "fixed_asset_account", item = args.item_code, company= args.company)
- #Set the UOM to the Default Sales UOM or Default Purchase UOM if configured in the Item Master
- if not args.get('uom'):
- if args.get('doctype') in sales_doctypes:
+ expense_account = get_asset_category_account(
+ fieldname="fixed_asset_account", item=args.item_code, company=args.company
+ )
+
+ # Set the UOM to the Default Sales UOM or Default Purchase UOM if configured in the Item Master
+ if not args.get("uom"):
+ if args.get("doctype") in sales_doctypes:
args.uom = item.sales_uom if item.sales_uom else item.stock_uom
- elif (args.get('doctype') in ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice']) or \
- (args.get('doctype') == 'Material Request' and args.get('material_request_type') == 'Purchase'):
+ elif (args.get("doctype") in ["Purchase Order", "Purchase Receipt", "Purchase Invoice"]) or (
+ args.get("doctype") == "Material Request" and args.get("material_request_type") == "Purchase"
+ ):
args.uom = item.purchase_uom if item.purchase_uom else item.stock_uom
else:
args.uom = item.stock_uom
- if (args.get("batch_no") and
- item.name != frappe.get_cached_value('Batch', args.get("batch_no"), 'item')):
- args['batch_no'] = ''
+ if args.get("batch_no") and item.name != frappe.get_cached_value(
+ "Batch", args.get("batch_no"), "item"
+ ):
+ args["batch_no"] = ""
- out = frappe._dict({
- "item_code": item.name,
- "item_name": item.item_name,
- "description": cstr(item.description).strip(),
- "image": cstr(item.image).strip(),
- "warehouse": warehouse,
- "income_account": get_default_income_account(args, item_defaults, item_group_defaults, brand_defaults),
- "expense_account": expense_account or get_default_expense_account(args, item_defaults, item_group_defaults, brand_defaults) ,
- "discount_account": get_default_discount_account(args, item_defaults),
- "cost_center": get_default_cost_center(args, item_defaults, item_group_defaults, brand_defaults),
- 'has_serial_no': item.has_serial_no,
- 'has_batch_no': item.has_batch_no,
- "batch_no": args.get("batch_no"),
- "uom": args.uom,
- "min_order_qty": flt(item.min_order_qty) if args.doctype == "Material Request" else "",
- "qty": flt(args.qty) or 1.0,
- "stock_qty": flt(args.qty) or 1.0,
- "price_list_rate": 0.0,
- "base_price_list_rate": 0.0,
- "rate": 0.0,
- "base_rate": 0.0,
- "amount": 0.0,
- "base_amount": 0.0,
- "net_rate": 0.0,
- "net_amount": 0.0,
- "discount_percentage": 0.0,
- "discount_amount": 0.0,
- "supplier": get_default_supplier(args, item_defaults, item_group_defaults, brand_defaults),
- "update_stock": args.get("update_stock") if args.get('doctype') in ['Sales Invoice', 'Purchase Invoice'] else 0,
- "delivered_by_supplier": item.delivered_by_supplier if args.get("doctype") in ["Sales Order", "Sales Invoice"] else 0,
- "is_fixed_asset": item.is_fixed_asset,
- "last_purchase_rate": item.last_purchase_rate if args.get("doctype") in ["Purchase Order"] else 0,
- "transaction_date": args.get("transaction_date"),
- "against_blanket_order": args.get("against_blanket_order"),
- "bom_no": item.get("default_bom"),
- "weight_per_unit": args.get("weight_per_unit") or item.get("weight_per_unit"),
- "weight_uom": args.get("weight_uom") or item.get("weight_uom"),
- "grant_commission": item.get("grant_commission")
- })
+ out = frappe._dict(
+ {
+ "item_code": item.name,
+ "item_name": item.item_name,
+ "description": cstr(item.description).strip(),
+ "image": cstr(item.image).strip(),
+ "warehouse": warehouse,
+ "income_account": get_default_income_account(
+ args, item_defaults, item_group_defaults, brand_defaults
+ ),
+ "expense_account": expense_account
+ or get_default_expense_account(args, item_defaults, item_group_defaults, brand_defaults),
+ "discount_account": get_default_discount_account(args, item_defaults),
+ "cost_center": get_default_cost_center(
+ args, item_defaults, item_group_defaults, brand_defaults
+ ),
+ "has_serial_no": item.has_serial_no,
+ "has_batch_no": item.has_batch_no,
+ "batch_no": args.get("batch_no"),
+ "uom": args.uom,
+ "min_order_qty": flt(item.min_order_qty) if args.doctype == "Material Request" else "",
+ "qty": flt(args.qty) or 1.0,
+ "stock_qty": flt(args.qty) or 1.0,
+ "price_list_rate": 0.0,
+ "base_price_list_rate": 0.0,
+ "rate": 0.0,
+ "base_rate": 0.0,
+ "amount": 0.0,
+ "base_amount": 0.0,
+ "net_rate": 0.0,
+ "net_amount": 0.0,
+ "discount_percentage": 0.0,
+ "discount_amount": 0.0,
+ "supplier": get_default_supplier(args, item_defaults, item_group_defaults, brand_defaults),
+ "update_stock": args.get("update_stock")
+ if args.get("doctype") in ["Sales Invoice", "Purchase Invoice"]
+ else 0,
+ "delivered_by_supplier": item.delivered_by_supplier
+ if args.get("doctype") in ["Sales Order", "Sales Invoice"]
+ else 0,
+ "is_fixed_asset": item.is_fixed_asset,
+ "last_purchase_rate": item.last_purchase_rate
+ if args.get("doctype") in ["Purchase Order"]
+ else 0,
+ "transaction_date": args.get("transaction_date"),
+ "against_blanket_order": args.get("against_blanket_order"),
+ "bom_no": item.get("default_bom"),
+ "weight_per_unit": args.get("weight_per_unit") or item.get("weight_per_unit"),
+ "weight_uom": args.get("weight_uom") or item.get("weight_uom"),
+ "grant_commission": item.get("grant_commission"),
+ }
+ )
if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):
out.update(calculate_service_end_date(args, item))
@@ -346,26 +389,31 @@
if item.stock_uom == args.uom:
out.conversion_factor = 1.0
else:
- out.conversion_factor = args.conversion_factor or \
- get_conversion_factor(item.name, args.uom).get("conversion_factor")
+ out.conversion_factor = args.conversion_factor or get_conversion_factor(item.name, args.uom).get(
+ "conversion_factor"
+ )
args.conversion_factor = out.conversion_factor
out.stock_qty = out.qty * out.conversion_factor
args.stock_qty = out.stock_qty
# calculate last purchase rate
- if args.get('doctype') in purchase_doctypes:
+ if args.get("doctype") in purchase_doctypes:
from erpnext.buying.doctype.purchase_order.purchase_order import item_last_purchase_rate
- out.last_purchase_rate = item_last_purchase_rate(args.name, args.conversion_rate, item.name, out.conversion_factor)
+
+ out.last_purchase_rate = item_last_purchase_rate(
+ args.name, args.conversion_rate, item.name, out.conversion_factor
+ )
# if default specified in item is for another company, fetch from company
for d in [
["Account", "income_account", "default_income_account"],
["Account", "expense_account", "default_expense_account"],
["Cost Center", "cost_center", "cost_center"],
- ["Warehouse", "warehouse", ""]]:
- if not out[d[1]]:
- out[d[1]] = frappe.get_cached_value('Company', args.company, d[2]) if d[2] else None
+ ["Warehouse", "warehouse", ""],
+ ]:
+ if not out[d[1]]:
+ out[d[1]] = frappe.get_cached_value("Company", args.company, d[2]) if d[2] else None
for fieldname in ("item_name", "item_group", "brand", "stock_uom"):
out[fieldname] = item.get(fieldname)
@@ -378,53 +426,58 @@
out["manufacturer_part_no"] = None
out["manufacturer"] = None
else:
- data = frappe.get_value("Item", item.name,
- ["default_item_manufacturer", "default_manufacturer_part_no"] , as_dict=1)
+ data = frappe.get_value(
+ "Item", item.name, ["default_item_manufacturer", "default_manufacturer_part_no"], as_dict=1
+ )
if data:
- out.update({
- "manufacturer": data.default_item_manufacturer,
- "manufacturer_part_no": data.default_manufacturer_part_no
- })
+ out.update(
+ {
+ "manufacturer": data.default_item_manufacturer,
+ "manufacturer_part_no": data.default_manufacturer_part_no,
+ }
+ )
- child_doctype = args.doctype + ' Item'
+ child_doctype = args.doctype + " Item"
meta = frappe.get_meta(child_doctype)
if meta.get_field("barcode"):
update_barcode_value(out)
if out.get("weight_per_unit"):
- out['total_weight'] = out.weight_per_unit * out.stock_qty
+ out["total_weight"] = out.weight_per_unit * out.stock_qty
return out
+
def get_item_warehouse(item, args, overwrite_warehouse, defaults=None):
if not defaults:
- defaults = frappe._dict({
- 'item_defaults' : get_item_defaults(item.name, args.company),
- 'item_group_defaults' : get_item_group_defaults(item.name, args.company),
- 'brand_defaults' : get_brand_defaults(item.name, args.company)
- })
+ defaults = frappe._dict(
+ {
+ "item_defaults": get_item_defaults(item.name, args.company),
+ "item_group_defaults": get_item_group_defaults(item.name, args.company),
+ "brand_defaults": get_brand_defaults(item.name, args.company),
+ }
+ )
if overwrite_warehouse or not args.warehouse:
warehouse = (
- args.get("set_warehouse") or
- defaults.item_defaults.get("default_warehouse") or
- defaults.item_group_defaults.get("default_warehouse") or
- defaults.brand_defaults.get("default_warehouse") or
- args.get('warehouse')
+ args.get("set_warehouse")
+ or defaults.item_defaults.get("default_warehouse")
+ or defaults.item_group_defaults.get("default_warehouse")
+ or defaults.brand_defaults.get("default_warehouse")
+ or args.get("warehouse")
)
if not warehouse:
defaults = frappe.defaults.get_defaults() or {}
- warehouse_exists = frappe.db.exists("Warehouse", {
- 'name': defaults.default_warehouse,
- 'company': args.company
- })
+ warehouse_exists = frappe.db.exists(
+ "Warehouse", {"name": defaults.default_warehouse, "company": args.company}
+ )
if defaults.get("default_warehouse") and warehouse_exists:
warehouse = defaults.default_warehouse
else:
- warehouse = args.get('warehouse')
+ warehouse = args.get("warehouse")
if not warehouse:
default_warehouse = frappe.db.get_single_value("Stock Settings", "default_warehouse")
@@ -433,12 +486,14 @@
return warehouse
+
def update_barcode_value(out):
barcode_data = get_barcode_data([out])
# If item has one barcode then update the value of the barcode field
if barcode_data and len(barcode_data.get(out.item_code)) == 1:
- out['barcode'] = barcode_data.get(out.item_code)[0]
+ out["barcode"] = barcode_data.get(out.item_code)[0]
+
def get_barcode_data(items_list):
# get itemwise batch no data
@@ -447,9 +502,13 @@
itemwise_barcode = {}
for item in items_list:
- barcodes = frappe.db.sql("""
+ barcodes = frappe.db.sql(
+ """
select barcode from `tabItem Barcode` where parent = %s
- """, item.item_code, as_dict=1)
+ """,
+ item.item_code,
+ as_dict=1,
+ )
for barcode in barcodes:
if item.item_code not in itemwise_barcode:
@@ -458,6 +517,7 @@
return itemwise_barcode
+
@frappe.whitelist()
def get_item_tax_info(company, tax_category, item_codes, item_rates=None, item_tax_templates=None):
out = {}
@@ -483,22 +543,29 @@
out[item_code[1]] = {}
item = frappe.get_cached_doc("Item", item_code[0])
- args = {"company": company, "tax_category": tax_category, "net_rate": item_rates.get(item_code[1])}
+ args = {
+ "company": company,
+ "tax_category": tax_category,
+ "net_rate": item_rates.get(item_code[1]),
+ }
if item_tax_templates:
args.update({"item_tax_template": item_tax_templates.get(item_code[1])})
get_item_tax_template(args, item, out[item_code[1]])
- out[item_code[1]]["item_tax_rate"] = get_item_tax_map(company, out[item_code[1]].get("item_tax_template"), as_json=True)
+ out[item_code[1]]["item_tax_rate"] = get_item_tax_map(
+ company, out[item_code[1]].get("item_tax_template"), as_json=True
+ )
return out
+
def get_item_tax_template(args, item, out):
"""
- args = {
- "tax_category": None
- "item_tax_template": None
- }
+ args = {
+ "tax_category": None
+ "item_tax_template": None
+ }
"""
item_tax_template = None
if item.taxes:
@@ -511,6 +578,7 @@
item_tax_template = _get_item_tax_template(args, item_group_doc.taxes, out)
item_group = item_group_doc.parent_item_group
+
def _get_item_tax_template(args, taxes, out=None, for_validate=False):
if out is None:
out = {}
@@ -518,36 +586,43 @@
taxes_with_no_validity = []
for tax in taxes:
- tax_company = frappe.get_cached_value("Item Tax Template", tax.item_tax_template, 'company')
- if tax_company == args['company']:
- if (tax.valid_from or tax.maximum_net_rate):
+ tax_company = frappe.get_cached_value("Item Tax Template", tax.item_tax_template, "company")
+ if tax_company == args["company"]:
+ if tax.valid_from or tax.maximum_net_rate:
# In purchase Invoice first preference will be given to supplier invoice date
# if supplier date is not present then posting date
- validation_date = args.get('transaction_date') or args.get('bill_date') or args.get('posting_date')
+ validation_date = (
+ args.get("transaction_date") or args.get("bill_date") or args.get("posting_date")
+ )
- if getdate(tax.valid_from) <= getdate(validation_date) \
- and is_within_valid_range(args, tax):
+ if getdate(tax.valid_from) <= getdate(validation_date) and is_within_valid_range(args, tax):
taxes_with_validity.append(tax)
else:
taxes_with_no_validity.append(tax)
if taxes_with_validity:
- taxes = sorted(taxes_with_validity, key = lambda i: i.valid_from, reverse=True)
+ taxes = sorted(taxes_with_validity, key=lambda i: i.valid_from, reverse=True)
else:
taxes = taxes_with_no_validity
if for_validate:
- return [tax.item_tax_template for tax in taxes if (cstr(tax.tax_category) == cstr(args.get('tax_category')) \
- and (tax.item_tax_template not in taxes))]
+ return [
+ tax.item_tax_template
+ for tax in taxes
+ if (
+ cstr(tax.tax_category) == cstr(args.get("tax_category"))
+ and (tax.item_tax_template not in taxes)
+ )
+ ]
# all templates have validity and no template is valid
if not taxes_with_validity and (not taxes_with_no_validity):
return None
# do not change if already a valid template
- if args.get('item_tax_template') in {t.item_tax_template for t in taxes}:
- out["item_tax_template"] = args.get('item_tax_template')
- return args.get('item_tax_template')
+ if args.get("item_tax_template") in {t.item_tax_template for t in taxes}:
+ out["item_tax_template"] = args.get("item_tax_template")
+ return args.get("item_tax_template")
for tax in taxes:
if cstr(tax.tax_category) == cstr(args.get("tax_category")):
@@ -555,15 +630,17 @@
return tax.item_tax_template
return None
+
def is_within_valid_range(args, tax):
if not flt(tax.maximum_net_rate):
# No range specified, just ignore
return True
- elif flt(tax.minimum_net_rate) <= flt(args.get('net_rate')) <= flt(tax.maximum_net_rate):
+ elif flt(tax.minimum_net_rate) <= flt(args.get("net_rate")) <= flt(tax.maximum_net_rate):
return True
return False
+
@frappe.whitelist()
def get_item_tax_map(company, item_tax_template, as_json=True):
item_tax_map = {}
@@ -575,6 +652,7 @@
return json.dumps(item_tax_map) if as_json else item_tax_map
+
@frappe.whitelist()
def calculate_service_end_date(args, item=None):
args = process_args(args)
@@ -593,53 +671,68 @@
service_start_date = args.service_start_date if args.service_start_date else args.transaction_date
service_end_date = add_months(service_start_date, item.get(no_of_months))
- deferred_detail = {
- "service_start_date": service_start_date,
- "service_end_date": service_end_date
- }
+ deferred_detail = {"service_start_date": service_start_date, "service_end_date": service_end_date}
deferred_detail[enable_deferred] = item.get(enable_deferred)
deferred_detail[account] = get_default_deferred_account(args, item, fieldname=account)
return deferred_detail
+
def get_default_income_account(args, item, item_group, brand):
- return (item.get("income_account")
+ return (
+ item.get("income_account")
or item_group.get("income_account")
or brand.get("income_account")
- or args.income_account)
+ or args.income_account
+ )
+
def get_default_expense_account(args, item, item_group, brand):
- return (item.get("expense_account")
+ return (
+ item.get("expense_account")
or item_group.get("expense_account")
or brand.get("expense_account")
- or args.expense_account)
+ or args.expense_account
+ )
+
def get_default_discount_account(args, item):
- return (item.get("default_discount_account")
- or args.discount_account)
+ return item.get("default_discount_account") or args.discount_account
+
def get_default_deferred_account(args, item, fieldname=None):
if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):
- return (item.get(fieldname)
+ return (
+ item.get(fieldname)
or args.get(fieldname)
- or frappe.get_cached_value('Company', args.company, "default_"+fieldname))
+ or frappe.get_cached_value("Company", args.company, "default_" + fieldname)
+ )
else:
return None
+
def get_default_cost_center(args, item=None, item_group=None, brand=None, company=None):
cost_center = None
if not company and args.get("company"):
company = args.get("company")
- if args.get('project'):
+ if args.get("project"):
cost_center = frappe.db.get_value("Project", args.get("project"), "cost_center", cache=True)
if not cost_center and (item and item_group and brand):
- if args.get('customer'):
- cost_center = item.get('selling_cost_center') or item_group.get('selling_cost_center') or brand.get('selling_cost_center')
+ if args.get("customer"):
+ cost_center = (
+ item.get("selling_cost_center")
+ or item_group.get("selling_cost_center")
+ or brand.get("selling_cost_center")
+ )
else:
- cost_center = item.get('buying_cost_center') or item_group.get('buying_cost_center') or brand.get('buying_cost_center')
+ cost_center = (
+ item.get("buying_cost_center")
+ or item_group.get("buying_cost_center")
+ or brand.get("buying_cost_center")
+ )
elif not cost_center and args.get("item_code") and company:
for method in ["get_item_defaults", "get_item_group_defaults", "get_brand_defaults"]:
@@ -652,20 +745,26 @@
if not cost_center and args.get("cost_center"):
cost_center = args.get("cost_center")
- if (company and cost_center
- and frappe.get_cached_value("Cost Center", cost_center, "company") != company):
+ if (
+ company
+ and cost_center
+ and frappe.get_cached_value("Cost Center", cost_center, "company") != company
+ ):
return None
if not cost_center and company:
- cost_center = frappe.get_cached_value("Company",
- company, "cost_center")
+ cost_center = frappe.get_cached_value("Company", company, "cost_center")
return cost_center
+
def get_default_supplier(args, item, item_group, brand):
- return (item.get("default_supplier")
+ return (
+ item.get("default_supplier")
or item_group.get("default_supplier")
- or brand.get("default_supplier"))
+ or brand.get("default_supplier")
+ )
+
def get_price_list_rate(args, item_doc, out=None):
if out is None:
@@ -673,7 +772,7 @@
meta = frappe.get_meta(args.parenttype or args.doctype)
- if meta.get_field("currency") or args.get('currency'):
+ if meta.get_field("currency") or args.get("currency"):
if not args.get("price_list_currency") or not args.get("plc_conversion_rate"):
# if currency and plc_conversion_rate exist then
# `get_price_list_currency_and_exchange_rate` has already been called
@@ -695,54 +794,72 @@
insert_item_price(args)
return out
- out.price_list_rate = flt(price_list_rate) * flt(args.plc_conversion_rate) \
- / flt(args.conversion_rate)
+ out.price_list_rate = (
+ flt(price_list_rate) * flt(args.plc_conversion_rate) / flt(args.conversion_rate)
+ )
- if not out.price_list_rate and args.transaction_type=="buying":
+ if not out.price_list_rate and args.transaction_type == "buying":
from erpnext.stock.doctype.item.item import get_last_purchase_details
- out.update(get_last_purchase_details(item_doc.name,
- args.name, args.conversion_rate))
+
+ out.update(get_last_purchase_details(item_doc.name, args.name, args.conversion_rate))
return out
+
def insert_item_price(args):
"""Insert Item Price if Price List and Price List Rate are specified and currency is the same"""
- if frappe.db.get_value("Price List", args.price_list, "currency", cache=True) == args.currency \
- and cint(frappe.db.get_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing")):
+ if frappe.db.get_value(
+ "Price List", args.price_list, "currency", cache=True
+ ) == args.currency and cint(
+ frappe.db.get_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing")
+ ):
if frappe.has_permission("Item Price", "write"):
- price_list_rate = (args.rate / args.get('conversion_factor')
- if args.get("conversion_factor") else args.rate)
+ price_list_rate = (
+ args.rate / args.get("conversion_factor") if args.get("conversion_factor") else args.rate
+ )
- item_price = frappe.db.get_value('Item Price',
- {'item_code': args.item_code, 'price_list': args.price_list, 'currency': args.currency},
- ['name', 'price_list_rate'], as_dict=1)
+ item_price = frappe.db.get_value(
+ "Item Price",
+ {"item_code": args.item_code, "price_list": args.price_list, "currency": args.currency},
+ ["name", "price_list_rate"],
+ as_dict=1,
+ )
if item_price and item_price.name:
- if item_price.price_list_rate != price_list_rate and frappe.db.get_single_value('Stock Settings', 'update_existing_price_list_rate'):
- frappe.db.set_value('Item Price', item_price.name, "price_list_rate", price_list_rate)
- frappe.msgprint(_("Item Price updated for {0} in Price List {1}").format(args.item_code,
- args.price_list), alert=True)
+ if item_price.price_list_rate != price_list_rate and frappe.db.get_single_value(
+ "Stock Settings", "update_existing_price_list_rate"
+ ):
+ frappe.db.set_value("Item Price", item_price.name, "price_list_rate", price_list_rate)
+ frappe.msgprint(
+ _("Item Price updated for {0} in Price List {1}").format(args.item_code, args.price_list),
+ alert=True,
+ )
else:
- item_price = frappe.get_doc({
- "doctype": "Item Price",
- "price_list": args.price_list,
- "item_code": args.item_code,
- "currency": args.currency,
- "price_list_rate": price_list_rate
- })
+ item_price = frappe.get_doc(
+ {
+ "doctype": "Item Price",
+ "price_list": args.price_list,
+ "item_code": args.item_code,
+ "currency": args.currency,
+ "price_list_rate": price_list_rate,
+ }
+ )
item_price.insert()
- frappe.msgprint(_("Item Price added for {0} in Price List {1}").format(args.item_code,
- args.price_list), alert=True)
+ frappe.msgprint(
+ _("Item Price added for {0} in Price List {1}").format(args.item_code, args.price_list),
+ alert=True,
+ )
+
def get_item_price(args, item_code, ignore_party=False):
"""
- Get name, price_list_rate from Item Price based on conditions
- Check if the desired qty is within the increment of the packing list.
- :param args: dict (or frappe._dict) with mandatory fields price_list, uom
- optional fields transaction_date, customer, supplier
- :param item_code: str, Item Doctype field item_code
+ Get name, price_list_rate from Item Price based on conditions
+ Check if the desired qty is within the increment of the packing list.
+ :param args: dict (or frappe._dict) with mandatory fields price_list, uom
+ optional fields transaction_date, customer, supplier
+ :param item_code: str, Item Doctype field item_code
"""
- args['item_code'] = item_code
+ args["item_code"] = item_code
conditions = """where item_code=%(item_code)s
and price_list=%(price_list)s
@@ -758,36 +875,42 @@
else:
conditions += "and (customer is null or customer = '') and (supplier is null or supplier = '')"
- if args.get('transaction_date'):
+ if args.get("transaction_date"):
conditions += """ and %(transaction_date)s between
ifnull(valid_from, '2000-01-01') and ifnull(valid_upto, '2500-12-31')"""
- if args.get('posting_date'):
+ if args.get("posting_date"):
conditions += """ and %(posting_date)s between
ifnull(valid_from, '2000-01-01') and ifnull(valid_upto, '2500-12-31')"""
- return frappe.db.sql(""" select name, price_list_rate, uom
+ return frappe.db.sql(
+ """ select name, price_list_rate, uom
from `tabItem Price` {conditions}
- order by valid_from desc, batch_no desc, uom desc """.format(conditions=conditions), args)
+ order by valid_from desc, batch_no desc, uom desc """.format(
+ conditions=conditions
+ ),
+ args,
+ )
+
def get_price_list_rate_for(args, item_code):
"""
- :param customer: link to Customer DocType
- :param supplier: link to Supplier DocType
- :param price_list: str (Standard Buying or Standard Selling)
- :param item_code: str, Item Doctype field item_code
- :param qty: Desired Qty
- :param transaction_date: Date of the price
+ :param customer: link to Customer DocType
+ :param supplier: link to Supplier DocType
+ :param price_list: str (Standard Buying or Standard Selling)
+ :param item_code: str, Item Doctype field item_code
+ :param qty: Desired Qty
+ :param transaction_date: Date of the price
"""
item_price_args = {
- "item_code": item_code,
- "price_list": args.get('price_list'),
- "customer": args.get('customer'),
- "supplier": args.get('supplier'),
- "uom": args.get('uom'),
- "transaction_date": args.get('transaction_date'),
- "posting_date": args.get('posting_date'),
- "batch_no": args.get('batch_no')
+ "item_code": item_code,
+ "price_list": args.get("price_list"),
+ "customer": args.get("customer"),
+ "supplier": args.get("supplier"),
+ "uom": args.get("uom"),
+ "transaction_date": args.get("transaction_date"),
+ "posting_date": args.get("posting_date"),
+ "batch_no": args.get("batch_no"),
}
item_price_data = 0
@@ -800,12 +923,15 @@
for field in ["customer", "supplier"]:
del item_price_args[field]
- general_price_list_rate = get_item_price(item_price_args, item_code,
- ignore_party=args.get("ignore_party"))
+ general_price_list_rate = get_item_price(
+ item_price_args, item_code, ignore_party=args.get("ignore_party")
+ )
if not general_price_list_rate and args.get("uom") != args.get("stock_uom"):
item_price_args["uom"] = args.get("stock_uom")
- general_price_list_rate = get_item_price(item_price_args, item_code, ignore_party=args.get("ignore_party"))
+ general_price_list_rate = get_item_price(
+ item_price_args, item_code, ignore_party=args.get("ignore_party")
+ )
if general_price_list_rate:
item_price_data = general_price_list_rate
@@ -813,18 +939,19 @@
if item_price_data:
if item_price_data[0][2] == args.get("uom"):
return item_price_data[0][1]
- elif not args.get('price_list_uom_dependant'):
+ elif not args.get("price_list_uom_dependant"):
return flt(item_price_data[0][1] * flt(args.get("conversion_factor", 1)))
else:
return item_price_data[0][1]
+
def check_packing_list(price_list_rate_name, desired_qty, item_code):
"""
- Check if the desired qty is within the increment of the packing list.
- :param price_list_rate_name: Name of Item Price
- :param desired_qty: Desired Qt
- :param item_code: str, Item Doctype field item_code
- :param qty: Desired Qt
+ Check if the desired qty is within the increment of the packing list.
+ :param price_list_rate_name: Name of Item Price
+ :param desired_qty: Desired Qt
+ :param item_code: str, Item Doctype field item_code
+ :param qty: Desired Qt
"""
flag = True
@@ -837,47 +964,62 @@
return flag
+
def validate_conversion_rate(args, meta):
from erpnext.controllers.accounts_controller import validate_conversion_rate
- company_currency = frappe.get_cached_value('Company', args.company, "default_currency")
- if (not args.conversion_rate and args.currency==company_currency):
+ company_currency = frappe.get_cached_value("Company", args.company, "default_currency")
+ if not args.conversion_rate and args.currency == company_currency:
args.conversion_rate = 1.0
- if (not args.ignore_conversion_rate and args.conversion_rate == 1 and args.currency!=company_currency):
- args.conversion_rate = get_exchange_rate(args.currency,
- company_currency, args.transaction_date, "for_buying") or 1.0
+ if (
+ not args.ignore_conversion_rate
+ and args.conversion_rate == 1
+ and args.currency != company_currency
+ ):
+ args.conversion_rate = (
+ get_exchange_rate(args.currency, company_currency, args.transaction_date, "for_buying") or 1.0
+ )
# validate currency conversion rate
- validate_conversion_rate(args.currency, args.conversion_rate,
- meta.get_label("conversion_rate"), args.company)
+ validate_conversion_rate(
+ args.currency, args.conversion_rate, meta.get_label("conversion_rate"), args.company
+ )
- args.conversion_rate = flt(args.conversion_rate,
- get_field_precision(meta.get_field("conversion_rate"),
- frappe._dict({"fields": args})))
+ args.conversion_rate = flt(
+ args.conversion_rate,
+ get_field_precision(meta.get_field("conversion_rate"), frappe._dict({"fields": args})),
+ )
if args.price_list:
- if (not args.plc_conversion_rate
- and args.price_list_currency==frappe.db.get_value("Price List", args.price_list, "currency", cache=True)):
+ if not args.plc_conversion_rate and args.price_list_currency == frappe.db.get_value(
+ "Price List", args.price_list, "currency", cache=True
+ ):
args.plc_conversion_rate = 1.0
# validate price list currency conversion rate
if not args.get("price_list_currency"):
throw(_("Price List Currency not selected"))
else:
- validate_conversion_rate(args.price_list_currency, args.plc_conversion_rate,
- meta.get_label("plc_conversion_rate"), args.company)
+ validate_conversion_rate(
+ args.price_list_currency,
+ args.plc_conversion_rate,
+ meta.get_label("plc_conversion_rate"),
+ args.company,
+ )
if meta.get_field("plc_conversion_rate"):
- args.plc_conversion_rate = flt(args.plc_conversion_rate,
- get_field_precision(meta.get_field("plc_conversion_rate"),
- frappe._dict({"fields": args})))
+ args.plc_conversion_rate = flt(
+ args.plc_conversion_rate,
+ get_field_precision(meta.get_field("plc_conversion_rate"), frappe._dict({"fields": args})),
+ )
+
def get_party_item_code(args, item_doc, out):
- if args.transaction_type=="selling" and args.customer:
+ if args.transaction_type == "selling" and args.customer:
out.customer_item_code = None
- if args.quotation_to and args.quotation_to != 'Customer':
+ if args.quotation_to and args.quotation_to != "Customer":
return
customer_item_code = item_doc.get("customer_items", {"customer_name": args.customer})
@@ -890,15 +1032,16 @@
if customer_group_item_code and not customer_group_item_code[0].customer_name:
out.customer_item_code = customer_group_item_code[0].ref_code
- if args.transaction_type=="buying" and args.supplier:
+ if args.transaction_type == "buying" and args.supplier:
item_supplier = item_doc.get("supplier_items", {"supplier": args.supplier})
out.supplier_part_no = item_supplier[0].supplier_part_no if item_supplier else None
+
def get_pos_profile_item_details(company, args, pos_profile=None, update_data=False):
res = frappe._dict()
if not frappe.flags.pos_profile and not pos_profile:
- pos_profile = frappe.flags.pos_profile = get_pos_profile(company, args.get('pos_profile'))
+ pos_profile = frappe.flags.pos_profile = get_pos_profile(company, args.get("pos_profile"))
if pos_profile:
for fieldname in ("income_account", "cost_center", "warehouse", "expense_account"):
@@ -910,70 +1053,89 @@
return res
+
@frappe.whitelist()
def get_pos_profile(company, pos_profile=None, user=None):
- if pos_profile: return frappe.get_cached_doc('POS Profile', pos_profile)
+ if pos_profile:
+ return frappe.get_cached_doc("POS Profile", pos_profile)
if not user:
- user = frappe.session['user']
+ user = frappe.session["user"]
condition = "pfu.user = %(user)s AND pfu.default=1"
if user and company:
condition = "pfu.user = %(user)s AND pf.company = %(company)s AND pfu.default=1"
- pos_profile = frappe.db.sql("""SELECT pf.*
+ pos_profile = frappe.db.sql(
+ """SELECT pf.*
FROM
`tabPOS Profile` pf LEFT JOIN `tabPOS Profile User` pfu
ON
pf.name = pfu.parent
WHERE
{cond} AND pf.disabled = 0
- """.format(cond = condition), {
- 'user': user,
- 'company': company
- }, as_dict=1)
+ """.format(
+ cond=condition
+ ),
+ {"user": user, "company": company},
+ as_dict=1,
+ )
if not pos_profile and company:
- pos_profile = frappe.db.sql("""SELECT pf.*
+ pos_profile = frappe.db.sql(
+ """SELECT pf.*
FROM
`tabPOS Profile` pf LEFT JOIN `tabPOS Profile User` pfu
ON
pf.name = pfu.parent
WHERE
pf.company = %(company)s AND pf.disabled = 0
- """, {
- 'company': company
- }, as_dict=1)
+ """,
+ {"company": company},
+ as_dict=1,
+ )
return pos_profile and pos_profile[0] or None
+
def get_serial_nos_by_fifo(args, sales_order=None):
if frappe.db.get_single_value("Stock Settings", "automatically_set_serial_nos_based_on_fifo"):
- return "\n".join(frappe.db.sql_list("""select name from `tabSerial No`
+ return "\n".join(
+ frappe.db.sql_list(
+ """select name from `tabSerial No`
where item_code=%(item_code)s and warehouse=%(warehouse)s and
sales_order=IF(%(sales_order)s IS NULL, sales_order, %(sales_order)s)
order by timestamp(purchase_date, purchase_time)
asc limit %(qty)s""",
- {
- "item_code": args.item_code,
- "warehouse": args.warehouse,
- "qty": abs(cint(args.stock_qty)),
- "sales_order": sales_order
- }))
+ {
+ "item_code": args.item_code,
+ "warehouse": args.warehouse,
+ "qty": abs(cint(args.stock_qty)),
+ "sales_order": sales_order,
+ },
+ )
+ )
+
def get_serial_no_batchwise(args, sales_order=None):
if frappe.db.get_single_value("Stock Settings", "automatically_set_serial_nos_based_on_fifo"):
- return "\n".join(frappe.db.sql_list("""select name from `tabSerial No`
+ return "\n".join(
+ frappe.db.sql_list(
+ """select name from `tabSerial No`
where item_code=%(item_code)s and warehouse=%(warehouse)s and
sales_order=IF(%(sales_order)s IS NULL, sales_order, %(sales_order)s)
and batch_no=IF(%(batch_no)s IS NULL, batch_no, %(batch_no)s) order
- by timestamp(purchase_date, purchase_time) asc limit %(qty)s""", {
- "item_code": args.item_code,
- "warehouse": args.warehouse,
- "batch_no": args.batch_no,
- "qty": abs(cint(args.stock_qty)),
- "sales_order": sales_order
- }))
+ by timestamp(purchase_date, purchase_time) asc limit %(qty)s""",
+ {
+ "item_code": args.item_code,
+ "warehouse": args.warehouse,
+ "batch_no": args.batch_no,
+ "qty": abs(cint(args.stock_qty)),
+ "sales_order": sales_order,
+ },
+ )
+ )
+
@frappe.whitelist()
def get_conversion_factor(item_code, uom):
@@ -981,69 +1143,94 @@
filters = {"parent": item_code, "uom": uom}
if variant_of:
filters["parent"] = ("in", (item_code, variant_of))
- conversion_factor = frappe.db.get_value("UOM Conversion Detail",
- filters, "conversion_factor")
+ conversion_factor = frappe.db.get_value("UOM Conversion Detail", filters, "conversion_factor")
if not conversion_factor:
stock_uom = frappe.db.get_value("Item", item_code, "stock_uom")
conversion_factor = get_uom_conv_factor(uom, stock_uom)
return {"conversion_factor": conversion_factor or 1.0}
+
@frappe.whitelist()
def get_projected_qty(item_code, warehouse):
- return {"projected_qty": frappe.db.get_value("Bin",
- {"item_code": item_code, "warehouse": warehouse}, "projected_qty")}
+ return {
+ "projected_qty": frappe.db.get_value(
+ "Bin", {"item_code": item_code, "warehouse": warehouse}, "projected_qty"
+ )
+ }
+
@frappe.whitelist()
def get_bin_details(item_code, warehouse, company=None):
- bin_details = frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse},
- ["projected_qty", "actual_qty", "reserved_qty"], as_dict=True, cache=True) \
- or {"projected_qty": 0, "actual_qty": 0, "reserved_qty": 0}
+ bin_details = frappe.db.get_value(
+ "Bin",
+ {"item_code": item_code, "warehouse": warehouse},
+ ["projected_qty", "actual_qty", "reserved_qty"],
+ as_dict=True,
+ cache=True,
+ ) or {"projected_qty": 0, "actual_qty": 0, "reserved_qty": 0}
if company:
- bin_details['company_total_stock'] = get_company_total_stock(item_code, company)
+ bin_details["company_total_stock"] = get_company_total_stock(item_code, company)
return bin_details
+
def get_company_total_stock(item_code, company):
- return frappe.db.sql("""SELECT sum(actual_qty) from
+ return frappe.db.sql(
+ """SELECT sum(actual_qty) from
(`tabBin` INNER JOIN `tabWarehouse` ON `tabBin`.warehouse = `tabWarehouse`.name)
WHERE `tabWarehouse`.company = %s and `tabBin`.item_code = %s""",
- (company, item_code))[0][0]
+ (company, item_code),
+ )[0][0]
+
@frappe.whitelist()
def get_serial_no_details(item_code, warehouse, stock_qty, serial_no):
- args = frappe._dict({"item_code":item_code, "warehouse":warehouse, "stock_qty":stock_qty, "serial_no":serial_no})
+ args = frappe._dict(
+ {"item_code": item_code, "warehouse": warehouse, "stock_qty": stock_qty, "serial_no": serial_no}
+ )
serial_no = get_serial_no(args)
- return {'serial_no': serial_no}
+ return {"serial_no": serial_no}
+
@frappe.whitelist()
-def get_bin_details_and_serial_nos(item_code, warehouse, has_batch_no=None, stock_qty=None, serial_no=None):
+def get_bin_details_and_serial_nos(
+ item_code, warehouse, has_batch_no=None, stock_qty=None, serial_no=None
+):
bin_details_and_serial_nos = {}
bin_details_and_serial_nos.update(get_bin_details(item_code, warehouse))
if flt(stock_qty) > 0:
if has_batch_no:
- args = frappe._dict({"item_code":item_code, "warehouse":warehouse, "stock_qty":stock_qty})
+ args = frappe._dict({"item_code": item_code, "warehouse": warehouse, "stock_qty": stock_qty})
serial_no = get_serial_no(args)
- bin_details_and_serial_nos.update({'serial_no': serial_no})
+ bin_details_and_serial_nos.update({"serial_no": serial_no})
return bin_details_and_serial_nos
- bin_details_and_serial_nos.update(get_serial_no_details(item_code, warehouse, stock_qty, serial_no))
+ bin_details_and_serial_nos.update(
+ get_serial_no_details(item_code, warehouse, stock_qty, serial_no)
+ )
return bin_details_and_serial_nos
+
@frappe.whitelist()
def get_batch_qty_and_serial_no(batch_no, stock_qty, warehouse, item_code, has_serial_no):
batch_qty_and_serial_no = {}
batch_qty_and_serial_no.update(get_batch_qty(batch_no, warehouse, item_code))
- if (flt(batch_qty_and_serial_no.get('actual_batch_qty')) >= flt(stock_qty)) and has_serial_no:
- args = frappe._dict({"item_code":item_code, "warehouse":warehouse, "stock_qty":stock_qty, "batch_no":batch_no})
+ if (flt(batch_qty_and_serial_no.get("actual_batch_qty")) >= flt(stock_qty)) and has_serial_no:
+ args = frappe._dict(
+ {"item_code": item_code, "warehouse": warehouse, "stock_qty": stock_qty, "batch_no": batch_no}
+ )
serial_no = get_serial_no(args)
- batch_qty_and_serial_no.update({'serial_no': serial_no})
+ batch_qty_and_serial_no.update({"serial_no": serial_no})
return batch_qty_and_serial_no
+
@frappe.whitelist()
def get_batch_qty(batch_no, warehouse, item_code):
from erpnext.stock.doctype.batch import batch
+
if batch_no:
- return {'actual_batch_qty': batch.get_batch_qty(batch_no, warehouse)}
+ return {"actual_batch_qty": batch.get_batch_qty(batch_no, warehouse)}
+
@frappe.whitelist()
def apply_price_list(args, as_doc=False):
@@ -1053,23 +1240,23 @@
:param args: See below
:param as_doc: Updates value in the passed dict
- args = {
- "doctype": "",
- "name": "",
- "items": [{"doctype": "", "name": "", "item_code": "", "brand": "", "item_group": ""}, ...],
- "conversion_rate": 1.0,
- "selling_price_list": None,
- "price_list_currency": None,
- "price_list_uom_dependant": None,
- "plc_conversion_rate": 1.0,
- "doctype": "",
- "name": "",
- "supplier": None,
- "transaction_date": None,
- "conversion_rate": 1.0,
- "buying_price_list": None,
- "ignore_pricing_rule": 0/1
- }
+ args = {
+ "doctype": "",
+ "name": "",
+ "items": [{"doctype": "", "name": "", "item_code": "", "brand": "", "item_group": ""}, ...],
+ "conversion_rate": 1.0,
+ "selling_price_list": None,
+ "price_list_currency": None,
+ "price_list_uom_dependant": None,
+ "plc_conversion_rate": 1.0,
+ "doctype": "",
+ "name": "",
+ "supplier": None,
+ "transaction_date": None,
+ "conversion_rate": 1.0,
+ "buying_price_list": None,
+ "ignore_pricing_rule": 0/1
+ }
"""
args = process_args(args)
@@ -1089,10 +1276,10 @@
children.append(item_details)
if as_doc:
- args.price_list_currency = parent.price_list_currency,
+ args.price_list_currency = (parent.price_list_currency,)
args.plc_conversion_rate = parent.plc_conversion_rate
- if args.get('items'):
- for i, item in enumerate(args.get('items')):
+ if args.get("items"):
+ for i, item in enumerate(args.get("items")):
for fieldname in children[i]:
# if the field exists in the original doc
# update the value
@@ -1100,26 +1287,25 @@
item[fieldname] = children[i][fieldname]
return args
else:
- return {
- "parent": parent,
- "children": children
- }
+ return {"parent": parent, "children": children}
+
def apply_price_list_on_item(args):
- item_doc = frappe.db.get_value("Item", args.item_code, ['name', 'variant_of'], as_dict=1)
+ item_doc = frappe.db.get_value("Item", args.item_code, ["name", "variant_of"], as_dict=1)
item_details = get_price_list_rate(args, item_doc)
item_details.update(get_pricing_rule_for_item(args, item_details.price_list_rate))
return item_details
+
def get_price_list_currency_and_exchange_rate(args):
if not args.price_list:
return {}
- if args.doctype in ['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice']:
+ if args.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]:
args.update({"exchange_rate": "for_selling"})
- elif args.doctype in ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice']:
+ elif args.doctype in ["Purchase Order", "Purchase Receipt", "Purchase Invoice"]:
args.update({"exchange_rate": "for_buying"})
price_list_details = get_price_list_details(args.price_list)
@@ -1130,25 +1316,38 @@
plc_conversion_rate = args.plc_conversion_rate
company_currency = get_company_currency(args.company)
- if (not plc_conversion_rate) or (price_list_currency and args.price_list_currency \
- and price_list_currency != args.price_list_currency):
- # cksgb 19/09/2016: added args.transaction_date as posting_date argument for get_exchange_rate
- plc_conversion_rate = get_exchange_rate(price_list_currency, company_currency,
- args.transaction_date, args.exchange_rate) or plc_conversion_rate
+ if (not plc_conversion_rate) or (
+ price_list_currency
+ and args.price_list_currency
+ and price_list_currency != args.price_list_currency
+ ):
+ # cksgb 19/09/2016: added args.transaction_date as posting_date argument for get_exchange_rate
+ plc_conversion_rate = (
+ get_exchange_rate(
+ price_list_currency, company_currency, args.transaction_date, args.exchange_rate
+ )
+ or plc_conversion_rate
+ )
- return frappe._dict({
- "price_list_currency": price_list_currency,
- "price_list_uom_dependant": price_list_uom_dependant,
- "plc_conversion_rate": plc_conversion_rate or 1
- })
+ return frappe._dict(
+ {
+ "price_list_currency": price_list_currency,
+ "price_list_uom_dependant": price_list_uom_dependant,
+ "plc_conversion_rate": plc_conversion_rate or 1,
+ }
+ )
+
@frappe.whitelist()
def get_default_bom(item_code=None):
if item_code:
- bom = frappe.db.get_value("BOM", {"docstatus": 1, "is_default": 1, "is_active": 1, "item": item_code})
+ bom = frappe.db.get_value(
+ "BOM", {"docstatus": 1, "is_default": 1, "is_active": 1, "item": item_code}
+ )
if bom:
return bom
+
@frappe.whitelist()
def get_valuation_rate(item_code, company, warehouse=None):
item = get_item_defaults(item_code, company)
@@ -1157,43 +1356,57 @@
# item = frappe.get_doc("Item", item_code)
if item.get("is_stock_item"):
if not warehouse:
- warehouse = item.get("default_warehouse") or item_group.get("default_warehouse") or brand.get("default_warehouse")
+ warehouse = (
+ item.get("default_warehouse")
+ or item_group.get("default_warehouse")
+ or brand.get("default_warehouse")
+ )
- return frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse},
- ["valuation_rate"], as_dict=True) or {"valuation_rate": 0}
+ return frappe.db.get_value(
+ "Bin", {"item_code": item_code, "warehouse": warehouse}, ["valuation_rate"], as_dict=True
+ ) or {"valuation_rate": 0}
elif not item.get("is_stock_item"):
- valuation_rate =frappe.db.sql("""select sum(base_net_amount) / sum(qty*conversion_factor)
+ valuation_rate = frappe.db.sql(
+ """select sum(base_net_amount) / sum(qty*conversion_factor)
from `tabPurchase Invoice Item`
- where item_code = %s and docstatus=1""", item_code)
+ where item_code = %s and docstatus=1""",
+ item_code,
+ )
if valuation_rate:
return {"valuation_rate": valuation_rate[0][0] or 0.0}
else:
return {"valuation_rate": 0.0}
+
def get_gross_profit(out):
if out.valuation_rate:
- out.update({
- "gross_profit": ((out.base_rate - out.valuation_rate) * out.stock_qty)
- })
+ out.update({"gross_profit": ((out.base_rate - out.valuation_rate) * out.stock_qty)})
return out
+
@frappe.whitelist()
def get_serial_no(args, serial_nos=None, sales_order=None):
serial_no = None
if isinstance(args, str):
args = json.loads(args)
args = frappe._dict(args)
- if args.get('doctype') == 'Sales Invoice' and not args.get('update_stock'):
+ if args.get("doctype") == "Sales Invoice" and not args.get("update_stock"):
return ""
- if args.get('warehouse') and args.get('stock_qty') and args.get('item_code'):
- has_serial_no = frappe.get_value('Item', {'item_code': args.item_code}, "has_serial_no")
- if args.get('batch_no') and has_serial_no == 1:
+ if args.get("warehouse") and args.get("stock_qty") and args.get("item_code"):
+ has_serial_no = frappe.get_value("Item", {"item_code": args.item_code}, "has_serial_no")
+ if args.get("batch_no") and has_serial_no == 1:
return get_serial_no_batchwise(args, sales_order)
elif has_serial_no == 1:
- args = json.dumps({"item_code": args.get('item_code'),"warehouse": args.get('warehouse'),"stock_qty": args.get('stock_qty')})
+ args = json.dumps(
+ {
+ "item_code": args.get("item_code"),
+ "warehouse": args.get("warehouse"),
+ "stock_qty": args.get("stock_qty"),
+ }
+ )
args = process_args(args)
serial_no = get_serial_nos_by_fifo(args, sales_order)
@@ -1210,53 +1423,68 @@
if blanket_order_details:
out.update(blanket_order_details)
+
@frappe.whitelist()
def get_blanket_order_details(args):
if isinstance(args, str):
args = frappe._dict(json.loads(args))
blanket_order_details = None
- condition = ''
+ condition = ""
if args.item_code:
if args.customer and args.doctype == "Sales Order":
- condition = ' and bo.customer=%(customer)s'
+ condition = " and bo.customer=%(customer)s"
elif args.supplier and args.doctype == "Purchase Order":
- condition = ' and bo.supplier=%(supplier)s'
+ condition = " and bo.supplier=%(supplier)s"
if args.blanket_order:
- condition += ' and bo.name =%(blanket_order)s'
+ condition += " and bo.name =%(blanket_order)s"
if args.transaction_date:
- condition += ' and bo.to_date>=%(transaction_date)s'
+ condition += " and bo.to_date>=%(transaction_date)s"
- blanket_order_details = frappe.db.sql('''
+ blanket_order_details = frappe.db.sql(
+ """
select boi.rate as blanket_order_rate, bo.name as blanket_order
from `tabBlanket Order` bo, `tabBlanket Order Item` boi
where bo.company=%(company)s and boi.item_code=%(item_code)s
and bo.docstatus=1 and bo.name = boi.parent {0}
- '''.format(condition), args, as_dict=True)
+ """.format(
+ condition
+ ),
+ args,
+ as_dict=True,
+ )
- blanket_order_details = blanket_order_details[0] if blanket_order_details else ''
+ blanket_order_details = blanket_order_details[0] if blanket_order_details else ""
return blanket_order_details
+
def get_so_reservation_for_item(args):
reserved_so = None
- if args.get('against_sales_order'):
- if get_reserved_qty_for_so(args.get('against_sales_order'), args.get('item_code')):
- reserved_so = args.get('against_sales_order')
- elif args.get('against_sales_invoice'):
- sales_order = frappe.db.sql("""select sales_order from `tabSales Invoice Item` where
- parent=%s and item_code=%s""", (args.get('against_sales_invoice'), args.get('item_code')))
+ if args.get("against_sales_order"):
+ if get_reserved_qty_for_so(args.get("against_sales_order"), args.get("item_code")):
+ reserved_so = args.get("against_sales_order")
+ elif args.get("against_sales_invoice"):
+ sales_order = frappe.db.sql(
+ """select sales_order from `tabSales Invoice Item` where
+ parent=%s and item_code=%s""",
+ (args.get("against_sales_invoice"), args.get("item_code")),
+ )
if sales_order and sales_order[0]:
- if get_reserved_qty_for_so(sales_order[0][0], args.get('item_code')):
+ if get_reserved_qty_for_so(sales_order[0][0], args.get("item_code")):
reserved_so = sales_order[0]
elif args.get("sales_order"):
- if get_reserved_qty_for_so(args.get('sales_order'), args.get('item_code')):
- reserved_so = args.get('sales_order')
+ if get_reserved_qty_for_so(args.get("sales_order"), args.get("item_code")):
+ reserved_so = args.get("sales_order")
return reserved_so
+
def get_reserved_qty_for_so(sales_order, item_code):
- reserved_qty = frappe.db.sql("""select sum(qty) from `tabSales Order Item`
+ reserved_qty = frappe.db.sql(
+ """select sum(qty) from `tabSales Order Item`
where parent=%s and item_code=%s and ensure_delivery_based_on_produced_serial_no=1
- """, (sales_order, item_code))
+ """,
+ (sales_order, item_code),
+ )
if reserved_qty and reserved_qty[0][0]:
return reserved_qty[0][0]
else:
diff --git a/erpnext/stock/reorder_item.py b/erpnext/stock/reorder_item.py
index 21f2573..a96ffef 100644
--- a/erpnext/stock/reorder_item.py
+++ b/erpnext/stock/reorder_item.py
@@ -13,22 +13,29 @@
def reorder_item():
- """ Reorder item if stock reaches reorder level"""
+ """Reorder item if stock reaches reorder level"""
# if initial setup not completed, return
if not (frappe.db.a_row_exists("Company") and frappe.db.a_row_exists("Fiscal Year")):
return
- if cint(frappe.db.get_value('Stock Settings', None, 'auto_indent')):
+ if cint(frappe.db.get_value("Stock Settings", None, "auto_indent")):
return _reorder_item()
+
def _reorder_item():
material_requests = {"Purchase": {}, "Transfer": {}, "Material Issue": {}, "Manufacture": {}}
- warehouse_company = frappe._dict(frappe.db.sql("""select name, company from `tabWarehouse`
- where disabled=0"""))
- default_company = (erpnext.get_default_company() or
- frappe.db.sql("""select name from tabCompany limit 1""")[0][0])
+ warehouse_company = frappe._dict(
+ frappe.db.sql(
+ """select name, company from `tabWarehouse`
+ where disabled=0"""
+ )
+ )
+ default_company = (
+ erpnext.get_default_company() or frappe.db.sql("""select name from tabCompany limit 1""")[0][0]
+ )
- items_to_consider = frappe.db.sql_list("""select name from `tabItem` item
+ items_to_consider = frappe.db.sql_list(
+ """select name from `tabItem` item
where is_stock_item=1 and has_variants=0
and disabled=0
and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %(today)s)
@@ -36,14 +43,17 @@
or (variant_of is not null and variant_of != ''
and exists (select name from `tabItem Reorder` ir where ir.parent=item.variant_of))
)""",
- {"today": nowdate()})
+ {"today": nowdate()},
+ )
if not items_to_consider:
return
item_warehouse_projected_qty = get_item_warehouse_projected_qty(items_to_consider)
- def add_to_material_request(item_code, warehouse, reorder_level, reorder_qty, material_request_type, warehouse_group=None):
+ def add_to_material_request(
+ item_code, warehouse, reorder_level, reorder_qty, material_request_type, warehouse_group=None
+ ):
if warehouse not in warehouse_company:
# a disabled warehouse
return
@@ -64,11 +74,9 @@
company = warehouse_company.get(warehouse) or default_company
- material_requests[material_request_type].setdefault(company, []).append({
- "item_code": item_code,
- "warehouse": warehouse,
- "reorder_qty": reorder_qty
- })
+ material_requests[material_request_type].setdefault(company, []).append(
+ {"item_code": item_code, "warehouse": warehouse, "reorder_qty": reorder_qty}
+ )
for item_code in items_to_consider:
item = frappe.get_doc("Item", item_code)
@@ -78,19 +86,30 @@
if item.get("reorder_levels"):
for d in item.get("reorder_levels"):
- add_to_material_request(item_code, d.warehouse, d.warehouse_reorder_level,
- d.warehouse_reorder_qty, d.material_request_type, warehouse_group=d.warehouse_group)
+ add_to_material_request(
+ item_code,
+ d.warehouse,
+ d.warehouse_reorder_level,
+ d.warehouse_reorder_qty,
+ d.material_request_type,
+ warehouse_group=d.warehouse_group,
+ )
if material_requests:
return create_material_request(material_requests)
+
def get_item_warehouse_projected_qty(items_to_consider):
item_warehouse_projected_qty = {}
- for item_code, warehouse, projected_qty in frappe.db.sql("""select item_code, warehouse, projected_qty
+ for item_code, warehouse, projected_qty in frappe.db.sql(
+ """select item_code, warehouse, projected_qty
from tabBin where item_code in ({0})
- and (warehouse != "" and warehouse is not null)"""\
- .format(", ".join(["%s"] * len(items_to_consider))), items_to_consider):
+ and (warehouse != "" and warehouse is not null)""".format(
+ ", ".join(["%s"] * len(items_to_consider))
+ ),
+ items_to_consider,
+ ):
if item_code not in item_warehouse_projected_qty:
item_warehouse_projected_qty.setdefault(item_code, {})
@@ -102,15 +121,18 @@
while warehouse_doc.parent_warehouse:
if not item_warehouse_projected_qty.get(item_code, {}).get(warehouse_doc.parent_warehouse):
- item_warehouse_projected_qty.setdefault(item_code, {})[warehouse_doc.parent_warehouse] = flt(projected_qty)
+ item_warehouse_projected_qty.setdefault(item_code, {})[warehouse_doc.parent_warehouse] = flt(
+ projected_qty
+ )
else:
item_warehouse_projected_qty[item_code][warehouse_doc.parent_warehouse] += flt(projected_qty)
warehouse_doc = frappe.get_doc("Warehouse", warehouse_doc.parent_warehouse)
return item_warehouse_projected_qty
+
def create_material_request(material_requests):
- """ Create indent on reaching reorder level """
+ """Create indent on reaching reorder level"""
mr_list = []
exceptions_list = []
@@ -131,11 +153,13 @@
continue
mr = frappe.new_doc("Material Request")
- mr.update({
- "company": company,
- "transaction_date": nowdate(),
- "material_request_type": "Material Transfer" if request_type=="Transfer" else request_type
- })
+ mr.update(
+ {
+ "company": company,
+ "transaction_date": nowdate(),
+ "material_request_type": "Material Transfer" if request_type == "Transfer" else request_type,
+ }
+ )
for d in items:
d = frappe._dict(d)
@@ -143,30 +167,37 @@
uom = item.stock_uom
conversion_factor = 1.0
- if request_type == 'Purchase':
+ if request_type == "Purchase":
uom = item.purchase_uom or item.stock_uom
if uom != item.stock_uom:
- conversion_factor = frappe.db.get_value("UOM Conversion Detail",
- {'parent': item.name, 'uom': uom}, 'conversion_factor') or 1.0
+ conversion_factor = (
+ frappe.db.get_value(
+ "UOM Conversion Detail", {"parent": item.name, "uom": uom}, "conversion_factor"
+ )
+ or 1.0
+ )
must_be_whole_number = frappe.db.get_value("UOM", uom, "must_be_whole_number", cache=True)
qty = d.reorder_qty / conversion_factor
if must_be_whole_number:
qty = ceil(qty)
- mr.append("items", {
- "doctype": "Material Request Item",
- "item_code": d.item_code,
- "schedule_date": add_days(nowdate(),cint(item.lead_time_days)),
- "qty": qty,
- "uom": uom,
- "stock_uom": item.stock_uom,
- "warehouse": d.warehouse,
- "item_name": item.item_name,
- "description": item.description,
- "item_group": item.item_group,
- "brand": item.brand,
- })
+ mr.append(
+ "items",
+ {
+ "doctype": "Material Request Item",
+ "item_code": d.item_code,
+ "schedule_date": add_days(nowdate(), cint(item.lead_time_days)),
+ "qty": qty,
+ "uom": uom,
+ "stock_uom": item.stock_uom,
+ "warehouse": d.warehouse,
+ "item_name": item.item_name,
+ "description": item.description,
+ "item_group": item.item_group,
+ "brand": item.brand,
+ },
+ )
schedule_dates = [d.schedule_date for d in mr.items]
mr.schedule_date = max(schedule_dates or [nowdate()])
@@ -180,10 +211,11 @@
if mr_list:
if getattr(frappe.local, "reorder_email_notify", None) is None:
- frappe.local.reorder_email_notify = cint(frappe.db.get_value('Stock Settings', None,
- 'reorder_email_notify'))
+ frappe.local.reorder_email_notify = cint(
+ frappe.db.get_value("Stock Settings", None, "reorder_email_notify")
+ )
- if(frappe.local.reorder_email_notify):
+ if frappe.local.reorder_email_notify:
send_email_notification(mr_list)
if exceptions_list:
@@ -191,33 +223,44 @@
return mr_list
-def send_email_notification(mr_list):
- """ Notify user about auto creation of indent"""
- email_list = frappe.db.sql_list("""select distinct r.parent
+def send_email_notification(mr_list):
+ """Notify user about auto creation of indent"""
+
+ email_list = frappe.db.sql_list(
+ """select distinct r.parent
from `tabHas Role` r, tabUser p
where p.name = r.parent and p.enabled = 1 and p.docstatus < 2
and r.role in ('Purchase Manager','Stock Manager')
- and p.name not in ('Administrator', 'All', 'Guest')""")
+ and p.name not in ('Administrator', 'All', 'Guest')"""
+ )
- msg = frappe.render_template("templates/emails/reorder_item.html", {
- "mr_list": mr_list
- })
+ msg = frappe.render_template("templates/emails/reorder_item.html", {"mr_list": mr_list})
- frappe.sendmail(recipients=email_list,
- subject=_('Auto Material Requests Generated'), message = msg)
+ frappe.sendmail(recipients=email_list, subject=_("Auto Material Requests Generated"), message=msg)
+
def notify_errors(exceptions_list):
subject = _("[Important] [ERPNext] Auto Reorder Errors")
- content = _("Dear System Manager,") + "<br>" + _("An error occured for certain Items while creating Material Requests based on Re-order level. \
- Please rectify these issues :") + "<br>"
+ content = (
+ _("Dear System Manager,")
+ + "<br>"
+ + _(
+ "An error occured for certain Items while creating Material Requests based on Re-order level. \
+ Please rectify these issues :"
+ )
+ + "<br>"
+ )
for exception in exceptions_list:
exception = json.loads(exception)
- error_message = """<div class='small text-muted'>{0}</div><br>""".format(_(exception.get("message")))
+ error_message = """<div class='small text-muted'>{0}</div><br>""".format(
+ _(exception.get("message"))
+ )
content += error_message
content += _("Regards,") + "<br>" + _("Administrator")
from frappe.email import sendmail_to_system_managers
+
sendmail_to_system_managers(subject, content)
diff --git a/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py b/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py
index 87097c7..3d9b046 100644
--- a/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py
+++ b/erpnext/stock/report/batch_item_expiry_status/batch_item_expiry_status.py
@@ -8,7 +8,8 @@
def execute(filters=None):
- if not filters: filters = {}
+ if not filters:
+ filters = {}
float_precision = cint(frappe.db.get_default("float_precision")) or 3
@@ -22,22 +23,37 @@
for batch in sorted(iwb_map[item][wh]):
qty_dict = iwb_map[item][wh][batch]
- data.append([item, item_map[item]["item_name"], item_map[item]["description"], wh, batch,
- frappe.db.get_value('Batch', batch, 'expiry_date'), qty_dict.expiry_status
- ])
-
+ data.append(
+ [
+ item,
+ item_map[item]["item_name"],
+ item_map[item]["description"],
+ wh,
+ batch,
+ frappe.db.get_value("Batch", batch, "expiry_date"),
+ qty_dict.expiry_status,
+ ]
+ )
return columns, data
+
def get_columns(filters):
"""return columns based on filters"""
- columns = [_("Item") + ":Link/Item:100"] + [_("Item Name") + "::150"] + [_("Description") + "::150"] + \
- [_("Warehouse") + ":Link/Warehouse:100"] + [_("Batch") + ":Link/Batch:100"] + [_("Expires On") + ":Date:90"] + \
- [_("Expiry (In Days)") + ":Int:120"]
+ columns = (
+ [_("Item") + ":Link/Item:100"]
+ + [_("Item Name") + "::150"]
+ + [_("Description") + "::150"]
+ + [_("Warehouse") + ":Link/Warehouse:100"]
+ + [_("Batch") + ":Link/Batch:100"]
+ + [_("Expires On") + ":Date:90"]
+ + [_("Expiry (In Days)") + ":Int:120"]
+ )
return columns
+
def get_conditions(filters):
conditions = ""
if not filters.get("from_date"):
@@ -50,14 +66,19 @@
return conditions
+
def get_stock_ledger_entries(filters):
conditions = get_conditions(filters)
- return frappe.db.sql("""select item_code, batch_no, warehouse,
+ return frappe.db.sql(
+ """select item_code, batch_no, warehouse,
posting_date, actual_qty
from `tabStock Ledger Entry`
where is_cancelled = 0
- and docstatus < 2 and ifnull(batch_no, '') != '' %s order by item_code, warehouse""" %
- conditions, as_dict=1)
+ and docstatus < 2 and ifnull(batch_no, '') != '' %s order by item_code, warehouse"""
+ % conditions,
+ as_dict=1,
+ )
+
def get_item_warehouse_batch_map(filters, float_precision):
sle = get_stock_ledger_entries(filters)
@@ -67,13 +88,13 @@
to_date = getdate(filters["to_date"])
for d in sle:
- iwb_map.setdefault(d.item_code, {}).setdefault(d.warehouse, {})\
- .setdefault(d.batch_no, frappe._dict({
- "expires_on": None, "expiry_status": None}))
+ iwb_map.setdefault(d.item_code, {}).setdefault(d.warehouse, {}).setdefault(
+ d.batch_no, frappe._dict({"expires_on": None, "expiry_status": None})
+ )
qty_dict = iwb_map[d.item_code][d.warehouse][d.batch_no]
- expiry_date_unicode = frappe.db.get_value('Batch', d.batch_no, 'expiry_date')
+ expiry_date_unicode = frappe.db.get_value("Batch", d.batch_no, "expiry_date")
qty_dict.expires_on = expiry_date_unicode
exp_date = frappe.utils.data.getdate(expiry_date_unicode)
@@ -88,6 +109,7 @@
return iwb_map
+
def get_item_details(filters):
item_map = {}
for d in frappe.db.sql("select name, item_name, description from tabItem", as_dict=1):
diff --git a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py
index 9b21dea..8a13300 100644
--- a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py
+++ b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py
@@ -8,7 +8,8 @@
def execute(filters=None):
- if not filters: filters = {}
+ if not filters:
+ filters = {}
if filters.from_date > filters.to_date:
frappe.throw(_("From Date must be before To Date"))
@@ -26,11 +27,20 @@
for batch in sorted(iwb_map[item][wh]):
qty_dict = iwb_map[item][wh][batch]
if qty_dict.opening_qty or qty_dict.in_qty or qty_dict.out_qty or qty_dict.bal_qty:
- data.append([item, item_map[item]["item_name"], item_map[item]["description"], wh, batch,
- flt(qty_dict.opening_qty, float_precision), flt(qty_dict.in_qty, float_precision),
- flt(qty_dict.out_qty, float_precision), flt(qty_dict.bal_qty, float_precision),
- item_map[item]["stock_uom"]
- ])
+ data.append(
+ [
+ item,
+ item_map[item]["item_name"],
+ item_map[item]["description"],
+ wh,
+ batch,
+ flt(qty_dict.opening_qty, float_precision),
+ flt(qty_dict.in_qty, float_precision),
+ flt(qty_dict.out_qty, float_precision),
+ flt(qty_dict.bal_qty, float_precision),
+ item_map[item]["stock_uom"],
+ ]
+ )
return columns, data
@@ -38,10 +48,18 @@
def get_columns(filters):
"""return columns based on filters"""
- columns = [_("Item") + ":Link/Item:100"] + [_("Item Name") + "::150"] + [_("Description") + "::150"] + \
- [_("Warehouse") + ":Link/Warehouse:100"] + [_("Batch") + ":Link/Batch:100"] + [_("Opening Qty") + ":Float:90"] + \
- [_("In Qty") + ":Float:80"] + [_("Out Qty") + ":Float:80"] + [_("Balance Qty") + ":Float:90"] + \
- [_("UOM") + "::90"]
+ columns = (
+ [_("Item") + ":Link/Item:100"]
+ + [_("Item Name") + "::150"]
+ + [_("Description") + "::150"]
+ + [_("Warehouse") + ":Link/Warehouse:100"]
+ + [_("Batch") + ":Link/Batch:100"]
+ + [_("Opening Qty") + ":Float:90"]
+ + [_("In Qty") + ":Float:80"]
+ + [_("Out Qty") + ":Float:80"]
+ + [_("Balance Qty") + ":Float:90"]
+ + [_("UOM") + "::90"]
+ )
return columns
@@ -66,13 +84,16 @@
# get all details
def get_stock_ledger_entries(filters):
conditions = get_conditions(filters)
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
select item_code, batch_no, warehouse, posting_date, sum(actual_qty) as actual_qty
from `tabStock Ledger Entry`
where is_cancelled = 0 and docstatus < 2 and ifnull(batch_no, '') != '' %s
group by voucher_no, batch_no, item_code, warehouse
- order by item_code, warehouse""" %
- conditions, as_dict=1)
+ order by item_code, warehouse"""
+ % conditions,
+ as_dict=1,
+ )
def get_item_warehouse_batch_map(filters, float_precision):
@@ -83,20 +104,21 @@
to_date = getdate(filters["to_date"])
for d in sle:
- iwb_map.setdefault(d.item_code, {}).setdefault(d.warehouse, {})\
- .setdefault(d.batch_no, frappe._dict({
- "opening_qty": 0.0, "in_qty": 0.0, "out_qty": 0.0, "bal_qty": 0.0
- }))
+ iwb_map.setdefault(d.item_code, {}).setdefault(d.warehouse, {}).setdefault(
+ d.batch_no, frappe._dict({"opening_qty": 0.0, "in_qty": 0.0, "out_qty": 0.0, "bal_qty": 0.0})
+ )
qty_dict = iwb_map[d.item_code][d.warehouse][d.batch_no]
if d.posting_date < from_date:
- qty_dict.opening_qty = flt(qty_dict.opening_qty, float_precision) \
- + flt(d.actual_qty, float_precision)
+ qty_dict.opening_qty = flt(qty_dict.opening_qty, float_precision) + flt(
+ d.actual_qty, float_precision
+ )
elif d.posting_date >= from_date and d.posting_date <= to_date:
if flt(d.actual_qty) > 0:
qty_dict.in_qty = flt(qty_dict.in_qty, float_precision) + flt(d.actual_qty, float_precision)
else:
- qty_dict.out_qty = flt(qty_dict.out_qty, float_precision) \
- + abs(flt(d.actual_qty, float_precision))
+ qty_dict.out_qty = flt(qty_dict.out_qty, float_precision) + abs(
+ flt(d.actual_qty, float_precision)
+ )
qty_dict.bal_qty = flt(qty_dict.bal_qty, float_precision) + flt(d.actual_qty, float_precision)
diff --git a/erpnext/stock/report/bom_search/bom_search.py b/erpnext/stock/report/bom_search/bom_search.py
index a22b224..3be87ab 100644
--- a/erpnext/stock/report/bom_search/bom_search.py
+++ b/erpnext/stock/report/bom_search/bom_search.py
@@ -10,11 +10,13 @@
parents = {
"Product Bundle Item": "Product Bundle",
"BOM Explosion Item": "BOM",
- "BOM Item": "BOM"
+ "BOM Item": "BOM",
}
- for doctype in ("Product Bundle Item",
- "BOM Explosion Item" if filters.search_sub_assemblies else "BOM Item"):
+ for doctype in (
+ "Product Bundle Item",
+ "BOM Explosion Item" if filters.search_sub_assemblies else "BOM Item",
+ ):
all_boms = {}
for d in frappe.get_all(doctype, fields=["parent", "item_code"]):
all_boms.setdefault(d.parent, []).append(d.item_code)
@@ -29,16 +31,13 @@
if valid:
data.append((parent, parents[doctype]))
- return [{
- "fieldname": "parent",
- "label": "BOM",
- "width": 200,
- "fieldtype": "Dynamic Link",
- "options": "doctype"
- },
- {
- "fieldname": "doctype",
- "label": "Type",
- "width": 200,
- "fieldtype": "Data"
- }], data
+ return [
+ {
+ "fieldname": "parent",
+ "label": "BOM",
+ "width": 200,
+ "fieldtype": "Dynamic Link",
+ "options": "doctype",
+ },
+ {"fieldname": "doctype", "label": "Type", "width": 200, "fieldtype": "Data"},
+ ], data
diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py
index 058af77..4642a53 100644
--- a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py
+++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py
@@ -41,18 +41,8 @@
def get_columns() -> Columns:
return [
- {
- 'label': _('Item Group'),
- 'fieldname': 'item_group',
- 'fieldtype': 'Data',
- 'width': '200'
- },
- {
- 'label': _('COGS Debit'),
- 'fieldname': 'cogs_debit',
- 'fieldtype': 'Currency',
- 'width': '200'
- }
+ {"label": _("Item Group"), "fieldname": "item_group", "fieldtype": "Data", "width": "200"},
+ {"label": _("COGS Debit"), "fieldname": "cogs_debit", "fieldtype": "Currency", "width": "200"},
]
@@ -67,11 +57,11 @@
data = []
for item in leveled_dict.items():
i = item[1]
- if i['agg_value'] == 0:
+ if i["agg_value"] == 0:
continue
- data.append(get_row(i['name'], i['agg_value'], i['is_group'], i['level']))
- if i['self_value'] < i['agg_value'] and i['self_value'] > 0:
- data.append(get_row(i['name'], i['self_value'], 0, i['level'] + 1))
+ data.append(get_row(i["name"], i["agg_value"], i["is_group"], i["level"]))
+ if i["self_value"] < i["agg_value"] and i["self_value"] > 0:
+ data.append(get_row(i["name"], i["self_value"], 0, i["level"] + 1))
return data
@@ -79,8 +69,8 @@
gl_entries = get_gl_entries(filters, [])
filtered_entries = []
for entry in gl_entries:
- posting_date = entry.get('posting_date')
- from_date = filters.get('from_date')
+ posting_date = entry.get("posting_date")
+ from_date = filters.get("from_date")
if date_diff(from_date, posting_date) > 0:
continue
filtered_entries.append(entry)
@@ -88,10 +78,11 @@
def get_stock_value_difference_list(filtered_entries: FilteredEntries) -> SVDList:
- voucher_nos = [fe.get('voucher_no') for fe in filtered_entries]
+ voucher_nos = [fe.get("voucher_no") for fe in filtered_entries]
svd_list = frappe.get_list(
- 'Stock Ledger Entry', fields=['item_code','stock_value_difference'],
- filters=[('voucher_no', 'in', voucher_nos), ("is_cancelled", "=", 0)]
+ "Stock Ledger Entry",
+ fields=["item_code", "stock_value_difference"],
+ filters=[("voucher_no", "in", voucher_nos), ("is_cancelled", "=", 0)],
)
assign_item_groups_to_svd_list(svd_list)
return svd_list
@@ -99,7 +90,7 @@
def get_leveled_dict() -> OrderedDict:
item_groups_dict = get_item_groups_dict()
- lr_list = sorted(item_groups_dict, key=lambda x : int(x[0]))
+ lr_list = sorted(item_groups_dict, key=lambda x: int(x[0]))
leveled_dict = OrderedDict()
current_level = 0
nesting_r = []
@@ -108,10 +99,10 @@
nesting_r.pop()
current_level -= 1
- leveled_dict[(l,r)] = {
- 'level' : current_level,
- 'name' : item_groups_dict[(l,r)]['name'],
- 'is_group' : item_groups_dict[(l,r)]['is_group']
+ leveled_dict[(l, r)] = {
+ "level": current_level,
+ "name": item_groups_dict[(l, r)]["name"],
+ "is_group": item_groups_dict[(l, r)]["is_group"],
}
if int(r) - int(l) > 1:
@@ -123,38 +114,38 @@
def assign_self_values(leveled_dict: OrderedDict, svd_list: SVDList) -> None:
- key_dict = {v['name']:k for k, v in leveled_dict.items()}
+ key_dict = {v["name"]: k for k, v in leveled_dict.items()}
for item in svd_list:
key = key_dict[item.get("item_group")]
- leveled_dict[key]['self_value'] += -item.get("stock_value_difference")
+ leveled_dict[key]["self_value"] += -item.get("stock_value_difference")
def assign_agg_values(leveled_dict: OrderedDict) -> None:
keys = list(leveled_dict.keys())[::-1]
- prev_level = leveled_dict[keys[-1]]['level']
+ prev_level = leveled_dict[keys[-1]]["level"]
accu = [0]
for k in keys[:-1]:
- curr_level = leveled_dict[k]['level']
+ curr_level = leveled_dict[k]["level"]
if curr_level == prev_level:
- accu[-1] += leveled_dict[k]['self_value']
- leveled_dict[k]['agg_value'] = leveled_dict[k]['self_value']
+ accu[-1] += leveled_dict[k]["self_value"]
+ leveled_dict[k]["agg_value"] = leveled_dict[k]["self_value"]
elif curr_level > prev_level:
- accu.append(leveled_dict[k]['self_value'])
- leveled_dict[k]['agg_value'] = accu[-1]
+ accu.append(leveled_dict[k]["self_value"])
+ leveled_dict[k]["agg_value"] = accu[-1]
elif curr_level < prev_level:
- accu[-1] += leveled_dict[k]['self_value']
- leveled_dict[k]['agg_value'] = accu[-1]
+ accu[-1] += leveled_dict[k]["self_value"]
+ leveled_dict[k]["agg_value"] = accu[-1]
prev_level = curr_level
# root node
rk = keys[-1]
- leveled_dict[rk]['agg_value'] = sum(accu) + leveled_dict[rk]['self_value']
+ leveled_dict[rk]["agg_value"] = sum(accu) + leveled_dict[rk]["self_value"]
-def get_row(name:str, value:float, is_bold:int, indent:int) -> Row:
+def get_row(name: str, value: float, is_bold: int, indent: int) -> Row:
item_group = name
if is_bold:
item_group = frappe.bold(item_group)
@@ -168,20 +159,20 @@
def get_item_groups_map(svd_list: SVDList) -> Dict[str, str]:
- item_codes = set(i['item_code'] for i in svd_list)
+ item_codes = set(i["item_code"] for i in svd_list)
ig_list = frappe.get_list(
- 'Item', fields=['item_code','item_group'],
- filters=[('item_code', 'in', item_codes)]
+ "Item", fields=["item_code", "item_group"], filters=[("item_code", "in", item_codes)]
)
- return {i['item_code']:i['item_group'] for i in ig_list}
+ return {i["item_code"]: i["item_group"] for i in ig_list}
def get_item_groups_dict() -> ItemGroupsDict:
item_groups_list = frappe.get_all("Item Group", fields=("name", "is_group", "lft", "rgt"))
- return {(i['lft'],i['rgt']):{'name':i['name'], 'is_group':i['is_group']}
- for i in item_groups_list}
+ return {
+ (i["lft"], i["rgt"]): {"name": i["name"], "is_group": i["is_group"]} for i in item_groups_list
+ }
def update_leveled_dict(leveled_dict: OrderedDict) -> None:
for k in leveled_dict:
- leveled_dict[k].update({'self_value':0, 'agg_value':0})
+ leveled_dict[k].update({"self_value": 0, "agg_value": 0})
diff --git a/erpnext/stock/report/delayed_item_report/delayed_item_report.py b/erpnext/stock/report/delayed_item_report/delayed_item_report.py
index 4ec36ea..9df24d6 100644
--- a/erpnext/stock/report/delayed_item_report/delayed_item_report.py
+++ b/erpnext/stock/report/delayed_item_report/delayed_item_report.py
@@ -7,11 +7,12 @@
from frappe.utils import date_diff
-def execute(filters=None, consolidated = False):
+def execute(filters=None, consolidated=False):
data, columns = DelayedItemReport(filters).run()
return data, columns
+
class DelayedItemReport(object):
def __init__(self, filters=None):
self.filters = frappe._dict(filters or {})
@@ -23,28 +24,38 @@
conditions = ""
doctype = self.filters.get("based_on")
- child_doc= "%s Item" % doctype
+ child_doc = "%s Item" % doctype
if doctype == "Sales Invoice":
conditions = " and `tabSales Invoice`.update_stock = 1 and `tabSales Invoice`.is_pos = 0"
if self.filters.get("item_group"):
- conditions += " and `tab%s`.item_group = %s" % (child_doc,
- frappe.db.escape(self.filters.get("item_group")))
+ conditions += " and `tab%s`.item_group = %s" % (
+ child_doc,
+ frappe.db.escape(self.filters.get("item_group")),
+ )
for field in ["customer", "customer_group", "company"]:
if self.filters.get(field):
- conditions += " and `tab%s`.%s = %s" % (doctype,
- field, frappe.db.escape(self.filters.get(field)))
+ conditions += " and `tab%s`.%s = %s" % (
+ doctype,
+ field,
+ frappe.db.escape(self.filters.get(field)),
+ )
sales_order_field = "against_sales_order"
if doctype == "Sales Invoice":
sales_order_field = "sales_order"
if self.filters.get("sales_order"):
- conditions = " and `tab%s`.%s = '%s'" %(child_doc, sales_order_field, self.filters.get("sales_order"))
+ conditions = " and `tab%s`.%s = '%s'" % (
+ child_doc,
+ sales_order_field,
+ self.filters.get("sales_order"),
+ )
- self.transactions = frappe.db.sql(""" SELECT `tab{child_doc}`.item_code, `tab{child_doc}`.item_name,
+ self.transactions = frappe.db.sql(
+ """ SELECT `tab{child_doc}`.item_code, `tab{child_doc}`.item_name,
`tab{child_doc}`.item_group, `tab{child_doc}`.qty, `tab{child_doc}`.rate, `tab{child_doc}`.amount,
`tab{child_doc}`.so_detail, `tab{child_doc}`.{so_field} as sales_order,
`tab{doctype}`.shipping_address_name, `tab{doctype}`.po_no, `tab{doctype}`.customer,
@@ -54,10 +65,12 @@
`tab{child_doc}`.parent = `tab{doctype}`.name and `tab{doctype}`.docstatus = 1 and
`tab{doctype}`.posting_date between %(from_date)s and %(to_date)s and
`tab{child_doc}`.{so_field} is not null and `tab{child_doc}`.{so_field} != '' {cond}
- """.format(cond=conditions, doctype=doctype, child_doc=child_doc, so_field=sales_order_field), {
- 'from_date': self.filters.get('from_date'),
- 'to_date': self.filters.get('to_date')
- }, as_dict=1)
+ """.format(
+ cond=conditions, doctype=doctype, child_doc=child_doc, so_field=sales_order_field
+ ),
+ {"from_date": self.filters.get("from_date"), "to_date": self.filters.get("to_date")},
+ as_dict=1,
+ )
if self.transactions:
self.filter_transactions_data(consolidated)
@@ -67,112 +80,85 @@
def filter_transactions_data(self, consolidated=False):
sales_orders = [d.sales_order for d in self.transactions]
doctype = "Sales Order"
- filters = {'name': ('in', sales_orders)}
+ filters = {"name": ("in", sales_orders)}
if not consolidated:
sales_order_items = [d.so_detail for d in self.transactions]
doctype = "Sales Order Item"
- filters = {'parent': ('in', sales_orders), 'name': ('in', sales_order_items)}
+ filters = {"parent": ("in", sales_orders), "name": ("in", sales_order_items)}
so_data = {}
- for d in frappe.get_all(doctype, filters = filters,
- fields = ["delivery_date", "parent", "name"]):
+ for d in frappe.get_all(doctype, filters=filters, fields=["delivery_date", "parent", "name"]):
key = d.name if consolidated else (d.parent, d.name)
if key not in so_data:
so_data.setdefault(key, d.delivery_date)
for row in self.transactions:
key = row.sales_order if consolidated else (row.sales_order, row.so_detail)
- row.update({
- 'delivery_date': so_data.get(key),
- 'delayed_days': date_diff(row.posting_date, so_data.get(key))
- })
+ row.update(
+ {
+ "delivery_date": so_data.get(key),
+ "delayed_days": date_diff(row.posting_date, so_data.get(key)),
+ }
+ )
return self.transactions
def get_columns(self):
based_on = self.filters.get("based_on")
- return [{
- "label": _(based_on),
- "fieldname": "name",
- "fieldtype": "Link",
- "options": based_on,
- "width": 100
- },
- {
- "label": _("Customer"),
- "fieldname": "customer",
- "fieldtype": "Link",
- "options": "Customer",
- "width": 200
- },
- {
- "label": _("Shipping Address"),
- "fieldname": "shipping_address_name",
- "fieldtype": "Link",
- "options": "Address",
- "width": 120
- },
- {
- "label": _("Expected Delivery Date"),
- "fieldname": "delivery_date",
- "fieldtype": "Date",
- "width": 100
- },
- {
- "label": _("Actual Delivery Date"),
- "fieldname": "posting_date",
- "fieldtype": "Date",
- "width": 100
- },
- {
- "label": _("Item Code"),
- "fieldname": "item_code",
- "fieldtype": "Link",
- "options": "Item",
- "width": 100
- },
- {
- "label": _("Item Name"),
- "fieldname": "item_name",
- "fieldtype": "Data",
- "width": 100
- },
- {
- "label": _("Quantity"),
- "fieldname": "qty",
- "fieldtype": "Float",
- "width": 100
- },
- {
- "label": _("Rate"),
- "fieldname": "rate",
- "fieldtype": "Currency",
- "width": 100
- },
- {
- "label": _("Amount"),
- "fieldname": "amount",
- "fieldtype": "Currency",
- "width": 100
- },
- {
- "label": _("Delayed Days"),
- "fieldname": "delayed_days",
- "fieldtype": "Int",
- "width": 100
- },
- {
- "label": _("Sales Order"),
- "fieldname": "sales_order",
- "fieldtype": "Link",
- "options": "Sales Order",
- "width": 100
- },
- {
- "label": _("Customer PO"),
- "fieldname": "po_no",
- "fieldtype": "Data",
- "width": 100
- }]
+ return [
+ {
+ "label": _(based_on),
+ "fieldname": "name",
+ "fieldtype": "Link",
+ "options": based_on,
+ "width": 100,
+ },
+ {
+ "label": _("Customer"),
+ "fieldname": "customer",
+ "fieldtype": "Link",
+ "options": "Customer",
+ "width": 200,
+ },
+ {
+ "label": _("Shipping Address"),
+ "fieldname": "shipping_address_name",
+ "fieldtype": "Link",
+ "options": "Address",
+ "width": 120,
+ },
+ {
+ "label": _("Expected Delivery Date"),
+ "fieldname": "delivery_date",
+ "fieldtype": "Date",
+ "width": 100,
+ },
+ {
+ "label": _("Actual Delivery Date"),
+ "fieldname": "posting_date",
+ "fieldtype": "Date",
+ "width": 100,
+ },
+ {
+ "label": _("Item Code"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 100,
+ },
+ {"label": _("Item Name"), "fieldname": "item_name", "fieldtype": "Data", "width": 100},
+ {"label": _("Quantity"), "fieldname": "qty", "fieldtype": "Float", "width": 100},
+ {"label": _("Rate"), "fieldname": "rate", "fieldtype": "Currency", "width": 100},
+ {"label": _("Amount"), "fieldname": "amount", "fieldtype": "Currency", "width": 100},
+ {"label": _("Delayed Days"), "fieldname": "delayed_days", "fieldtype": "Int", "width": 100},
+ {
+ "label": _("Sales Order"),
+ "fieldname": "sales_order",
+ "fieldtype": "Link",
+ "options": "Sales Order",
+ "width": 100,
+ },
+ {"label": _("Customer PO"), "fieldname": "po_no", "fieldtype": "Data", "width": 100},
+ ]
diff --git a/erpnext/stock/report/delayed_order_report/delayed_order_report.py b/erpnext/stock/report/delayed_order_report/delayed_order_report.py
index 26090ab..197218d 100644
--- a/erpnext/stock/report/delayed_order_report/delayed_order_report.py
+++ b/erpnext/stock/report/delayed_order_report/delayed_order_report.py
@@ -14,6 +14,7 @@
return columns, data
+
class DelayedOrderReport(DelayedItemReport):
def run(self):
return self.get_columns(), self.get_data(consolidated=True) or []
@@ -33,60 +34,48 @@
def get_columns(self):
based_on = self.filters.get("based_on")
- return [{
- "label": _(based_on),
- "fieldname": "name",
- "fieldtype": "Link",
- "options": based_on,
- "width": 100
- },{
- "label": _("Customer"),
- "fieldname": "customer",
- "fieldtype": "Link",
- "options": "Customer",
- "width": 200
- },
- {
- "label": _("Shipping Address"),
- "fieldname": "shipping_address_name",
- "fieldtype": "Link",
- "options": "Address",
- "width": 140
- },
- {
- "label": _("Expected Delivery Date"),
- "fieldname": "delivery_date",
- "fieldtype": "Date",
- "width": 100
- },
- {
- "label": _("Actual Delivery Date"),
- "fieldname": "posting_date",
- "fieldtype": "Date",
- "width": 100
- },
- {
- "label": _("Amount"),
- "fieldname": "grand_total",
- "fieldtype": "Currency",
- "width": 100
- },
- {
- "label": _("Delayed Days"),
- "fieldname": "delayed_days",
- "fieldtype": "Int",
- "width": 100
- },
- {
- "label": _("Sales Order"),
- "fieldname": "sales_order",
- "fieldtype": "Link",
- "options": "Sales Order",
- "width": 150
- },
- {
- "label": _("Customer PO"),
- "fieldname": "po_no",
- "fieldtype": "Data",
- "width": 110
- }]
+ return [
+ {
+ "label": _(based_on),
+ "fieldname": "name",
+ "fieldtype": "Link",
+ "options": based_on,
+ "width": 100,
+ },
+ {
+ "label": _("Customer"),
+ "fieldname": "customer",
+ "fieldtype": "Link",
+ "options": "Customer",
+ "width": 200,
+ },
+ {
+ "label": _("Shipping Address"),
+ "fieldname": "shipping_address_name",
+ "fieldtype": "Link",
+ "options": "Address",
+ "width": 140,
+ },
+ {
+ "label": _("Expected Delivery Date"),
+ "fieldname": "delivery_date",
+ "fieldtype": "Date",
+ "width": 100,
+ },
+ {
+ "label": _("Actual Delivery Date"),
+ "fieldname": "posting_date",
+ "fieldtype": "Date",
+ "width": 100,
+ },
+ {"label": _("Amount"), "fieldname": "grand_total", "fieldtype": "Currency", "width": 100},
+ {"label": _("Delayed Days"), "fieldname": "delayed_days", "fieldtype": "Int", "width": 100},
+ {
+ "label": _("Sales Order"),
+ "fieldname": "sales_order",
+ "fieldtype": "Link",
+ "options": "Sales Order",
+ "width": 150,
+ },
+ {"label": _("Customer PO"), "fieldname": "po_no", "fieldtype": "Data", "width": 110},
+ ]
diff --git a/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py b/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py
index b7ac7ff..7a1b8c0 100644
--- a/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py
+++ b/erpnext/stock/report/delivery_note_trends/delivery_note_trends.py
@@ -8,7 +8,8 @@
def execute(filters=None):
- if not filters: filters ={}
+ if not filters:
+ filters = {}
data = []
conditions = get_columns(filters, "Delivery Note")
data = get_data(filters, conditions)
@@ -17,6 +18,7 @@
return conditions["columns"], data, None, chart_data
+
def get_chart_data(data, filters):
if not data:
return []
@@ -27,7 +29,7 @@
# consider only consolidated row
data = [row for row in data if row[0]]
- data = sorted(data, key = lambda i: i[-1],reverse=True)
+ data = sorted(data, key=lambda i: i[-1], reverse=True)
if len(data) > 10:
# get top 10 if data too long
@@ -39,13 +41,8 @@
return {
"data": {
- "labels" : labels,
- "datasets" : [
- {
- "name": _("Total Delivered Amount"),
- "values": datapoints
- }
- ]
+ "labels": labels,
+ "datasets": [{"name": _("Total Delivered Amount"), "values": datapoints}],
},
- "type" : "bar"
+ "type": "bar",
}
diff --git a/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.py b/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.py
index 6aa12ac..bcc2139 100644
--- a/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.py
+++ b/erpnext/stock/report/incorrect_balance_qty_after_transaction/incorrect_balance_qty_after_transaction.py
@@ -12,6 +12,7 @@
data = get_data(filters)
return columns, data
+
def get_data(filters):
data = get_stock_ledger_entries(filters)
itewise_balance_qty = {}
@@ -23,6 +24,7 @@
res = validate_data(itewise_balance_qty)
return res
+
def validate_data(itewise_balance_qty):
res = []
for key, data in itewise_balance_qty.items():
@@ -33,6 +35,7 @@
return res
+
def get_incorrect_data(data):
balance_qty = 0.0
for row in data:
@@ -45,67 +48,84 @@
row.differnce = abs(flt(row.expected_balance_qty) - flt(row.qty_after_transaction))
return row
+
def get_stock_ledger_entries(report_filters):
filters = {"is_cancelled": 0}
- fields = ['name', 'voucher_type', 'voucher_no', 'item_code', 'actual_qty',
- 'posting_date', 'posting_time', 'company', 'warehouse', 'qty_after_transaction', 'batch_no']
+ fields = [
+ "name",
+ "voucher_type",
+ "voucher_no",
+ "item_code",
+ "actual_qty",
+ "posting_date",
+ "posting_time",
+ "company",
+ "warehouse",
+ "qty_after_transaction",
+ "batch_no",
+ ]
- for field in ['warehouse', 'item_code', 'company']:
+ for field in ["warehouse", "item_code", "company"]:
if report_filters.get(field):
filters[field] = report_filters.get(field)
- return frappe.get_all('Stock Ledger Entry', fields = fields, filters = filters,
- order_by = 'timestamp(posting_date, posting_time) asc, creation asc')
+ return frappe.get_all(
+ "Stock Ledger Entry",
+ fields=fields,
+ filters=filters,
+ order_by="timestamp(posting_date, posting_time) asc, creation asc",
+ )
+
def get_columns():
- return [{
- 'label': _('Id'),
- 'fieldtype': 'Link',
- 'fieldname': 'name',
- 'options': 'Stock Ledger Entry',
- 'width': 120
- }, {
- 'label': _('Posting Date'),
- 'fieldtype': 'Date',
- 'fieldname': 'posting_date',
- 'width': 110
- }, {
- 'label': _('Voucher Type'),
- 'fieldtype': 'Link',
- 'fieldname': 'voucher_type',
- 'options': 'DocType',
- 'width': 120
- }, {
- 'label': _('Voucher No'),
- 'fieldtype': 'Dynamic Link',
- 'fieldname': 'voucher_no',
- 'options': 'voucher_type',
- 'width': 120
- }, {
- 'label': _('Item Code'),
- 'fieldtype': 'Link',
- 'fieldname': 'item_code',
- 'options': 'Item',
- 'width': 120
- }, {
- 'label': _('Warehouse'),
- 'fieldtype': 'Link',
- 'fieldname': 'warehouse',
- 'options': 'Warehouse',
- 'width': 120
- }, {
- 'label': _('Expected Balance Qty'),
- 'fieldtype': 'Float',
- 'fieldname': 'expected_balance_qty',
- 'width': 170
- }, {
- 'label': _('Actual Balance Qty'),
- 'fieldtype': 'Float',
- 'fieldname': 'qty_after_transaction',
- 'width': 150
- }, {
- 'label': _('Difference'),
- 'fieldtype': 'Float',
- 'fieldname': 'differnce',
- 'width': 110
- }]
+ return [
+ {
+ "label": _("Id"),
+ "fieldtype": "Link",
+ "fieldname": "name",
+ "options": "Stock Ledger Entry",
+ "width": 120,
+ },
+ {"label": _("Posting Date"), "fieldtype": "Date", "fieldname": "posting_date", "width": 110},
+ {
+ "label": _("Voucher Type"),
+ "fieldtype": "Link",
+ "fieldname": "voucher_type",
+ "options": "DocType",
+ "width": 120,
+ },
+ {
+ "label": _("Voucher No"),
+ "fieldtype": "Dynamic Link",
+ "fieldname": "voucher_no",
+ "options": "voucher_type",
+ "width": 120,
+ },
+ {
+ "label": _("Item Code"),
+ "fieldtype": "Link",
+ "fieldname": "item_code",
+ "options": "Item",
+ "width": 120,
+ },
+ {
+ "label": _("Warehouse"),
+ "fieldtype": "Link",
+ "fieldname": "warehouse",
+ "options": "Warehouse",
+ "width": 120,
+ },
+ {
+ "label": _("Expected Balance Qty"),
+ "fieldtype": "Float",
+ "fieldname": "expected_balance_qty",
+ "width": 170,
+ },
+ {
+ "label": _("Actual Balance Qty"),
+ "fieldtype": "Float",
+ "fieldname": "qty_after_transaction",
+ "width": 150,
+ },
+ {"label": _("Difference"), "fieldtype": "Float", "fieldname": "differnce", "width": 110},
+ ]
diff --git a/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py b/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py
index be8597d..78c6961 100644
--- a/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py
+++ b/erpnext/stock/report/incorrect_serial_no_valuation/incorrect_serial_no_valuation.py
@@ -15,6 +15,7 @@
data = get_data(filters)
return columns, data
+
def get_data(filters):
data = get_stock_ledger_entries(filters)
serial_nos_data = prepare_serial_nos(data)
@@ -22,6 +23,7 @@
return data
+
def prepare_serial_nos(data):
serial_no_wise_data = {}
for row in data:
@@ -37,13 +39,16 @@
return serial_no_wise_data
+
def get_incorrect_serial_nos(serial_nos_data):
result = []
- total_value = frappe._dict({'qty': 0, 'valuation_rate': 0, 'serial_no': frappe.bold(_('Balance'))})
+ total_value = frappe._dict(
+ {"qty": 0, "valuation_rate": 0, "serial_no": frappe.bold(_("Balance"))}
+ )
for serial_no, data in serial_nos_data.items():
- total_dict = frappe._dict({'qty': 0, 'valuation_rate': 0, 'serial_no': frappe.bold(_('Total'))})
+ total_dict = frappe._dict({"qty": 0, "valuation_rate": 0, "serial_no": frappe.bold(_("Total"))})
if check_incorrect_serial_data(data, total_dict):
result.extend(data)
@@ -58,93 +63,111 @@
return result
+
def check_incorrect_serial_data(data, total_dict):
incorrect_data = False
for row in data:
total_dict.qty += row.qty
total_dict.valuation_rate += row.valuation_rate
- if ((total_dict.qty == 0 and abs(total_dict.valuation_rate) > 0) or total_dict.qty < 0):
+ if (total_dict.qty == 0 and abs(total_dict.valuation_rate) > 0) or total_dict.qty < 0:
incorrect_data = True
return incorrect_data
+
def get_stock_ledger_entries(report_filters):
- fields = ['name', 'voucher_type', 'voucher_no', 'item_code', 'serial_no as serial_nos', 'actual_qty',
- 'posting_date', 'posting_time', 'company', 'warehouse', '(stock_value_difference / actual_qty) as valuation_rate']
+ fields = [
+ "name",
+ "voucher_type",
+ "voucher_no",
+ "item_code",
+ "serial_no as serial_nos",
+ "actual_qty",
+ "posting_date",
+ "posting_time",
+ "company",
+ "warehouse",
+ "(stock_value_difference / actual_qty) as valuation_rate",
+ ]
- filters = {'serial_no': ("is", "set"), "is_cancelled": 0}
+ filters = {"serial_no": ("is", "set"), "is_cancelled": 0}
- if report_filters.get('item_code'):
- filters['item_code'] = report_filters.get('item_code')
+ if report_filters.get("item_code"):
+ filters["item_code"] = report_filters.get("item_code")
- if report_filters.get('from_date') and report_filters.get('to_date'):
- filters['posting_date'] = ('between', [report_filters.get('from_date'), report_filters.get('to_date')])
+ if report_filters.get("from_date") and report_filters.get("to_date"):
+ filters["posting_date"] = (
+ "between",
+ [report_filters.get("from_date"), report_filters.get("to_date")],
+ )
- return frappe.get_all('Stock Ledger Entry', fields = fields, filters = filters,
- order_by = 'timestamp(posting_date, posting_time) asc, creation asc')
+ return frappe.get_all(
+ "Stock Ledger Entry",
+ fields=fields,
+ filters=filters,
+ order_by="timestamp(posting_date, posting_time) asc, creation asc",
+ )
+
def get_columns():
- return [{
- 'label': _('Company'),
- 'fieldtype': 'Link',
- 'fieldname': 'company',
- 'options': 'Company',
- 'width': 120
- }, {
- 'label': _('Id'),
- 'fieldtype': 'Link',
- 'fieldname': 'name',
- 'options': 'Stock Ledger Entry',
- 'width': 120
- }, {
- 'label': _('Posting Date'),
- 'fieldtype': 'Date',
- 'fieldname': 'posting_date',
- 'width': 90
- }, {
- 'label': _('Posting Time'),
- 'fieldtype': 'Time',
- 'fieldname': 'posting_time',
- 'width': 90
- }, {
- 'label': _('Voucher Type'),
- 'fieldtype': 'Link',
- 'fieldname': 'voucher_type',
- 'options': 'DocType',
- 'width': 100
- }, {
- 'label': _('Voucher No'),
- 'fieldtype': 'Dynamic Link',
- 'fieldname': 'voucher_no',
- 'options': 'voucher_type',
- 'width': 110
- }, {
- 'label': _('Item Code'),
- 'fieldtype': 'Link',
- 'fieldname': 'item_code',
- 'options': 'Item',
- 'width': 120
- }, {
- 'label': _('Warehouse'),
- 'fieldtype': 'Link',
- 'fieldname': 'warehouse',
- 'options': 'Warehouse',
- 'width': 120
- }, {
- 'label': _('Serial No'),
- 'fieldtype': 'Link',
- 'fieldname': 'serial_no',
- 'options': 'Serial No',
- 'width': 100
- }, {
- 'label': _('Qty'),
- 'fieldtype': 'Float',
- 'fieldname': 'qty',
- 'width': 80
- }, {
- 'label': _('Valuation Rate (In / Out)'),
- 'fieldtype': 'Currency',
- 'fieldname': 'valuation_rate',
- 'width': 110
- }]
+ return [
+ {
+ "label": _("Company"),
+ "fieldtype": "Link",
+ "fieldname": "company",
+ "options": "Company",
+ "width": 120,
+ },
+ {
+ "label": _("Id"),
+ "fieldtype": "Link",
+ "fieldname": "name",
+ "options": "Stock Ledger Entry",
+ "width": 120,
+ },
+ {"label": _("Posting Date"), "fieldtype": "Date", "fieldname": "posting_date", "width": 90},
+ {"label": _("Posting Time"), "fieldtype": "Time", "fieldname": "posting_time", "width": 90},
+ {
+ "label": _("Voucher Type"),
+ "fieldtype": "Link",
+ "fieldname": "voucher_type",
+ "options": "DocType",
+ "width": 100,
+ },
+ {
+ "label": _("Voucher No"),
+ "fieldtype": "Dynamic Link",
+ "fieldname": "voucher_no",
+ "options": "voucher_type",
+ "width": 110,
+ },
+ {
+ "label": _("Item Code"),
+ "fieldtype": "Link",
+ "fieldname": "item_code",
+ "options": "Item",
+ "width": 120,
+ },
+ {
+ "label": _("Warehouse"),
+ "fieldtype": "Link",
+ "fieldname": "warehouse",
+ "options": "Warehouse",
+ "width": 120,
+ },
+ {
+ "label": _("Serial No"),
+ "fieldtype": "Link",
+ "fieldname": "serial_no",
+ "options": "Serial No",
+ "width": 100,
+ },
+ {"label": _("Qty"), "fieldtype": "Float", "fieldname": "qty", "width": 80},
+ {
+ "label": _("Valuation Rate (In / Out)"),
+ "fieldtype": "Currency",
+ "fieldname": "valuation_rate",
+ "width": 110,
+ },
+ ]
diff --git a/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py b/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py
index 28e6cb2..23e3c8a 100644
--- a/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py
+++ b/erpnext/stock/report/incorrect_stock_value_report/incorrect_stock_value_report.py
@@ -13,14 +13,18 @@
def execute(filters=None):
if not erpnext.is_perpetual_inventory_enabled(filters.company):
- frappe.throw(_("Perpetual inventory required for the company {0} to view this report.")
- .format(filters.company))
+ frappe.throw(
+ _("Perpetual inventory required for the company {0} to view this report.").format(
+ filters.company
+ )
+ )
data = get_data(filters)
columns = get_columns(filters)
return columns, data
+
def get_unsync_date(filters):
date = filters.from_date
if not date:
@@ -31,14 +35,16 @@
return
while getdate(date) < getdate(today()):
- account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(posting_date=date,
- company=filters.company, account = filters.account)
+ account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(
+ posting_date=date, company=filters.company, account=filters.account
+ )
if abs(account_bal - stock_bal) > 0.1:
return date
date = add_days(date, 1)
+
def get_data(report_filters):
from_date = get_unsync_date(report_filters)
@@ -48,7 +54,8 @@
result = []
voucher_wise_dict = {}
- data = frappe.db.sql('''
+ data = frappe.db.sql(
+ """
SELECT
name, posting_date, posting_time, voucher_type, voucher_no,
stock_value_difference, stock_value, warehouse, item_code
@@ -59,14 +66,19 @@
= %s and company = %s
and is_cancelled = 0
ORDER BY timestamp(posting_date, posting_time) asc, creation asc
- ''', (from_date, report_filters.company), as_dict=1)
+ """,
+ (from_date, report_filters.company),
+ as_dict=1,
+ )
for d in data:
voucher_wise_dict.setdefault((d.item_code, d.warehouse), []).append(d)
closing_date = add_days(from_date, -1)
for key, stock_data in voucher_wise_dict.items():
- prev_stock_value = get_stock_value_on(posting_date = closing_date, item_code=key[0], warehouse =key[1])
+ prev_stock_value = get_stock_value_on(
+ posting_date=closing_date, item_code=key[0], warehouse=key[1]
+ )
for data in stock_data:
expected_stock_value = prev_stock_value + data.stock_value_difference
if abs(data.stock_value - expected_stock_value) > 0.1:
@@ -76,6 +88,7 @@
return result
+
def get_columns(filters):
return [
{
@@ -83,60 +96,43 @@
"fieldname": "name",
"fieldtype": "Link",
"options": "Stock Ledger Entry",
- "width": "80"
+ "width": "80",
},
- {
- "label": _("Posting Date"),
- "fieldname": "posting_date",
- "fieldtype": "Date"
- },
- {
- "label": _("Posting Time"),
- "fieldname": "posting_time",
- "fieldtype": "Time"
- },
- {
- "label": _("Voucher Type"),
- "fieldname": "voucher_type",
- "width": "110"
- },
+ {"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date"},
+ {"label": _("Posting Time"), "fieldname": "posting_time", "fieldtype": "Time"},
+ {"label": _("Voucher Type"), "fieldname": "voucher_type", "width": "110"},
{
"label": _("Voucher No"),
"fieldname": "voucher_no",
"fieldtype": "Dynamic Link",
"options": "voucher_type",
- "width": "110"
+ "width": "110",
},
{
"label": _("Item Code"),
"fieldname": "item_code",
"fieldtype": "Link",
"options": "Item",
- "width": "110"
+ "width": "110",
},
{
"label": _("Warehouse"),
"fieldname": "warehouse",
"fieldtype": "Link",
"options": "Warehouse",
- "width": "110"
+ "width": "110",
},
{
"label": _("Expected Stock Value"),
"fieldname": "expected_stock_value",
"fieldtype": "Currency",
- "width": "150"
+ "width": "150",
},
- {
- "label": _("Stock Value"),
- "fieldname": "stock_value",
- "fieldtype": "Currency",
- "width": "120"
- },
+ {"label": _("Stock Value"), "fieldname": "stock_value", "fieldtype": "Currency", "width": "120"},
{
"label": _("Difference Value"),
"fieldname": "difference_value",
"fieldtype": "Currency",
- "width": "150"
- }
+ "width": "150",
+ },
]
diff --git a/erpnext/stock/report/item_price_stock/item_price_stock.py b/erpnext/stock/report/item_price_stock/item_price_stock.py
index 65af9f5..15218e6 100644
--- a/erpnext/stock/report/item_price_stock/item_price_stock.py
+++ b/erpnext/stock/report/item_price_stock/item_price_stock.py
@@ -7,10 +7,11 @@
def execute(filters=None):
columns, data = [], []
- columns=get_columns()
- data=get_data(filters,columns)
+ columns = get_columns()
+ data = get_data(filters, columns)
return columns, data
+
def get_columns():
return [
{
@@ -18,77 +19,64 @@
"fieldname": "item_code",
"fieldtype": "Link",
"options": "Item",
- "width": 120
+ "width": 120,
},
- {
- "label": _("Item Name"),
- "fieldname": "item_name",
- "fieldtype": "Data",
- "width": 120
- },
- {
- "label": _("Brand"),
- "fieldname": "brand",
- "fieldtype": "Data",
- "width": 100
- },
+ {"label": _("Item Name"), "fieldname": "item_name", "fieldtype": "Data", "width": 120},
+ {"label": _("Brand"), "fieldname": "brand", "fieldtype": "Data", "width": 100},
{
"label": _("Warehouse"),
"fieldname": "warehouse",
"fieldtype": "Link",
"options": "Warehouse",
- "width": 120
+ "width": 120,
},
{
"label": _("Stock Available"),
"fieldname": "stock_available",
"fieldtype": "Float",
- "width": 120
+ "width": 120,
},
{
"label": _("Buying Price List"),
"fieldname": "buying_price_list",
"fieldtype": "Link",
"options": "Price List",
- "width": 120
+ "width": 120,
},
- {
- "label": _("Buying Rate"),
- "fieldname": "buying_rate",
- "fieldtype": "Currency",
- "width": 120
- },
+ {"label": _("Buying Rate"), "fieldname": "buying_rate", "fieldtype": "Currency", "width": 120},
{
"label": _("Selling Price List"),
"fieldname": "selling_price_list",
"fieldtype": "Link",
"options": "Price List",
- "width": 120
+ "width": 120,
},
- {
- "label": _("Selling Rate"),
- "fieldname": "selling_rate",
- "fieldtype": "Currency",
- "width": 120
- }
+ {"label": _("Selling Rate"), "fieldname": "selling_rate", "fieldtype": "Currency", "width": 120},
]
+
def get_data(filters, columns):
item_price_qty_data = []
item_price_qty_data = get_item_price_qty_data(filters)
return item_price_qty_data
+
def get_item_price_qty_data(filters):
conditions = ""
if filters.get("item_code"):
conditions += "where a.item_code=%(item_code)s"
- item_results = frappe.db.sql("""select a.item_code, a.item_name, a.name as price_list_name,
+ item_results = frappe.db.sql(
+ """select a.item_code, a.item_name, a.name as price_list_name,
a.brand as brand, b.warehouse as warehouse, b.actual_qty as actual_qty
from `tabItem Price` a left join `tabBin` b
ON a.item_code = b.item_code
- {conditions}"""
- .format(conditions=conditions), filters, as_dict=1)
+ {conditions}""".format(
+ conditions=conditions
+ ),
+ filters,
+ as_dict=1,
+ )
price_list_names = list(set(item.price_list_name for item in item_results))
@@ -99,15 +87,15 @@
if item_results:
for item_dict in item_results:
data = {
- 'item_code': item_dict.item_code,
- 'item_name': item_dict.item_name,
- 'brand': item_dict.brand,
- 'warehouse': item_dict.warehouse,
- 'stock_available': item_dict.actual_qty or 0,
- 'buying_price_list': "",
- 'buying_rate': 0.0,
- 'selling_price_list': "",
- 'selling_rate': 0.0
+ "item_code": item_dict.item_code,
+ "item_name": item_dict.item_name,
+ "brand": item_dict.brand,
+ "warehouse": item_dict.warehouse,
+ "stock_available": item_dict.actual_qty or 0,
+ "buying_price_list": "",
+ "buying_rate": 0.0,
+ "selling_price_list": "",
+ "selling_rate": 0.0,
}
price_list = item_dict["price_list_name"]
@@ -122,6 +110,7 @@
return result
+
def get_price_map(price_list_names, buying=0, selling=0):
price_map = {}
@@ -137,14 +126,12 @@
else:
filters["selling"] = 1
- pricing_details = frappe.get_all("Item Price",
- fields = ["name", "price_list", "price_list_rate"], filters=filters)
+ pricing_details = frappe.get_all(
+ "Item Price", fields=["name", "price_list", "price_list_rate"], filters=filters
+ )
for d in pricing_details:
name = d["name"]
- price_map[name] = {
- price_list_key :d["price_list"],
- rate_key :d["price_list_rate"]
- }
+ price_map[name] = {price_list_key: d["price_list"], rate_key: d["price_list_rate"]}
return price_map
diff --git a/erpnext/stock/report/item_prices/item_prices.py b/erpnext/stock/report/item_prices/item_prices.py
index 0d0e8d2..87f1a42 100644
--- a/erpnext/stock/report/item_prices/item_prices.py
+++ b/erpnext/stock/report/item_prices/item_prices.py
@@ -8,7 +8,8 @@
def execute(filters=None):
- if not filters: filters = {}
+ if not filters:
+ filters = {}
columns = get_columns(filters)
conditions = get_condition(filters)
@@ -19,64 +20,95 @@
val_rate_map = get_valuation_rate()
from erpnext.accounts.utils import get_currency_precision
+
precision = get_currency_precision() or 2
data = []
for item in sorted(item_map):
- data.append([item, item_map[item]["item_name"],item_map[item]["item_group"],
- item_map[item]["brand"], item_map[item]["description"], item_map[item]["stock_uom"],
- flt(last_purchase_rate.get(item, 0), precision),
- flt(val_rate_map.get(item, 0), precision),
- pl.get(item, {}).get("Selling"),
- pl.get(item, {}).get("Buying"),
- flt(bom_rate.get(item, 0), precision)
- ])
+ data.append(
+ [
+ item,
+ item_map[item]["item_name"],
+ item_map[item]["item_group"],
+ item_map[item]["brand"],
+ item_map[item]["description"],
+ item_map[item]["stock_uom"],
+ flt(last_purchase_rate.get(item, 0), precision),
+ flt(val_rate_map.get(item, 0), precision),
+ pl.get(item, {}).get("Selling"),
+ pl.get(item, {}).get("Buying"),
+ flt(bom_rate.get(item, 0), precision),
+ ]
+ )
return columns, data
+
def get_columns(filters):
"""return columns based on filters"""
- columns = [_("Item") + ":Link/Item:100", _("Item Name") + "::150",_("Item Group") + ":Link/Item Group:125",
- _("Brand") + "::100", _("Description") + "::150", _("UOM") + ":Link/UOM:80",
- _("Last Purchase Rate") + ":Currency:90", _("Valuation Rate") + ":Currency:80", _("Sales Price List") + "::180",
- _("Purchase Price List") + "::180", _("BOM Rate") + ":Currency:90"]
+ columns = [
+ _("Item") + ":Link/Item:100",
+ _("Item Name") + "::150",
+ _("Item Group") + ":Link/Item Group:125",
+ _("Brand") + "::100",
+ _("Description") + "::150",
+ _("UOM") + ":Link/UOM:80",
+ _("Last Purchase Rate") + ":Currency:90",
+ _("Valuation Rate") + ":Currency:80",
+ _("Sales Price List") + "::180",
+ _("Purchase Price List") + "::180",
+ _("BOM Rate") + ":Currency:90",
+ ]
return columns
+
def get_item_details(conditions):
"""returns all items details"""
item_map = {}
- for i in frappe.db.sql("""select name, item_group, item_name, description,
+ for i in frappe.db.sql(
+ """select name, item_group, item_name, description,
brand, stock_uom from tabItem %s
- order by item_code, item_group""" % (conditions), as_dict=1):
- item_map.setdefault(i.name, i)
+ order by item_code, item_group"""
+ % (conditions),
+ as_dict=1,
+ ):
+ item_map.setdefault(i.name, i)
return item_map
+
def get_price_list():
"""Get selling & buying price list of every item"""
rate = {}
- price_list = frappe.db.sql("""select ip.item_code, ip.buying, ip.selling,
+ price_list = frappe.db.sql(
+ """select ip.item_code, ip.buying, ip.selling,
concat(ifnull(cu.symbol,ip.currency), " ", round(ip.price_list_rate,2), " - ", ip.price_list) as price
from `tabItem Price` ip, `tabPrice List` pl, `tabCurrency` cu
- where ip.price_list=pl.name and pl.currency=cu.name and pl.enabled=1""", as_dict=1)
+ where ip.price_list=pl.name and pl.currency=cu.name and pl.enabled=1""",
+ as_dict=1,
+ )
for j in price_list:
if j.price:
- rate.setdefault(j.item_code, {}).setdefault("Buying" if j.buying else "Selling", []).append(j.price)
+ rate.setdefault(j.item_code, {}).setdefault("Buying" if j.buying else "Selling", []).append(
+ j.price
+ )
item_rate_map = {}
for item in rate:
for buying_or_selling in rate[item]:
- item_rate_map.setdefault(item, {}).setdefault(buying_or_selling,
- ", ".join(rate[item].get(buying_or_selling, [])))
+ item_rate_map.setdefault(item, {}).setdefault(
+ buying_or_selling, ", ".join(rate[item].get(buying_or_selling, []))
+ )
return item_rate_map
+
def get_last_purchase_rate():
item_last_purchase_rate_map = {}
@@ -108,29 +140,38 @@
return item_last_purchase_rate_map
+
def get_item_bom_rate():
"""Get BOM rate of an item from BOM"""
item_bom_map = {}
- for b in frappe.db.sql("""select item, (total_cost/quantity) as bom_rate
- from `tabBOM` where is_active=1 and is_default=1""", as_dict=1):
- item_bom_map.setdefault(b.item, flt(b.bom_rate))
+ for b in frappe.db.sql(
+ """select item, (total_cost/quantity) as bom_rate
+ from `tabBOM` where is_active=1 and is_default=1""",
+ as_dict=1,
+ ):
+ item_bom_map.setdefault(b.item, flt(b.bom_rate))
return item_bom_map
+
def get_valuation_rate():
"""Get an average valuation rate of an item from all warehouses"""
item_val_rate_map = {}
- for d in frappe.db.sql("""select item_code,
+ for d in frappe.db.sql(
+ """select item_code,
sum(actual_qty*valuation_rate)/sum(actual_qty) as val_rate
- from tabBin where actual_qty > 0 group by item_code""", as_dict=1):
- item_val_rate_map.setdefault(d.item_code, d.val_rate)
+ from tabBin where actual_qty > 0 group by item_code""",
+ as_dict=1,
+ ):
+ item_val_rate_map.setdefault(d.item_code, d.val_rate)
return item_val_rate_map
+
def get_condition(filters):
"""Get Filter Items"""
diff --git a/erpnext/stock/report/item_shortage_report/item_shortage_report.py b/erpnext/stock/report/item_shortage_report/item_shortage_report.py
index 30c7614..03a3a6a 100644
--- a/erpnext/stock/report/item_shortage_report/item_shortage_report.py
+++ b/erpnext/stock/report/item_shortage_report/item_shortage_report.py
@@ -18,6 +18,7 @@
return columns, data, None, chart_data
+
def get_conditions(filters):
conditions = ""
@@ -28,8 +29,10 @@
return conditions
+
def get_data(conditions, filters):
- data = frappe.db.sql("""
+ data = frappe.db.sql(
+ """
SELECT
bin.warehouse,
bin.item_code,
@@ -51,10 +54,16 @@
AND warehouse.name = bin.warehouse
AND bin.item_code=item.name
{0}
- ORDER BY bin.projected_qty;""".format(conditions), filters, as_dict=1)
+ ORDER BY bin.projected_qty;""".format(
+ conditions
+ ),
+ filters,
+ as_dict=1,
+ )
return data
+
def get_chart_data(data):
labels, datapoints = [], []
@@ -67,18 +76,11 @@
datapoints = datapoints[:10]
return {
- "data": {
- "labels": labels,
- "datasets":[
- {
- "name": _("Projected Qty"),
- "values": datapoints
- }
- ]
- },
- "type": "bar"
+ "data": {"labels": labels, "datasets": [{"name": _("Projected Qty"), "values": datapoints}]},
+ "type": "bar",
}
+
def get_columns():
columns = [
{
@@ -86,76 +88,66 @@
"fieldname": "warehouse",
"fieldtype": "Link",
"options": "Warehouse",
- "width": 150
+ "width": 150,
},
{
"label": _("Item"),
"fieldname": "item_code",
"fieldtype": "Link",
"options": "Item",
- "width": 150
+ "width": 150,
},
{
"label": _("Actual Quantity"),
"fieldname": "actual_qty",
"fieldtype": "Float",
"width": 120,
- "convertible": "qty"
+ "convertible": "qty",
},
{
"label": _("Ordered Quantity"),
"fieldname": "ordered_qty",
"fieldtype": "Float",
"width": 120,
- "convertible": "qty"
+ "convertible": "qty",
},
{
"label": _("Planned Quantity"),
"fieldname": "planned_qty",
"fieldtype": "Float",
"width": 120,
- "convertible": "qty"
+ "convertible": "qty",
},
{
"label": _("Reserved Quantity"),
"fieldname": "reserved_qty",
"fieldtype": "Float",
"width": 120,
- "convertible": "qty"
+ "convertible": "qty",
},
{
"label": _("Reserved Quantity for Production"),
"fieldname": "reserved_qty_for_production",
"fieldtype": "Float",
"width": 120,
- "convertible": "qty"
+ "convertible": "qty",
},
{
"label": _("Projected Quantity"),
"fieldname": "projected_qty",
"fieldtype": "Float",
"width": 120,
- "convertible": "qty"
+ "convertible": "qty",
},
{
"label": _("Company"),
"fieldname": "company",
"fieldtype": "Link",
"options": "Company",
- "width": 120
+ "width": 120,
},
- {
- "label": _("Item Name"),
- "fieldname": "item_name",
- "fieldtype": "Data",
- "width": 100
- },
- {
- "label": _("Description"),
- "fieldname": "description",
- "fieldtype": "Data",
- "width": 120
- }
+ {"label": _("Item Name"), "fieldname": "item_name", "fieldtype": "Data", "width": 100},
+ {"label": _("Description"), "fieldname": "description", "fieldtype": "Data", "width": 120},
]
return columns
diff --git a/erpnext/stock/report/item_variant_details/item_variant_details.py b/erpnext/stock/report/item_variant_details/item_variant_details.py
index 10cef70..d1bf220 100644
--- a/erpnext/stock/report/item_variant_details/item_variant_details.py
+++ b/erpnext/stock/report/item_variant_details/item_variant_details.py
@@ -11,25 +11,21 @@
data = get_data(filters.item)
return columns, data
+
def get_data(item):
if not item:
return []
item_dicts = []
variant_results = frappe.db.get_all(
- "Item",
- fields=["name"],
- filters={
- "variant_of": ["=", item],
- "disabled": 0
- }
+ "Item", fields=["name"], filters={"variant_of": ["=", item], "disabled": 0}
)
if not variant_results:
frappe.msgprint(_("There aren't any item variants for the selected item"))
return []
else:
- variant_list = [variant['name'] for variant in variant_results]
+ variant_list = [variant["name"] for variant in variant_results]
order_count_map = get_open_sales_orders_count(variant_list)
stock_details_map = get_stock_details_map(variant_list)
@@ -40,15 +36,13 @@
attributes = frappe.db.get_all(
"Item Variant Attribute",
fields=["attribute"],
- filters={
- "parent": ["in", variant_list]
- },
- group_by="attribute"
+ filters={"parent": ["in", variant_list]},
+ group_by="attribute",
)
attribute_list = [row.get("attribute") for row in attributes]
# Prepare dicts
- variant_dicts = [{"variant_name": d['name']} for d in variant_results]
+ variant_dicts = [{"variant_name": d["name"]} for d in variant_results]
for item_dict in variant_dicts:
name = item_dict.get("variant_name")
@@ -72,73 +66,66 @@
return item_dicts
+
def get_columns(item):
- columns = [{
- "fieldname": "variant_name",
- "label": "Variant",
- "fieldtype": "Link",
- "options": "Item",
- "width": 200
- }]
+ columns = [
+ {
+ "fieldname": "variant_name",
+ "label": "Variant",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 200,
+ }
+ ]
item_doc = frappe.get_doc("Item", item)
for entry in item_doc.attributes:
- columns.append({
- "fieldname": frappe.scrub(entry.attribute),
- "label": entry.attribute,
- "fieldtype": "Data",
- "width": 100
- })
+ columns.append(
+ {
+ "fieldname": frappe.scrub(entry.attribute),
+ "label": entry.attribute,
+ "fieldtype": "Data",
+ "width": 100,
+ }
+ )
additional_columns = [
{
"fieldname": "avg_buying_price_list_rate",
"label": _("Avg. Buying Price List Rate"),
"fieldtype": "Currency",
- "width": 150
+ "width": 150,
},
{
"fieldname": "avg_selling_price_list_rate",
"label": _("Avg. Selling Price List Rate"),
"fieldtype": "Currency",
- "width": 150
+ "width": 150,
},
- {
- "fieldname": "current_stock",
- "label": _("Current Stock"),
- "fieldtype": "Float",
- "width": 120
- },
- {
- "fieldname": "in_production",
- "label": _("In Production"),
- "fieldtype": "Float",
- "width": 150
- },
+ {"fieldname": "current_stock", "label": _("Current Stock"), "fieldtype": "Float", "width": 120},
+ {"fieldname": "in_production", "label": _("In Production"), "fieldtype": "Float", "width": 150},
{
"fieldname": "open_orders",
"label": _("Open Sales Orders"),
"fieldtype": "Float",
- "width": 150
- }
+ "width": 150,
+ },
]
columns.extend(additional_columns)
return columns
+
def get_open_sales_orders_count(variants_list):
open_sales_orders = frappe.db.get_list(
"Sales Order",
- fields=[
- "name",
- "`tabSales Order Item`.item_code"
- ],
+ fields=["name", "`tabSales Order Item`.item_code"],
filters=[
["Sales Order", "docstatus", "=", 1],
- ["Sales Order Item", "item_code", "in", variants_list]
+ ["Sales Order Item", "item_code", "in", variants_list],
],
- distinct=1
+ distinct=1,
)
order_count_map = {}
@@ -151,6 +138,7 @@
return order_count_map
+
def get_stock_details_map(variant_list):
stock_details = frappe.db.get_all(
"Bin",
@@ -160,10 +148,8 @@
"sum(projected_qty) as projected_qty",
"item_code",
],
- filters={
- "item_code": ["in", variant_list]
- },
- group_by="item_code"
+ filters={"item_code": ["in", variant_list]},
+ group_by="item_code",
)
stock_details_map = {}
@@ -171,11 +157,12 @@
name = row.get("item_code")
stock_details_map[name] = {
"Inventory": row.get("actual_qty"),
- "In Production": row.get("planned_qty")
+ "In Production": row.get("planned_qty"),
}
return stock_details_map
+
def get_buying_price_map(variant_list):
buying = frappe.db.get_all(
"Item Price",
@@ -183,11 +170,8 @@
"avg(price_list_rate) as avg_rate",
"item_code",
],
- filters={
- "item_code": ["in", variant_list],
- "buying": 1
- },
- group_by="item_code"
+ filters={"item_code": ["in", variant_list], "buying": 1},
+ group_by="item_code",
)
buying_price_map = {}
@@ -196,6 +180,7 @@
return buying_price_map
+
def get_selling_price_map(variant_list):
selling = frappe.db.get_all(
"Item Price",
@@ -203,11 +188,8 @@
"avg(price_list_rate) as avg_rate",
"item_code",
],
- filters={
- "item_code": ["in", variant_list],
- "selling": 1
- },
- group_by="item_code"
+ filters={"item_code": ["in", variant_list], "selling": 1},
+ group_by="item_code",
)
selling_price_map = {}
@@ -216,17 +198,12 @@
return selling_price_map
+
def get_attribute_values_map(variant_list):
attribute_list = frappe.db.get_all(
"Item Variant Attribute",
- fields=[
- "attribute",
- "attribute_value",
- "parent"
- ],
- filters={
- "parent": ["in", variant_list]
- }
+ fields=["attribute", "attribute_value", "parent"],
+ filters={"parent": ["in", variant_list]},
)
attr_val_map = {}
diff --git a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py
index cfa1e47..f308e9e 100644
--- a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py
+++ b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py
@@ -7,13 +7,14 @@
def execute(filters=None):
- if not filters: filters = {}
+ if not filters:
+ filters = {}
float_precision = frappe.db.get_default("float_precision")
condition = get_condition(filters)
avg_daily_outgoing = 0
- diff = ((getdate(filters.get("to_date")) - getdate(filters.get("from_date"))).days)+1
+ diff = ((getdate(filters.get("to_date")) - getdate(filters.get("from_date"))).days) + 1
if diff <= 0:
frappe.throw(_("'From Date' must be after 'To Date'"))
@@ -24,42 +25,72 @@
data = []
for item in items:
- total_outgoing = flt(consumed_item_map.get(item.name, 0)) + flt(delivered_item_map.get(item.name,0))
+ total_outgoing = flt(consumed_item_map.get(item.name, 0)) + flt(
+ delivered_item_map.get(item.name, 0)
+ )
avg_daily_outgoing = flt(total_outgoing / diff, float_precision)
reorder_level = (avg_daily_outgoing * flt(item.lead_time_days)) + flt(item.safety_stock)
- data.append([item.name, item.item_name, item.item_group, item.brand, item.description,
- item.safety_stock, item.lead_time_days, consumed_item_map.get(item.name, 0),
- delivered_item_map.get(item.name,0), total_outgoing, avg_daily_outgoing, reorder_level])
+ data.append(
+ [
+ item.name,
+ item.item_name,
+ item.item_group,
+ item.brand,
+ item.description,
+ item.safety_stock,
+ item.lead_time_days,
+ consumed_item_map.get(item.name, 0),
+ delivered_item_map.get(item.name, 0),
+ total_outgoing,
+ avg_daily_outgoing,
+ reorder_level,
+ ]
+ )
- return columns , data
+ return columns, data
+
def get_columns():
- return[
- _("Item") + ":Link/Item:120", _("Item Name") + ":Data:120", _("Item Group") + ":Link/Item Group:100",
- _("Brand") + ":Link/Brand:100", _("Description") + "::160",
- _("Safety Stock") + ":Float:160", _("Lead Time Days") + ":Float:120", _("Consumed") + ":Float:120",
- _("Delivered") + ":Float:120", _("Total Outgoing") + ":Float:120", _("Avg Daily Outgoing") + ":Float:160",
- _("Reorder Level") + ":Float:120"
+ return [
+ _("Item") + ":Link/Item:120",
+ _("Item Name") + ":Data:120",
+ _("Item Group") + ":Link/Item Group:100",
+ _("Brand") + ":Link/Brand:100",
+ _("Description") + "::160",
+ _("Safety Stock") + ":Float:160",
+ _("Lead Time Days") + ":Float:120",
+ _("Consumed") + ":Float:120",
+ _("Delivered") + ":Float:120",
+ _("Total Outgoing") + ":Float:120",
+ _("Avg Daily Outgoing") + ":Float:160",
+ _("Reorder Level") + ":Float:120",
]
+
def get_item_info(filters):
from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition
+
conditions = [get_item_group_condition(filters.get("item_group"))]
if filters.get("brand"):
conditions.append("item.brand=%(brand)s")
conditions.append("is_stock_item = 1")
- return frappe.db.sql("""select name, item_name, description, brand, item_group,
- safety_stock, lead_time_days from `tabItem` item where {}"""
- .format(" and ".join(conditions)), filters, as_dict=1)
+ return frappe.db.sql(
+ """select name, item_name, description, brand, item_group,
+ safety_stock, lead_time_days from `tabItem` item where {}""".format(
+ " and ".join(conditions)
+ ),
+ filters,
+ as_dict=1,
+ )
def get_consumed_items(condition):
purpose_to_exclude = [
"Material Transfer for Manufacture",
"Material Transfer",
- "Send to Subcontractor"
+ "Send to Subcontractor",
]
condition += """
@@ -67,10 +98,13 @@
purpose is NULL
or purpose not in ({})
)
- """.format(', '.join(f"'{p}'" for p in purpose_to_exclude))
+ """.format(
+ ", ".join(f"'{p}'" for p in purpose_to_exclude)
+ )
condition = condition.replace("posting_date", "sle.posting_date")
- consumed_items = frappe.db.sql("""
+ consumed_items = frappe.db.sql(
+ """
select item_code, abs(sum(actual_qty)) as consumed_qty
from `tabStock Ledger Entry` as sle left join `tabStock Entry` as se
on sle.voucher_no = se.name
@@ -79,22 +113,34 @@
and is_cancelled = 0
and voucher_type not in ('Delivery Note', 'Sales Invoice')
%s
- group by item_code""" % condition, as_dict=1)
+ group by item_code"""
+ % condition,
+ as_dict=1,
+ )
- consumed_items_map = {item.item_code : item.consumed_qty for item in consumed_items}
+ consumed_items_map = {item.item_code: item.consumed_qty for item in consumed_items}
return consumed_items_map
+
def get_delivered_items(condition):
- dn_items = frappe.db.sql("""select dn_item.item_code, sum(dn_item.stock_qty) as dn_qty
+ dn_items = frappe.db.sql(
+ """select dn_item.item_code, sum(dn_item.stock_qty) as dn_qty
from `tabDelivery Note` dn, `tabDelivery Note Item` dn_item
where dn.name = dn_item.parent and dn.docstatus = 1 %s
- group by dn_item.item_code""" % (condition), as_dict=1)
+ group by dn_item.item_code"""
+ % (condition),
+ as_dict=1,
+ )
- si_items = frappe.db.sql("""select si_item.item_code, sum(si_item.stock_qty) as si_qty
+ si_items = frappe.db.sql(
+ """select si_item.item_code, sum(si_item.stock_qty) as si_qty
from `tabSales Invoice` si, `tabSales Invoice Item` si_item
where si.name = si_item.parent and si.docstatus = 1 and
si.update_stock = 1 %s
- group by si_item.item_code""" % (condition), as_dict=1)
+ group by si_item.item_code"""
+ % (condition),
+ as_dict=1,
+ )
dn_item_map = {}
for item in dn_items:
@@ -105,10 +151,14 @@
return dn_item_map
+
def get_condition(filters):
conditions = ""
if filters.get("from_date") and filters.get("to_date"):
- conditions += " and posting_date between '%s' and '%s'" % (filters["from_date"],filters["to_date"])
+ conditions += " and posting_date between '%s' and '%s'" % (
+ filters["from_date"],
+ filters["to_date"],
+ )
else:
frappe.throw(_("From and To dates required"))
return conditions
diff --git a/erpnext/stock/report/product_bundle_balance/product_bundle_balance.py b/erpnext/stock/report/product_bundle_balance/product_bundle_balance.py
index d9adced..854875a 100644
--- a/erpnext/stock/report/product_bundle_balance/product_bundle_balance.py
+++ b/erpnext/stock/report/product_bundle_balance/product_bundle_balance.py
@@ -44,7 +44,9 @@
child_rows = []
for child_item_detail in required_items:
- child_item_balance = stock_balance.get(child_item_detail.item_code, frappe._dict()).get(warehouse, frappe._dict())
+ child_item_balance = stock_balance.get(child_item_detail.item_code, frappe._dict()).get(
+ warehouse, frappe._dict()
+ )
child_row = {
"indent": 1,
"parent_item": parent_item,
@@ -73,16 +75,46 @@
def get_columns():
columns = [
- {"fieldname": "item_code", "label": _("Item"), "fieldtype": "Link", "options": "Item", "width": 300},
- {"fieldname": "warehouse", "label": _("Warehouse"), "fieldtype": "Link", "options": "Warehouse", "width": 100},
+ {
+ "fieldname": "item_code",
+ "label": _("Item"),
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 300,
+ },
+ {
+ "fieldname": "warehouse",
+ "label": _("Warehouse"),
+ "fieldtype": "Link",
+ "options": "Warehouse",
+ "width": 100,
+ },
{"fieldname": "uom", "label": _("UOM"), "fieldtype": "Link", "options": "UOM", "width": 70},
{"fieldname": "bundle_qty", "label": _("Bundle Qty"), "fieldtype": "Float", "width": 100},
{"fieldname": "actual_qty", "label": _("Actual Qty"), "fieldtype": "Float", "width": 100},
{"fieldname": "minimum_qty", "label": _("Minimum Qty"), "fieldtype": "Float", "width": 100},
- {"fieldname": "item_group", "label": _("Item Group"), "fieldtype": "Link", "options": "Item Group", "width": 100},
- {"fieldname": "brand", "label": _("Brand"), "fieldtype": "Link", "options": "Brand", "width": 100},
+ {
+ "fieldname": "item_group",
+ "label": _("Item Group"),
+ "fieldtype": "Link",
+ "options": "Item Group",
+ "width": 100,
+ },
+ {
+ "fieldname": "brand",
+ "label": _("Brand"),
+ "fieldtype": "Link",
+ "options": "Brand",
+ "width": 100,
+ },
{"fieldname": "description", "label": _("Description"), "width": 140},
- {"fieldname": "company", "label": _("Company"), "fieldtype": "Link", "options": "Company", "width": 100}
+ {
+ "fieldname": "company",
+ "label": _("Company"),
+ "fieldtype": "Link",
+ "options": "Company",
+ "width": 100,
+ },
]
return columns
@@ -92,12 +124,18 @@
item_details = frappe._dict()
conditions = get_parent_item_conditions(filters)
- parent_item_details = frappe.db.sql("""
+ parent_item_details = frappe.db.sql(
+ """
select item.name as item_code, item.item_name, pb.description, item.item_group, item.brand, item.stock_uom
from `tabItem` item
inner join `tabProduct Bundle` pb on pb.new_item_code = item.name
where ifnull(item.disabled, 0) = 0 {0}
- """.format(conditions), filters, as_dict=1) # nosec
+ """.format(
+ conditions
+ ),
+ filters,
+ as_dict=1,
+ ) # nosec
parent_items = []
for d in parent_item_details:
@@ -105,7 +143,8 @@
item_details[d.item_code] = d
if parent_items:
- child_item_details = frappe.db.sql("""
+ child_item_details = frappe.db.sql(
+ """
select
pb.new_item_code as parent_item, pbi.item_code, item.item_name, pbi.description, item.item_group, item.brand,
item.stock_uom, pbi.uom, pbi.qty
@@ -113,7 +152,12 @@
inner join `tabProduct Bundle` pb on pb.name = pbi.parent
inner join `tabItem` item on item.name = pbi.item_code
where pb.new_item_code in ({0})
- """.format(", ".join(["%s"] * len(parent_items))), parent_items, as_dict=1) # nosec
+ """.format(
+ ", ".join(["%s"] * len(parent_items))
+ ),
+ parent_items,
+ as_dict=1,
+ ) # nosec
else:
child_item_details = []
@@ -140,12 +184,14 @@
if not items:
return []
- item_conditions_sql = ' and sle.item_code in ({})' \
- .format(', '.join(frappe.db.escape(i) for i in items))
+ item_conditions_sql = " and sle.item_code in ({})".format(
+ ", ".join(frappe.db.escape(i) for i in items)
+ )
conditions = get_sle_conditions(filters)
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
select
sle.item_code, sle.warehouse, sle.qty_after_transaction, sle.company
from
@@ -153,7 +199,10 @@
left join `tabStock Ledger Entry` sle2 on
sle.item_code = sle2.item_code and sle.warehouse = sle2.warehouse
and (sle.posting_date, sle.posting_time, sle.name) < (sle2.posting_date, sle2.posting_time, sle2.name)
- where sle2.name is null and sle.docstatus < 2 %s %s""" % (item_conditions_sql, conditions), as_dict=1) # nosec
+ where sle2.name is null and sle.docstatus < 2 %s %s"""
+ % (item_conditions_sql, conditions),
+ as_dict=1,
+ ) # nosec
def get_parent_item_conditions(filters):
@@ -179,9 +228,14 @@
conditions += " and sle.posting_date <= %s" % frappe.db.escape(filters.get("date"))
if filters.get("warehouse"):
- warehouse_details = frappe.db.get_value("Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1)
+ warehouse_details = frappe.db.get_value(
+ "Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1
+ )
if warehouse_details:
- conditions += " and exists (select name from `tabWarehouse` wh \
- where wh.lft >= %s and wh.rgt <= %s and sle.warehouse = wh.name)" % (warehouse_details.lft, warehouse_details.rgt) # nosec
+ conditions += (
+ " and exists (select name from `tabWarehouse` wh \
+ where wh.lft >= %s and wh.rgt <= %s and sle.warehouse = wh.name)"
+ % (warehouse_details.lft, warehouse_details.rgt)
+ ) # nosec
return conditions
diff --git a/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py b/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py
index 9738442..fe2d55a 100644
--- a/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py
+++ b/erpnext/stock/report/purchase_receipt_trends/purchase_receipt_trends.py
@@ -8,7 +8,8 @@
def execute(filters=None):
- if not filters: filters ={}
+ if not filters:
+ filters = {}
data = []
conditions = get_columns(filters, "Purchase Receipt")
data = get_data(filters, conditions)
@@ -17,6 +18,7 @@
return conditions["columns"], data, None, chart_data
+
def get_chart_data(data, filters):
if not data:
return []
@@ -27,7 +29,7 @@
# consider only consolidated row
data = [row for row in data if row[0]]
- data = sorted(data, key = lambda i: i[-1], reverse=True)
+ data = sorted(data, key=lambda i: i[-1], reverse=True)
if len(data) > 10:
# get top 10 if data too long
@@ -39,14 +41,9 @@
return {
"data": {
- "labels" : labels,
- "datasets" : [
- {
- "name": _("Total Received Amount"),
- "values": datapoints
- }
- ]
+ "labels": labels,
+ "datasets": [{"name": _("Total Received Amount"), "values": datapoints}],
},
- "type" : "bar",
- "colors":["#5e64ff"]
+ "type": "bar",
+ "colors": ["#5e64ff"],
}
diff --git a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py
index 80ec848..e439f51 100644
--- a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py
+++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py
@@ -12,42 +12,43 @@
data = get_data(filters)
return columns, data
+
def get_columns(filters):
- columns = [{
- 'label': _('Posting Date'),
- 'fieldtype': 'Date',
- 'fieldname': 'posting_date'
- }, {
- 'label': _('Posting Time'),
- 'fieldtype': 'Time',
- 'fieldname': 'posting_time'
- }, {
- 'label': _('Voucher Type'),
- 'fieldtype': 'Link',
- 'fieldname': 'voucher_type',
- 'options': 'DocType',
- 'width': 220
- }, {
- 'label': _('Voucher No'),
- 'fieldtype': 'Dynamic Link',
- 'fieldname': 'voucher_no',
- 'options': 'voucher_type',
- 'width': 220
- }, {
- 'label': _('Company'),
- 'fieldtype': 'Link',
- 'fieldname': 'company',
- 'options': 'Company',
- 'width': 220
- }, {
- 'label': _('Warehouse'),
- 'fieldtype': 'Link',
- 'fieldname': 'warehouse',
- 'options': 'Warehouse',
- 'width': 220
- }]
+ columns = [
+ {"label": _("Posting Date"), "fieldtype": "Date", "fieldname": "posting_date"},
+ {"label": _("Posting Time"), "fieldtype": "Time", "fieldname": "posting_time"},
+ {
+ "label": _("Voucher Type"),
+ "fieldtype": "Link",
+ "fieldname": "voucher_type",
+ "options": "DocType",
+ "width": 220,
+ },
+ {
+ "label": _("Voucher No"),
+ "fieldtype": "Dynamic Link",
+ "fieldname": "voucher_no",
+ "options": "voucher_type",
+ "width": 220,
+ },
+ {
+ "label": _("Company"),
+ "fieldtype": "Link",
+ "fieldname": "company",
+ "options": "Company",
+ "width": 220,
+ },
+ {
+ "label": _("Warehouse"),
+ "fieldtype": "Link",
+ "fieldname": "warehouse",
+ "options": "Warehouse",
+ "width": 220,
+ },
+ ]
return columns
+
def get_data(filters):
- return get_stock_ledger_entries(filters, '<=', order="asc") or []
+ return get_stock_ledger_entries(filters, "<=", order="asc") or []
diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py
index 7ca4003..1956238 100644
--- a/erpnext/stock/report/stock_ageing/stock_ageing.py
+++ b/erpnext/stock/report/stock_ageing/stock_ageing.py
@@ -25,6 +25,7 @@
return columns, data, None, chart_data
+
def format_report_data(filters: Filters, item_details: Dict, to_date: str) -> List[Dict]:
"Returns ordered, formatted data with ranges."
_func = itemgetter(1)
@@ -38,31 +39,38 @@
fifo_queue = sorted(filter(_func, item_dict["fifo_queue"]), key=_func)
- if not fifo_queue: continue
+ if not fifo_queue:
+ continue
average_age = get_average_age(fifo_queue, to_date)
earliest_age = date_diff(to_date, fifo_queue[0][1])
latest_age = date_diff(to_date, fifo_queue[-1][1])
range1, range2, range3, above_range3 = get_range_age(filters, fifo_queue, to_date, item_dict)
- row = [details.name, details.item_name, details.description,
- details.item_group, details.brand]
+ row = [details.name, details.item_name, details.description, details.item_group, details.brand]
if filters.get("show_warehouse_wise_stock"):
row.append(details.warehouse)
- row.extend([
- flt(item_dict.get("total_qty"), precision),
- average_age,
- range1, range2, range3, above_range3,
- earliest_age, latest_age,
- details.stock_uom
- ])
+ row.extend(
+ [
+ flt(item_dict.get("total_qty"), precision),
+ average_age,
+ range1,
+ range2,
+ range3,
+ above_range3,
+ earliest_age,
+ latest_age,
+ details.stock_uom,
+ ]
+ )
data.append(row)
return data
+
def get_average_age(fifo_queue: List, to_date: str) -> float:
batch_age = age_qty = total_qty = 0.0
for batch in fifo_queue:
@@ -77,6 +85,7 @@
return flt(age_qty / total_qty, 2) if total_qty else 0.0
+
def get_range_age(filters: Filters, fifo_queue: List, to_date: str, item_dict: Dict) -> Tuple:
precision = cint(frappe.db.get_single_value("System Settings", "float_precision", cache=True))
@@ -98,6 +107,7 @@
return range1, range2, range3, above_range3
+
def get_columns(filters: Filters) -> List[Dict]:
range_columns = []
setup_ageing_columns(filters, range_columns)
@@ -107,82 +117,55 @@
"fieldname": "item_code",
"fieldtype": "Link",
"options": "Item",
- "width": 100
+ "width": 100,
},
- {
- "label": _("Item Name"),
- "fieldname": "item_name",
- "fieldtype": "Data",
- "width": 100
- },
- {
- "label": _("Description"),
- "fieldname": "description",
- "fieldtype": "Data",
- "width": 200
- },
+ {"label": _("Item Name"), "fieldname": "item_name", "fieldtype": "Data", "width": 100},
+ {"label": _("Description"), "fieldname": "description", "fieldtype": "Data", "width": 200},
{
"label": _("Item Group"),
"fieldname": "item_group",
"fieldtype": "Link",
"options": "Item Group",
- "width": 100
+ "width": 100,
},
{
"label": _("Brand"),
"fieldname": "brand",
"fieldtype": "Link",
"options": "Brand",
- "width": 100
- }]
+ "width": 100,
+ },
+ ]
if filters.get("show_warehouse_wise_stock"):
- columns +=[{
- "label": _("Warehouse"),
- "fieldname": "warehouse",
- "fieldtype": "Link",
- "options": "Warehouse",
- "width": 100
- }]
+ columns += [
+ {
+ "label": _("Warehouse"),
+ "fieldname": "warehouse",
+ "fieldtype": "Link",
+ "options": "Warehouse",
+ "width": 100,
+ }
+ ]
- columns.extend([
- {
- "label": _("Available Qty"),
- "fieldname": "qty",
- "fieldtype": "Float",
- "width": 100
- },
- {
- "label": _("Average Age"),
- "fieldname": "average_age",
- "fieldtype": "Float",
- "width": 100
- }])
+ columns.extend(
+ [
+ {"label": _("Available Qty"), "fieldname": "qty", "fieldtype": "Float", "width": 100},
+ {"label": _("Average Age"), "fieldname": "average_age", "fieldtype": "Float", "width": 100},
+ ]
+ )
columns.extend(range_columns)
- columns.extend([
- {
- "label": _("Earliest"),
- "fieldname": "earliest",
- "fieldtype": "Int",
- "width": 80
- },
- {
- "label": _("Latest"),
- "fieldname": "latest",
- "fieldtype": "Int",
- "width": 80
- },
- {
- "label": _("UOM"),
- "fieldname": "uom",
- "fieldtype": "Link",
- "options": "UOM",
- "width": 100
- }
- ])
+ columns.extend(
+ [
+ {"label": _("Earliest"), "fieldname": "earliest", "fieldtype": "Int", "width": 80},
+ {"label": _("Latest"), "fieldname": "latest", "fieldtype": "Int", "width": 80},
+ {"label": _("UOM"), "fieldname": "uom", "fieldtype": "Link", "options": "UOM", "width": 100},
+ ]
+ )
return columns
+
def get_chart_data(data: List, filters: Filters) -> Dict:
if not data:
return []
@@ -192,7 +175,7 @@
if filters.get("show_warehouse_wise_stock"):
return {}
- data.sort(key = lambda row: row[6], reverse=True)
+ data.sort(key=lambda row: row[6], reverse=True)
if len(data) > 10:
data = data[:10]
@@ -202,42 +185,33 @@
datapoints.append(row[6])
return {
- "data" : {
- "labels": labels,
- "datasets": [
- {
- "name": _("Average Age"),
- "values": datapoints
- }
- ]
- },
- "type" : "bar"
+ "data": {"labels": labels, "datasets": [{"name": _("Average Age"), "values": datapoints}]},
+ "type": "bar",
}
+
def setup_ageing_columns(filters: Filters, range_columns: List):
ranges = [
f"0 - {filters['range1']}",
f"{cint(filters['range1']) + 1} - {cint(filters['range2'])}",
f"{cint(filters['range2']) + 1} - {cint(filters['range3'])}",
- f"{cint(filters['range3']) + 1} - {_('Above')}"
+ f"{cint(filters['range3']) + 1} - {_('Above')}",
]
for i, label in enumerate(ranges):
- fieldname = 'range' + str(i+1)
- add_column(range_columns, label=f"Age ({label})",fieldname=fieldname)
+ fieldname = "range" + str(i + 1)
+ add_column(range_columns, label=f"Age ({label})", fieldname=fieldname)
-def add_column(range_columns: List, label:str, fieldname: str, fieldtype: str = 'Float', width: int = 140):
- range_columns.append(dict(
- label=label,
- fieldname=fieldname,
- fieldtype=fieldtype,
- width=width
- ))
+
+def add_column(
+ range_columns: List, label: str, fieldname: str, fieldtype: str = "Float", width: int = 140
+):
+ range_columns.append(dict(label=label, fieldname=fieldname, fieldtype=fieldtype, width=width))
class FIFOSlots:
"Returns FIFO computed slots of inwarded stock as per date."
- def __init__(self, filters: Dict = None , sle: List = None):
+ def __init__(self, filters: Dict = None, sle: List = None):
self.item_details = {}
self.transferred_item_details = {}
self.serial_no_batch_purchase_details = {}
@@ -246,13 +220,13 @@
def generate(self) -> Dict:
"""
- Returns dict of the foll.g structure:
- Key = Item A / (Item A, Warehouse A)
- Key: {
- 'details' -> Dict: ** item details **,
- 'fifo_queue' -> List: ** list of lists containing entries/slots for existing stock,
- consumed/updated and maintained via FIFO. **
- }
+ Returns dict of the foll.g structure:
+ Key = Item A / (Item A, Warehouse A)
+ Key: {
+ 'details' -> Dict: ** item details **,
+ 'fifo_queue' -> List: ** list of lists containing entries/slots for existing stock,
+ consumed/updated and maintained via FIFO. **
+ }
"""
if self.sle is None:
self.sle = self.__get_stock_ledger_entries()
@@ -292,7 +266,9 @@
return key, fifo_queue, transferred_item_key
- def __compute_incoming_stock(self, row: Dict, fifo_queue: List, transfer_key: Tuple, serial_nos: List):
+ def __compute_incoming_stock(
+ self, row: Dict, fifo_queue: List, transfer_key: Tuple, serial_nos: List
+ ):
"Update FIFO Queue on inward stock."
transfer_data = self.transferred_item_details.get(transfer_key)
@@ -318,7 +294,9 @@
self.serial_no_batch_purchase_details.setdefault(serial_no, row.posting_date)
fifo_queue.append([serial_no, row.posting_date])
- def __compute_outgoing_stock(self, row: Dict, fifo_queue: List, transfer_key: Tuple, serial_nos: List):
+ def __compute_outgoing_stock(
+ self, row: Dict, fifo_queue: List, transfer_key: Tuple, serial_nos: List
+ ):
"Update FIFO Queue on outward stock."
if serial_nos:
fifo_queue[:] = [serial_no for serial_no in fifo_queue if serial_no[0] not in serial_nos]
@@ -384,15 +362,13 @@
def __aggregate_details_by_item(self, wh_wise_data: Dict) -> Dict:
"Aggregate Item-Wh wise data into single Item entry."
item_aggregated_data = {}
- for key,row in wh_wise_data.items():
+ for key, row in wh_wise_data.items():
item = key[0]
if not item_aggregated_data.get(item):
- item_aggregated_data.setdefault(item, {
- "details": frappe._dict(),
- "fifo_queue": [],
- "qty_after_transaction": 0.0,
- "total_qty": 0.0
- })
+ item_aggregated_data.setdefault(
+ item,
+ {"details": frappe._dict(), "fifo_queue": [], "qty_after_transaction": 0.0, "total_qty": 0.0},
+ )
item_row = item_aggregated_data.get(item)
item_row["details"].update(row["details"])
item_row["fifo_queue"].extend(row["fifo_queue"])
@@ -404,19 +380,29 @@
def __get_stock_ledger_entries(self) -> List[Dict]:
sle = frappe.qb.DocType("Stock Ledger Entry")
- item = self.__get_item_query() # used as derived table in sle query
+ item = self.__get_item_query() # used as derived table in sle query
sle_query = (
- frappe.qb.from_(sle).from_(item)
+ frappe.qb.from_(sle)
+ .from_(item)
.select(
- item.name, item.item_name, item.item_group,
- item.brand, item.description,
- item.stock_uom, item.has_serial_no,
- sle.actual_qty, sle.posting_date,
- sle.voucher_type, sle.voucher_no,
- sle.serial_no, sle.batch_no,
- sle.qty_after_transaction, sle.warehouse
- ).where(
+ item.name,
+ item.item_name,
+ item.item_group,
+ item.brand,
+ item.description,
+ item.stock_uom,
+ item.has_serial_no,
+ sle.actual_qty,
+ sle.posting_date,
+ sle.voucher_type,
+ sle.voucher_no,
+ sle.serial_no,
+ sle.batch_no,
+ sle.qty_after_transaction,
+ sle.warehouse,
+ )
+ .where(
(sle.item_code == item.name)
& (sle.company == self.filters.get("company"))
& (sle.posting_date <= self.filters.get("to_date"))
@@ -427,9 +413,7 @@
if self.filters.get("warehouse"):
sle_query = self.__get_warehouse_conditions(sle, sle_query)
- sle_query = sle_query.orderby(
- sle.posting_date, sle.posting_time, sle.creation, sle.actual_qty
- )
+ sle_query = sle_query.orderby(sle.posting_date, sle.posting_time, sle.creation, sle.actual_qty)
return sle_query.run(as_dict=True)
@@ -437,8 +421,7 @@
item_table = frappe.qb.DocType("Item")
item = frappe.qb.from_("Item").select(
- "name", "item_name", "description", "stock_uom",
- "brand", "item_group", "has_serial_no"
+ "name", "item_name", "description", "stock_uom", "brand", "item_group", "has_serial_no"
)
if self.filters.get("item_code"):
@@ -451,18 +434,13 @@
def __get_warehouse_conditions(self, sle, sle_query) -> str:
warehouse = frappe.qb.DocType("Warehouse")
- lft, rgt = frappe.db.get_value(
- "Warehouse",
- self.filters.get("warehouse"),
- ['lft', 'rgt']
- )
+ lft, rgt = frappe.db.get_value("Warehouse", self.filters.get("warehouse"), ["lft", "rgt"])
warehouse_results = (
frappe.qb.from_(warehouse)
- .select("name").where(
- (warehouse.lft >= lft)
- & (warehouse.rgt <= rgt)
- ).run()
+ .select("name")
+ .where((warehouse.lft >= lft) & (warehouse.rgt <= rgt))
+ .run()
)
warehouse_results = [x[0] for x in warehouse_results]
diff --git a/erpnext/stock/report/stock_ageing/test_stock_ageing.py b/erpnext/stock/report/stock_ageing/test_stock_ageing.py
index ca963b7..fb36360 100644
--- a/erpnext/stock/report/stock_ageing/test_stock_ageing.py
+++ b/erpnext/stock/report/stock_ageing/test_stock_ageing.py
@@ -10,9 +10,7 @@
class TestStockAgeing(FrappeTestCase):
def setUp(self) -> None:
self.filters = frappe._dict(
- company="_Test Company",
- to_date="2021-12-10",
- range1=30, range2=60, range3=90
+ company="_Test Company", to_date="2021-12-10", range1=30, range2=60, range3=90
)
def test_normal_inward_outward_queue(self):
@@ -20,28 +18,37 @@
sle = [
frappe._dict(
name="Flask Item",
- actual_qty=30, qty_after_transaction=30,
+ actual_qty=30,
+ qty_after_transaction=30,
warehouse="WH 1",
- posting_date="2021-12-01", voucher_type="Stock Entry",
+ posting_date="2021-12-01",
+ voucher_type="Stock Entry",
voucher_no="001",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
frappe._dict(
name="Flask Item",
- actual_qty=20, qty_after_transaction=50,
+ actual_qty=20,
+ qty_after_transaction=50,
warehouse="WH 1",
- posting_date="2021-12-02", voucher_type="Stock Entry",
+ posting_date="2021-12-02",
+ voucher_type="Stock Entry",
voucher_no="002",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
frappe._dict(
name="Flask Item",
- actual_qty=(-10), qty_after_transaction=40,
+ actual_qty=(-10),
+ qty_after_transaction=40,
warehouse="WH 1",
- posting_date="2021-12-03", voucher_type="Stock Entry",
+ posting_date="2021-12-03",
+ voucher_type="Stock Entry",
voucher_no="003",
- has_serial_no=False, serial_no=None
- )
+ has_serial_no=False,
+ serial_no=None,
+ ),
]
slots = FIFOSlots(self.filters, sle).generate()
@@ -58,36 +65,48 @@
sle = [
frappe._dict(
name="Flask Item",
- actual_qty=(-30), qty_after_transaction=(-30),
+ actual_qty=(-30),
+ qty_after_transaction=(-30),
warehouse="WH 1",
- posting_date="2021-12-01", voucher_type="Stock Entry",
+ posting_date="2021-12-01",
+ voucher_type="Stock Entry",
voucher_no="001",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
frappe._dict(
name="Flask Item",
- actual_qty=20, qty_after_transaction=(-10),
+ actual_qty=20,
+ qty_after_transaction=(-10),
warehouse="WH 1",
- posting_date="2021-12-02", voucher_type="Stock Entry",
+ posting_date="2021-12-02",
+ voucher_type="Stock Entry",
voucher_no="002",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
frappe._dict(
name="Flask Item",
- actual_qty=20, qty_after_transaction=10,
+ actual_qty=20,
+ qty_after_transaction=10,
warehouse="WH 1",
- posting_date="2021-12-03", voucher_type="Stock Entry",
+ posting_date="2021-12-03",
+ voucher_type="Stock Entry",
voucher_no="003",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
frappe._dict(
name="Flask Item",
- actual_qty=10, qty_after_transaction=20,
+ actual_qty=10,
+ qty_after_transaction=20,
warehouse="WH 1",
- posting_date="2021-12-03", voucher_type="Stock Entry",
+ posting_date="2021-12-03",
+ voucher_type="Stock Entry",
voucher_no="004",
- has_serial_no=False, serial_no=None
- )
+ has_serial_no=False,
+ serial_no=None,
+ ),
]
slots = FIFOSlots(self.filters, sle).generate()
@@ -107,28 +126,37 @@
sle = [
frappe._dict(
name="Flask Item",
- actual_qty=30, qty_after_transaction=30,
+ actual_qty=30,
+ qty_after_transaction=30,
warehouse="WH 1",
- posting_date="2021-12-01", voucher_type="Stock Entry",
+ posting_date="2021-12-01",
+ voucher_type="Stock Entry",
voucher_no="001",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
frappe._dict(
name="Flask Item",
- actual_qty=0, qty_after_transaction=50,
+ actual_qty=0,
+ qty_after_transaction=50,
warehouse="WH 1",
- posting_date="2021-12-02", voucher_type="Stock Reconciliation",
+ posting_date="2021-12-02",
+ voucher_type="Stock Reconciliation",
voucher_no="002",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
frappe._dict(
name="Flask Item",
- actual_qty=(-10), qty_after_transaction=40,
+ actual_qty=(-10),
+ qty_after_transaction=40,
warehouse="WH 1",
- posting_date="2021-12-03", voucher_type="Stock Entry",
+ posting_date="2021-12-03",
+ voucher_type="Stock Entry",
voucher_no="003",
- has_serial_no=False, serial_no=None
- )
+ has_serial_no=False,
+ serial_no=None,
+ ),
]
slots = FIFOSlots(self.filters, sle).generate()
@@ -150,28 +178,37 @@
sle = [
frappe._dict(
name="Flask Item",
- actual_qty=0, qty_after_transaction=1000,
+ actual_qty=0,
+ qty_after_transaction=1000,
warehouse="WH 1",
- posting_date="2021-12-01", voucher_type="Stock Reconciliation",
+ posting_date="2021-12-01",
+ voucher_type="Stock Reconciliation",
voucher_no="002",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
frappe._dict(
name="Flask Item",
- actual_qty=0, qty_after_transaction=400,
+ actual_qty=0,
+ qty_after_transaction=400,
warehouse="WH 1",
- posting_date="2021-12-02", voucher_type="Stock Reconciliation",
+ posting_date="2021-12-02",
+ voucher_type="Stock Reconciliation",
voucher_no="003",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
frappe._dict(
name="Flask Item",
- actual_qty=(-10), qty_after_transaction=390,
+ actual_qty=(-10),
+ qty_after_transaction=390,
warehouse="WH 1",
- posting_date="2021-12-03", voucher_type="Stock Entry",
+ posting_date="2021-12-03",
+ voucher_type="Stock Entry",
voucher_no="003",
- has_serial_no=False, serial_no=None
- )
+ has_serial_no=False,
+ serial_no=None,
+ ),
]
slots = FIFOSlots(self.filters, sle).generate()
@@ -196,32 +233,41 @@
sle = [
frappe._dict(
name="Flask Item",
- actual_qty=0, qty_after_transaction=1000,
+ actual_qty=0,
+ qty_after_transaction=1000,
warehouse="WH 1",
- posting_date="2021-12-01", voucher_type="Stock Reconciliation",
+ posting_date="2021-12-01",
+ voucher_type="Stock Reconciliation",
voucher_no="002",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
frappe._dict(
name="Flask Item",
- actual_qty=0, qty_after_transaction=400,
+ actual_qty=0,
+ qty_after_transaction=400,
warehouse="WH 2",
- posting_date="2021-12-02", voucher_type="Stock Reconciliation",
+ posting_date="2021-12-02",
+ voucher_type="Stock Reconciliation",
voucher_no="003",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
frappe._dict(
name="Flask Item",
- actual_qty=(-10), qty_after_transaction=990,
+ actual_qty=(-10),
+ qty_after_transaction=990,
warehouse="WH 1",
- posting_date="2021-12-03", voucher_type="Stock Entry",
+ posting_date="2021-12-03",
+ voucher_type="Stock Entry",
voucher_no="004",
- has_serial_no=False, serial_no=None
- )
+ has_serial_no=False,
+ serial_no=None,
+ ),
]
item_wise_slots, item_wh_wise_slots = generate_item_and_item_wh_wise_slots(
- filters=self.filters,sle=sle
+ filters=self.filters, sle=sle
)
# test without 'show_warehouse_wise_stock'
@@ -234,7 +280,9 @@
self.assertEqual(queue[1][0], 400.0)
# test with 'show_warehouse_wise_stock' checked
- item_wh_balances = [item_wh_wise_slots.get(i).get("qty_after_transaction") for i in item_wh_wise_slots]
+ item_wh_balances = [
+ item_wh_wise_slots.get(i).get("qty_after_transaction") for i in item_wh_wise_slots
+ ]
self.assertEqual(sum(item_wh_balances), item_result["qty_after_transaction"])
def test_repack_entry_same_item_split_rows(self):
@@ -251,37 +299,49 @@
Case most likely for batch items. Test time bucket computation.
"""
sle = [
- frappe._dict( # stock up item
+ frappe._dict( # stock up item
name="Flask Item",
- actual_qty=500, qty_after_transaction=500,
+ actual_qty=500,
+ qty_after_transaction=500,
warehouse="WH 1",
- posting_date="2021-12-03", voucher_type="Stock Entry",
+ posting_date="2021-12-03",
+ voucher_type="Stock Entry",
voucher_no="001",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
frappe._dict(
name="Flask Item",
- actual_qty=(-50), qty_after_transaction=450,
+ actual_qty=(-50),
+ qty_after_transaction=450,
warehouse="WH 1",
- posting_date="2021-12-04", voucher_type="Stock Entry",
+ posting_date="2021-12-04",
+ voucher_type="Stock Entry",
voucher_no="002",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
frappe._dict(
name="Flask Item",
- actual_qty=(-50), qty_after_transaction=400,
+ actual_qty=(-50),
+ qty_after_transaction=400,
warehouse="WH 1",
- posting_date="2021-12-04", voucher_type="Stock Entry",
+ posting_date="2021-12-04",
+ voucher_type="Stock Entry",
voucher_no="002",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
frappe._dict(
name="Flask Item",
- actual_qty=100, qty_after_transaction=500,
+ actual_qty=100,
+ qty_after_transaction=500,
warehouse="WH 1",
- posting_date="2021-12-04", voucher_type="Stock Entry",
+ posting_date="2021-12-04",
+ voucher_type="Stock Entry",
voucher_no="002",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
]
slots = FIFOSlots(self.filters, sle).generate()
@@ -308,29 +368,38 @@
Case most likely for batch items. Test time bucket computation.
"""
sle = [
- frappe._dict( # stock up item
+ frappe._dict( # stock up item
name="Flask Item",
- actual_qty=500, qty_after_transaction=500,
+ actual_qty=500,
+ qty_after_transaction=500,
warehouse="WH 1",
- posting_date="2021-12-03", voucher_type="Stock Entry",
+ posting_date="2021-12-03",
+ voucher_type="Stock Entry",
voucher_no="001",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
frappe._dict(
name="Flask Item",
- actual_qty=(-100), qty_after_transaction=400,
+ actual_qty=(-100),
+ qty_after_transaction=400,
warehouse="WH 1",
- posting_date="2021-12-04", voucher_type="Stock Entry",
+ posting_date="2021-12-04",
+ voucher_type="Stock Entry",
voucher_no="002",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
frappe._dict(
name="Flask Item",
- actual_qty=50, qty_after_transaction=450,
+ actual_qty=50,
+ qty_after_transaction=450,
warehouse="WH 1",
- posting_date="2021-12-04", voucher_type="Stock Entry",
+ posting_date="2021-12-04",
+ voucher_type="Stock Entry",
voucher_no="002",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
]
slots = FIFOSlots(self.filters, sle).generate()
@@ -355,37 +424,49 @@
Item 1 | 50 | 002 (repack)
"""
sle = [
- frappe._dict( # stock up item
+ frappe._dict( # stock up item
name="Flask Item",
- actual_qty=20, qty_after_transaction=20,
+ actual_qty=20,
+ qty_after_transaction=20,
warehouse="WH 1",
- posting_date="2021-12-03", voucher_type="Stock Entry",
+ posting_date="2021-12-03",
+ voucher_type="Stock Entry",
voucher_no="001",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
frappe._dict(
name="Flask Item",
- actual_qty=(-50), qty_after_transaction=(-30),
+ actual_qty=(-50),
+ qty_after_transaction=(-30),
warehouse="WH 1",
- posting_date="2021-12-04", voucher_type="Stock Entry",
+ posting_date="2021-12-04",
+ voucher_type="Stock Entry",
voucher_no="002",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
frappe._dict(
name="Flask Item",
- actual_qty=(-50), qty_after_transaction=(-80),
+ actual_qty=(-50),
+ qty_after_transaction=(-80),
warehouse="WH 1",
- posting_date="2021-12-04", voucher_type="Stock Entry",
+ posting_date="2021-12-04",
+ voucher_type="Stock Entry",
voucher_no="002",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
frappe._dict(
name="Flask Item",
- actual_qty=50, qty_after_transaction=(-30),
+ actual_qty=50,
+ qty_after_transaction=(-30),
warehouse="WH 1",
- posting_date="2021-12-04", voucher_type="Stock Entry",
+ posting_date="2021-12-04",
+ voucher_type="Stock Entry",
voucher_no="002",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
]
fifo_slots = FIFOSlots(self.filters, sle)
@@ -397,7 +478,7 @@
self.assertEqual(queue[0][0], -30.0)
# check transfer bucket
- transfer_bucket = fifo_slots.transferred_item_details[('002', 'Flask Item', 'WH 1')]
+ transfer_bucket = fifo_slots.transferred_item_details[("002", "Flask Item", "WH 1")]
self.assertEqual(transfer_bucket[0][0], 50)
def test_repack_entry_same_item_overproduce(self):
@@ -413,29 +494,38 @@
Case most likely for batch items. Test time bucket computation.
"""
sle = [
- frappe._dict( # stock up item
+ frappe._dict( # stock up item
name="Flask Item",
- actual_qty=500, qty_after_transaction=500,
+ actual_qty=500,
+ qty_after_transaction=500,
warehouse="WH 1",
- posting_date="2021-12-03", voucher_type="Stock Entry",
+ posting_date="2021-12-03",
+ voucher_type="Stock Entry",
voucher_no="001",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
frappe._dict(
name="Flask Item",
- actual_qty=(-50), qty_after_transaction=450,
+ actual_qty=(-50),
+ qty_after_transaction=450,
warehouse="WH 1",
- posting_date="2021-12-04", voucher_type="Stock Entry",
+ posting_date="2021-12-04",
+ voucher_type="Stock Entry",
voucher_no="002",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
frappe._dict(
name="Flask Item",
- actual_qty=100, qty_after_transaction=550,
+ actual_qty=100,
+ qty_after_transaction=550,
warehouse="WH 1",
- posting_date="2021-12-04", voucher_type="Stock Entry",
+ posting_date="2021-12-04",
+ voucher_type="Stock Entry",
voucher_no="002",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
]
slots = FIFOSlots(self.filters, sle).generate()
@@ -461,37 +551,49 @@
Item 1 | 50 | 002 (repack)
"""
sle = [
- frappe._dict( # stock up item
+ frappe._dict( # stock up item
name="Flask Item",
- actual_qty=20, qty_after_transaction=20,
+ actual_qty=20,
+ qty_after_transaction=20,
warehouse="WH 1",
- posting_date="2021-12-03", voucher_type="Stock Entry",
+ posting_date="2021-12-03",
+ voucher_type="Stock Entry",
voucher_no="001",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
frappe._dict(
name="Flask Item",
- actual_qty=(-50), qty_after_transaction=(-30),
+ actual_qty=(-50),
+ qty_after_transaction=(-30),
warehouse="WH 1",
- posting_date="2021-12-04", voucher_type="Stock Entry",
+ posting_date="2021-12-04",
+ voucher_type="Stock Entry",
voucher_no="002",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
frappe._dict(
name="Flask Item",
- actual_qty=50, qty_after_transaction=20,
+ actual_qty=50,
+ qty_after_transaction=20,
warehouse="WH 1",
- posting_date="2021-12-04", voucher_type="Stock Entry",
+ posting_date="2021-12-04",
+ voucher_type="Stock Entry",
voucher_no="002",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
frappe._dict(
name="Flask Item",
- actual_qty=50, qty_after_transaction=70,
+ actual_qty=50,
+ qty_after_transaction=70,
warehouse="WH 1",
- posting_date="2021-12-04", voucher_type="Stock Entry",
+ posting_date="2021-12-04",
+ voucher_type="Stock Entry",
voucher_no="002",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
]
fifo_slots = FIFOSlots(self.filters, sle)
@@ -504,7 +606,7 @@
self.assertEqual(queue[1][0], 50.0)
# check transfer bucket
- transfer_bucket = fifo_slots.transferred_item_details[('002', 'Flask Item', 'WH 1')]
+ transfer_bucket = fifo_slots.transferred_item_details[("002", "Flask Item", "WH 1")]
self.assertFalse(transfer_bucket)
def test_negative_stock_same_voucher(self):
@@ -519,29 +621,38 @@
Item 1 | 80 | 001
"""
sle = [
- frappe._dict( # stock up item
+ frappe._dict( # stock up item
name="Flask Item",
- actual_qty=(-50), qty_after_transaction=(-50),
+ actual_qty=(-50),
+ qty_after_transaction=(-50),
warehouse="WH 1",
- posting_date="2021-12-01", voucher_type="Stock Entry",
+ posting_date="2021-12-01",
+ voucher_type="Stock Entry",
voucher_no="001",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
- frappe._dict( # stock up item
+ frappe._dict( # stock up item
name="Flask Item",
- actual_qty=(-50), qty_after_transaction=(-100),
+ actual_qty=(-50),
+ qty_after_transaction=(-100),
warehouse="WH 1",
- posting_date="2021-12-01", voucher_type="Stock Entry",
+ posting_date="2021-12-01",
+ voucher_type="Stock Entry",
voucher_no="001",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
- frappe._dict( # stock up item
+ frappe._dict( # stock up item
name="Flask Item",
- actual_qty=30, qty_after_transaction=(-70),
+ actual_qty=30,
+ qty_after_transaction=(-70),
warehouse="WH 1",
- posting_date="2021-12-01", voucher_type="Stock Entry",
+ posting_date="2021-12-01",
+ voucher_type="Stock Entry",
voucher_no="001",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
]
fifo_slots = FIFOSlots(self.filters, sle)
@@ -549,59 +660,71 @@
item_result = slots["Flask Item"]
# check transfer bucket
- transfer_bucket = fifo_slots.transferred_item_details[('001', 'Flask Item', 'WH 1')]
+ transfer_bucket = fifo_slots.transferred_item_details[("001", "Flask Item", "WH 1")]
self.assertEqual(transfer_bucket[0][0], 20)
self.assertEqual(transfer_bucket[1][0], 50)
self.assertEqual(item_result["fifo_queue"][0][0], -70.0)
- sle.append(frappe._dict(
- name="Flask Item",
- actual_qty=80, qty_after_transaction=10,
- warehouse="WH 1",
- posting_date="2021-12-01", voucher_type="Stock Entry",
- voucher_no="001",
- has_serial_no=False, serial_no=None
- ))
+ sle.append(
+ frappe._dict(
+ name="Flask Item",
+ actual_qty=80,
+ qty_after_transaction=10,
+ warehouse="WH 1",
+ posting_date="2021-12-01",
+ voucher_type="Stock Entry",
+ voucher_no="001",
+ has_serial_no=False,
+ serial_no=None,
+ )
+ )
fifo_slots = FIFOSlots(self.filters, sle)
slots = fifo_slots.generate()
item_result = slots["Flask Item"]
- transfer_bucket = fifo_slots.transferred_item_details[('001', 'Flask Item', 'WH 1')]
+ transfer_bucket = fifo_slots.transferred_item_details[("001", "Flask Item", "WH 1")]
self.assertFalse(transfer_bucket)
self.assertEqual(item_result["fifo_queue"][0][0], 10.0)
def test_precision(self):
"Test if final balance qty is rounded off correctly."
sle = [
- frappe._dict( # stock up item
+ frappe._dict( # stock up item
name="Flask Item",
- actual_qty=0.3, qty_after_transaction=0.3,
+ actual_qty=0.3,
+ qty_after_transaction=0.3,
warehouse="WH 1",
- posting_date="2021-12-01", voucher_type="Stock Entry",
+ posting_date="2021-12-01",
+ voucher_type="Stock Entry",
voucher_no="001",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
- frappe._dict( # stock up item
+ frappe._dict( # stock up item
name="Flask Item",
- actual_qty=0.6, qty_after_transaction=0.9,
+ actual_qty=0.6,
+ qty_after_transaction=0.9,
warehouse="WH 1",
- posting_date="2021-12-01", voucher_type="Stock Entry",
+ posting_date="2021-12-01",
+ voucher_type="Stock Entry",
voucher_no="001",
- has_serial_no=False, serial_no=None
+ has_serial_no=False,
+ serial_no=None,
),
]
slots = FIFOSlots(self.filters, sle).generate()
report_data = format_report_data(self.filters, slots, self.filters["to_date"])
- row = report_data[0] # first row in report
+ row = report_data[0] # first row in report
bal_qty = row[5]
- range_qty_sum = sum([i for i in row[7:11]]) # get sum of range balance
+ range_qty_sum = sum([i for i in row[7:11]]) # get sum of range balance
# check if value of Available Qty column matches with range bucket post format
self.assertEqual(bal_qty, 0.9)
self.assertEqual(bal_qty, range_qty_sum)
+
def generate_item_and_item_wh_wise_slots(filters, sle):
"Return results with and without 'show_warehouse_wise_stock'"
item_wise_slots = FIFOSlots(filters, sle).generate()
diff --git a/erpnext/stock/report/stock_analytics/stock_analytics.py b/erpnext/stock/report/stock_analytics/stock_analytics.py
index ddc8310..da0776b 100644
--- a/erpnext/stock/report/stock_analytics/stock_analytics.py
+++ b/erpnext/stock/report/stock_analytics/stock_analytics.py
@@ -25,84 +25,64 @@
return columns, data, None, chart
+
def get_columns(filters):
columns = [
- {
- "label": _("Item"),
- "options":"Item",
- "fieldname": "name",
- "fieldtype": "Link",
- "width": 140
- },
+ {"label": _("Item"), "options": "Item", "fieldname": "name", "fieldtype": "Link", "width": 140},
{
"label": _("Item Name"),
- "options":"Item",
+ "options": "Item",
"fieldname": "item_name",
"fieldtype": "Link",
- "width": 140
+ "width": 140,
},
{
"label": _("Item Group"),
- "options":"Item Group",
+ "options": "Item Group",
"fieldname": "item_group",
"fieldtype": "Link",
- "width": 140
+ "width": 140,
},
- {
- "label": _("Brand"),
- "fieldname": "brand",
- "fieldtype": "Data",
- "width": 120
- },
- {
- "label": _("UOM"),
- "fieldname": "uom",
- "fieldtype": "Data",
- "width": 120
- }]
+ {"label": _("Brand"), "fieldname": "brand", "fieldtype": "Data", "width": 120},
+ {"label": _("UOM"), "fieldname": "uom", "fieldtype": "Data", "width": 120},
+ ]
ranges = get_period_date_ranges(filters)
for dummy, end_date in ranges:
period = get_period(end_date, filters)
- columns.append({
- "label": _(period),
- "fieldname":scrub(period),
- "fieldtype": "Float",
- "width": 120
- })
+ columns.append(
+ {"label": _(period), "fieldname": scrub(period), "fieldtype": "Float", "width": 120}
+ )
return columns
+
def get_period_date_ranges(filters):
- from dateutil.relativedelta import relativedelta
- from_date = round_down_to_nearest_frequency(filters.from_date, filters.range)
- to_date = getdate(filters.to_date)
+ from dateutil.relativedelta import relativedelta
- increment = {
- "Monthly": 1,
- "Quarterly": 3,
- "Half-Yearly": 6,
- "Yearly": 12
- }.get(filters.range,1)
+ from_date = round_down_to_nearest_frequency(filters.from_date, filters.range)
+ to_date = getdate(filters.to_date)
- periodic_daterange = []
- for dummy in range(1, 53, increment):
- if filters.range == "Weekly":
- period_end_date = from_date + relativedelta(days=6)
- else:
- period_end_date = from_date + relativedelta(months=increment, days=-1)
+ increment = {"Monthly": 1, "Quarterly": 3, "Half-Yearly": 6, "Yearly": 12}.get(filters.range, 1)
- if period_end_date > to_date:
- period_end_date = to_date
- periodic_daterange.append([from_date, period_end_date])
+ periodic_daterange = []
+ for dummy in range(1, 53, increment):
+ if filters.range == "Weekly":
+ period_end_date = from_date + relativedelta(days=6)
+ else:
+ period_end_date = from_date + relativedelta(months=increment, days=-1)
- from_date = period_end_date + relativedelta(days=1)
- if period_end_date == to_date:
- break
+ if period_end_date > to_date:
+ period_end_date = to_date
+ periodic_daterange.append([from_date, period_end_date])
- return periodic_daterange
+ from_date = period_end_date + relativedelta(days=1)
+ if period_end_date == to_date:
+ break
+
+ return periodic_daterange
def round_down_to_nearest_frequency(date: str, frequency: str) -> datetime.datetime:
@@ -132,12 +112,12 @@
def get_period(posting_date, filters):
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
- if filters.range == 'Weekly':
+ if filters.range == "Weekly":
period = "Week " + str(posting_date.isocalendar()[1]) + " " + str(posting_date.year)
- elif filters.range == 'Monthly':
+ elif filters.range == "Monthly":
period = str(months[posting_date.month - 1]) + " " + str(posting_date.year)
- elif filters.range == 'Quarterly':
- period = "Quarter " + str(((posting_date.month-1)//3)+1) +" " + str(posting_date.year)
+ elif filters.range == "Quarterly":
+ period = "Quarter " + str(((posting_date.month - 1) // 3) + 1) + " " + str(posting_date.year)
else:
year = get_fiscal_year(posting_date, company=filters.company)
period = str(year[2])
@@ -147,26 +127,26 @@
def get_periodic_data(entry, filters):
"""Structured as:
- Item 1
- - Balance (updated and carried forward):
- - Warehouse A : bal_qty/value
- - Warehouse B : bal_qty/value
- - Jun 2021 (sum of warehouse quantities used in report)
- - Warehouse A : bal_qty/value
- - Warehouse B : bal_qty/value
- - Jul 2021 (sum of warehouse quantities used in report)
- - Warehouse A : bal_qty/value
- - Warehouse B : bal_qty/value
- Item 2
- - Balance (updated and carried forward):
- - Warehouse A : bal_qty/value
- - Warehouse B : bal_qty/value
- - Jun 2021 (sum of warehouse quantities used in report)
- - Warehouse A : bal_qty/value
- - Warehouse B : bal_qty/value
- - Jul 2021 (sum of warehouse quantities used in report)
- - Warehouse A : bal_qty/value
- - Warehouse B : bal_qty/value
+ Item 1
+ - Balance (updated and carried forward):
+ - Warehouse A : bal_qty/value
+ - Warehouse B : bal_qty/value
+ - Jun 2021 (sum of warehouse quantities used in report)
+ - Warehouse A : bal_qty/value
+ - Warehouse B : bal_qty/value
+ - Jul 2021 (sum of warehouse quantities used in report)
+ - Warehouse A : bal_qty/value
+ - Warehouse B : bal_qty/value
+ Item 2
+ - Balance (updated and carried forward):
+ - Warehouse A : bal_qty/value
+ - Warehouse B : bal_qty/value
+ - Jun 2021 (sum of warehouse quantities used in report)
+ - Warehouse A : bal_qty/value
+ - Warehouse B : bal_qty/value
+ - Jul 2021 (sum of warehouse quantities used in report)
+ - Warehouse A : bal_qty/value
+ - Warehouse B : bal_qty/value
"""
periodic_data = {}
for d in entry:
@@ -176,31 +156,36 @@
# if period against item does not exist yet, instantiate it
# insert existing balance dict against period, and add/subtract to it
if periodic_data.get(d.item_code) and not periodic_data.get(d.item_code).get(period):
- previous_balance = periodic_data[d.item_code]['balance'].copy()
+ previous_balance = periodic_data[d.item_code]["balance"].copy()
periodic_data[d.item_code][period] = previous_balance
if d.voucher_type == "Stock Reconciliation":
- if periodic_data.get(d.item_code) and periodic_data.get(d.item_code).get('balance').get(d.warehouse):
- bal_qty = periodic_data[d.item_code]['balance'][d.warehouse]
+ if periodic_data.get(d.item_code) and periodic_data.get(d.item_code).get("balance").get(
+ d.warehouse
+ ):
+ bal_qty = periodic_data[d.item_code]["balance"][d.warehouse]
qty_diff = d.qty_after_transaction - bal_qty
else:
qty_diff = d.actual_qty
- if filters["value_quantity"] == 'Quantity':
+ if filters["value_quantity"] == "Quantity":
value = qty_diff
else:
value = d.stock_value_difference
# period-warehouse wise balance
- periodic_data.setdefault(d.item_code, {}).setdefault('balance', {}).setdefault(d.warehouse, 0.0)
+ periodic_data.setdefault(d.item_code, {}).setdefault("balance", {}).setdefault(d.warehouse, 0.0)
periodic_data.setdefault(d.item_code, {}).setdefault(period, {}).setdefault(d.warehouse, 0.0)
- periodic_data[d.item_code]['balance'][d.warehouse] += value
- periodic_data[d.item_code][period][d.warehouse] = periodic_data[d.item_code]['balance'][d.warehouse]
+ periodic_data[d.item_code]["balance"][d.warehouse] += value
+ periodic_data[d.item_code][period][d.warehouse] = periodic_data[d.item_code]["balance"][
+ d.warehouse
+ ]
return periodic_data
+
def get_data(filters):
data = []
items = get_items(filters)
@@ -229,14 +214,10 @@
return data
+
def get_chart_data(columns):
labels = [d.get("label") for d in columns[5:]]
- chart = {
- "data": {
- 'labels': labels,
- 'datasets':[]
- }
- }
+ chart = {"data": {"labels": labels, "datasets": []}}
chart["type"] = "line"
return chart
diff --git a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py
index 6fd3fe7..99f820e 100644
--- a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py
+++ b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py
@@ -12,21 +12,25 @@
def execute(filters=None):
if not erpnext.is_perpetual_inventory_enabled(filters.company):
- frappe.throw(_("Perpetual inventory required for the company {0} to view this report.")
- .format(filters.company))
+ frappe.throw(
+ _("Perpetual inventory required for the company {0} to view this report.").format(
+ filters.company
+ )
+ )
data = get_data(filters)
columns = get_columns(filters)
return columns, data
+
def get_data(report_filters):
data = []
filters = {
"is_cancelled": 0,
"company": report_filters.company,
- "posting_date": ("<=", report_filters.as_on_date)
+ "posting_date": ("<=", report_filters.as_on_date),
}
currency_precision = get_currency_precision() or 2
@@ -43,18 +47,28 @@
return data
+
def get_stock_ledger_data(report_filters, filters):
if report_filters.account:
- warehouses = get_warehouses_based_on_account(report_filters.account,
- report_filters.company)
+ warehouses = get_warehouses_based_on_account(report_filters.account, report_filters.company)
filters["warehouse"] = ("in", warehouses)
- return frappe.get_all("Stock Ledger Entry", filters=filters,
- fields = ["name", "voucher_type", "voucher_no",
- "sum(stock_value_difference) as stock_value", "posting_date", "posting_time"],
- group_by = "voucher_type, voucher_no",
- order_by = "posting_date ASC, posting_time ASC")
+ return frappe.get_all(
+ "Stock Ledger Entry",
+ filters=filters,
+ fields=[
+ "name",
+ "voucher_type",
+ "voucher_no",
+ "sum(stock_value_difference) as stock_value",
+ "posting_date",
+ "posting_time",
+ ],
+ group_by="voucher_type, voucher_no",
+ order_by="posting_date ASC, posting_time ASC",
+ )
+
def get_gl_data(report_filters, filters):
if report_filters.account:
@@ -62,17 +76,22 @@
else:
stock_accounts = get_stock_accounts(report_filters.company)
- filters.update({
- "account": ("in", stock_accounts)
- })
+ filters.update({"account": ("in", stock_accounts)})
if filters.get("warehouse"):
del filters["warehouse"]
- gl_entries = frappe.get_all("GL Entry", filters=filters,
- fields = ["name", "voucher_type", "voucher_no",
- "sum(debit_in_account_currency) - sum(credit_in_account_currency) as account_value"],
- group_by = "voucher_type, voucher_no")
+ gl_entries = frappe.get_all(
+ "GL Entry",
+ filters=filters,
+ fields=[
+ "name",
+ "voucher_type",
+ "voucher_no",
+ "sum(debit_in_account_currency) - sum(credit_in_account_currency) as account_value",
+ ],
+ group_by="voucher_type, voucher_no",
+ )
voucher_wise_gl_data = {}
for d in gl_entries:
@@ -81,6 +100,7 @@
return voucher_wise_gl_data
+
def get_columns(filters):
return [
{
@@ -88,46 +108,29 @@
"fieldname": "name",
"fieldtype": "Link",
"options": "Stock Ledger Entry",
- "width": "80"
+ "width": "80",
},
- {
- "label": _("Posting Date"),
- "fieldname": "posting_date",
- "fieldtype": "Date"
- },
- {
- "label": _("Posting Time"),
- "fieldname": "posting_time",
- "fieldtype": "Time"
- },
- {
- "label": _("Voucher Type"),
- "fieldname": "voucher_type",
- "width": "110"
- },
+ {"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date"},
+ {"label": _("Posting Time"), "fieldname": "posting_time", "fieldtype": "Time"},
+ {"label": _("Voucher Type"), "fieldname": "voucher_type", "width": "110"},
{
"label": _("Voucher No"),
"fieldname": "voucher_no",
"fieldtype": "Dynamic Link",
"options": "voucher_type",
- "width": "110"
+ "width": "110",
},
- {
- "label": _("Stock Value"),
- "fieldname": "stock_value",
- "fieldtype": "Currency",
- "width": "120"
- },
+ {"label": _("Stock Value"), "fieldname": "stock_value", "fieldtype": "Currency", "width": "120"},
{
"label": _("Account Value"),
"fieldname": "account_value",
"fieldtype": "Currency",
- "width": "120"
+ "width": "120",
},
{
"label": _("Difference Value"),
"fieldname": "difference_value",
"fieldtype": "Currency",
- "width": "120"
- }
+ "width": "120",
+ },
]
diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py
index 24f47c1..afbc6fe 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.py
+++ b/erpnext/stock/report/stock_balance/stock_balance.py
@@ -16,9 +16,10 @@
def execute(filters=None):
is_reposting_item_valuation_in_progress()
- if not filters: filters = {}
+ if not filters:
+ filters = {}
- to_date = filters.get('to_date')
+ to_date = filters.get("to_date")
if filters.get("company"):
company_currency = erpnext.get_company_currency(filters.get("company"))
@@ -30,8 +31,8 @@
items = get_items(filters)
sle = get_stock_ledger_entries(filters, items)
- if filters.get('show_stock_ageing_data'):
- filters['show_warehouse_wise_stock'] = True
+ if filters.get("show_stock_ageing_data"):
+ filters["show_warehouse_wise_stock"] = True
item_wise_fifo_queue = FIFOSlots(filters, sle).generate()
# if no stock ledger entry found return
@@ -57,12 +58,12 @@
item_reorder_qty = item_reorder_detail_map[item + warehouse]["warehouse_reorder_qty"]
report_data = {
- 'currency': company_currency,
- 'item_code': item,
- 'warehouse': warehouse,
- 'company': company,
- 'reorder_level': item_reorder_level,
- 'reorder_qty': item_reorder_qty,
+ "currency": company_currency,
+ "item_code": item,
+ "warehouse": warehouse,
+ "company": company,
+ "reorder_level": item_reorder_level,
+ "reorder_qty": item_reorder_qty,
}
report_data.update(item_map[item])
report_data.update(qty_dict)
@@ -70,21 +71,18 @@
if include_uom:
conversion_factors.setdefault(item, item_map[item].conversion_factor)
- if filters.get('show_stock_ageing_data'):
- fifo_queue = item_wise_fifo_queue[(item, warehouse)].get('fifo_queue')
+ if filters.get("show_stock_ageing_data"):
+ fifo_queue = item_wise_fifo_queue[(item, warehouse)].get("fifo_queue")
- stock_ageing_data = {
- 'average_age': 0,
- 'earliest_age': 0,
- 'latest_age': 0
- }
+ stock_ageing_data = {"average_age": 0, "earliest_age": 0, "latest_age": 0}
if fifo_queue:
fifo_queue = sorted(filter(_func, fifo_queue), key=_func)
- if not fifo_queue: continue
+ if not fifo_queue:
+ continue
- stock_ageing_data['average_age'] = get_average_age(fifo_queue, to_date)
- stock_ageing_data['earliest_age'] = date_diff(to_date, fifo_queue[0][1])
- stock_ageing_data['latest_age'] = date_diff(to_date, fifo_queue[-1][1])
+ stock_ageing_data["average_age"] = get_average_age(fifo_queue, to_date)
+ stock_ageing_data["earliest_age"] = date_diff(to_date, fifo_queue[0][1])
+ stock_ageing_data["latest_age"] = date_diff(to_date, fifo_queue[-1][1])
report_data.update(stock_ageing_data)
@@ -93,38 +91,130 @@
add_additional_uom_columns(columns, data, include_uom, conversion_factors)
return columns, data
+
def get_columns(filters):
"""return columns"""
columns = [
- {"label": _("Item"), "fieldname": "item_code", "fieldtype": "Link", "options": "Item", "width": 100},
+ {
+ "label": _("Item"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 100,
+ },
{"label": _("Item Name"), "fieldname": "item_name", "width": 150},
- {"label": _("Item Group"), "fieldname": "item_group", "fieldtype": "Link", "options": "Item Group", "width": 100},
- {"label": _("Warehouse"), "fieldname": "warehouse", "fieldtype": "Link", "options": "Warehouse", "width": 100},
- {"label": _("Stock UOM"), "fieldname": "stock_uom", "fieldtype": "Link", "options": "UOM", "width": 90},
- {"label": _("Balance Qty"), "fieldname": "bal_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
- {"label": _("Balance Value"), "fieldname": "bal_val", "fieldtype": "Currency", "width": 100, "options": "currency"},
- {"label": _("Opening Qty"), "fieldname": "opening_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
- {"label": _("Opening Value"), "fieldname": "opening_val", "fieldtype": "Currency", "width": 110, "options": "currency"},
- {"label": _("In Qty"), "fieldname": "in_qty", "fieldtype": "Float", "width": 80, "convertible": "qty"},
+ {
+ "label": _("Item Group"),
+ "fieldname": "item_group",
+ "fieldtype": "Link",
+ "options": "Item Group",
+ "width": 100,
+ },
+ {
+ "label": _("Warehouse"),
+ "fieldname": "warehouse",
+ "fieldtype": "Link",
+ "options": "Warehouse",
+ "width": 100,
+ },
+ {
+ "label": _("Stock UOM"),
+ "fieldname": "stock_uom",
+ "fieldtype": "Link",
+ "options": "UOM",
+ "width": 90,
+ },
+ {
+ "label": _("Balance Qty"),
+ "fieldname": "bal_qty",
+ "fieldtype": "Float",
+ "width": 100,
+ "convertible": "qty",
+ },
+ {
+ "label": _("Balance Value"),
+ "fieldname": "bal_val",
+ "fieldtype": "Currency",
+ "width": 100,
+ "options": "currency",
+ },
+ {
+ "label": _("Opening Qty"),
+ "fieldname": "opening_qty",
+ "fieldtype": "Float",
+ "width": 100,
+ "convertible": "qty",
+ },
+ {
+ "label": _("Opening Value"),
+ "fieldname": "opening_val",
+ "fieldtype": "Currency",
+ "width": 110,
+ "options": "currency",
+ },
+ {
+ "label": _("In Qty"),
+ "fieldname": "in_qty",
+ "fieldtype": "Float",
+ "width": 80,
+ "convertible": "qty",
+ },
{"label": _("In Value"), "fieldname": "in_val", "fieldtype": "Float", "width": 80},
- {"label": _("Out Qty"), "fieldname": "out_qty", "fieldtype": "Float", "width": 80, "convertible": "qty"},
+ {
+ "label": _("Out Qty"),
+ "fieldname": "out_qty",
+ "fieldtype": "Float",
+ "width": 80,
+ "convertible": "qty",
+ },
{"label": _("Out Value"), "fieldname": "out_val", "fieldtype": "Float", "width": 80},
- {"label": _("Valuation Rate"), "fieldname": "val_rate", "fieldtype": "Currency", "width": 90, "convertible": "rate", "options": "currency"},
- {"label": _("Reorder Level"), "fieldname": "reorder_level", "fieldtype": "Float", "width": 80, "convertible": "qty"},
- {"label": _("Reorder Qty"), "fieldname": "reorder_qty", "fieldtype": "Float", "width": 80, "convertible": "qty"},
- {"label": _("Company"), "fieldname": "company", "fieldtype": "Link", "options": "Company", "width": 100}
+ {
+ "label": _("Valuation Rate"),
+ "fieldname": "val_rate",
+ "fieldtype": "Currency",
+ "width": 90,
+ "convertible": "rate",
+ "options": "currency",
+ },
+ {
+ "label": _("Reorder Level"),
+ "fieldname": "reorder_level",
+ "fieldtype": "Float",
+ "width": 80,
+ "convertible": "qty",
+ },
+ {
+ "label": _("Reorder Qty"),
+ "fieldname": "reorder_qty",
+ "fieldtype": "Float",
+ "width": 80,
+ "convertible": "qty",
+ },
+ {
+ "label": _("Company"),
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "options": "Company",
+ "width": 100,
+ },
]
- if filters.get('show_stock_ageing_data'):
- columns += [{'label': _('Average Age'), 'fieldname': 'average_age', 'width': 100},
- {'label': _('Earliest Age'), 'fieldname': 'earliest_age', 'width': 100},
- {'label': _('Latest Age'), 'fieldname': 'latest_age', 'width': 100}]
+ if filters.get("show_stock_ageing_data"):
+ columns += [
+ {"label": _("Average Age"), "fieldname": "average_age", "width": 100},
+ {"label": _("Earliest Age"), "fieldname": "earliest_age", "width": 100},
+ {"label": _("Latest Age"), "fieldname": "latest_age", "width": 100},
+ ]
- if filters.get('show_variant_attributes'):
- columns += [{'label': att_name, 'fieldname': att_name, 'width': 100} for att_name in get_variants_attributes()]
+ if filters.get("show_variant_attributes"):
+ columns += [
+ {"label": att_name, "fieldname": att_name, "width": 100}
+ for att_name in get_variants_attributes()
+ ]
return columns
+
def get_conditions(filters):
conditions = ""
if not filters.get("from_date"):
@@ -139,28 +229,37 @@
conditions += " and sle.company = %s" % frappe.db.escape(filters.get("company"))
if filters.get("warehouse"):
- warehouse_details = frappe.db.get_value("Warehouse",
- filters.get("warehouse"), ["lft", "rgt"], as_dict=1)
+ warehouse_details = frappe.db.get_value(
+ "Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1
+ )
if warehouse_details:
- conditions += " and exists (select name from `tabWarehouse` wh \
- where wh.lft >= %s and wh.rgt <= %s and sle.warehouse = wh.name)"%(warehouse_details.lft,
- warehouse_details.rgt)
+ conditions += (
+ " and exists (select name from `tabWarehouse` wh \
+ where wh.lft >= %s and wh.rgt <= %s and sle.warehouse = wh.name)"
+ % (warehouse_details.lft, warehouse_details.rgt)
+ )
if filters.get("warehouse_type") and not filters.get("warehouse"):
- conditions += " and exists (select name from `tabWarehouse` wh \
- where wh.warehouse_type = '%s' and sle.warehouse = wh.name)"%(filters.get("warehouse_type"))
+ conditions += (
+ " and exists (select name from `tabWarehouse` wh \
+ where wh.warehouse_type = '%s' and sle.warehouse = wh.name)"
+ % (filters.get("warehouse_type"))
+ )
return conditions
+
def get_stock_ledger_entries(filters, items):
- item_conditions_sql = ''
+ item_conditions_sql = ""
if items:
- item_conditions_sql = ' and sle.item_code in ({})'\
- .format(', '.join(frappe.db.escape(i, percent=False) for i in items))
+ item_conditions_sql = " and sle.item_code in ({})".format(
+ ", ".join(frappe.db.escape(i, percent=False) for i in items)
+ )
conditions = get_conditions(filters)
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
select
sle.item_code, warehouse, sle.posting_date, sle.actual_qty, sle.valuation_rate,
sle.company, sle.voucher_type, sle.qty_after_transaction, sle.stock_value_difference,
@@ -169,8 +268,11 @@
`tabStock Ledger Entry` sle
where sle.docstatus < 2 %s %s
and is_cancelled = 0
- order by sle.posting_date, sle.posting_time, sle.creation, sle.actual_qty""" % #nosec
- (item_conditions_sql, conditions), as_dict=1)
+ order by sle.posting_date, sle.posting_time, sle.creation, sle.actual_qty"""
+ % (item_conditions_sql, conditions), # nosec
+ as_dict=1,
+ )
+
def get_item_warehouse_map(filters, sle):
iwb_map = {}
@@ -182,13 +284,19 @@
for d in sle:
key = (d.company, d.item_code, d.warehouse)
if key not in iwb_map:
- iwb_map[key] = frappe._dict({
- "opening_qty": 0.0, "opening_val": 0.0,
- "in_qty": 0.0, "in_val": 0.0,
- "out_qty": 0.0, "out_val": 0.0,
- "bal_qty": 0.0, "bal_val": 0.0,
- "val_rate": 0.0
- })
+ iwb_map[key] = frappe._dict(
+ {
+ "opening_qty": 0.0,
+ "opening_val": 0.0,
+ "in_qty": 0.0,
+ "in_val": 0.0,
+ "out_qty": 0.0,
+ "out_val": 0.0,
+ "bal_qty": 0.0,
+ "bal_val": 0.0,
+ "val_rate": 0.0,
+ }
+ )
qty_dict = iwb_map[(d.company, d.item_code, d.warehouse)]
@@ -199,9 +307,11 @@
value_diff = flt(d.stock_value_difference)
- if d.posting_date < from_date or (d.posting_date == from_date
- and d.voucher_type == "Stock Reconciliation" and
- frappe.db.get_value("Stock Reconciliation", d.voucher_no, "purpose") == "Opening Stock"):
+ if d.posting_date < from_date or (
+ d.posting_date == from_date
+ and d.voucher_type == "Stock Reconciliation"
+ and frappe.db.get_value("Stock Reconciliation", d.voucher_no, "purpose") == "Opening Stock"
+ ):
qty_dict.opening_qty += qty_diff
qty_dict.opening_val += value_diff
@@ -221,6 +331,7 @@
return iwb_map
+
def filter_items_with_no_transactions(iwb_map, float_precision):
for (company, item, warehouse) in sorted(iwb_map):
qty_dict = iwb_map[(company, item, warehouse)]
@@ -237,6 +348,7 @@
return iwb_map
+
def get_items(filters):
"Get items based on item code, item group or brand."
conditions = []
@@ -245,15 +357,17 @@
else:
if filters.get("item_group"):
conditions.append(get_item_group_condition(filters.get("item_group")))
- if filters.get("brand"): # used in stock analytics report
+ if filters.get("brand"): # used in stock analytics report
conditions.append("item.brand=%(brand)s")
items = []
if conditions:
- items = frappe.db.sql_list("""select name from `tabItem` item where {}"""
- .format(" and ".join(conditions)), filters)
+ items = frappe.db.sql_list(
+ """select name from `tabItem` item where {}""".format(" and ".join(conditions)), filters
+ )
return items
+
def get_item_details(items, sle, filters):
item_details = {}
if not items:
@@ -265,10 +379,13 @@
cf_field = cf_join = ""
if filters.get("include_uom"):
cf_field = ", ucd.conversion_factor"
- cf_join = "left join `tabUOM Conversion Detail` ucd on ucd.parent=item.name and ucd.uom=%s" \
+ cf_join = (
+ "left join `tabUOM Conversion Detail` ucd on ucd.parent=item.name and ucd.uom=%s"
% frappe.db.escape(filters.get("include_uom"))
+ )
- res = frappe.db.sql("""
+ res = frappe.db.sql(
+ """
select
item.name, item.item_name, item.description, item.item_group, item.brand, item.stock_uom %s
from
@@ -276,40 +393,57 @@
%s
where
item.name in (%s)
- """ % (cf_field, cf_join, ','.join(['%s'] *len(items))), items, as_dict=1)
+ """
+ % (cf_field, cf_join, ",".join(["%s"] * len(items))),
+ items,
+ as_dict=1,
+ )
for item in res:
item_details.setdefault(item.name, item)
- if filters.get('show_variant_attributes', 0) == 1:
+ if filters.get("show_variant_attributes", 0) == 1:
variant_values = get_variant_values_for(list(item_details))
item_details = {k: v.update(variant_values.get(k, {})) for k, v in item_details.items()}
return item_details
+
def get_item_reorder_details(items):
item_reorder_details = frappe._dict()
if items:
- item_reorder_details = frappe.db.sql("""
+ item_reorder_details = frappe.db.sql(
+ """
select parent, warehouse, warehouse_reorder_qty, warehouse_reorder_level
from `tabItem Reorder`
where parent in ({0})
- """.format(', '.join(frappe.db.escape(i, percent=False) for i in items)), as_dict=1)
+ """.format(
+ ", ".join(frappe.db.escape(i, percent=False) for i in items)
+ ),
+ as_dict=1,
+ )
return dict((d.parent + d.warehouse, d) for d in item_reorder_details)
+
def get_variants_attributes():
- '''Return all item variant attributes.'''
- return [i.name for i in frappe.get_all('Item Attribute')]
+ """Return all item variant attributes."""
+ return [i.name for i in frappe.get_all("Item Attribute")]
+
def get_variant_values_for(items):
- '''Returns variant values for items.'''
+ """Returns variant values for items."""
attribute_map = {}
- for attr in frappe.db.sql('''select parent, attribute, attribute_value
+ for attr in frappe.db.sql(
+ """select parent, attribute, attribute_value
from `tabItem Variant Attribute` where parent in (%s)
- ''' % ", ".join(["%s"] * len(items)), tuple(items), as_dict=1):
- attribute_map.setdefault(attr['parent'], {})
- attribute_map[attr['parent']].update({attr['attribute']: attr['attribute_value']})
+ """
+ % ", ".join(["%s"] * len(items)),
+ tuple(items),
+ as_dict=1,
+ ):
+ attribute_map.setdefault(attr["parent"], {})
+ attribute_map[attr["parent"]].update({attr["attribute"]: attr["attribute_value"]})
return attribute_map
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py
index 9fde47e..409e238 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.py
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.py
@@ -42,19 +42,13 @@
actual_qty += flt(sle.actual_qty, precision)
stock_value += sle.stock_value_difference
- if sle.voucher_type == 'Stock Reconciliation' and not sle.actual_qty:
+ if sle.voucher_type == "Stock Reconciliation" and not sle.actual_qty:
actual_qty = sle.qty_after_transaction
stock_value = sle.stock_value
- sle.update({
- "qty_after_transaction": actual_qty,
- "stock_value": stock_value
- })
+ sle.update({"qty_after_transaction": actual_qty, "stock_value": stock_value})
- sle.update({
- "in_qty": max(sle.actual_qty, 0),
- "out_qty": min(sle.actual_qty, 0)
- })
+ sle.update({"in_qty": max(sle.actual_qty, 0), "out_qty": min(sle.actual_qty, 0)})
if sle.serial_no:
update_available_serial_nos(available_serial_nos, sle)
@@ -67,13 +61,15 @@
update_included_uom_in_report(columns, data, include_uom, conversion_factors)
return columns, data
+
def update_available_serial_nos(available_serial_nos, sle):
serial_nos = get_serial_nos(sle.serial_no)
key = (sle.item_code, sle.warehouse)
if key not in available_serial_nos:
- stock_balance = get_stock_balance_for(sle.item_code, sle.warehouse, sle.date.split(' ')[0],
- sle.date.split(' ')[1])
- serials = get_serial_nos(stock_balance['serial_nos']) if stock_balance['serial_nos'] else []
+ stock_balance = get_stock_balance_for(
+ sle.item_code, sle.warehouse, sle.date.split(" ")[0], sle.date.split(" ")[1]
+ )
+ serials = get_serial_nos(stock_balance["serial_nos"]) if stock_balance["serial_nos"] else []
available_serial_nos.setdefault(key, serials)
existing_serial_no = available_serial_nos[key]
@@ -89,45 +85,158 @@
else:
existing_serial_no.append(sn)
- sle.balance_serial_no = '\n'.join(existing_serial_no)
+ sle.balance_serial_no = "\n".join(existing_serial_no)
+
def get_columns():
columns = [
{"label": _("Date"), "fieldname": "date", "fieldtype": "Datetime", "width": 150},
- {"label": _("Item"), "fieldname": "item_code", "fieldtype": "Link", "options": "Item", "width": 100},
+ {
+ "label": _("Item"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 100,
+ },
{"label": _("Item Name"), "fieldname": "item_name", "width": 100},
- {"label": _("Stock UOM"), "fieldname": "stock_uom", "fieldtype": "Link", "options": "UOM", "width": 90},
- {"label": _("In Qty"), "fieldname": "in_qty", "fieldtype": "Float", "width": 80, "convertible": "qty"},
- {"label": _("Out Qty"), "fieldname": "out_qty", "fieldtype": "Float", "width": 80, "convertible": "qty"},
- {"label": _("Balance Qty"), "fieldname": "qty_after_transaction", "fieldtype": "Float", "width": 100, "convertible": "qty"},
- {"label": _("Voucher #"), "fieldname": "voucher_no", "fieldtype": "Dynamic Link", "options": "voucher_type", "width": 150},
- {"label": _("Warehouse"), "fieldname": "warehouse", "fieldtype": "Link", "options": "Warehouse", "width": 150},
- {"label": _("Item Group"), "fieldname": "item_group", "fieldtype": "Link", "options": "Item Group", "width": 100},
- {"label": _("Brand"), "fieldname": "brand", "fieldtype": "Link", "options": "Brand", "width": 100},
+ {
+ "label": _("Stock UOM"),
+ "fieldname": "stock_uom",
+ "fieldtype": "Link",
+ "options": "UOM",
+ "width": 90,
+ },
+ {
+ "label": _("In Qty"),
+ "fieldname": "in_qty",
+ "fieldtype": "Float",
+ "width": 80,
+ "convertible": "qty",
+ },
+ {
+ "label": _("Out Qty"),
+ "fieldname": "out_qty",
+ "fieldtype": "Float",
+ "width": 80,
+ "convertible": "qty",
+ },
+ {
+ "label": _("Balance Qty"),
+ "fieldname": "qty_after_transaction",
+ "fieldtype": "Float",
+ "width": 100,
+ "convertible": "qty",
+ },
+ {
+ "label": _("Voucher #"),
+ "fieldname": "voucher_no",
+ "fieldtype": "Dynamic Link",
+ "options": "voucher_type",
+ "width": 150,
+ },
+ {
+ "label": _("Warehouse"),
+ "fieldname": "warehouse",
+ "fieldtype": "Link",
+ "options": "Warehouse",
+ "width": 150,
+ },
+ {
+ "label": _("Item Group"),
+ "fieldname": "item_group",
+ "fieldtype": "Link",
+ "options": "Item Group",
+ "width": 100,
+ },
+ {
+ "label": _("Brand"),
+ "fieldname": "brand",
+ "fieldtype": "Link",
+ "options": "Brand",
+ "width": 100,
+ },
{"label": _("Description"), "fieldname": "description", "width": 200},
- {"label": _("Incoming Rate"), "fieldname": "incoming_rate", "fieldtype": "Currency", "width": 110, "options": "Company:company:default_currency", "convertible": "rate"},
- {"label": _("Valuation Rate"), "fieldname": "valuation_rate", "fieldtype": "Currency", "width": 110, "options": "Company:company:default_currency", "convertible": "rate"},
- {"label": _("Balance Value"), "fieldname": "stock_value", "fieldtype": "Currency", "width": 110, "options": "Company:company:default_currency"},
- {"label": _("Value Change"), "fieldname": "stock_value_difference", "fieldtype": "Currency", "width": 110, "options": "Company:company:default_currency"},
+ {
+ "label": _("Incoming Rate"),
+ "fieldname": "incoming_rate",
+ "fieldtype": "Currency",
+ "width": 110,
+ "options": "Company:company:default_currency",
+ "convertible": "rate",
+ },
+ {
+ "label": _("Valuation Rate"),
+ "fieldname": "valuation_rate",
+ "fieldtype": "Currency",
+ "width": 110,
+ "options": "Company:company:default_currency",
+ "convertible": "rate",
+ },
+ {
+ "label": _("Balance Value"),
+ "fieldname": "stock_value",
+ "fieldtype": "Currency",
+ "width": 110,
+ "options": "Company:company:default_currency",
+ },
+ {
+ "label": _("Value Change"),
+ "fieldname": "stock_value_difference",
+ "fieldtype": "Currency",
+ "width": 110,
+ "options": "Company:company:default_currency",
+ },
{"label": _("Voucher Type"), "fieldname": "voucher_type", "width": 110},
- {"label": _("Voucher #"), "fieldname": "voucher_no", "fieldtype": "Dynamic Link", "options": "voucher_type", "width": 100},
- {"label": _("Batch"), "fieldname": "batch_no", "fieldtype": "Link", "options": "Batch", "width": 100},
- {"label": _("Serial No"), "fieldname": "serial_no", "fieldtype": "Link", "options": "Serial No", "width": 100},
+ {
+ "label": _("Voucher #"),
+ "fieldname": "voucher_no",
+ "fieldtype": "Dynamic Link",
+ "options": "voucher_type",
+ "width": 100,
+ },
+ {
+ "label": _("Batch"),
+ "fieldname": "batch_no",
+ "fieldtype": "Link",
+ "options": "Batch",
+ "width": 100,
+ },
+ {
+ "label": _("Serial No"),
+ "fieldname": "serial_no",
+ "fieldtype": "Link",
+ "options": "Serial No",
+ "width": 100,
+ },
{"label": _("Balance Serial No"), "fieldname": "balance_serial_no", "width": 100},
- {"label": _("Project"), "fieldname": "project", "fieldtype": "Link", "options": "Project", "width": 100},
- {"label": _("Company"), "fieldname": "company", "fieldtype": "Link", "options": "Company", "width": 110}
+ {
+ "label": _("Project"),
+ "fieldname": "project",
+ "fieldtype": "Link",
+ "options": "Project",
+ "width": 100,
+ },
+ {
+ "label": _("Company"),
+ "fieldname": "company",
+ "fieldtype": "Link",
+ "options": "Company",
+ "width": 110,
+ },
]
return columns
def get_stock_ledger_entries(filters, items):
- item_conditions_sql = ''
+ item_conditions_sql = ""
if items:
- item_conditions_sql = 'and sle.item_code in ({})'\
- .format(', '.join(frappe.db.escape(i) for i in items))
+ item_conditions_sql = "and sle.item_code in ({})".format(
+ ", ".join(frappe.db.escape(i) for i in items)
+ )
- sl_entries = frappe.db.sql("""
+ sl_entries = frappe.db.sql(
+ """
SELECT
concat_ws(" ", posting_date, posting_time) AS date,
item_code,
@@ -153,8 +262,12 @@
{item_conditions_sql}
ORDER BY
posting_date asc, posting_time asc, creation asc
- """.format(sle_conditions=get_sle_conditions(filters), item_conditions_sql=item_conditions_sql),
- filters, as_dict=1)
+ """.format(
+ sle_conditions=get_sle_conditions(filters), item_conditions_sql=item_conditions_sql
+ ),
+ filters,
+ as_dict=1,
+ )
return sl_entries
@@ -171,8 +284,9 @@
items = []
if conditions:
- items = frappe.db.sql_list("""select name from `tabItem` item where {}"""
- .format(" and ".join(conditions)), filters)
+ items = frappe.db.sql_list(
+ """select name from `tabItem` item where {}""".format(" and ".join(conditions)), filters
+ )
return items
@@ -187,10 +301,13 @@
cf_field = cf_join = ""
if include_uom:
cf_field = ", ucd.conversion_factor"
- cf_join = "left join `tabUOM Conversion Detail` ucd on ucd.parent=item.name and ucd.uom=%s" \
+ cf_join = (
+ "left join `tabUOM Conversion Detail` ucd on ucd.parent=item.name and ucd.uom=%s"
% frappe.db.escape(include_uom)
+ )
- res = frappe.db.sql("""
+ res = frappe.db.sql(
+ """
select
item.name, item.item_name, item.description, item.item_group, item.brand, item.stock_uom {cf_field}
from
@@ -198,7 +315,12 @@
{cf_join}
where
item.name in ({item_codes})
- """.format(cf_field=cf_field, cf_join=cf_join, item_codes=','.join(['%s'] *len(items))), items, as_dict=1)
+ """.format(
+ cf_field=cf_field, cf_join=cf_join, item_codes=",".join(["%s"] * len(items))
+ ),
+ items,
+ as_dict=1,
+ )
for item in res:
item_details.setdefault(item.name, item)
@@ -227,16 +349,20 @@
return
from erpnext.stock.stock_ledger import get_previous_sle
- last_entry = get_previous_sle({
- "item_code": filters.item_code,
- "warehouse_condition": get_warehouse_condition(filters.warehouse),
- "posting_date": filters.from_date,
- "posting_time": "00:00:00"
- })
+
+ last_entry = get_previous_sle(
+ {
+ "item_code": filters.item_code,
+ "warehouse_condition": get_warehouse_condition(filters.warehouse),
+ "posting_date": filters.from_date,
+ "posting_time": "00:00:00",
+ }
+ )
# check if any SLEs are actually Opening Stock Reconciliation
for sle in sl_entries:
- if (sle.get("voucher_type") == "Stock Reconciliation"
+ if (
+ sle.get("voucher_type") == "Stock Reconciliation"
and sle.get("date").split()[0] == filters.from_date
and frappe.db.get_value("Stock Reconciliation", sle.voucher_no, "purpose") == "Opening Stock"
):
@@ -247,7 +373,7 @@
"item_code": _("'Opening'"),
"qty_after_transaction": last_entry.get("qty_after_transaction", 0),
"valuation_rate": last_entry.get("valuation_rate", 0),
- "stock_value": last_entry.get("stock_value", 0)
+ "stock_value": last_entry.get("stock_value", 0),
}
return row
@@ -256,18 +382,22 @@
def get_warehouse_condition(warehouse):
warehouse_details = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"], as_dict=1)
if warehouse_details:
- return " exists (select name from `tabWarehouse` wh \
- where wh.lft >= %s and wh.rgt <= %s and warehouse = wh.name)"%(warehouse_details.lft,
- warehouse_details.rgt)
+ return (
+ " exists (select name from `tabWarehouse` wh \
+ where wh.lft >= %s and wh.rgt <= %s and warehouse = wh.name)"
+ % (warehouse_details.lft, warehouse_details.rgt)
+ )
- return ''
+ return ""
def get_item_group_condition(item_group):
item_group_details = frappe.db.get_value("Item Group", item_group, ["lft", "rgt"], as_dict=1)
if item_group_details:
- return "item.item_group in (select ig.name from `tabItem Group` ig \
- where ig.lft >= %s and ig.rgt <= %s and item.item_group = ig.name)"%(item_group_details.lft,
- item_group_details.rgt)
+ return (
+ "item.item_group in (select ig.name from `tabItem Group` ig \
+ where ig.lft >= %s and ig.rgt <= %s and item.item_group = ig.name)"
+ % (item_group_details.lft, item_group_details.rgt)
+ )
- return ''
+ return ""
diff --git a/erpnext/stock/report/stock_ledger/test_stock_ledger_report.py b/erpnext/stock/report/stock_ledger/test_stock_ledger_report.py
index 163b205..f93bd66 100644
--- a/erpnext/stock/report/stock_ledger/test_stock_ledger_report.py
+++ b/erpnext/stock/report/stock_ledger/test_stock_ledger_report.py
@@ -17,8 +17,10 @@
def setUp(self) -> None:
make_serial_item_with_serial("_Test Stock Report Serial Item")
self.filters = frappe._dict(
- company="_Test Company", from_date=today(), to_date=add_days(today(), 30),
- item_code="_Test Stock Report Serial Item"
+ company="_Test Company",
+ from_date=today(),
+ to_date=add_days(today(), 30),
+ item_code="_Test Stock Report Serial Item",
)
def tearDown(self) -> None:
@@ -38,4 +40,3 @@
self.assertEqual(data[0].out_qty, -1)
self.assertEqual(data[0].serial_no, serials_added[1])
self.assertEqual(data[0].balance_serial_no, serials_added[0])
-
diff --git a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py
index 1ba2482..6cc9061 100644
--- a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py
+++ b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py
@@ -40,11 +40,7 @@
return frappe.get_all(
"Stock Ledger Entry",
fields=SLE_FIELDS,
- filters={
- "item_code": filters.item_code,
- "warehouse": filters.warehouse,
- "is_cancelled": 0
- },
+ filters={"item_code": filters.item_code, "warehouse": filters.warehouse, "is_cancelled": 0},
order_by="timestamp(posting_date, posting_time), creation",
)
@@ -62,7 +58,7 @@
fifo_value += qty * rate
if sle.actual_qty < 0:
- sle.consumption_rate = sle.stock_value_difference / sle.actual_qty
+ sle.consumption_rate = sle.stock_value_difference / sle.actual_qty
balance_qty += sle.actual_qty
balance_stock_value += sle.stock_value_difference
@@ -90,14 +86,16 @@
sle.valuation_diff = (
sle.valuation_rate - sle.balance_value_by_qty if sle.balance_value_by_qty else None
)
- sle.diff_value_diff = sle.stock_value_from_diff - sle.stock_value
+ sle.diff_value_diff = sle.stock_value_from_diff - sle.stock_value
if idx > 0:
sle.fifo_stock_diff = sle.fifo_stock_value - sles[idx - 1].fifo_stock_value
sle.fifo_difference_diff = sle.fifo_stock_diff - sle.stock_value_difference
if sle.batch_no:
- sle.use_batchwise_valuation = frappe.db.get_value("Batch", sle.batch_no, "use_batchwise_valuation", cache=True)
+ sle.use_batchwise_valuation = frappe.db.get_value(
+ "Batch", sle.batch_no, "use_batchwise_valuation", cache=True
+ )
return sles
@@ -183,7 +181,6 @@
"fieldtype": "Data",
"label": "FIFO/LIFO Queue",
},
-
{
"fieldname": "fifo_queue_qty",
"fieldtype": "Float",
@@ -244,7 +241,6 @@
"fieldtype": "Float",
"label": "(I) Valuation Rate as per FIFO",
},
-
{
"fieldname": "fifo_valuation_diff",
"fieldtype": "Float",
diff --git a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
index a28b752..49e797d 100644
--- a/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
+++ b/erpnext/stock/report/stock_projected_qty/stock_projected_qty.py
@@ -32,8 +32,9 @@
continue
# item = item_map.setdefault(bin.item_code, get_item(bin.item_code))
- company = warehouse_company.setdefault(bin.warehouse,
- frappe.db.get_value("Warehouse", bin.warehouse, "company"))
+ company = warehouse_company.setdefault(
+ bin.warehouse, frappe.db.get_value("Warehouse", bin.warehouse, "company")
+ )
if filters.brand and filters.brand != item.brand:
continue
@@ -59,10 +60,29 @@
if reserved_qty_for_pos:
bin.projected_qty -= reserved_qty_for_pos
- data.append([item.name, item.item_name, item.description, item.item_group, item.brand, bin.warehouse,
- item.stock_uom, bin.actual_qty, bin.planned_qty, bin.indented_qty, bin.ordered_qty,
- bin.reserved_qty, bin.reserved_qty_for_production, bin.reserved_qty_for_sub_contract, reserved_qty_for_pos,
- bin.projected_qty, re_order_level, re_order_qty, shortage_qty])
+ data.append(
+ [
+ item.name,
+ item.item_name,
+ item.description,
+ item.item_group,
+ item.brand,
+ bin.warehouse,
+ item.stock_uom,
+ bin.actual_qty,
+ bin.planned_qty,
+ bin.indented_qty,
+ bin.ordered_qty,
+ bin.reserved_qty,
+ bin.reserved_qty_for_production,
+ bin.reserved_qty_for_sub_contract,
+ reserved_qty_for_pos,
+ bin.projected_qty,
+ re_order_level,
+ re_order_qty,
+ shortage_qty,
+ ]
+ )
if include_uom:
conversion_factors.append(item.conversion_factor)
@@ -70,66 +90,180 @@
update_included_uom_in_report(columns, data, include_uom, conversion_factors)
return columns, data
+
def get_columns():
return [
- {"label": _("Item Code"), "fieldname": "item_code", "fieldtype": "Link", "options": "Item", "width": 140},
+ {
+ "label": _("Item Code"),
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item",
+ "width": 140,
+ },
{"label": _("Item Name"), "fieldname": "item_name", "width": 100},
{"label": _("Description"), "fieldname": "description", "width": 200},
- {"label": _("Item Group"), "fieldname": "item_group", "fieldtype": "Link", "options": "Item Group", "width": 100},
- {"label": _("Brand"), "fieldname": "brand", "fieldtype": "Link", "options": "Brand", "width": 100},
- {"label": _("Warehouse"), "fieldname": "warehouse", "fieldtype": "Link", "options": "Warehouse", "width": 120},
- {"label": _("UOM"), "fieldname": "stock_uom", "fieldtype": "Link", "options": "UOM", "width": 100},
- {"label": _("Actual Qty"), "fieldname": "actual_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
- {"label": _("Planned Qty"), "fieldname": "planned_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
- {"label": _("Requested Qty"), "fieldname": "indented_qty", "fieldtype": "Float", "width": 110, "convertible": "qty"},
- {"label": _("Ordered Qty"), "fieldname": "ordered_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
- {"label": _("Reserved Qty"), "fieldname": "reserved_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
- {"label": _("Reserved for Production"), "fieldname": "reserved_qty_for_production", "fieldtype": "Float",
- "width": 100, "convertible": "qty"},
- {"label": _("Reserved for Sub Contracting"), "fieldname": "reserved_qty_for_sub_contract", "fieldtype": "Float",
- "width": 100, "convertible": "qty"},
- {"label": _("Reserved for POS Transactions"), "fieldname": "reserved_qty_for_pos", "fieldtype": "Float",
- "width": 100, "convertible": "qty"},
- {"label": _("Projected Qty"), "fieldname": "projected_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
- {"label": _("Reorder Level"), "fieldname": "re_order_level", "fieldtype": "Float", "width": 100, "convertible": "qty"},
- {"label": _("Reorder Qty"), "fieldname": "re_order_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"},
- {"label": _("Shortage Qty"), "fieldname": "shortage_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"}
+ {
+ "label": _("Item Group"),
+ "fieldname": "item_group",
+ "fieldtype": "Link",
+ "options": "Item Group",
+ "width": 100,
+ },
+ {
+ "label": _("Brand"),
+ "fieldname": "brand",
+ "fieldtype": "Link",
+ "options": "Brand",
+ "width": 100,
+ },
+ {
+ "label": _("Warehouse"),
+ "fieldname": "warehouse",
+ "fieldtype": "Link",
+ "options": "Warehouse",
+ "width": 120,
+ },
+ {
+ "label": _("UOM"),
+ "fieldname": "stock_uom",
+ "fieldtype": "Link",
+ "options": "UOM",
+ "width": 100,
+ },
+ {
+ "label": _("Actual Qty"),
+ "fieldname": "actual_qty",
+ "fieldtype": "Float",
+ "width": 100,
+ "convertible": "qty",
+ },
+ {
+ "label": _("Planned Qty"),
+ "fieldname": "planned_qty",
+ "fieldtype": "Float",
+ "width": 100,
+ "convertible": "qty",
+ },
+ {
+ "label": _("Requested Qty"),
+ "fieldname": "indented_qty",
+ "fieldtype": "Float",
+ "width": 110,
+ "convertible": "qty",
+ },
+ {
+ "label": _("Ordered Qty"),
+ "fieldname": "ordered_qty",
+ "fieldtype": "Float",
+ "width": 100,
+ "convertible": "qty",
+ },
+ {
+ "label": _("Reserved Qty"),
+ "fieldname": "reserved_qty",
+ "fieldtype": "Float",
+ "width": 100,
+ "convertible": "qty",
+ },
+ {
+ "label": _("Reserved for Production"),
+ "fieldname": "reserved_qty_for_production",
+ "fieldtype": "Float",
+ "width": 100,
+ "convertible": "qty",
+ },
+ {
+ "label": _("Reserved for Sub Contracting"),
+ "fieldname": "reserved_qty_for_sub_contract",
+ "fieldtype": "Float",
+ "width": 100,
+ "convertible": "qty",
+ },
+ {
+ "label": _("Reserved for POS Transactions"),
+ "fieldname": "reserved_qty_for_pos",
+ "fieldtype": "Float",
+ "width": 100,
+ "convertible": "qty",
+ },
+ {
+ "label": _("Projected Qty"),
+ "fieldname": "projected_qty",
+ "fieldtype": "Float",
+ "width": 100,
+ "convertible": "qty",
+ },
+ {
+ "label": _("Reorder Level"),
+ "fieldname": "re_order_level",
+ "fieldtype": "Float",
+ "width": 100,
+ "convertible": "qty",
+ },
+ {
+ "label": _("Reorder Qty"),
+ "fieldname": "re_order_qty",
+ "fieldtype": "Float",
+ "width": 100,
+ "convertible": "qty",
+ },
+ {
+ "label": _("Shortage Qty"),
+ "fieldname": "shortage_qty",
+ "fieldtype": "Float",
+ "width": 100,
+ "convertible": "qty",
+ },
]
+
def get_bin_list(filters):
conditions = []
if filters.item_code:
- conditions.append("item_code = '%s' "%filters.item_code)
+ conditions.append("item_code = '%s' " % filters.item_code)
if filters.warehouse:
- warehouse_details = frappe.db.get_value("Warehouse", filters.warehouse, ["lft", "rgt"], as_dict=1)
+ warehouse_details = frappe.db.get_value(
+ "Warehouse", filters.warehouse, ["lft", "rgt"], as_dict=1
+ )
if warehouse_details:
- conditions.append(" exists (select name from `tabWarehouse` wh \
- where wh.lft >= %s and wh.rgt <= %s and bin.warehouse = wh.name)"%(warehouse_details.lft,
- warehouse_details.rgt))
+ conditions.append(
+ " exists (select name from `tabWarehouse` wh \
+ where wh.lft >= %s and wh.rgt <= %s and bin.warehouse = wh.name)"
+ % (warehouse_details.lft, warehouse_details.rgt)
+ )
- bin_list = frappe.db.sql("""select item_code, warehouse, actual_qty, planned_qty, indented_qty,
+ bin_list = frappe.db.sql(
+ """select item_code, warehouse, actual_qty, planned_qty, indented_qty,
ordered_qty, reserved_qty, reserved_qty_for_production, reserved_qty_for_sub_contract, projected_qty
from tabBin bin {conditions} order by item_code, warehouse
- """.format(conditions=" where " + " and ".join(conditions) if conditions else ""), as_dict=1)
+ """.format(
+ conditions=" where " + " and ".join(conditions) if conditions else ""
+ ),
+ as_dict=1,
+ )
return bin_list
+
def get_item_map(item_code, include_uom):
"""Optimization: get only the item doc and re_order_levels table"""
condition = ""
if item_code:
- condition = 'and item_code = {0}'.format(frappe.db.escape(item_code, percent=False))
+ condition = "and item_code = {0}".format(frappe.db.escape(item_code, percent=False))
cf_field = cf_join = ""
if include_uom:
cf_field = ", ucd.conversion_factor"
- cf_join = "left join `tabUOM Conversion Detail` ucd on ucd.parent=item.name and ucd.uom=%(include_uom)s"
+ cf_join = (
+ "left join `tabUOM Conversion Detail` ucd on ucd.parent=item.name and ucd.uom=%(include_uom)s"
+ )
- items = frappe.db.sql("""
+ items = frappe.db.sql(
+ """
select item.name, item.item_name, item.description, item.item_group, item.brand, item.stock_uom{cf_field}
from `tabItem` item
{cf_join}
@@ -137,16 +271,21 @@
and item.disabled=0
{condition}
and (item.end_of_life > %(today)s or item.end_of_life is null or item.end_of_life='0000-00-00')
- and exists (select name from `tabBin` bin where bin.item_code=item.name)"""\
- .format(cf_field=cf_field, cf_join=cf_join, condition=condition),
- {"today": today(), "include_uom": include_uom}, as_dict=True)
+ and exists (select name from `tabBin` bin where bin.item_code=item.name)""".format(
+ cf_field=cf_field, cf_join=cf_join, condition=condition
+ ),
+ {"today": today(), "include_uom": include_uom},
+ as_dict=True,
+ )
condition = ""
if item_code:
- condition = 'where parent={0}'.format(frappe.db.escape(item_code, percent=False))
+ condition = "where parent={0}".format(frappe.db.escape(item_code, percent=False))
reorder_levels = frappe._dict()
- for ir in frappe.db.sql("""select * from `tabItem Reorder` {condition}""".format(condition=condition), as_dict=1):
+ for ir in frappe.db.sql(
+ """select * from `tabItem Reorder` {condition}""".format(condition=condition), as_dict=1
+ ):
if ir.parent not in reorder_levels:
reorder_levels[ir.parent] = []
diff --git a/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.py b/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.py
index a7b4835..70f04da 100644
--- a/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.py
+++ b/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.py
@@ -12,12 +12,14 @@
data = get_data(filters.warehouse)
return columns, data
+
def validate_warehouse(filters):
company = filters.company
warehouse = filters.warehouse
if not frappe.db.exists("Warehouse", {"name": warehouse, "company": company}):
frappe.throw(_("Warehouse: {0} does not belong to {1}").format(warehouse, company))
+
def get_columns():
columns = [
{
@@ -25,49 +27,37 @@
"fieldname": "item_code",
"fieldtype": "Link",
"options": "Item",
- "width": 200
+ "width": 200,
},
- {
- "label": _("Item Name"),
- "fieldname": "item_name",
- "fieldtype": "Data",
- "width": 200
- },
- {
- "label": _("Serial No Count"),
- "fieldname": "total",
- "fieldtype": "Float",
- "width": 150
- },
- {
- "label": _("Stock Qty"),
- "fieldname": "stock_qty",
- "fieldtype": "Float",
- "width": 150
- },
- {
- "label": _("Difference"),
- "fieldname": "difference",
- "fieldtype": "Float",
- "width": 150
- },
+ {"label": _("Item Name"), "fieldname": "item_name", "fieldtype": "Data", "width": 200},
+ {"label": _("Serial No Count"), "fieldname": "total", "fieldtype": "Float", "width": 150},
+ {"label": _("Stock Qty"), "fieldname": "stock_qty", "fieldtype": "Float", "width": 150},
+ {"label": _("Difference"), "fieldname": "difference", "fieldtype": "Float", "width": 150},
]
return columns
-def get_data(warehouse):
- serial_item_list = frappe.get_all("Item", filters={
- 'has_serial_no': True,
- }, fields=['item_code', 'item_name'])
- status_list = ['Active', 'Expired']
+def get_data(warehouse):
+ serial_item_list = frappe.get_all(
+ "Item",
+ filters={
+ "has_serial_no": True,
+ },
+ fields=["item_code", "item_name"],
+ )
+
+ status_list = ["Active", "Expired"]
data = []
for item in serial_item_list:
- total_serial_no = frappe.db.count("Serial No",
- filters={"item_code": item.item_code, "status": ("in", status_list), "warehouse": warehouse})
+ total_serial_no = frappe.db.count(
+ "Serial No",
+ filters={"item_code": item.item_code, "status": ("in", status_list), "warehouse": warehouse},
+ )
- actual_qty = frappe.db.get_value('Bin', fieldname=['actual_qty'],
- filters={"warehouse": warehouse, "item_code": item.item_code})
+ actual_qty = frappe.db.get_value(
+ "Bin", fieldname=["actual_qty"], filters={"warehouse": warehouse, "item_code": item.item_code}
+ )
# frappe.db.get_value returns null if no record exist.
if not actual_qty:
diff --git a/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py b/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py
index d1748ed..5430fe6 100644
--- a/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py
+++ b/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py
@@ -20,11 +20,11 @@
if consumed_details.get(item_code):
for cd in consumed_details.get(item_code):
- if (cd.voucher_no not in material_transfer_vouchers):
+ if cd.voucher_no not in material_transfer_vouchers:
if cd.voucher_type in ["Delivery Note", "Sales Invoice"]:
delivered_qty += abs(flt(cd.actual_qty))
delivered_amount += abs(flt(cd.stock_value_difference))
- elif cd.voucher_type!="Delivery Note":
+ elif cd.voucher_type != "Delivery Note":
consumed_qty += abs(flt(cd.actual_qty))
consumed_amount += abs(flt(cd.stock_value_difference))
@@ -32,66 +32,98 @@
total_qty += delivered_qty + consumed_qty
total_amount += delivered_amount + consumed_amount
- row = [cd.item_code, cd.item_name, cd.description, cd.stock_uom, \
- consumed_qty, consumed_amount, delivered_qty, delivered_amount, \
- total_qty, total_amount, ','.join(list(set(suppliers)))]
+ row = [
+ cd.item_code,
+ cd.item_name,
+ cd.description,
+ cd.stock_uom,
+ consumed_qty,
+ consumed_amount,
+ delivered_qty,
+ delivered_amount,
+ total_qty,
+ total_amount,
+ ",".join(list(set(suppliers))),
+ ]
data.append(row)
return columns, data
+
def get_columns(filters):
"""return columns based on filters"""
- columns = [_("Item") + ":Link/Item:100"] + [_("Item Name") + "::100"] + \
- [_("Description") + "::150"] + [_("UOM") + ":Link/UOM:90"] + \
- [_("Consumed Qty") + ":Float:110"] + [_("Consumed Amount") + ":Currency:130"] + \
- [_("Delivered Qty") + ":Float:110"] + [_("Delivered Amount") + ":Currency:130"] + \
- [_("Total Qty") + ":Float:110"] + [_("Total Amount") + ":Currency:130"] + \
- [_("Supplier(s)") + "::250"]
+ columns = (
+ [_("Item") + ":Link/Item:100"]
+ + [_("Item Name") + "::100"]
+ + [_("Description") + "::150"]
+ + [_("UOM") + ":Link/UOM:90"]
+ + [_("Consumed Qty") + ":Float:110"]
+ + [_("Consumed Amount") + ":Currency:130"]
+ + [_("Delivered Qty") + ":Float:110"]
+ + [_("Delivered Amount") + ":Currency:130"]
+ + [_("Total Qty") + ":Float:110"]
+ + [_("Total Amount") + ":Currency:130"]
+ + [_("Supplier(s)") + "::250"]
+ )
return columns
+
def get_conditions(filters):
conditions = ""
values = []
- if filters.get('from_date') and filters.get('to_date'):
+ if filters.get("from_date") and filters.get("to_date"):
conditions = "and sle.posting_date>=%s and sle.posting_date<=%s"
- values = [filters.get('from_date'), filters.get('to_date')]
+ values = [filters.get("from_date"), filters.get("to_date")]
return conditions, values
+
def get_consumed_details(filters):
conditions, values = get_conditions(filters)
consumed_details = {}
- for d in frappe.db.sql("""select sle.item_code, i.item_name, i.description,
+ for d in frappe.db.sql(
+ """select sle.item_code, i.item_name, i.description,
i.stock_uom, sle.actual_qty, sle.stock_value_difference,
sle.voucher_no, sle.voucher_type
from `tabStock Ledger Entry` sle, `tabItem` i
- where sle.is_cancelled = 0 and sle.item_code=i.name and sle.actual_qty < 0 %s""" % conditions, values, as_dict=1):
- consumed_details.setdefault(d.item_code, []).append(d)
+ where sle.is_cancelled = 0 and sle.item_code=i.name and sle.actual_qty < 0 %s"""
+ % conditions,
+ values,
+ as_dict=1,
+ ):
+ consumed_details.setdefault(d.item_code, []).append(d)
return consumed_details
+
def get_suppliers_details(filters):
item_supplier_map = {}
- supplier = filters.get('supplier')
+ supplier = filters.get("supplier")
- for d in frappe.db.sql("""select pr.supplier, pri.item_code from
+ for d in frappe.db.sql(
+ """select pr.supplier, pri.item_code from
`tabPurchase Receipt` pr, `tabPurchase Receipt Item` pri
where pr.name=pri.parent and pr.docstatus=1 and
pri.item_code=(select name from `tabItem` where
- is_stock_item=1 and name=pri.item_code)""", as_dict=1):
- item_supplier_map.setdefault(d.item_code, []).append(d.supplier)
+ is_stock_item=1 and name=pri.item_code)""",
+ as_dict=1,
+ ):
+ item_supplier_map.setdefault(d.item_code, []).append(d.supplier)
- for d in frappe.db.sql("""select pr.supplier, pri.item_code from
+ for d in frappe.db.sql(
+ """select pr.supplier, pri.item_code from
`tabPurchase Invoice` pr, `tabPurchase Invoice Item` pri
where pr.name=pri.parent and pr.docstatus=1 and
ifnull(pr.update_stock, 0) = 1 and pri.item_code=(select name from `tabItem`
- where is_stock_item=1 and name=pri.item_code)""", as_dict=1):
- if d.item_code not in item_supplier_map:
- item_supplier_map.setdefault(d.item_code, []).append(d.supplier)
+ where is_stock_item=1 and name=pri.item_code)""",
+ as_dict=1,
+ ):
+ if d.item_code not in item_supplier_map:
+ item_supplier_map.setdefault(d.item_code, []).append(d.supplier)
if supplier:
invalid_items = []
@@ -104,6 +136,9 @@
return item_supplier_map
+
def get_material_transfer_vouchers():
- return frappe.db.sql_list("""select name from `tabStock Entry` where
- purpose='Material Transfer' and docstatus=1""")
+ return frappe.db.sql_list(
+ """select name from `tabStock Entry` where
+ purpose='Material Transfer' and docstatus=1"""
+ )
diff --git a/erpnext/stock/report/test_reports.py b/erpnext/stock/report/test_reports.py
index 76c2079..55b9104 100644
--- a/erpnext/stock/report/test_reports.py
+++ b/erpnext/stock/report/test_reports.py
@@ -43,8 +43,18 @@
},
),
("Warehouse wise Item Balance Age and Value", {"_optional": True}),
- ("Item Variant Details", {"item": "_Test Variant Item",}),
- ("Total Stock Summary", {"group_by": "warehouse",}),
+ (
+ "Item Variant Details",
+ {
+ "item": "_Test Variant Item",
+ },
+ ),
+ (
+ "Total Stock Summary",
+ {
+ "group_by": "warehouse",
+ },
+ ),
("Batch Item Expiry Status", {}),
("Incorrect Stock Value Report", {"company": "_Test Company with perpetual inventory"}),
("Incorrect Serial No Valuation", {}),
@@ -54,12 +64,7 @@
("Delayed Item Report", {"based_on": "Sales Invoice"}),
("Delayed Item Report", {"based_on": "Delivery Note"}),
("Stock Ageing", {"range1": 30, "range2": 60, "range3": 90, "_optional": True}),
- ("Stock Ledger Invariant Check",
- {
- "warehouse": "_Test Warehouse - _TC",
- "item": "_Test Item"
- }
- ),
+ ("Stock Ledger Invariant Check", {"warehouse": "_Test Warehouse - _TC", "item": "_Test Item"}),
]
OPTIONAL_FILTERS = {
diff --git a/erpnext/stock/report/total_stock_summary/total_stock_summary.py b/erpnext/stock/report/total_stock_summary/total_stock_summary.py
index 6f27558..21529da 100644
--- a/erpnext/stock/report/total_stock_summary/total_stock_summary.py
+++ b/erpnext/stock/report/total_stock_summary/total_stock_summary.py
@@ -15,6 +15,7 @@
return columns, stock
+
def get_columns():
columns = [
_("Company") + ":Link/Company:250",
@@ -26,13 +27,16 @@
return columns
+
def get_total_stock(filters):
conditions = ""
columns = ""
if filters.get("group_by") == "Warehouse":
if filters.get("company"):
- conditions += " AND warehouse.company = %s" % frappe.db.escape(filters.get("company"), percent=False)
+ conditions += " AND warehouse.company = %s" % frappe.db.escape(
+ filters.get("company"), percent=False
+ )
conditions += " GROUP BY ledger.warehouse, item.item_code"
columns += "'' as company, ledger.warehouse"
@@ -40,7 +44,8 @@
conditions += " GROUP BY warehouse.company, item.item_code"
columns += " warehouse.company, '' as warehouse"
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
SELECT
%s,
item.item_code,
@@ -53,4 +58,6 @@
INNER JOIN `tabWarehouse` warehouse
ON warehouse.name = ledger.warehouse
WHERE
- ledger.actual_qty != 0 %s""" % (columns, conditions))
+ ledger.actual_qty != 0 %s"""
+ % (columns, conditions)
+ )
diff --git a/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py b/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py
index 22bdb89..a54373f 100644
--- a/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py
+++ b/erpnext/stock/report/warehouse_wise_item_balance_age_and_value/warehouse_wise_item_balance_age_and_value.py
@@ -21,7 +21,8 @@
def execute(filters=None):
is_reposting_item_valuation_in_progress()
- if not filters: filters = {}
+ if not filters:
+ filters = {}
validate_filters(filters)
@@ -39,7 +40,8 @@
item_value = {}
for (company, item, warehouse) in sorted(iwb_map):
- if not item_map.get(item): continue
+ if not item_map.get(item):
+ continue
row = []
qty_dict = iwb_map[(company, item, warehouse)]
@@ -50,13 +52,13 @@
total_stock_value += qty_dict.bal_val if wh.name == warehouse else 0.00
item_balance[(item, item_map[item]["item_group"])].append(row)
- item_value.setdefault((item, item_map[item]["item_group"]),[])
+ item_value.setdefault((item, item_map[item]["item_group"]), [])
item_value[(item, item_map[item]["item_group"])].append(total_stock_value)
-
# sum bal_qty by item
for (item, item_group), wh_balance in item_balance.items():
- if not item_ageing.get(item): continue
+ if not item_ageing.get(item):
+ continue
total_stock_value = sum(item_value[(item, item_group)])
row = [item, item_group, total_stock_value]
@@ -81,17 +83,19 @@
add_warehouse_column(columns, warehouse_list)
return columns, data
+
def get_columns(filters):
"""return columns"""
columns = [
- _("Item")+":Link/Item:180",
- _("Item Group")+"::100",
- _("Value")+":Currency:100",
- _("Age")+":Float:60",
+ _("Item") + ":Link/Item:180",
+ _("Item Group") + "::100",
+ _("Value") + ":Currency:100",
+ _("Age") + ":Float:60",
]
return columns
+
def validate_filters(filters):
if not (filters.get("item_code") or filters.get("warehouse")):
sle_count = flt(frappe.db.sql("""select count(name) from `tabStock Ledger Entry`""")[0][0])
@@ -100,11 +104,12 @@
if not filters.get("company"):
filters["company"] = frappe.defaults.get_user_default("Company")
+
def get_warehouse_list(filters):
from frappe.core.doctype.user_permission.user_permission import get_permitted_documents
- condition = ''
- user_permitted_warehouse = get_permitted_documents('Warehouse')
+ condition = ""
+ user_permitted_warehouse = get_permitted_documents("Warehouse")
value = ()
if user_permitted_warehouse:
condition = "and name in %s"
@@ -113,13 +118,20 @@
condition = "and name = %s"
value = filters.get("warehouse")
- return frappe.db.sql("""select name
+ return frappe.db.sql(
+ """select name
from `tabWarehouse` where is_group = 0
- {condition}""".format(condition=condition), value, as_dict=1)
+ {condition}""".format(
+ condition=condition
+ ),
+ value,
+ as_dict=1,
+ )
+
def add_warehouse_column(columns, warehouse_list):
if len(warehouse_list) > 1:
- columns += [_("Total Qty")+":Int:50"]
+ columns += [_("Total Qty") + ":Int:50"]
for wh in warehouse_list:
- columns += [_(wh.name)+":Int:54"]
+ columns += [_(wh.name) + ":Int:54"]
diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py
index 62017e4..e05d1c3 100644
--- a/erpnext/stock/stock_balance.py
+++ b/erpnext/stock/stock_balance.py
@@ -15,16 +15,20 @@
frappe.db.auto_commit_on_many_writes = 1
if allow_negative_stock:
- existing_allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock")
+ existing_allow_negative_stock = frappe.db.get_value(
+ "Stock Settings", None, "allow_negative_stock"
+ )
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
- item_warehouses = frappe.db.sql("""
+ item_warehouses = frappe.db.sql(
+ """
select distinct item_code, warehouse
from
(select item_code, warehouse from tabBin
union
select item_code, warehouse from `tabStock Ledger Entry`) a
- """)
+ """
+ )
for d in item_warehouses:
try:
repost_stock(d[0], d[1], allow_zero_rate, only_actual, only_bin, allow_negative_stock)
@@ -33,11 +37,20 @@
frappe.db.rollback()
if allow_negative_stock:
- frappe.db.set_value("Stock Settings", None, "allow_negative_stock", existing_allow_negative_stock)
+ frappe.db.set_value(
+ "Stock Settings", None, "allow_negative_stock", existing_allow_negative_stock
+ )
frappe.db.auto_commit_on_many_writes = 0
-def repost_stock(item_code, warehouse, allow_zero_rate=False,
- only_actual=False, only_bin=False, allow_negative_stock=False):
+
+def repost_stock(
+ item_code,
+ warehouse,
+ allow_zero_rate=False,
+ only_actual=False,
+ only_bin=False,
+ allow_negative_stock=False,
+):
if not only_bin:
repost_actual_qty(item_code, warehouse, allow_zero_rate, allow_negative_stock)
@@ -47,35 +60,42 @@
"reserved_qty": get_reserved_qty(item_code, warehouse),
"indented_qty": get_indented_qty(item_code, warehouse),
"ordered_qty": get_ordered_qty(item_code, warehouse),
- "planned_qty": get_planned_qty(item_code, warehouse)
+ "planned_qty": get_planned_qty(item_code, warehouse),
}
if only_bin:
- qty_dict.update({
- "actual_qty": get_balance_qty_from_sle(item_code, warehouse)
- })
+ qty_dict.update({"actual_qty": get_balance_qty_from_sle(item_code, warehouse)})
update_bin_qty(item_code, warehouse, qty_dict)
+
def repost_actual_qty(item_code, warehouse, allow_zero_rate=False, allow_negative_stock=False):
- create_repost_item_valuation_entry({
- "item_code": item_code,
- "warehouse": warehouse,
- "posting_date": "1900-01-01",
- "posting_time": "00:01",
- "allow_negative_stock": allow_negative_stock,
- "allow_zero_rate": allow_zero_rate
- })
+ create_repost_item_valuation_entry(
+ {
+ "item_code": item_code,
+ "warehouse": warehouse,
+ "posting_date": "1900-01-01",
+ "posting_time": "00:01",
+ "allow_negative_stock": allow_negative_stock,
+ "allow_zero_rate": allow_zero_rate,
+ }
+ )
+
def get_balance_qty_from_sle(item_code, warehouse):
- balance_qty = frappe.db.sql("""select qty_after_transaction from `tabStock Ledger Entry`
+ balance_qty = frappe.db.sql(
+ """select qty_after_transaction from `tabStock Ledger Entry`
where item_code=%s and warehouse=%s and is_cancelled=0
order by posting_date desc, posting_time desc, creation desc
- limit 1""", (item_code, warehouse))
+ limit 1""",
+ (item_code, warehouse),
+ )
return flt(balance_qty[0][0]) if balance_qty else 0.0
+
def get_reserved_qty(item_code, warehouse):
- reserved_qty = frappe.db.sql("""
+ reserved_qty = frappe.db.sql(
+ """
select
sum(dnpi_qty * ((so_item_qty - so_item_delivered_qty) / so_item_qty))
from
@@ -115,58 +135,76 @@
) tab
where
so_item_qty >= so_item_delivered_qty
- """, (item_code, warehouse, item_code, warehouse))
+ """,
+ (item_code, warehouse, item_code, warehouse),
+ )
return flt(reserved_qty[0][0]) if reserved_qty else 0
+
def get_indented_qty(item_code, warehouse):
# Ordered Qty is always maintained in stock UOM
- inward_qty = frappe.db.sql("""
+ inward_qty = frappe.db.sql(
+ """
select sum(mr_item.stock_qty - mr_item.ordered_qty)
from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr
where mr_item.item_code=%s and mr_item.warehouse=%s
and mr.material_request_type in ('Purchase', 'Manufacture', 'Customer Provided', 'Material Transfer')
and mr_item.stock_qty > mr_item.ordered_qty and mr_item.parent=mr.name
and mr.status!='Stopped' and mr.docstatus=1
- """, (item_code, warehouse))
+ """,
+ (item_code, warehouse),
+ )
inward_qty = flt(inward_qty[0][0]) if inward_qty else 0
- outward_qty = frappe.db.sql("""
+ outward_qty = frappe.db.sql(
+ """
select sum(mr_item.stock_qty - mr_item.ordered_qty)
from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr
where mr_item.item_code=%s and mr_item.warehouse=%s
and mr.material_request_type = 'Material Issue'
and mr_item.stock_qty > mr_item.ordered_qty and mr_item.parent=mr.name
and mr.status!='Stopped' and mr.docstatus=1
- """, (item_code, warehouse))
+ """,
+ (item_code, warehouse),
+ )
outward_qty = flt(outward_qty[0][0]) if outward_qty else 0
requested_qty = inward_qty - outward_qty
return requested_qty
+
def get_ordered_qty(item_code, warehouse):
- ordered_qty = frappe.db.sql("""
+ ordered_qty = frappe.db.sql(
+ """
select sum((po_item.qty - po_item.received_qty)*po_item.conversion_factor)
from `tabPurchase Order Item` po_item, `tabPurchase Order` po
where po_item.item_code=%s and po_item.warehouse=%s
and po_item.qty > po_item.received_qty and po_item.parent=po.name
and po.status not in ('Closed', 'Delivered') and po.docstatus=1
- and po_item.delivered_by_supplier = 0""", (item_code, warehouse))
+ and po_item.delivered_by_supplier = 0""",
+ (item_code, warehouse),
+ )
return flt(ordered_qty[0][0]) if ordered_qty else 0
+
def get_planned_qty(item_code, warehouse):
- planned_qty = frappe.db.sql("""
+ planned_qty = frappe.db.sql(
+ """
select sum(qty - produced_qty) from `tabWork Order`
where production_item = %s and fg_warehouse = %s and status not in ("Stopped", "Completed", "Closed")
- and docstatus=1 and qty > produced_qty""", (item_code, warehouse))
+ and docstatus=1 and qty > produced_qty""",
+ (item_code, warehouse),
+ )
return flt(planned_qty[0][0]) if planned_qty else 0
def update_bin_qty(item_code, warehouse, qty_dict=None):
from erpnext.stock.utils import get_bin
+
bin = get_bin(item_code, warehouse)
mismatch = False
for field, value in qty_dict.items():
@@ -180,41 +218,54 @@
bin.db_update()
bin.clear_cache()
-def set_stock_balance_as_per_serial_no(item_code=None, posting_date=None, posting_time=None,
- fiscal_year=None):
- if not posting_date: posting_date = nowdate()
- if not posting_time: posting_time = nowtime()
- condition = " and item.name='%s'" % item_code.replace("'", "\'") if item_code else ""
+def set_stock_balance_as_per_serial_no(
+ item_code=None, posting_date=None, posting_time=None, fiscal_year=None
+):
+ if not posting_date:
+ posting_date = nowdate()
+ if not posting_time:
+ posting_time = nowtime()
- bin = frappe.db.sql("""select bin.item_code, bin.warehouse, bin.actual_qty, item.stock_uom
+ condition = " and item.name='%s'" % item_code.replace("'", "'") if item_code else ""
+
+ bin = frappe.db.sql(
+ """select bin.item_code, bin.warehouse, bin.actual_qty, item.stock_uom
from `tabBin` bin, tabItem item
- where bin.item_code = item.name and item.has_serial_no = 1 %s""" % condition)
+ where bin.item_code = item.name and item.has_serial_no = 1 %s"""
+ % condition
+ )
for d in bin:
- serial_nos = frappe.db.sql("""select count(name) from `tabSerial No`
- where item_code=%s and warehouse=%s and docstatus < 2""", (d[0], d[1]))
+ serial_nos = frappe.db.sql(
+ """select count(name) from `tabSerial No`
+ where item_code=%s and warehouse=%s and docstatus < 2""",
+ (d[0], d[1]),
+ )
- sle = frappe.db.sql("""select valuation_rate, company from `tabStock Ledger Entry`
+ sle = frappe.db.sql(
+ """select valuation_rate, company from `tabStock Ledger Entry`
where item_code = %s and warehouse = %s and is_cancelled = 0
- order by posting_date desc limit 1""", (d[0], d[1]))
+ order by posting_date desc limit 1""",
+ (d[0], d[1]),
+ )
sle_dict = {
- 'doctype' : 'Stock Ledger Entry',
- 'item_code' : d[0],
- 'warehouse' : d[1],
- 'transaction_date' : nowdate(),
- 'posting_date' : posting_date,
- 'posting_time' : posting_time,
- 'voucher_type' : 'Stock Reconciliation (Manual)',
- 'voucher_no' : '',
- 'voucher_detail_no' : '',
- 'actual_qty' : flt(serial_nos[0][0]) - flt(d[2]),
- 'stock_uom' : d[3],
- 'incoming_rate' : sle and flt(serial_nos[0][0]) > flt(d[2]) and flt(sle[0][0]) or 0,
- 'company' : sle and cstr(sle[0][1]) or 0,
- 'batch_no' : '',
- 'serial_no' : ''
+ "doctype": "Stock Ledger Entry",
+ "item_code": d[0],
+ "warehouse": d[1],
+ "transaction_date": nowdate(),
+ "posting_date": posting_date,
+ "posting_time": posting_time,
+ "voucher_type": "Stock Reconciliation (Manual)",
+ "voucher_no": "",
+ "voucher_detail_no": "",
+ "actual_qty": flt(serial_nos[0][0]) - flt(d[2]),
+ "stock_uom": d[3],
+ "incoming_rate": sle and flt(serial_nos[0][0]) > flt(d[2]) and flt(sle[0][0]) or 0,
+ "company": sle and cstr(sle[0][1]) or 0,
+ "batch_no": "",
+ "serial_no": "",
}
sle_doc = frappe.get_doc(sle_dict)
@@ -223,16 +274,17 @@
sle_doc.insert()
args = sle_dict.copy()
- args.update({
- "sle_id": sle_doc.name
- })
+ args.update({"sle_id": sle_doc.name})
- create_repost_item_valuation_entry({
- "item_code": d[0],
- "warehouse": d[1],
- "posting_date": posting_date,
- "posting_time": posting_time
- })
+ create_repost_item_valuation_entry(
+ {
+ "item_code": d[0],
+ "warehouse": d[1],
+ "posting_date": posting_date,
+ "posting_time": posting_time,
+ }
+ )
+
def reset_serial_no_status_and_warehouse(serial_nos=None):
if not serial_nos:
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 1aaaad2..967b2b2 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -22,28 +22,32 @@
from erpnext.stock.valuation import FIFOValuation, LIFOValuation, round_off_if_near_zero
-class NegativeStockError(frappe.ValidationError): pass
+class NegativeStockError(frappe.ValidationError):
+ pass
+
+
class SerialNoExistsInFutureTransaction(frappe.ValidationError):
pass
def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_voucher=False):
- """ Create SL entries from SL entry dicts
+ """Create SL entries from SL entry dicts
- args:
- - allow_negative_stock: disable negative stock valiations if true
- - via_landed_cost_voucher: landed cost voucher cancels and reposts
- entries of purchase document. This flag is used to identify if
- cancellation and repost is happening via landed cost voucher, in
- such cases certain validations need to be ignored (like negative
- stock)
+ args:
+ - allow_negative_stock: disable negative stock valiations if true
+ - via_landed_cost_voucher: landed cost voucher cancels and reposts
+ entries of purchase document. This flag is used to identify if
+ cancellation and repost is happening via landed cost voucher, in
+ such cases certain validations need to be ignored (like negative
+ stock)
"""
from erpnext.controllers.stock_controller import future_sle_exists
+
if sl_entries:
cancel = sl_entries[0].get("is_cancelled")
if cancel:
validate_cancellation(sl_entries)
- set_as_cancel(sl_entries[0].get('voucher_type'), sl_entries[0].get('voucher_no'))
+ set_as_cancel(sl_entries[0].get("voucher_type"), sl_entries[0].get("voucher_no"))
args = get_args_for_future_sle(sl_entries[0])
future_sle_exists(args, sl_entries)
@@ -53,19 +57,21 @@
validate_serial_no(sle)
if cancel:
- sle['actual_qty'] = -flt(sle.get('actual_qty'))
+ sle["actual_qty"] = -flt(sle.get("actual_qty"))
- if sle['actual_qty'] < 0 and not sle.get('outgoing_rate'):
- sle['outgoing_rate'] = get_incoming_outgoing_rate_for_cancel(sle.item_code,
- sle.voucher_type, sle.voucher_no, sle.voucher_detail_no)
- sle['incoming_rate'] = 0.0
+ if sle["actual_qty"] < 0 and not sle.get("outgoing_rate"):
+ sle["outgoing_rate"] = get_incoming_outgoing_rate_for_cancel(
+ sle.item_code, sle.voucher_type, sle.voucher_no, sle.voucher_detail_no
+ )
+ sle["incoming_rate"] = 0.0
- if sle['actual_qty'] > 0 and not sle.get('incoming_rate'):
- sle['incoming_rate'] = get_incoming_outgoing_rate_for_cancel(sle.item_code,
- sle.voucher_type, sle.voucher_no, sle.voucher_detail_no)
- sle['outgoing_rate'] = 0.0
+ if sle["actual_qty"] > 0 and not sle.get("incoming_rate"):
+ sle["incoming_rate"] = get_incoming_outgoing_rate_for_cancel(
+ sle.item_code, sle.voucher_type, sle.voucher_no, sle.voucher_detail_no
+ )
+ sle["outgoing_rate"] = 0.0
- 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_doc = make_entry(sle, allow_negative_stock, via_landed_cost_voucher)
args = sle_doc.as_dict()
@@ -74,13 +80,16 @@
# preserve previous_qty_after_transaction for qty reposting
args.previous_qty_after_transaction = sle.get("previous_qty_after_transaction")
- is_stock_item = frappe.get_cached_value('Item', args.get("item_code"), 'is_stock_item')
+ is_stock_item = frappe.get_cached_value("Item", args.get("item_code"), "is_stock_item")
if is_stock_item:
bin_name = get_or_make_bin(args.get("item_code"), args.get("warehouse"))
repost_current_voucher(args, allow_negative_stock, via_landed_cost_voucher)
update_bin_qty(bin_name, args)
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"))
+ )
+
def repost_current_voucher(args, allow_negative_stock=False, via_landed_cost_voucher=False):
if args.get("actual_qty") or args.get("voucher_type") == "Stock Reconciliation":
@@ -92,28 +101,35 @@
# Reposts only current voucher SL Entries
# Updates valuation rate, stock value, stock queue for current transaction
- update_entries_after({
- "item_code": args.get('item_code'),
- "warehouse": args.get('warehouse'),
- "posting_date": args.get("posting_date"),
- "posting_time": args.get("posting_time"),
- "voucher_type": args.get("voucher_type"),
- "voucher_no": args.get("voucher_no"),
- "sle_id": args.get('name'),
- "creation": args.get('creation')
- }, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher)
+ update_entries_after(
+ {
+ "item_code": args.get("item_code"),
+ "warehouse": args.get("warehouse"),
+ "posting_date": args.get("posting_date"),
+ "posting_time": args.get("posting_time"),
+ "voucher_type": args.get("voucher_type"),
+ "voucher_no": args.get("voucher_no"),
+ "sle_id": args.get("name"),
+ "creation": args.get("creation"),
+ },
+ allow_negative_stock=allow_negative_stock,
+ via_landed_cost_voucher=via_landed_cost_voucher,
+ )
# update qty in future sle and Validate negative qty
update_qty_in_future_sle(args, allow_negative_stock)
def get_args_for_future_sle(row):
- return frappe._dict({
- 'voucher_type': row.get('voucher_type'),
- 'voucher_no': row.get('voucher_no'),
- 'posting_date': row.get('posting_date'),
- 'posting_time': row.get('posting_time')
- })
+ return frappe._dict(
+ {
+ "voucher_type": row.get("voucher_type"),
+ "voucher_no": row.get("voucher_no"),
+ "posting_date": row.get("posting_date"),
+ "posting_time": row.get("posting_time"),
+ }
+ )
+
def validate_serial_no(sle):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@@ -121,58 +137,79 @@
for sn in get_serial_nos(sle.serial_no):
args = copy.deepcopy(sle)
args.serial_no = sn
- args.warehouse = ''
+ args.warehouse = ""
vouchers = []
- for row in get_stock_ledger_entries(args, '>'):
+ for row in get_stock_ledger_entries(args, ">"):
voucher_type = frappe.bold(row.voucher_type)
voucher_no = frappe.bold(get_link_to_form(row.voucher_type, row.voucher_no))
- vouchers.append(f'{voucher_type} {voucher_no}')
+ vouchers.append(f"{voucher_type} {voucher_no}")
if vouchers:
serial_no = frappe.bold(sn)
- msg = (f'''The serial no {serial_no} has been used in the future transactions so you need to cancel them first.
- The list of the transactions are as below.''' + '<br><br><ul><li>')
+ msg = (
+ f"""The serial no {serial_no} has been used in the future transactions so you need to cancel them first.
+ The list of the transactions are as below."""
+ + "<br><br><ul><li>"
+ )
- msg += '</li><li>'.join(vouchers)
- msg += '</li></ul>'
+ msg += "</li><li>".join(vouchers)
+ msg += "</li></ul>"
- title = 'Cannot Submit' if not sle.get('is_cancelled') else 'Cannot Cancel'
+ title = "Cannot Submit" if not sle.get("is_cancelled") else "Cannot Cancel"
frappe.throw(_(msg), title=_(title), exc=SerialNoExistsInFutureTransaction)
+
def validate_cancellation(args):
if args[0].get("is_cancelled"):
- repost_entry = frappe.db.get_value("Repost Item Valuation", {
- 'voucher_type': args[0].voucher_type,
- 'voucher_no': args[0].voucher_no,
- 'docstatus': 1
- }, ['name', 'status'], as_dict=1)
+ repost_entry = frappe.db.get_value(
+ "Repost Item Valuation",
+ {"voucher_type": args[0].voucher_type, "voucher_no": args[0].voucher_no, "docstatus": 1},
+ ["name", "status"],
+ as_dict=1,
+ )
if repost_entry:
- if repost_entry.status == 'In Progress':
- frappe.throw(_("Cannot cancel the transaction. Reposting of item valuation on submission is not completed yet."))
- if repost_entry.status == 'Queued':
+ if repost_entry.status == "In Progress":
+ frappe.throw(
+ _(
+ "Cannot cancel the transaction. Reposting of item valuation on submission is not completed yet."
+ )
+ )
+ if repost_entry.status == "Queued":
doc = frappe.get_doc("Repost Item Valuation", repost_entry.name)
doc.flags.ignore_permissions = True
doc.cancel()
doc.delete()
+
def set_as_cancel(voucher_type, voucher_no):
- frappe.db.sql("""update `tabStock Ledger Entry` set is_cancelled=1,
+ frappe.db.sql(
+ """update `tabStock Ledger Entry` set is_cancelled=1,
modified=%s, modified_by=%s
where voucher_type=%s and voucher_no=%s and is_cancelled = 0""",
- (now(), frappe.session.user, voucher_type, voucher_no))
+ (now(), frappe.session.user, voucher_type, voucher_no),
+ )
+
def make_entry(args, allow_negative_stock=False, via_landed_cost_voucher=False):
args["doctype"] = "Stock Ledger Entry"
sle = frappe.get_doc(args)
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.submit()
return sle
-def repost_future_sle(args=None, voucher_type=None, voucher_no=None, allow_negative_stock=None, via_landed_cost_voucher=False, doc=None):
+
+def repost_future_sle(
+ args=None,
+ voucher_type=None,
+ voucher_no=None,
+ allow_negative_stock=None,
+ via_landed_cost_voucher=False,
+ doc=None,
+):
if not args and voucher_type and voucher_no:
args = get_items_to_be_repost(voucher_type, voucher_no, doc)
@@ -182,20 +219,28 @@
while i < len(args):
validate_item_warehouse(args[i])
- obj = update_entries_after({
- 'item_code': args[i].get('item_code'),
- 'warehouse': args[i].get('warehouse'),
- 'posting_date': args[i].get('posting_date'),
- 'posting_time': args[i].get('posting_time'),
- 'creation': args[i].get('creation'),
- 'distinct_item_warehouses': distinct_item_warehouses
- }, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher)
+ obj = update_entries_after(
+ {
+ "item_code": args[i].get("item_code"),
+ "warehouse": args[i].get("warehouse"),
+ "posting_date": args[i].get("posting_date"),
+ "posting_time": args[i].get("posting_time"),
+ "creation": args[i].get("creation"),
+ "distinct_item_warehouses": distinct_item_warehouses,
+ },
+ allow_negative_stock=allow_negative_stock,
+ via_landed_cost_voucher=via_landed_cost_voucher,
+ )
- distinct_item_warehouses[(args[i].get('item_code'), args[i].get('warehouse'))].reposting_status = True
+ distinct_item_warehouses[
+ (args[i].get("item_code"), args[i].get("warehouse"))
+ ].reposting_status = True
if obj.new_items_found:
for item_wh, data in distinct_item_warehouses.items():
- if ('args_idx' not in data and not data.reposting_status) or (data.sle_changed and data.reposting_status):
+ if ("args_idx" not in data and not data.reposting_status) or (
+ data.sle_changed and data.reposting_status
+ ):
data.args_idx = len(args)
args.append(data.sle)
elif data.sle_changed and not data.reposting_status:
@@ -210,82 +255,104 @@
if doc and args:
update_args_in_repost_item_valuation(doc, i, args, distinct_item_warehouses)
+
def validate_item_warehouse(args):
- for field in ['item_code', 'warehouse', 'posting_date', 'posting_time']:
+ for field in ["item_code", "warehouse", "posting_date", "posting_time"]:
if not args.get(field):
- validation_msg = f'The field {frappe.unscrub(args.get(field))} is required for the reposting'
+ validation_msg = f"The field {frappe.unscrub(args.get(field))} is required for the reposting"
frappe.throw(_(validation_msg))
+
def update_args_in_repost_item_valuation(doc, index, args, distinct_item_warehouses):
- frappe.db.set_value(doc.doctype, doc.name, {
- 'items_to_be_repost': json.dumps(args, default=str),
- 'distinct_item_and_warehouse': json.dumps({str(k): v for k,v in distinct_item_warehouses.items()}, default=str),
- 'current_index': index
- })
+ frappe.db.set_value(
+ doc.doctype,
+ doc.name,
+ {
+ "items_to_be_repost": json.dumps(args, default=str),
+ "distinct_item_and_warehouse": json.dumps(
+ {str(k): v for k, v in distinct_item_warehouses.items()}, default=str
+ ),
+ "current_index": index,
+ },
+ )
frappe.db.commit()
- frappe.publish_realtime('item_reposting_progress', {
- 'name': doc.name,
- 'items_to_be_repost': json.dumps(args, default=str),
- 'current_index': index
- })
+ frappe.publish_realtime(
+ "item_reposting_progress",
+ {"name": doc.name, "items_to_be_repost": json.dumps(args, default=str), "current_index": index},
+ )
+
def get_items_to_be_repost(voucher_type, voucher_no, doc=None):
if doc and doc.items_to_be_repost:
return json.loads(doc.items_to_be_repost) or []
- return frappe.db.get_all("Stock Ledger Entry",
+ return frappe.db.get_all(
+ "Stock Ledger Entry",
filters={"voucher_type": voucher_type, "voucher_no": voucher_no},
fields=["item_code", "warehouse", "posting_date", "posting_time", "creation"],
order_by="creation asc",
- group_by="item_code, warehouse"
+ group_by="item_code, warehouse",
)
+
def get_distinct_item_warehouse(args=None, doc=None):
distinct_item_warehouses = {}
if doc and doc.distinct_item_and_warehouse:
distinct_item_warehouses = json.loads(doc.distinct_item_and_warehouse)
- distinct_item_warehouses = {frappe.safe_eval(k): frappe._dict(v) for k, v in distinct_item_warehouses.items()}
+ distinct_item_warehouses = {
+ frappe.safe_eval(k): frappe._dict(v) for k, v in distinct_item_warehouses.items()
+ }
else:
for i, d in enumerate(args):
- distinct_item_warehouses.setdefault((d.item_code, d.warehouse), frappe._dict({
- "reposting_status": False,
- "sle": d,
- "args_idx": i
- }))
+ distinct_item_warehouses.setdefault(
+ (d.item_code, d.warehouse), frappe._dict({"reposting_status": False, "sle": d, "args_idx": i})
+ )
return distinct_item_warehouses
+
def get_current_index(doc=None):
if doc and doc.current_index:
return doc.current_index
+
class update_entries_after(object):
"""
- update valution rate and qty after transaction
- from the current time-bucket onwards
+ update valution rate and qty after transaction
+ from the current time-bucket onwards
- :param args: args as dict
+ :param args: args as dict
- args = {
- "item_code": "ABC",
- "warehouse": "XYZ",
- "posting_date": "2012-12-12",
- "posting_time": "12:00"
- }
+ args = {
+ "item_code": "ABC",
+ "warehouse": "XYZ",
+ "posting_date": "2012-12-12",
+ "posting_time": "12:00"
+ }
"""
- def __init__(self, args, allow_zero_rate=False, allow_negative_stock=None, via_landed_cost_voucher=False, verbose=1):
+
+ def __init__(
+ self,
+ args,
+ allow_zero_rate=False,
+ allow_negative_stock=None,
+ via_landed_cost_voucher=False,
+ verbose=1,
+ ):
self.exceptions = {}
self.verbose = verbose
self.allow_zero_rate = allow_zero_rate
self.via_landed_cost_voucher = via_landed_cost_voucher
self.item_code = args.get("item_code")
- self.allow_negative_stock = allow_negative_stock or is_negative_stock_allowed(item_code=self.item_code)
+ self.allow_negative_stock = allow_negative_stock or is_negative_stock_allowed(
+ item_code=self.item_code
+ )
self.args = frappe._dict(args)
if self.args.sle_id:
- self.args['name'] = self.args.sle_id
+ self.args["name"] = self.args.sle_id
self.company = frappe.get_cached_value("Warehouse", self.args.warehouse, "company")
self.get_precision()
@@ -299,28 +366,29 @@
self.build()
def get_precision(self):
- company_base_currency = frappe.get_cached_value('Company', self.company, "default_currency")
- self.precision = get_field_precision(frappe.get_meta("Stock Ledger Entry").get_field("stock_value"),
- currency=company_base_currency)
+ company_base_currency = frappe.get_cached_value("Company", self.company, "default_currency")
+ self.precision = get_field_precision(
+ frappe.get_meta("Stock Ledger Entry").get_field("stock_value"), currency=company_base_currency
+ )
def initialize_previous_data(self, args):
"""
- Get previous sl entries for current item for each related warehouse
- and assigns into self.data dict
+ Get previous sl entries for current item for each related warehouse
+ and assigns into self.data dict
- :Data Structure:
+ :Data Structure:
- self.data = {
- warehouse1: {
- 'previus_sle': {},
- 'qty_after_transaction': 10,
- 'valuation_rate': 100,
- 'stock_value': 1000,
- 'prev_stock_value': 1000,
- 'stock_queue': '[[10, 100]]',
- 'stock_value_difference': 1000
- }
- }
+ self.data = {
+ warehouse1: {
+ 'previus_sle': {},
+ 'qty_after_transaction': 10,
+ 'valuation_rate': 100,
+ 'stock_value': 1000,
+ 'prev_stock_value': 1000,
+ 'stock_queue': '[[10, 100]]',
+ 'stock_value_difference': 1000
+ }
+ }
"""
self.data.setdefault(args.warehouse, frappe._dict())
@@ -331,11 +399,13 @@
for key in ("qty_after_transaction", "valuation_rate", "stock_value"):
setattr(warehouse_dict, key, flt(previous_sle.get(key)))
- warehouse_dict.update({
- "prev_stock_value": previous_sle.stock_value or 0.0,
- "stock_queue": json.loads(previous_sle.stock_queue or "[]"),
- "stock_value_difference": 0.0
- })
+ warehouse_dict.update(
+ {
+ "prev_stock_value": previous_sle.stock_value or 0.0,
+ "stock_queue": json.loads(previous_sle.stock_queue or "[]"),
+ "stock_value_difference": 0.0,
+ }
+ )
def build(self):
from erpnext.controllers.stock_controller import future_sle_exists
@@ -368,9 +438,10 @@
self.process_sle(sle)
def get_sle_against_current_voucher(self):
- self.args['time_format'] = '%H:%i:%s'
+ self.args["time_format"] = "%H:%i:%s"
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
select
*, timestamp(posting_date, posting_time) as "timestamp"
from
@@ -384,22 +455,29 @@
order by
creation ASC
for update
- """, self.args, as_dict=1)
+ """,
+ self.args,
+ as_dict=1,
+ )
def get_future_entries_to_fix(self):
# includes current entry!
- args = self.data[self.args.warehouse].previous_sle \
- or frappe._dict({"item_code": self.item_code, "warehouse": self.args.warehouse})
+ args = self.data[self.args.warehouse].previous_sle or frappe._dict(
+ {"item_code": self.item_code, "warehouse": self.args.warehouse}
+ )
return list(self.get_sle_after_datetime(args))
def get_dependent_entries_to_fix(self, entries_to_fix, sle):
- dependant_sle = get_sle_by_voucher_detail_no(sle.dependant_sle_voucher_detail_no,
- excluded_sle=sle.name)
+ dependant_sle = get_sle_by_voucher_detail_no(
+ sle.dependant_sle_voucher_detail_no, excluded_sle=sle.name
+ )
if not dependant_sle:
return entries_to_fix
- elif dependant_sle.item_code == self.item_code and dependant_sle.warehouse == self.args.warehouse:
+ elif (
+ dependant_sle.item_code == self.item_code and dependant_sle.warehouse == self.args.warehouse
+ ):
return entries_to_fix
elif dependant_sle.item_code != self.item_code:
self.update_distinct_item_warehouses(dependant_sle)
@@ -411,14 +489,14 @@
def update_distinct_item_warehouses(self, dependant_sle):
key = (dependant_sle.item_code, dependant_sle.warehouse)
- val = frappe._dict({
- "sle": dependant_sle
- })
+ val = frappe._dict({"sle": dependant_sle})
if key not in self.distinct_item_warehouses:
self.distinct_item_warehouses[key] = val
self.new_items_found = True
else:
- existing_sle_posting_date = self.distinct_item_warehouses[key].get("sle", {}).get("posting_date")
+ existing_sle_posting_date = (
+ self.distinct_item_warehouses[key].get("sle", {}).get("posting_date")
+ )
if getdate(dependant_sle.posting_date) < getdate(existing_sle_posting_date):
val.sle_changed = True
self.distinct_item_warehouses[key] = val
@@ -427,12 +505,13 @@
def append_future_sle_for_dependant(self, dependant_sle, entries_to_fix):
self.initialize_previous_data(dependant_sle)
- args = self.data[dependant_sle.warehouse].previous_sle \
- or frappe._dict({"item_code": self.item_code, "warehouse": dependant_sle.warehouse})
+ args = self.data[dependant_sle.warehouse].previous_sle or frappe._dict(
+ {"item_code": self.item_code, "warehouse": dependant_sle.warehouse}
+ )
future_sle_for_dependant = list(self.get_sle_after_datetime(args))
entries_to_fix.extend(future_sle_for_dependant)
- return sorted(entries_to_fix, key=lambda k: k['timestamp'])
+ return sorted(entries_to_fix, key=lambda k: k["timestamp"])
def process_sle(self, sle):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@@ -457,22 +536,30 @@
if sle.voucher_type == "Stock Reconciliation":
self.wh_data.qty_after_transaction = sle.qty_after_transaction
- self.wh_data.stock_value = flt(self.wh_data.qty_after_transaction) * flt(self.wh_data.valuation_rate)
- elif sle.batch_no and frappe.db.get_value("Batch", sle.batch_no, "use_batchwise_valuation", cache=True):
+ self.wh_data.stock_value = flt(self.wh_data.qty_after_transaction) * flt(
+ self.wh_data.valuation_rate
+ )
+ elif sle.batch_no and frappe.db.get_value(
+ "Batch", sle.batch_no, "use_batchwise_valuation", cache=True
+ ):
self.update_batched_values(sle)
else:
- if sle.voucher_type=="Stock Reconciliation" and not sle.batch_no:
+ if sle.voucher_type == "Stock Reconciliation" and not sle.batch_no:
# assert
self.wh_data.valuation_rate = sle.valuation_rate
self.wh_data.qty_after_transaction = sle.qty_after_transaction
- self.wh_data.stock_value = flt(self.wh_data.qty_after_transaction) * flt(self.wh_data.valuation_rate)
+ self.wh_data.stock_value = flt(self.wh_data.qty_after_transaction) * flt(
+ self.wh_data.valuation_rate
+ )
if self.valuation_method != "Moving Average":
self.wh_data.stock_queue = [[self.wh_data.qty_after_transaction, self.wh_data.valuation_rate]]
else:
if self.valuation_method == "Moving Average":
self.get_moving_average_values(sle)
self.wh_data.qty_after_transaction += flt(sle.actual_qty)
- self.wh_data.stock_value = flt(self.wh_data.qty_after_transaction) * flt(self.wh_data.valuation_rate)
+ self.wh_data.stock_value = flt(self.wh_data.qty_after_transaction) * flt(
+ self.wh_data.valuation_rate
+ )
else:
self.update_queue_values(sle)
@@ -489,17 +576,16 @@
sle.stock_value = self.wh_data.stock_value
sle.stock_queue = json.dumps(self.wh_data.stock_queue)
sle.stock_value_difference = stock_value_difference
- sle.doctype="Stock Ledger Entry"
+ sle.doctype = "Stock Ledger Entry"
frappe.get_doc(sle).db_update()
if not self.args.get("sle_id"):
self.update_outgoing_rate_on_transaction(sle)
-
def validate_negative_stock(self, sle):
"""
- validate negative stock for entries current datetime onwards
- will not consider cancelled entries
+ validate negative stock for entries current datetime onwards
+ will not consider cancelled entries
"""
diff = self.wh_data.qty_after_transaction + flt(sle.actual_qty)
@@ -528,13 +614,24 @@
self.recalculate_amounts_in_stock_entry(sle.voucher_no)
rate = frappe.db.get_value("Stock Entry Detail", sle.voucher_detail_no, "valuation_rate")
# Sales and Purchase Return
- elif sle.voucher_type in ("Purchase Receipt", "Purchase Invoice", "Delivery Note", "Sales Invoice"):
+ elif sle.voucher_type in (
+ "Purchase Receipt",
+ "Purchase Invoice",
+ "Delivery Note",
+ "Sales Invoice",
+ ):
if frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_return"):
from erpnext.controllers.sales_and_purchase_return import (
get_rate_for_return, # don't move this import to top
)
- rate = get_rate_for_return(sle.voucher_type, sle.voucher_no, sle.item_code,
- voucher_detail_no=sle.voucher_detail_no, sle = sle)
+
+ rate = get_rate_for_return(
+ sle.voucher_type,
+ sle.voucher_no,
+ sle.item_code,
+ voucher_detail_no=sle.voucher_detail_no,
+ sle=sle,
+ )
else:
if sle.voucher_type in ("Purchase Receipt", "Purchase Invoice"):
rate_field = "valuation_rate"
@@ -542,8 +639,9 @@
rate_field = "incoming_rate"
# check in item table
- item_code, incoming_rate = frappe.db.get_value(sle.voucher_type + " Item",
- sle.voucher_detail_no, ["item_code", rate_field])
+ item_code, incoming_rate = frappe.db.get_value(
+ sle.voucher_type + " Item", sle.voucher_detail_no, ["item_code", rate_field]
+ )
if item_code == sle.item_code:
rate = incoming_rate
@@ -553,15 +651,18 @@
else:
ref_doctype = "Purchase Receipt Item Supplied"
- rate = frappe.db.get_value(ref_doctype, {"parent_detail_docname": sle.voucher_detail_no,
- "item_code": sle.item_code}, rate_field)
+ rate = frappe.db.get_value(
+ ref_doctype,
+ {"parent_detail_docname": sle.voucher_detail_no, "item_code": sle.item_code},
+ rate_field,
+ )
return rate
def update_outgoing_rate_on_transaction(self, sle):
"""
- Update outgoing rate in Stock Entry, Delivery Note, Sales Invoice and Sales Return
- In case of Stock Entry, also calculate FG Item rate and total incoming/outgoing amount
+ Update outgoing rate in Stock Entry, Delivery Note, Sales Invoice and Sales Return
+ In case of Stock Entry, also calculate FG Item rate and total incoming/outgoing amount
"""
if sle.actual_qty and sle.voucher_detail_no:
outgoing_rate = abs(flt(sle.stock_value_difference)) / abs(sle.actual_qty)
@@ -591,24 +692,33 @@
# Update item's incoming rate on transaction
item_code = frappe.db.get_value(sle.voucher_type + " Item", sle.voucher_detail_no, "item_code")
if item_code == sle.item_code:
- frappe.db.set_value(sle.voucher_type + " Item", sle.voucher_detail_no, "incoming_rate", outgoing_rate)
+ frappe.db.set_value(
+ sle.voucher_type + " Item", sle.voucher_detail_no, "incoming_rate", outgoing_rate
+ )
else:
# packed item
- frappe.db.set_value("Packed Item",
+ frappe.db.set_value(
+ "Packed Item",
{"parent_detail_docname": sle.voucher_detail_no, "item_code": sle.item_code},
- "incoming_rate", outgoing_rate)
+ "incoming_rate",
+ outgoing_rate,
+ )
def update_rate_on_purchase_receipt(self, sle, outgoing_rate):
if frappe.db.exists(sle.voucher_type + " Item", sle.voucher_detail_no):
- frappe.db.set_value(sle.voucher_type + " Item", sle.voucher_detail_no, "base_net_rate", outgoing_rate)
+ frappe.db.set_value(
+ sle.voucher_type + " Item", sle.voucher_detail_no, "base_net_rate", outgoing_rate
+ )
else:
- frappe.db.set_value("Purchase Receipt Item Supplied", sle.voucher_detail_no, "rate", outgoing_rate)
+ frappe.db.set_value(
+ "Purchase Receipt Item Supplied", sle.voucher_detail_no, "rate", outgoing_rate
+ )
# Recalculate subcontracted item's rate in case of subcontracted purchase receipt/invoice
- if frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_subcontracted") == 'Yes':
+ if frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_subcontracted") == "Yes":
doc = frappe.get_doc(sle.voucher_type, sle.voucher_no)
doc.update_valuation_rate(reset_outgoing_rate=False)
- for d in (doc.items + doc.supplied_items):
+ for d in doc.items + doc.supplied_items:
d.db_update()
def get_serialized_values(self, sle):
@@ -635,29 +745,34 @@
new_stock_qty = self.wh_data.qty_after_transaction + actual_qty
if new_stock_qty > 0:
- new_stock_value = (self.wh_data.qty_after_transaction * self.wh_data.valuation_rate) + stock_value_change
+ new_stock_value = (
+ self.wh_data.qty_after_transaction * self.wh_data.valuation_rate
+ ) + stock_value_change
if new_stock_value >= 0:
# calculate new valuation rate only if stock value is positive
# else it remains the same as that of previous entry
self.wh_data.valuation_rate = new_stock_value / new_stock_qty
if not self.wh_data.valuation_rate and sle.voucher_detail_no:
- allow_zero_rate = self.check_if_allow_zero_valuation_rate(sle.voucher_type, sle.voucher_detail_no)
+ allow_zero_rate = self.check_if_allow_zero_valuation_rate(
+ sle.voucher_type, sle.voucher_detail_no
+ )
if not allow_zero_rate:
self.wh_data.valuation_rate = self.get_fallback_rate(sle)
def get_incoming_value_for_serial_nos(self, sle, serial_nos):
# get rate from serial nos within same company
- all_serial_nos = frappe.get_all("Serial No",
- fields=["purchase_rate", "name", "company"],
- filters = {'name': ('in', serial_nos)})
+ all_serial_nos = frappe.get_all(
+ "Serial No", fields=["purchase_rate", "name", "company"], filters={"name": ("in", serial_nos)}
+ )
- incoming_values = sum(flt(d.purchase_rate) for d in all_serial_nos if d.company==sle.company)
+ incoming_values = sum(flt(d.purchase_rate) for d in all_serial_nos if d.company == sle.company)
# Get rate for serial nos which has been transferred to other company
- invalid_serial_nos = [d.name for d in all_serial_nos if d.company!=sle.company]
+ invalid_serial_nos = [d.name for d in all_serial_nos if d.company != sle.company]
for serial_no in invalid_serial_nos:
- incoming_rate = frappe.db.sql("""
+ incoming_rate = frappe.db.sql(
+ """
select incoming_rate
from `tabStock Ledger Entry`
where
@@ -671,7 +786,9 @@
)
order by posting_date desc
limit 1
- """, (sle.company, serial_no, serial_no+'\n%', '%\n'+serial_no, '%\n'+serial_no+'\n%'))
+ """,
+ (sle.company, serial_no, serial_no + "\n%", "%\n" + serial_no, "%\n" + serial_no + "\n%"),
+ )
incoming_values += flt(incoming_rate[0][0]) if incoming_rate else 0
@@ -685,15 +802,17 @@
if flt(self.wh_data.qty_after_transaction) <= 0:
self.wh_data.valuation_rate = sle.incoming_rate
else:
- new_stock_value = (self.wh_data.qty_after_transaction * self.wh_data.valuation_rate) + \
- (actual_qty * sle.incoming_rate)
+ new_stock_value = (self.wh_data.qty_after_transaction * self.wh_data.valuation_rate) + (
+ actual_qty * sle.incoming_rate
+ )
self.wh_data.valuation_rate = new_stock_value / new_stock_qty
elif sle.outgoing_rate:
if new_stock_qty:
- new_stock_value = (self.wh_data.qty_after_transaction * self.wh_data.valuation_rate) + \
- (actual_qty * sle.outgoing_rate)
+ new_stock_value = (self.wh_data.qty_after_transaction * self.wh_data.valuation_rate) + (
+ actual_qty * sle.outgoing_rate
+ )
self.wh_data.valuation_rate = new_stock_value / new_stock_qty
else:
@@ -708,7 +827,9 @@
# Get valuation rate from previous SLE or Item master, if item does not have the
# allow zero valuration rate flag set
if not self.wh_data.valuation_rate and sle.voucher_detail_no:
- allow_zero_valuation_rate = self.check_if_allow_zero_valuation_rate(sle.voucher_type, sle.voucher_detail_no)
+ allow_zero_valuation_rate = self.check_if_allow_zero_valuation_rate(
+ sle.voucher_type, sle.voucher_detail_no
+ )
if not allow_zero_valuation_rate:
self.wh_data.valuation_rate = self.get_fallback_rate(sle)
@@ -717,7 +838,9 @@
actual_qty = flt(sle.actual_qty)
outgoing_rate = flt(sle.outgoing_rate)
- self.wh_data.qty_after_transaction = round_off_if_near_zero(self.wh_data.qty_after_transaction + actual_qty)
+ self.wh_data.qty_after_transaction = round_off_if_near_zero(
+ self.wh_data.qty_after_transaction + actual_qty
+ )
if self.valuation_method == "LIFO":
stock_queue = LIFOValuation(self.wh_data.stock_queue)
@@ -729,24 +852,33 @@
if actual_qty > 0:
stock_queue.add_stock(qty=actual_qty, rate=incoming_rate)
else:
+
def rate_generator() -> float:
- allow_zero_valuation_rate = self.check_if_allow_zero_valuation_rate(sle.voucher_type, sle.voucher_detail_no)
+ allow_zero_valuation_rate = self.check_if_allow_zero_valuation_rate(
+ sle.voucher_type, sle.voucher_detail_no
+ )
if not allow_zero_valuation_rate:
return self.get_fallback_rate(sle)
else:
return 0.0
- stock_queue.remove_stock(qty=abs(actual_qty), outgoing_rate=outgoing_rate, rate_generator=rate_generator)
+ stock_queue.remove_stock(
+ qty=abs(actual_qty), outgoing_rate=outgoing_rate, rate_generator=rate_generator
+ )
_qty, stock_value = stock_queue.get_total_stock_and_value()
stock_value_difference = stock_value - prev_stock_value
self.wh_data.stock_queue = stock_queue.state
- self.wh_data.stock_value = round_off_if_near_zero(self.wh_data.stock_value + stock_value_difference)
+ self.wh_data.stock_value = round_off_if_near_zero(
+ self.wh_data.stock_value + stock_value_difference
+ )
if not self.wh_data.stock_queue:
- self.wh_data.stock_queue.append([0, sle.incoming_rate or sle.outgoing_rate or self.wh_data.valuation_rate])
+ self.wh_data.stock_queue.append(
+ [0, sle.incoming_rate or sle.outgoing_rate or self.wh_data.valuation_rate]
+ )
if self.wh_data.qty_after_transaction:
self.wh_data.valuation_rate = self.wh_data.stock_value / self.wh_data.qty_after_transaction
@@ -755,14 +887,21 @@
incoming_rate = flt(sle.incoming_rate)
actual_qty = flt(sle.actual_qty)
- self.wh_data.qty_after_transaction = round_off_if_near_zero(self.wh_data.qty_after_transaction + actual_qty)
+ self.wh_data.qty_after_transaction = round_off_if_near_zero(
+ self.wh_data.qty_after_transaction + actual_qty
+ )
if actual_qty > 0:
stock_value_difference = incoming_rate * actual_qty
else:
- outgoing_rate = get_batch_incoming_rate(item_code=sle.item_code,
- warehouse=sle.warehouse, batch_no=sle.batch_no, posting_date=sle.posting_date,
- posting_time=sle.posting_time, creation=sle.creation)
+ outgoing_rate = get_batch_incoming_rate(
+ item_code=sle.item_code,
+ warehouse=sle.warehouse,
+ batch_no=sle.batch_no,
+ posting_date=sle.posting_date,
+ posting_time=sle.posting_time,
+ creation=sle.creation,
+ )
if outgoing_rate is None:
# This can *only* happen if qty available for the batch is zero.
# in such case fall back various other rates.
@@ -771,7 +910,9 @@
outgoing_rate = self.get_fallback_rate(sle)
stock_value_difference = outgoing_rate * actual_qty
- self.wh_data.stock_value = round_off_if_near_zero(self.wh_data.stock_value + stock_value_difference)
+ self.wh_data.stock_value = round_off_if_near_zero(
+ self.wh_data.stock_value + stock_value_difference
+ )
if self.wh_data.qty_after_transaction:
self.wh_data.valuation_rate = self.wh_data.stock_value / self.wh_data.qty_after_transaction
@@ -790,10 +931,17 @@
def get_fallback_rate(self, sle) -> float:
"""When exact incoming rate isn't available use any of other "average" rates as fallback.
- This should only get used for negative stock."""
- return get_valuation_rate(sle.item_code, sle.warehouse,
- sle.voucher_type, sle.voucher_no, self.allow_zero_rate,
- currency=erpnext.get_company_currency(sle.company), company=sle.company, batch_no=sle.batch_no)
+ This should only get used for negative stock."""
+ return get_valuation_rate(
+ sle.item_code,
+ sle.warehouse,
+ sle.voucher_type,
+ sle.voucher_no,
+ self.allow_zero_rate,
+ currency=erpnext.get_company_currency(sle.company),
+ company=sle.company,
+ batch_no=sle.batch_no,
+ )
def get_sle_before_datetime(self, args):
"""get previous stock ledger entry before current time-bucket"""
@@ -810,18 +958,27 @@
for warehouse, exceptions in self.exceptions.items():
deficiency = min(e["diff"] for e in exceptions)
- if ((exceptions[0]["voucher_type"], exceptions[0]["voucher_no"]) in
- frappe.local.flags.currently_saving):
+ if (
+ exceptions[0]["voucher_type"],
+ exceptions[0]["voucher_no"],
+ ) in frappe.local.flags.currently_saving:
msg = _("{0} units of {1} needed in {2} to complete this transaction.").format(
- abs(deficiency), frappe.get_desk_link('Item', exceptions[0]["item_code"]),
- frappe.get_desk_link('Warehouse', warehouse))
+ abs(deficiency),
+ frappe.get_desk_link("Item", exceptions[0]["item_code"]),
+ frappe.get_desk_link("Warehouse", warehouse),
+ )
else:
- msg = _("{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction.").format(
- abs(deficiency), frappe.get_desk_link('Item', exceptions[0]["item_code"]),
- frappe.get_desk_link('Warehouse', warehouse),
- exceptions[0]["posting_date"], exceptions[0]["posting_time"],
- frappe.get_desk_link(exceptions[0]["voucher_type"], exceptions[0]["voucher_no"]))
+ msg = _(
+ "{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction."
+ ).format(
+ abs(deficiency),
+ frappe.get_desk_link("Item", exceptions[0]["item_code"]),
+ frappe.get_desk_link("Warehouse", warehouse),
+ exceptions[0]["posting_date"],
+ exceptions[0]["posting_time"],
+ frappe.get_desk_link(exceptions[0]["voucher_type"], exceptions[0]["voucher_no"]),
+ )
if msg:
msg_list.append(msg)
@@ -829,7 +986,7 @@
if msg_list:
message = "\n\n".join(msg_list)
if self.verbose:
- frappe.throw(message, NegativeStockError, title=_('Insufficient Stock'))
+ frappe.throw(message, NegativeStockError, title=_("Insufficient Stock"))
else:
raise NegativeStockError(message)
@@ -838,19 +995,16 @@
for warehouse, data in self.data.items():
bin_name = get_or_make_bin(self.item_code, warehouse)
- updated_values = {
- "actual_qty": data.qty_after_transaction,
- "stock_value": data.stock_value
- }
+ updated_values = {"actual_qty": data.qty_after_transaction, "stock_value": data.stock_value}
if data.valuation_rate is not None:
updated_values["valuation_rate"] = data.valuation_rate
- frappe.db.set_value('Bin', bin_name, updated_values)
+ frappe.db.set_value("Bin", bin_name, updated_values)
def get_previous_sle_of_current_voucher(args, exclude_current_voucher=False):
"""get stock ledger entries filtered by specific posting datetime conditions"""
- args['time_format'] = '%H:%i:%s'
+ args["time_format"] = "%H:%i:%s"
if not args.get("posting_date"):
args["posting_date"] = "1900-01-01"
if not args.get("posting_time"):
@@ -861,7 +1015,8 @@
voucher_no = args.get("voucher_no")
voucher_condition = f"and voucher_no != '{voucher_no}'"
- sle = frappe.db.sql("""
+ sle = frappe.db.sql(
+ """
select *, timestamp(posting_date, posting_time) as "timestamp"
from `tabStock Ledger Entry`
where item_code = %(item_code)s
@@ -871,32 +1026,48 @@
and timestamp(posting_date, time_format(posting_time, %(time_format)s)) < timestamp(%(posting_date)s, time_format(%(posting_time)s, %(time_format)s))
order by timestamp(posting_date, posting_time) desc, creation desc
limit 1
- for update""".format(voucher_condition=voucher_condition), args, as_dict=1)
+ for update""".format(
+ voucher_condition=voucher_condition
+ ),
+ args,
+ as_dict=1,
+ )
return sle[0] if sle else frappe._dict()
+
def get_previous_sle(args, for_update=False):
"""
- get the last sle on or before the current time-bucket,
- to get actual qty before transaction, this function
- is called from various transaction like stock entry, reco etc
+ get the last sle on or before the current time-bucket,
+ to get actual qty before transaction, this function
+ is called from various transaction like stock entry, reco etc
- args = {
- "item_code": "ABC",
- "warehouse": "XYZ",
- "posting_date": "2012-12-12",
- "posting_time": "12:00",
- "sle": "name of reference Stock Ledger Entry"
- }
+ args = {
+ "item_code": "ABC",
+ "warehouse": "XYZ",
+ "posting_date": "2012-12-12",
+ "posting_time": "12:00",
+ "sle": "name of reference Stock Ledger Entry"
+ }
"""
args["name"] = args.get("sle", None) or ""
sle = get_stock_ledger_entries(args, "<=", "desc", "limit 1", for_update=for_update)
return sle and sle[0] or {}
-def get_stock_ledger_entries(previous_sle, operator=None,
- order="desc", limit=None, for_update=False, debug=False, check_serial_no=True):
+
+def get_stock_ledger_entries(
+ previous_sle,
+ operator=None,
+ order="desc",
+ limit=None,
+ for_update=False,
+ debug=False,
+ check_serial_no=True,
+):
"""get stock ledger entries filtered by specific posting datetime conditions"""
- conditions = " and timestamp(posting_date, posting_time) {0} timestamp(%(posting_date)s, %(posting_time)s)".format(operator)
+ conditions = " and timestamp(posting_date, posting_time) {0} timestamp(%(posting_date)s, %(posting_time)s)".format(
+ operator
+ )
if previous_sle.get("warehouse"):
conditions += " and warehouse = %(warehouse)s"
elif previous_sle.get("warehouse_condition"):
@@ -905,15 +1076,21 @@
if check_serial_no and previous_sle.get("serial_no"):
# conditions += " and serial_no like {}".format(frappe.db.escape('%{0}%'.format(previous_sle.get("serial_no"))))
serial_no = previous_sle.get("serial_no")
- conditions += (""" and
+ conditions += (
+ """ and
(
serial_no = {0}
or serial_no like {1}
or serial_no like {2}
or serial_no like {3}
)
- """).format(frappe.db.escape(serial_no), frappe.db.escape('{}\n%'.format(serial_no)),
- frappe.db.escape('%\n{}'.format(serial_no)), frappe.db.escape('%\n{}\n%'.format(serial_no)))
+ """
+ ).format(
+ frappe.db.escape(serial_no),
+ frappe.db.escape("{}\n%".format(serial_no)),
+ frappe.db.escape("%\n{}".format(serial_no)),
+ frappe.db.escape("%\n{}\n%".format(serial_no)),
+ )
if not previous_sle.get("posting_date"):
previous_sle["posting_date"] = "1900-01-01"
@@ -923,70 +1100,95 @@
if operator in (">", "<=") and previous_sle.get("name"):
conditions += " and name!=%(name)s"
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
select *, timestamp(posting_date, posting_time) as "timestamp"
from `tabStock Ledger Entry`
where item_code = %%(item_code)s
and is_cancelled = 0
%(conditions)s
order by timestamp(posting_date, posting_time) %(order)s, creation %(order)s
- %(limit)s %(for_update)s""" % {
+ %(limit)s %(for_update)s"""
+ % {
"conditions": conditions,
"limit": limit or "",
"for_update": for_update and "for update" or "",
- "order": order
- }, previous_sle, as_dict=1, debug=debug)
+ "order": order,
+ },
+ previous_sle,
+ as_dict=1,
+ debug=debug,
+ )
+
def get_sle_by_voucher_detail_no(voucher_detail_no, excluded_sle=None):
- return frappe.db.get_value('Stock Ledger Entry',
- {'voucher_detail_no': voucher_detail_no, 'name': ['!=', excluded_sle]},
- ['item_code', 'warehouse', 'posting_date', 'posting_time', 'timestamp(posting_date, posting_time) as timestamp'],
- as_dict=1)
+ return frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"voucher_detail_no": voucher_detail_no, "name": ["!=", excluded_sle]},
+ [
+ "item_code",
+ "warehouse",
+ "posting_date",
+ "posting_time",
+ "timestamp(posting_date, posting_time) as timestamp",
+ ],
+ as_dict=1,
+ )
-def get_batch_incoming_rate(item_code, warehouse, batch_no, posting_date, posting_time, creation=None):
- Timestamp = CustomFunction('timestamp', ['date', 'time'])
+def get_batch_incoming_rate(
+ item_code, warehouse, batch_no, posting_date, posting_time, creation=None
+):
+
+ Timestamp = CustomFunction("timestamp", ["date", "time"])
sle = frappe.qb.DocType("Stock Ledger Entry")
- timestamp_condition = (Timestamp(sle.posting_date, sle.posting_time) < Timestamp(posting_date, posting_time))
+ timestamp_condition = Timestamp(sle.posting_date, sle.posting_time) < Timestamp(
+ posting_date, posting_time
+ )
if creation:
timestamp_condition |= (
- (Timestamp(sle.posting_date, sle.posting_time) == Timestamp(posting_date, posting_time))
- & (sle.creation < creation)
- )
+ Timestamp(sle.posting_date, sle.posting_time) == Timestamp(posting_date, posting_time)
+ ) & (sle.creation < creation)
batch_details = (
- frappe.qb
- .from_(sle)
- .select(
- Sum(sle.stock_value_difference).as_("batch_value"),
- Sum(sle.actual_qty).as_("batch_qty")
- )
- .where(
- (sle.item_code == item_code)
- & (sle.warehouse == warehouse)
- & (sle.batch_no == batch_no)
- & (sle.is_cancelled == 0)
- )
- .where(timestamp_condition)
+ frappe.qb.from_(sle)
+ .select(Sum(sle.stock_value_difference).as_("batch_value"), Sum(sle.actual_qty).as_("batch_qty"))
+ .where(
+ (sle.item_code == item_code)
+ & (sle.warehouse == warehouse)
+ & (sle.batch_no == batch_no)
+ & (sle.is_cancelled == 0)
+ )
+ .where(timestamp_condition)
).run(as_dict=True)
if batch_details and batch_details[0].batch_qty:
return batch_details[0].batch_value / batch_details[0].batch_qty
-def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no,
- allow_zero_rate=False, currency=None, company=None, raise_error_if_no_rate=True, batch_no=None):
+def get_valuation_rate(
+ item_code,
+ warehouse,
+ voucher_type,
+ voucher_no,
+ allow_zero_rate=False,
+ currency=None,
+ company=None,
+ raise_error_if_no_rate=True,
+ batch_no=None,
+):
if not company:
- company = frappe.get_cached_value("Warehouse", warehouse, "company")
+ company = frappe.get_cached_value("Warehouse", warehouse, "company")
last_valuation_rate = None
# Get moving average rate of a specific batch number
if warehouse and batch_no and frappe.db.get_value("Batch", batch_no, "use_batchwise_valuation"):
- last_valuation_rate = frappe.db.sql("""
+ last_valuation_rate = frappe.db.sql(
+ """
select sum(stock_value_difference) / sum(actual_qty)
from `tabStock Ledger Entry`
where
@@ -996,11 +1198,13 @@
AND is_cancelled = 0
AND NOT (voucher_no = %s AND voucher_type = %s)
""",
- (item_code, warehouse, batch_no, voucher_no, voucher_type))
+ (item_code, warehouse, batch_no, voucher_no, voucher_type),
+ )
# Get valuation rate from last sle for the same item and warehouse
if not last_valuation_rate or last_valuation_rate[0][0] is None:
- last_valuation_rate = frappe.db.sql("""select valuation_rate
+ last_valuation_rate = frappe.db.sql(
+ """select valuation_rate
from `tabStock Ledger Entry` force index (item_warehouse)
where
item_code = %s
@@ -1008,18 +1212,23 @@
AND valuation_rate >= 0
AND is_cancelled = 0
AND NOT (voucher_no = %s AND voucher_type = %s)
- order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, warehouse, voucher_no, voucher_type))
+ order by posting_date desc, posting_time desc, name desc limit 1""",
+ (item_code, warehouse, voucher_no, voucher_type),
+ )
if not last_valuation_rate:
# Get valuation rate from last sle for the item against any warehouse
- last_valuation_rate = frappe.db.sql("""select valuation_rate
+ last_valuation_rate = frappe.db.sql(
+ """select valuation_rate
from `tabStock Ledger Entry` force index (item_code)
where
item_code = %s
AND valuation_rate > 0
AND is_cancelled = 0
AND NOT(voucher_no = %s AND voucher_type = %s)
- order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, voucher_no, voucher_type))
+ order by posting_date desc, posting_time desc, name desc limit 1""",
+ (item_code, voucher_no, voucher_type),
+ )
if last_valuation_rate:
return flt(last_valuation_rate[0][0])
@@ -1034,18 +1243,36 @@
if not valuation_rate:
# try in price list
- valuation_rate = frappe.db.get_value('Item Price',
- dict(item_code=item_code, buying=1, currency=currency),
- 'price_list_rate')
+ valuation_rate = frappe.db.get_value(
+ "Item Price", dict(item_code=item_code, buying=1, currency=currency), "price_list_rate"
+ )
- if not allow_zero_rate and not valuation_rate and raise_error_if_no_rate \
- and cint(erpnext.is_perpetual_inventory_enabled(company)):
+ if (
+ not allow_zero_rate
+ and not valuation_rate
+ and raise_error_if_no_rate
+ and cint(erpnext.is_perpetual_inventory_enabled(company))
+ ):
form_link = get_link_to_form("Item", item_code)
- message = _("Valuation Rate for the Item {0}, is required to do accounting entries for {1} {2}.").format(form_link, voucher_type, voucher_no)
+ message = _(
+ "Valuation Rate for the Item {0}, is required to do accounting entries for {1} {2}."
+ ).format(form_link, voucher_type, voucher_no)
message += "<br><br>" + _("Here are the options to proceed:")
- solutions = "<li>" + _("If the item is transacting as a Zero Valuation Rate item in this entry, please enable 'Allow Zero Valuation Rate' in the {0} Item table.").format(voucher_type) + "</li>"
- solutions += "<li>" + _("If not, you can Cancel / Submit this entry") + " {0} ".format(frappe.bold("after")) + _("performing either one below:") + "</li>"
+ solutions = (
+ "<li>"
+ + _(
+ "If the item is transacting as a Zero Valuation Rate item in this entry, please enable 'Allow Zero Valuation Rate' in the {0} Item table."
+ ).format(voucher_type)
+ + "</li>"
+ )
+ solutions += (
+ "<li>"
+ + _("If not, you can Cancel / Submit this entry")
+ + " {0} ".format(frappe.bold("after"))
+ + _("performing either one below:")
+ + "</li>"
+ )
sub_solutions = "<ul><li>" + _("Create an incoming stock transaction for the Item.") + "</li>"
sub_solutions += "<li>" + _("Mention Valuation Rate in the Item master.") + "</li></ul>"
msg = message + solutions + sub_solutions + "</li>"
@@ -1054,6 +1281,7 @@
return valuation_rate
+
def update_qty_in_future_sle(args, allow_negative_stock=False):
"""Recalculate Qty after Transaction in future SLEs based on current SLE."""
datetime_limit_condition = ""
@@ -1070,7 +1298,8 @@
# add condition to update SLEs before this date & time
datetime_limit_condition = get_datetime_limit_condition(detail)
- frappe.db.sql("""
+ frappe.db.sql(
+ """
update `tabStock Ledger Entry`
set qty_after_transaction = qty_after_transaction + {qty_shift}
where
@@ -1085,10 +1314,15 @@
)
)
{datetime_limit_condition}
- """.format(qty_shift=qty_shift, datetime_limit_condition=datetime_limit_condition), args)
+ """.format(
+ qty_shift=qty_shift, datetime_limit_condition=datetime_limit_condition
+ ),
+ args,
+ )
validate_negative_qty_in_future_sle(args, allow_negative_stock)
+
def get_stock_reco_qty_shift(args):
stock_reco_qty_shift = 0
if args.get("is_cancelled"):
@@ -1100,8 +1334,9 @@
stock_reco_qty_shift = flt(args.actual_qty)
else:
# reco is being submitted
- last_balance = get_previous_sle_of_current_voucher(args,
- exclude_current_voucher=True).get("qty_after_transaction")
+ last_balance = get_previous_sle_of_current_voucher(args, exclude_current_voucher=True).get(
+ "qty_after_transaction"
+ )
if last_balance is not None:
stock_reco_qty_shift = flt(args.qty_after_transaction) - flt(last_balance)
@@ -1110,10 +1345,12 @@
return stock_reco_qty_shift
+
def get_next_stock_reco(args):
"""Returns next nearest stock reconciliaton's details."""
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
select
name, posting_date, posting_time, creation, voucher_no
from
@@ -1131,7 +1368,11 @@
)
)
limit 1
- """, args, as_dict=1)
+ """,
+ args,
+ as_dict=1,
+ )
+
def get_datetime_limit_condition(detail):
return f"""
@@ -1143,6 +1384,7 @@
)
)"""
+
def validate_negative_qty_in_future_sle(args, allow_negative_stock=False):
if allow_negative_stock or is_negative_stock_allowed(item_code=args.item_code):
return
@@ -1151,32 +1393,40 @@
neg_sle = get_future_sle_with_negative_qty(args)
if neg_sle:
- message = _("{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction.").format(
+ message = _(
+ "{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction."
+ ).format(
abs(neg_sle[0]["qty_after_transaction"]),
- frappe.get_desk_link('Item', args.item_code),
- frappe.get_desk_link('Warehouse', args.warehouse),
- neg_sle[0]["posting_date"], neg_sle[0]["posting_time"],
- frappe.get_desk_link(neg_sle[0]["voucher_type"], neg_sle[0]["voucher_no"]))
+ frappe.get_desk_link("Item", args.item_code),
+ frappe.get_desk_link("Warehouse", args.warehouse),
+ neg_sle[0]["posting_date"],
+ neg_sle[0]["posting_time"],
+ frappe.get_desk_link(neg_sle[0]["voucher_type"], neg_sle[0]["voucher_no"]),
+ )
- frappe.throw(message, NegativeStockError, title=_('Insufficient Stock'))
-
+ frappe.throw(message, NegativeStockError, title=_("Insufficient Stock"))
if not args.batch_no:
return
neg_batch_sle = get_future_sle_with_negative_batch_qty(args)
if neg_batch_sle:
- message = _("{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction.").format(
+ message = _(
+ "{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction."
+ ).format(
abs(neg_batch_sle[0]["cumulative_total"]),
- frappe.get_desk_link('Batch', args.batch_no),
- frappe.get_desk_link('Warehouse', args.warehouse),
- neg_batch_sle[0]["posting_date"], neg_batch_sle[0]["posting_time"],
- frappe.get_desk_link(neg_batch_sle[0]["voucher_type"], neg_batch_sle[0]["voucher_no"]))
+ frappe.get_desk_link("Batch", args.batch_no),
+ frappe.get_desk_link("Warehouse", args.warehouse),
+ neg_batch_sle[0]["posting_date"],
+ neg_batch_sle[0]["posting_time"],
+ frappe.get_desk_link(neg_batch_sle[0]["voucher_type"], neg_batch_sle[0]["voucher_no"]),
+ )
frappe.throw(message, NegativeStockError, title=_("Insufficient Stock for Batch"))
def get_future_sle_with_negative_qty(args):
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
select
qty_after_transaction, posting_date, posting_time,
voucher_type, voucher_no
@@ -1190,11 +1440,15 @@
and qty_after_transaction < 0
order by timestamp(posting_date, posting_time) asc
limit 1
- """, args, as_dict=1)
+ """,
+ args,
+ as_dict=1,
+ )
def get_future_sle_with_negative_batch_qty(args):
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
with batch_ledger as (
select
posting_date, posting_time, voucher_type, voucher_no,
@@ -1212,7 +1466,10 @@
cumulative_total < 0.0
and timestamp(posting_date, posting_time) >= timestamp(%(posting_date)s, %(posting_time)s)
limit 1
- """, args, as_dict=1)
+ """,
+ args,
+ as_dict=1,
+ )
def is_negative_stock_allowed(*, item_code: Optional[str] = None) -> bool:
diff --git a/erpnext/stock/tests/test_valuation.py b/erpnext/stock/tests/test_valuation.py
index b64ff8e..506a666 100644
--- a/erpnext/stock/tests/test_valuation.py
+++ b/erpnext/stock/tests/test_valuation.py
@@ -16,7 +16,6 @@
class TestFIFOValuation(unittest.TestCase):
-
def setUp(self):
self.queue = FIFOValuation([])
@@ -29,7 +28,9 @@
self.assertAlmostEqual(sum(q for q, _ in self.queue), qty, msg=f"queue: {self.queue}", places=4)
def assertTotalValue(self, value):
- self.assertAlmostEqual(sum(q * r for q, r in self.queue), value, msg=f"queue: {self.queue}", places=2)
+ self.assertAlmostEqual(
+ sum(q * r for q, r in self.queue), value, msg=f"queue: {self.queue}", places=2
+ )
def test_simple_addition(self):
self.queue.add_stock(1, 10)
@@ -55,7 +56,6 @@
self.queue.add_stock(6, 10)
self.assertEqual(self.queue, [[1, 10]])
-
def test_negative_stock(self):
self.queue.remove_stock(1, 5)
self.assertEqual(self.queue, [[-1, 5]])
@@ -75,7 +75,6 @@
self.queue.remove_stock(1, 20)
self.assertEqual(self.queue, [[1, 10]])
-
def test_remove_multiple_bins(self):
self.queue.add_stock(1, 10)
self.queue.add_stock(2, 20)
@@ -85,7 +84,6 @@
self.queue.remove_stock(4)
self.assertEqual(self.queue, [[5, 20]])
-
def test_remove_multiple_bins_with_rate(self):
self.queue.add_stock(1, 10)
self.queue.add_stock(2, 20)
@@ -143,7 +141,9 @@
else:
qty = abs(qty)
consumed = self.queue.remove_stock(qty)
- self.assertAlmostEqual(qty, sum(q for q, _ in consumed), msg=f"incorrect consumption {consumed}")
+ self.assertAlmostEqual(
+ qty, sum(q for q, _ in consumed), msg=f"incorrect consumption {consumed}"
+ )
total_qty -= qty
self.assertTotalQty(total_qty)
@@ -164,7 +164,9 @@
else:
qty = abs(qty)
consumed = self.queue.remove_stock(qty)
- self.assertAlmostEqual(qty, sum(q for q, _ in consumed), msg=f"incorrect consumption {consumed}")
+ self.assertAlmostEqual(
+ qty, sum(q for q, _ in consumed), msg=f"incorrect consumption {consumed}"
+ )
total_qty -= qty
total_value -= sum(q * r for q, r in consumed)
self.assertTotalQty(total_qty)
@@ -172,7 +174,6 @@
class TestLIFOValuation(unittest.TestCase):
-
def setUp(self):
self.stack = LIFOValuation([])
@@ -185,7 +186,9 @@
self.assertAlmostEqual(sum(q for q, _ in self.stack), qty, msg=f"stack: {self.stack}", places=4)
def assertTotalValue(self, value):
- self.assertAlmostEqual(sum(q * r for q, r in self.stack), value, msg=f"stack: {self.stack}", places=2)
+ self.assertAlmostEqual(
+ sum(q * r for q, r in self.stack), value, msg=f"stack: {self.stack}", places=2
+ )
def test_simple_addition(self):
self.stack.add_stock(1, 10)
@@ -248,7 +251,6 @@
consumed = self.stack.remove_stock(5)
self.assertEqual(consumed, [[5, 5]])
-
@given(stock_queue_generator)
def test_lifo_qty_hypothesis(self, stock_stack):
self.stack = LIFOValuation([])
@@ -263,7 +265,9 @@
else:
qty = abs(qty)
consumed = self.stack.remove_stock(qty)
- self.assertAlmostEqual(qty, sum(q for q, _ in consumed), msg=f"incorrect consumption {consumed}")
+ self.assertAlmostEqual(
+ qty, sum(q for q, _ in consumed), msg=f"incorrect consumption {consumed}"
+ )
total_qty -= qty
self.assertTotalQty(total_qty)
@@ -284,12 +288,15 @@
else:
qty = abs(qty)
consumed = self.stack.remove_stock(qty)
- self.assertAlmostEqual(qty, sum(q for q, _ in consumed), msg=f"incorrect consumption {consumed}")
+ self.assertAlmostEqual(
+ qty, sum(q for q, _ in consumed), msg=f"incorrect consumption {consumed}"
+ )
total_qty -= qty
total_value -= sum(q * r for q, r in consumed)
self.assertTotalQty(total_qty)
self.assertTotalValue(total_value)
+
class TestLIFOValuationSLE(FrappeTestCase):
ITEM_CODE = "_Test LIFO item"
WAREHOUSE = "_Test Warehouse - _TC"
@@ -309,7 +316,9 @@
return make_stock_entry(**kwargs)
def assertStockQueue(self, se, expected_queue):
- sle_name = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": se.name, "is_cancelled": 0, "voucher_type": "Stock Entry"})
+ sle_name = frappe.db.get_value(
+ "Stock Ledger Entry", {"voucher_no": se.name, "is_cancelled": 0, "voucher_type": "Stock Entry"}
+ )
sle = frappe.get_doc("Stock Ledger Entry", sle_name)
stock_queue = json.loads(sle.stock_queue)
@@ -321,7 +330,6 @@
if total_qty > 0:
self.assertEqual(stock_queue, expected_queue)
-
def test_lifo_values(self):
in1 = self._make_stock_entry(1, 1)
@@ -340,7 +348,7 @@
self.assertStockQueue(out2, [[1, 1]])
in4 = self._make_stock_entry(4, 4)
- self.assertStockQueue(in4, [[1, 1], [4,4]])
+ self.assertStockQueue(in4, [[1, 1], [4, 4]])
out3 = self._make_stock_entry(-5)
self.assertStockQueue(out3, [])
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index e205389..741646d 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -12,8 +12,13 @@
from erpnext.stock.valuation import FIFOValuation, LIFOValuation
-class InvalidWarehouseCompany(frappe.ValidationError): pass
-class PendingRepostingError(frappe.ValidationError): pass
+class InvalidWarehouseCompany(frappe.ValidationError):
+ pass
+
+
+class PendingRepostingError(frappe.ValidationError):
+ pass
+
def get_stock_value_from_bin(warehouse=None, item_code=None):
values = {}
@@ -26,22 +31,27 @@
and w2.lft between w1.lft and w1.rgt
) """
- values['warehouse'] = warehouse
+ values["warehouse"] = warehouse
if item_code:
conditions += " and `tabBin`.item_code = %(item_code)s"
- values['item_code'] = item_code
+ values["item_code"] = item_code
- query = """select sum(stock_value) from `tabBin`, `tabItem` where 1 = 1
- and `tabItem`.name = `tabBin`.item_code and ifnull(`tabItem`.disabled, 0) = 0 %s""" % conditions
+ query = (
+ """select sum(stock_value) from `tabBin`, `tabItem` where 1 = 1
+ and `tabItem`.name = `tabBin`.item_code and ifnull(`tabItem`.disabled, 0) = 0 %s"""
+ % conditions
+ )
stock_value = frappe.db.sql(query, values)
return stock_value
+
def get_stock_value_on(warehouse=None, posting_date=None, item_code=None):
- if not posting_date: posting_date = nowdate()
+ if not posting_date:
+ posting_date = nowdate()
values, condition = [posting_date], ""
@@ -63,13 +73,19 @@
values.append(item_code)
condition += " AND item_code = %s"
- stock_ledger_entries = frappe.db.sql("""
+ stock_ledger_entries = frappe.db.sql(
+ """
SELECT item_code, stock_value, name, warehouse
FROM `tabStock Ledger Entry` sle
WHERE posting_date <= %s {0}
and is_cancelled = 0
ORDER BY timestamp(posting_date, posting_time) DESC, creation DESC
- """.format(condition), values, as_dict=1)
+ """.format(
+ condition
+ ),
+ values,
+ as_dict=1,
+ )
sle_map = {}
for sle in stock_ledger_entries:
@@ -78,23 +94,32 @@
return sum(sle_map.values())
+
@frappe.whitelist()
-def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None,
- with_valuation_rate=False, with_serial_no=False):
+def get_stock_balance(
+ item_code,
+ warehouse,
+ posting_date=None,
+ posting_time=None,
+ with_valuation_rate=False,
+ with_serial_no=False,
+):
"""Returns stock balance quantity at given warehouse on given posting date or current date.
If `with_valuation_rate` is True, will return tuple (qty, rate)"""
from erpnext.stock.stock_ledger import get_previous_sle
- if posting_date is None: posting_date = nowdate()
- if posting_time is None: posting_time = nowtime()
+ if posting_date is None:
+ posting_date = nowdate()
+ if posting_time is None:
+ posting_time = nowtime()
args = {
"item_code": item_code,
- "warehouse":warehouse,
+ "warehouse": warehouse,
"posting_date": posting_date,
- "posting_time": posting_time
+ "posting_time": posting_time,
}
last_entry = get_previous_sle(args)
@@ -103,33 +128,41 @@
if with_serial_no:
serial_nos = get_serial_nos_data_after_transactions(args)
- return ((last_entry.qty_after_transaction, last_entry.valuation_rate, serial_nos)
- if last_entry else (0.0, 0.0, None))
+ return (
+ (last_entry.qty_after_transaction, last_entry.valuation_rate, serial_nos)
+ if last_entry
+ else (0.0, 0.0, None)
+ )
else:
- return (last_entry.qty_after_transaction, last_entry.valuation_rate) if last_entry else (0.0, 0.0)
+ return (
+ (last_entry.qty_after_transaction, last_entry.valuation_rate) if last_entry else (0.0, 0.0)
+ )
else:
return last_entry.qty_after_transaction if last_entry else 0.0
+
def get_serial_nos_data_after_transactions(args):
from pypika import CustomFunction
serial_nos = set()
args = frappe._dict(args)
- sle = frappe.qb.DocType('Stock Ledger Entry')
- Timestamp = CustomFunction('timestamp', ['date', 'time'])
+ sle = frappe.qb.DocType("Stock Ledger Entry")
+ Timestamp = CustomFunction("timestamp", ["date", "time"])
- stock_ledger_entries = frappe.qb.from_(
- sle
- ).select(
- 'serial_no','actual_qty'
- ).where(
- (sle.item_code == args.item_code)
- & (sle.warehouse == args.warehouse)
- & (Timestamp(sle.posting_date, sle.posting_time) < Timestamp(args.posting_date, args.posting_time))
- & (sle.is_cancelled == 0)
- ).orderby(
- sle.posting_date, sle.posting_time, sle.creation
- ).run(as_dict=1)
+ stock_ledger_entries = (
+ frappe.qb.from_(sle)
+ .select("serial_no", "actual_qty")
+ .where(
+ (sle.item_code == args.item_code)
+ & (sle.warehouse == args.warehouse)
+ & (
+ Timestamp(sle.posting_date, sle.posting_time) < Timestamp(args.posting_date, args.posting_time)
+ )
+ & (sle.is_cancelled == 0)
+ )
+ .orderby(sle.posting_date, sle.posting_time, sle.creation)
+ .run(as_dict=1)
+ )
for stock_ledger_entry in stock_ledger_entries:
changed_serial_no = get_serial_nos_data(stock_ledger_entry.serial_no)
@@ -138,12 +171,15 @@
else:
serial_nos.difference_update(changed_serial_no)
- return '\n'.join(serial_nos)
+ return "\n".join(serial_nos)
+
def get_serial_nos_data(serial_nos):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+
return get_serial_nos(serial_nos)
+
@frappe.whitelist()
def get_latest_stock_qty(item_code, warehouse=None):
values, condition = [item_code], ""
@@ -160,37 +196,48 @@
values.append(warehouse)
condition += " AND warehouse = %s"
- actual_qty = frappe.db.sql("""select sum(actual_qty) from tabBin
- where item_code=%s {0}""".format(condition), values)[0][0]
+ actual_qty = frappe.db.sql(
+ """select sum(actual_qty) from tabBin
+ where item_code=%s {0}""".format(
+ condition
+ ),
+ values,
+ )[0][0]
return actual_qty
def get_latest_stock_balance():
bin_map = {}
- for d in frappe.db.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))
+ for d in frappe.db.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 = frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse})
if not bin:
bin_obj = _create_bin(item_code, warehouse)
else:
- bin_obj = frappe.get_doc('Bin', bin, for_update=True)
+ bin_obj = frappe.get_doc("Bin", bin, for_update=True)
bin_obj.flags.ignore_permissions = True
return bin_obj
-def get_or_make_bin(item_code: str , warehouse: str) -> str:
- bin_record = frappe.db.get_value('Bin', {'item_code': item_code, 'warehouse': warehouse})
+
+def get_or_make_bin(item_code: str, warehouse: str) -> str:
+ bin_record = frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse})
if not bin_record:
bin_obj = _create_bin(item_code, warehouse)
bin_record = bin_obj.name
return bin_record
+
def _create_bin(item_code, warehouse):
"""Create a bin and take care of concurrent inserts."""
@@ -206,6 +253,7 @@
return bin_obj
+
@frappe.whitelist()
def get_incoming_rate(args, raise_error_if_no_rate=True):
"""Get Incoming Rate based on valuation method"""
@@ -214,19 +262,21 @@
get_previous_sle,
get_valuation_rate,
)
+
if isinstance(args, str):
args = json.loads(args)
- voucher_no = args.get('voucher_no') or args.get('name')
+ voucher_no = args.get("voucher_no") or args.get("name")
in_rate = None
if (args.get("serial_no") or "").strip():
in_rate = get_avg_purchase_rate(args.get("serial_no"))
- elif args.get("batch_no") and \
- frappe.db.get_value("Batch", args.get("batch_no"), "use_batchwise_valuation", cache=True):
+ elif args.get("batch_no") and frappe.db.get_value(
+ "Batch", args.get("batch_no"), "use_batchwise_valuation", cache=True
+ ):
in_rate = get_batch_incoming_rate(
- item_code=args.get('item_code'),
- warehouse=args.get('warehouse'),
+ item_code=args.get("item_code"),
+ warehouse=args.get("warehouse"),
batch_no=args.get("batch_no"),
posting_date=args.get("posting_date"),
posting_time=args.get("posting_time"),
@@ -234,40 +284,62 @@
else:
valuation_method = get_valuation_method(args.get("item_code"))
previous_sle = get_previous_sle(args)
- if valuation_method in ('FIFO', 'LIFO'):
+ if valuation_method in ("FIFO", "LIFO"):
if previous_sle:
- previous_stock_queue = json.loads(previous_sle.get('stock_queue', '[]') or '[]')
- in_rate = _get_fifo_lifo_rate(previous_stock_queue, args.get("qty") or 0, valuation_method) if previous_stock_queue else 0
- elif valuation_method == 'Moving Average':
- in_rate = previous_sle.get('valuation_rate') or 0
+ previous_stock_queue = json.loads(previous_sle.get("stock_queue", "[]") or "[]")
+ in_rate = (
+ _get_fifo_lifo_rate(previous_stock_queue, args.get("qty") or 0, valuation_method)
+ if previous_stock_queue
+ else 0
+ )
+ elif valuation_method == "Moving Average":
+ in_rate = previous_sle.get("valuation_rate") or 0
if in_rate is None:
- in_rate = get_valuation_rate(args.get('item_code'), args.get('warehouse'),
- args.get('voucher_type'), voucher_no, args.get('allow_zero_valuation'),
- currency=erpnext.get_company_currency(args.get('company')), company=args.get('company'),
- raise_error_if_no_rate=raise_error_if_no_rate, batch_no=args.get("batch_no"))
+ in_rate = get_valuation_rate(
+ args.get("item_code"),
+ args.get("warehouse"),
+ args.get("voucher_type"),
+ voucher_no,
+ args.get("allow_zero_valuation"),
+ currency=erpnext.get_company_currency(args.get("company")),
+ company=args.get("company"),
+ raise_error_if_no_rate=raise_error_if_no_rate,
+ batch_no=args.get("batch_no"),
+ )
return flt(in_rate)
+
def get_avg_purchase_rate(serial_nos):
"""get average value of serial numbers"""
serial_nos = get_valid_serial_nos(serial_nos)
- return flt(frappe.db.sql("""select avg(purchase_rate) from `tabSerial No`
- where name in (%s)""" % ", ".join(["%s"] * len(serial_nos)),
- tuple(serial_nos))[0][0])
+ return flt(
+ frappe.db.sql(
+ """select avg(purchase_rate) from `tabSerial No`
+ where name in (%s)"""
+ % ", ".join(["%s"] * len(serial_nos)),
+ tuple(serial_nos),
+ )[0][0]
+ )
+
def get_valuation_method(item_code):
"""get valuation method from item or default"""
- val_method = frappe.db.get_value('Item', item_code, 'valuation_method', cache=True)
+ val_method = frappe.db.get_value("Item", item_code, "valuation_method", cache=True)
if not val_method:
- val_method = frappe.db.get_value("Stock Settings", None, "valuation_method", cache=True) or "FIFO"
+ val_method = (
+ frappe.db.get_value("Stock Settings", None, "valuation_method", cache=True) or "FIFO"
+ )
return val_method
+
def get_fifo_rate(previous_stock_queue, qty):
"""get FIFO (average) Rate from Queue"""
return _get_fifo_lifo_rate(previous_stock_queue, qty, "FIFO")
+
def get_lifo_rate(previous_stock_queue, qty):
"""get LIFO (average) Rate from Queue"""
return _get_fifo_lifo_rate(previous_stock_queue, qty, "LIFO")
@@ -286,10 +358,11 @@
total_qty, total_value = ValuationKlass(popped_bins).get_total_stock_and_value()
return total_value / total_qty if total_qty else 0.0
-def get_valid_serial_nos(sr_nos, qty=0, item_code=''):
+
+def get_valid_serial_nos(sr_nos, qty=0, item_code=""):
"""split serial nos, validate and return list of valid serial nos"""
# TODO: remove duplicates in client side
- serial_nos = cstr(sr_nos).strip().replace(',', '\n').split('\n')
+ serial_nos = cstr(sr_nos).strip().replace(",", "\n").split("\n")
valid_serial_nos = []
for val in serial_nos:
@@ -305,19 +378,29 @@
return valid_serial_nos
+
def validate_warehouse_company(warehouse, company):
warehouse_company = frappe.db.get_value("Warehouse", warehouse, "company", cache=True)
if warehouse_company and warehouse_company != company:
- frappe.throw(_("Warehouse {0} does not belong to company {1}").format(warehouse, company),
- InvalidWarehouseCompany)
+ frappe.throw(
+ _("Warehouse {0} does not belong to company {1}").format(warehouse, company),
+ InvalidWarehouseCompany,
+ )
+
def is_group_warehouse(warehouse):
if frappe.db.get_value("Warehouse", warehouse, "is_group", cache=True):
frappe.throw(_("Group node warehouse is not allowed to select for transactions"))
+
def validate_disabled_warehouse(warehouse):
if frappe.db.get_value("Warehouse", warehouse, "disabled", cache=True):
- frappe.throw(_("Disabled Warehouse {0} cannot be used for this transaction.").format(get_link_to_form('Warehouse', warehouse)))
+ frappe.throw(
+ _("Disabled Warehouse {0} cannot be used for this transaction.").format(
+ get_link_to_form("Warehouse", warehouse)
+ )
+ )
+
def update_included_uom_in_report(columns, result, include_uom, conversion_factors):
if not include_uom or not conversion_factors:
@@ -335,11 +418,14 @@
convertible_columns.setdefault(key, d.get("convertible"))
# Add new column to show qty/rate as per the selected UOM
- columns.insert(idx+1, {
- 'label': "{0} (per {1})".format(d.get("label"), include_uom),
- 'fieldname': "{0}_{1}".format(d.get("fieldname"), frappe.scrub(include_uom)),
- 'fieldtype': 'Currency' if d.get("convertible") == 'rate' else 'Float'
- })
+ columns.insert(
+ idx + 1,
+ {
+ "label": "{0} (per {1})".format(d.get("label"), include_uom),
+ "fieldname": "{0}_{1}".format(d.get("fieldname"), frappe.scrub(include_uom)),
+ "fieldtype": "Currency" if d.get("convertible") == "rate" else "Float",
+ },
+ )
update_dict_values = []
for row_idx, row in enumerate(result):
@@ -351,13 +437,13 @@
if not conversion_factors[row_idx]:
conversion_factors[row_idx] = 1
- if convertible_columns.get(key) == 'rate':
+ if convertible_columns.get(key) == "rate":
new_value = flt(value) * conversion_factors[row_idx]
else:
new_value = flt(value) / conversion_factors[row_idx]
if not is_dict_obj:
- row.insert(key+1, new_value)
+ row.insert(key + 1, new_value)
else:
new_key = "{0}_{1}".format(key, frappe.scrub(include_uom))
update_dict_values.append([row, new_key, new_value])
@@ -366,11 +452,17 @@
row, key, value = data
row[key] = value
+
def get_available_serial_nos(args):
- return frappe.db.sql(""" SELECT name from `tabSerial No`
+ return frappe.db.sql(
+ """ SELECT name from `tabSerial No`
WHERE item_code = %(item_code)s and warehouse = %(warehouse)s
and timestamp(purchase_date, purchase_time) <= timestamp(%(posting_date)s, %(posting_time)s)
- """, args, as_dict=1)
+ """,
+ args,
+ as_dict=1,
+ )
+
def add_additional_uom_columns(columns, result, include_uom, conversion_factors):
if not include_uom or not conversion_factors:
@@ -379,70 +471,80 @@
convertible_column_map = {}
for col_idx in list(reversed(range(0, len(columns)))):
col = columns[col_idx]
- if isinstance(col, dict) and col.get('convertible') in ['rate', 'qty']:
+ if isinstance(col, dict) and col.get("convertible") in ["rate", "qty"]:
next_col = col_idx + 1
columns.insert(next_col, col.copy())
- columns[next_col]['fieldname'] += '_alt'
- convertible_column_map[col.get('fieldname')] = frappe._dict({
- 'converted_col': columns[next_col]['fieldname'],
- 'for_type': col.get('convertible')
- })
- if col.get('convertible') == 'rate':
- columns[next_col]['label'] += ' (per {})'.format(include_uom)
+ columns[next_col]["fieldname"] += "_alt"
+ convertible_column_map[col.get("fieldname")] = frappe._dict(
+ {"converted_col": columns[next_col]["fieldname"], "for_type": col.get("convertible")}
+ )
+ if col.get("convertible") == "rate":
+ columns[next_col]["label"] += " (per {})".format(include_uom)
else:
- columns[next_col]['label'] += ' ({})'.format(include_uom)
+ columns[next_col]["label"] += " ({})".format(include_uom)
for row_idx, row in enumerate(result):
for convertible_col, data in convertible_column_map.items():
- conversion_factor = conversion_factors[row.get('item_code')] or 1
+ conversion_factor = conversion_factors[row.get("item_code")] or 1
for_type = data.for_type
value_before_conversion = row.get(convertible_col)
- if for_type == 'rate':
+ if for_type == "rate":
row[data.converted_col] = flt(value_before_conversion) * conversion_factor
else:
row[data.converted_col] = flt(value_before_conversion) / conversion_factor
result[row_idx] = row
+
def get_incoming_outgoing_rate_for_cancel(item_code, voucher_type, voucher_no, voucher_detail_no):
- outgoing_rate = frappe.db.sql("""SELECT abs(stock_value_difference / actual_qty)
+ outgoing_rate = frappe.db.sql(
+ """SELECT abs(stock_value_difference / actual_qty)
FROM `tabStock Ledger Entry`
WHERE voucher_type = %s and voucher_no = %s
and item_code = %s and voucher_detail_no = %s
ORDER BY CREATION DESC limit 1""",
- (voucher_type, voucher_no, item_code, voucher_detail_no))
+ (voucher_type, voucher_no, item_code, voucher_detail_no),
+ )
outgoing_rate = outgoing_rate[0][0] if outgoing_rate else 0.0
return outgoing_rate
+
def is_reposting_item_valuation_in_progress():
- reposting_in_progress = frappe.db.exists("Repost Item Valuation",
- {'docstatus': 1, 'status': ['in', ['Queued','In Progress']]})
+ reposting_in_progress = frappe.db.exists(
+ "Repost Item Valuation", {"docstatus": 1, "status": ["in", ["Queued", "In Progress"]]}
+ )
if reposting_in_progress:
- frappe.msgprint(_("Item valuation reposting in progress. Report might show incorrect item valuation."), alert=1)
+ frappe.msgprint(
+ _("Item valuation reposting in progress. Report might show incorrect item valuation."), alert=1
+ )
+
def check_pending_reposting(posting_date: str, throw_error: bool = True) -> bool:
"""Check if there are pending reposting job till the specified posting date."""
filters = {
"docstatus": 1,
- "status": ["in", ["Queued","In Progress", "Failed"]],
+ "status": ["in", ["Queued", "In Progress", "Failed"]],
"posting_date": ["<=", posting_date],
}
- reposting_pending = frappe.db.exists("Repost Item Valuation", filters)
+ reposting_pending = frappe.db.exists("Repost Item Valuation", filters)
if reposting_pending and throw_error:
- msg = _("Stock/Accounts can not be frozen as processing of backdated entries is going on. Please try again later.")
- frappe.msgprint(msg,
- raise_exception=PendingRepostingError,
- title="Stock Reposting Ongoing",
- indicator="red",
- primary_action={
- "label": _("Show pending entries"),
- "client_action": "erpnext.route_to_pending_reposts",
- "args": filters,
- }
- )
+ msg = _(
+ "Stock/Accounts can not be frozen as processing of backdated entries is going on. Please try again later."
+ )
+ frappe.msgprint(
+ msg,
+ raise_exception=PendingRepostingError,
+ title="Stock Reposting Ongoing",
+ indicator="red",
+ primary_action={
+ "label": _("Show pending entries"),
+ "client_action": "erpnext.route_to_pending_reposts",
+ "args": filters,
+ },
+ )
return bool(reposting_pending)
diff --git a/erpnext/stock/valuation.py b/erpnext/stock/valuation.py
index e2bd1ad..648b218 100644
--- a/erpnext/stock/valuation.py
+++ b/erpnext/stock/valuation.py
@@ -11,7 +11,6 @@
class BinWiseValuation(ABC):
-
@abstractmethod
def add_stock(self, qty: float, rate: float) -> None:
pass
@@ -61,7 +60,9 @@
# specifying the attributes to save resources
# ref: https://docs.python.org/3/reference/datamodel.html#slots
- __slots__ = ["queue",]
+ __slots__ = [
+ "queue",
+ ]
def __init__(self, state: Optional[List[StockBin]]):
self.queue: List[StockBin] = state if state is not None else []
@@ -74,9 +75,9 @@
def add_stock(self, qty: float, rate: float) -> None:
"""Update fifo queue with new stock.
- args:
- qty: new quantity to add
- rate: incoming rate of new quantity"""
+ args:
+ qty: new quantity to add
+ rate: incoming rate of new quantity"""
if not len(self.queue):
self.queue.append([0, 0])
@@ -101,12 +102,12 @@
"""Remove stock from the queue and return popped bins.
args:
- qty: quantity to remove
- rate: outgoing rate
- rate_generator: function to be called if queue is not found and rate is required.
+ qty: quantity to remove
+ rate: outgoing rate
+ rate_generator: function to be called if queue is not found and rate is required.
"""
if not rate_generator:
- rate_generator = lambda : 0.0 # noqa
+ rate_generator = lambda: 0.0 # noqa
consumed_bins = []
while qty:
@@ -126,7 +127,9 @@
if index is None: # nosemgrep
new_stock_value = sum(d[QTY] * d[RATE] for d in self.queue) - qty * outgoing_rate
new_stock_qty = sum(d[QTY] for d in self.queue) - qty
- self.queue = [[new_stock_qty, new_stock_value / new_stock_qty if new_stock_qty > 0 else outgoing_rate]]
+ self.queue = [
+ [new_stock_qty, new_stock_value / new_stock_qty if new_stock_qty > 0 else outgoing_rate]
+ ]
consumed_bins.append([qty, outgoing_rate])
break
else:
@@ -169,7 +172,9 @@
# specifying the attributes to save resources
# ref: https://docs.python.org/3/reference/datamodel.html#slots
- __slots__ = ["stack",]
+ __slots__ = [
+ "stack",
+ ]
def __init__(self, state: Optional[List[StockBin]]):
self.stack: List[StockBin] = state if state is not None else []
@@ -182,11 +187,11 @@
def add_stock(self, qty: float, rate: float) -> None:
"""Update lifo stack with new stock.
- args:
- qty: new quantity to add
- rate: incoming rate of new quantity.
+ args:
+ qty: new quantity to add
+ rate: incoming rate of new quantity.
- Behaviour of this is same as FIFO valuation.
+ Behaviour of this is same as FIFO valuation.
"""
if not len(self.stack):
self.stack.append([0, 0])
@@ -205,19 +210,18 @@
else: # new balance qty is still negative, maintain same rate
self.stack[-1][QTY] = qty
-
def remove_stock(
self, qty: float, outgoing_rate: float = 0.0, rate_generator: Callable[[], float] = None
) -> List[StockBin]:
"""Remove stock from the stack and return popped bins.
args:
- qty: quantity to remove
- rate: outgoing rate - ignored. Kept for backwards compatibility.
- rate_generator: function to be called if stack is not found and rate is required.
+ qty: quantity to remove
+ rate: outgoing rate - ignored. Kept for backwards compatibility.
+ rate_generator: function to be called if stack is not found and rate is required.
"""
if not rate_generator:
- rate_generator = lambda : 0.0 # noqa
+ rate_generator = lambda: 0.0 # noqa
consumed_bins = []
while qty:
@@ -254,7 +258,7 @@
"""Rounds off the number to zero only if number is close to zero for decimal
specified in precision. Precision defaults to 7.
"""
- if abs(0.0 - flt(number)) < (1.0 / (10 ** precision)):
+ if abs(0.0 - flt(number)) < (1.0 / (10**precision)):
return 0.0
return flt(number)