diff --git a/erpnext/stock/dashboard/item_dashboard.py b/erpnext/stock/dashboard/item_dashboard.py
index 75aa1d36c7b..8fbb56ced0f 100644
--- a/erpnext/stock/dashboard/item_dashboard.py
+++ b/erpnext/stock/dashboard/item_dashboard.py
@@ -4,55 +4,72 @@ from frappe.utils import cint, flt
@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 c0666cffc78..24e0ef11ffa 100644
--- a/erpnext/stock/dashboard/warehouse_capacity_dashboard.py
+++ b/erpnext/stock/dashboard/warehouse_capacity_dashboard.py
@@ -6,8 +6,15 @@ from erpnext.stock.utils import get_stock_balance
@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 @@ def get_data(item_code=None, warehouse=None, parent_warehouse=None,
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 d835420b9e2..dbf6cf05e79 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 @@ from erpnext.stock.utils import get_stock_value_from_bin
@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 @@ def get(chart_name = None, chart = None, no_cache = None, filters = None, from_d
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 c9b4c147f1d..aac6cd386cd 100644
--- a/erpnext/stock/doctype/batch/batch.py
+++ b/erpnext/stock/doctype/batch/batch.py
@@ -23,7 +23,7 @@ def get_name_from_hash():
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 @@ def batch_uses_naming_series():
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 @@ def _get_batch_prefix():
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 @@ def _make_naming_series_key(prefix):
: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 @@ def get_batch_naming_series():
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 @@ class Batch(Document):
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 @@ class Batch(Document):
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 @@ class Batch(Document):
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 @@ class Batch(Document):
@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 @@ def get_batch_qty(batch_no=None, warehouse=None, item_code=None, posting_date=No
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_batch_qty(batch_no=None, warehouse=None, item_code=None, posting_date=No
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 @@ def get_batches_by_oldest(item_code, warehouse):
@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 split_batch(batch_no, item_code, warehouse, qty, new_batch_id=None):
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 @@ def get_batch_no(item_code, warehouse, qty=1, throw=False, serial_no=None):
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_batch_no(item_code, warehouse, qty=1, throw=False, serial_no=None):
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 @@ def get_batches(item_code, warehouse, qty=1, throw=False, serial_no=None):
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 @@ def get_batches(item_code, warehouse, qty=1, throw=False, serial_no=None):
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 @@ def get_pos_reserved_batch_qty(filters):
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 725365b2fc5..84b64f36f40 100644
--- a/erpnext/stock/doctype/batch/batch_dashboard.py
+++ b/erpnext/stock/doctype/batch/batch_dashboard.py
@@ -3,23 +3,11 @@ from frappe import _
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 57637538531..c76da626b54 100644
--- a/erpnext/stock/doctype/batch/test_batch.py
+++ b/erpnext/stock/doctype/batch/test_batch.py
@@ -21,134 +21,127 @@ from erpnext.stock.stock_ledger import get_valuation_rate
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 @@ class TestBatch(FrappeTestCase):
# 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 @@ class TestBatch(FrappeTestCase):
@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 @@ class TestBatch(FrappeTestCase):
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 @@ class TestBatch(FrappeTestCase):
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 @@ class TestBatch(FrappeTestCase):
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 @@ class TestBatch(FrappeTestCase):
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 @@ class TestBatch(FrappeTestCase):
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 @@ def create_batch(item_code, rate, create_item_price_for_batch):
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 @@ def make_new_batch(**args):
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.json b/erpnext/stock/doctype/bin/bin.json
index 56dc71c57e1..d822f4a6095 100644
--- a/erpnext/stock/doctype/bin/bin.json
+++ b/erpnext/stock/doctype/bin/bin.json
@@ -1,6 +1,6 @@
{
"actions": [],
- "autoname": "MAT-BIN-.YYYY.-.#####",
+ "autoname": "hash",
"creation": "2013-01-10 16:34:25",
"doctype": "DocType",
"engine": "InnoDB",
@@ -171,11 +171,11 @@
"idx": 1,
"in_create": 1,
"links": [],
- "modified": "2022-01-30 17:04:54.715288",
+ "modified": "2022-03-30 07:22:23.868602",
"modified_by": "Administrator",
"module": "Stock",
"name": "Bin",
- "naming_rule": "Expression (old style)",
+ "naming_rule": "Random",
"owner": "Administrator",
"permissions": [
{
diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py
index 3bc15a80250..6ea4525606f 100644
--- a/erpnext/stock/doctype/bin/bin.py
+++ b/erpnext/stock/doctype/bin/bin.py
@@ -12,93 +12,109 @@ from frappe.utils import flt
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)
+ & (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 == 1)
+ & (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 @@ def update_qty(bin_name, args):
# 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 ec0d8a88e3f..b79dee81e21 100644
--- a/erpnext/stock/doctype/bin/test_bin.py
+++ b/erpnext/stock/doctype/bin/test_bin.py
@@ -9,10 +9,8 @@ from erpnext.stock.utils import _create_bin
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 492f90b3029..0e68e858065 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.controllers.selling_controller import SellingController
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 @@ class DeliveryNote(SellingController):
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 @@ class DeliveryNote(SellingController):
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 @@ class DeliveryNote(SellingController):
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 @@ class DeliveryNote(SellingController):
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,20 +264,27 @@ class DeliveryNote(SellingController):
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
- extra_amount = self.base_grand_total
+ for d in self.get("items"):
+ if not d.against_sales_invoice:
+ validate_against_credit_limit = True
+ extra_amount = self.base_grand_total
+ break
else:
for d in self.get("items"):
if not (d.against_sales_order or d.against_sales_invoice):
@@ -256,48 +292,58 @@ class DeliveryNote(SellingController):
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 +356,7 @@ class DeliveryNote(SellingController):
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 +373,16 @@ class DeliveryNote(SellingController):
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 +391,35 @@ def update_billed_amount_based_on_so(so_detail, update_modified=True):
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 +431,11 @@ def update_billed_amount_based_on_so(so_detail, update_modified=True):
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 +448,71 @@ def update_billed_amount_based_on_so(so_detail, update_modified=True):
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 +529,21 @@ def make_sales_invoice(source_name, target_doc=None):
# 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 +565,52 @@ def make_sales_invoice(source_name, target_doc=None):
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 +626,110 @@ def make_delivery_trip(source_name, target_doc=None):
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]},
+ },
+ "Delivery Note Item": {
+ "doctype": "Packing Slip Item",
+ "field_map": {
+ "item_code": "item_code",
+ "item_name": "item_name",
+ "description": "description",
+ "qty": "qty",
+ },
},
- "validation": {
- "docstatus": ["=", 0]
- }
},
-
- "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 += '
' + user.email
+ pickup_contact_display += "
" + user.email
if user.phone:
- pickup_contact_display += '
' + user.phone
+ pickup_contact_display += "
" + user.phone
if user.mobile_no and not user.phone:
- pickup_contact_display += '
' + user.mobile_no
+ pickup_contact_display += "
" + 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 += '
' + contact.email_id
+ delivery_contact_display += "
" + contact.email_id
if contact.phone:
- delivery_contact_display += '
' + contact.phone
+ delivery_contact_display += "
" + contact.phone
if contact.mobile_no and not contact.phone:
- delivery_contact_display += '
' + contact.mobile_no
+ delivery_contact_display += "
" + contact.mobile_no
target.delivery_contact = delivery_contact_display
if source.shipping_address_name:
@@ -648,38 +739,44 @@ def make_shipment(source_name, target_doc=None):
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]},
+ },
+ "Delivery Note Item": {
+ "doctype": "Shipment Delivery Note",
+ "field_map": {
+ "name": "prevdoc_detail_docname",
+ "parent": "prevdoc_docname",
+ "parenttype": "prevdoc_doctype",
+ "base_amount": "grand_total",
+ },
},
- "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"
- }
- }
- }, 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 +785,12 @@ def update_delivery_note_status(docname, status):
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 +800,16 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
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 +818,18 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
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 +837,20 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
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 +860,52 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
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 ca61a369288..fd44e9cee5c 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 @@ from frappe import _
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 82f4e7dd294..b5a45578c6d 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 @@ class TestDeliveryNote(FrappeTestCase):
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 @@ class TestDeliveryNote(FrappeTestCase):
# 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 @@ class TestDeliveryNote(FrappeTestCase):
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 @@ class TestDeliveryNote(FrappeTestCase):
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 @@ class TestDeliveryNote(FrappeTestCase):
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 @@ class TestDeliveryNote(FrappeTestCase):
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 @@ class TestDeliveryNote(FrappeTestCase):
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 @@ class TestDeliveryNote(FrappeTestCase):
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 @@ class TestDeliveryNote(FrappeTestCase):
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 @@ class TestDeliveryNote(FrappeTestCase):
# 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 @@ class TestDeliveryNote(FrappeTestCase):
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 @@ class TestDeliveryNote(FrappeTestCase):
# 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 @@ class TestDeliveryNote(FrappeTestCase):
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 @@ class TestDeliveryNote(FrappeTestCase):
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 @@ class TestDeliveryNote(FrappeTestCase):
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 @@ class TestDeliveryNote(FrappeTestCase):
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 @@ class TestDeliveryNote(FrappeTestCase):
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 @@ class TestDeliveryNote(FrappeTestCase):
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 @@ class TestDeliveryNote(FrappeTestCase):
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 @@ class TestDeliveryNote(FrappeTestCase):
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 @@ class TestDeliveryNote(FrappeTestCase):
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 @@ class TestDeliveryNote(FrappeTestCase):
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 @@ class TestDeliveryNote(FrappeTestCase):
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 @@ class TestDeliveryNote(FrappeTestCase):
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 @@ def create_delivery_note(**args):
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 @@ def create_delivery_note(**args):
dn.submit()
return dn
+
test_dependencies = ["Product Bundle"]
diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
index f1f5d96e628..e2eb2a4bbb2 100644
--- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
+++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
@@ -74,6 +74,7 @@
"against_sales_invoice",
"si_detail",
"dn_detail",
+ "pick_list_item",
"section_break_40",
"batch_no",
"serial_no",
@@ -762,13 +763,22 @@
"fieldtype": "Check",
"label": "Grant Commission",
"read_only": 1
+ },
+ {
+ "fieldname": "pick_list_item",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Pick List Item",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
- "modified": "2022-02-24 14:42:20.211085",
+ "modified": "2022-03-31 18:36:24.671913",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note Item",
diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.py b/erpnext/stock/doctype/delivery_trip/delivery_trip.py
index c749b2e6706..73b250db54b 100644
--- a/erpnext/stock/doctype/delivery_trip/delivery_trip.py
+++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.py
@@ -17,9 +17,12 @@ class DeliveryTrip(Document):
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 @@ class DeliveryTrip(Document):
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 @@ class DeliveryTrip(Document):
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 @@ class DeliveryTrip(Document):
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 @@ class DeliveryTrip(Document):
# 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 @@ class DeliveryTrip(Document):
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 @@ class DeliveryTrip(Document):
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 @@ class DeliveryTrip(Document):
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 @@ class DeliveryTrip(Document):
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 @@ class DeliveryTrip(Document):
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 @@ class DeliveryTrip(Document):
return directions[0] if directions else False
-
@frappe.whitelist()
def get_contact_and_address(name):
out = frappe._dict()
@@ -265,7 +266,10 @@ def get_default_contact(out, name):
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 @@ def get_default_address(out, name):
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 @@ def get_default_address(out, name):
@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 = """
%(first_name)s %(last_name)s %(phone)s
%(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 = (
+ """
%(first_name)s %(last_name)s %(phone)s
%(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 @@ def sanitize_address(address):
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('
')
+ address = address.split("
")
# Only get the first 3 blocks of the address
- return ', '.join(address[:3])
+ return ", ".join(address[:3])
@frappe.whitelist()
@@ -349,11 +358,15 @@ def notify_customers(delivery_trip):
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 @@ def notify_customers(delivery_trip):
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 notify_customers(delivery_trip):
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 dcdff4a0f1e..555361afbcd 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 @@ class TestDeliveryTrip(FrappeTestCase):
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 @@ def create_address(driver):
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 @@ def create_delivery_trip(driver, address, contact=None):
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.js b/erpnext/stock/doctype/item/item.js
index 9e8b3bd4637..23301a6a78e 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -55,10 +55,15 @@ frappe.ui.form.on("Item", {
if (frm.doc.has_variants) {
frm.set_intro(__("This Item is a Template and cannot be used in transactions. Item attributes will be copied over into the variants unless 'No Copy' is set"), true);
+
frm.add_custom_button(__("Show Variants"), function() {
frappe.set_route("List", "Item", {"variant_of": frm.doc.name});
}, __("View"));
+ frm.add_custom_button(__("Item Variant Settings"), function() {
+ frappe.set_route("Form", "Item Variant Settings");
+ }, __("View"));
+
frm.add_custom_button(__("Variant Details Report"), function() {
frappe.set_route("query-report", "Item Variant Details", {"item": frm.doc.name});
}, __("View"));
@@ -110,6 +115,13 @@ frappe.ui.form.on("Item", {
}
});
}, __('Actions'));
+ } else {
+ frm.add_custom_button(__("Website Item"), function() {
+ frappe.db.get_value("Website Item", {item_code: frm.doc.name}, "name", (d) => {
+ if (!d.name) frappe.throw(__("Website Item not found"));
+ frappe.set_route("Form", "Website Item", d.name);
+ });
+ }, __("View"));
}
erpnext.item.edit_prices_button(frm);
@@ -131,12 +143,6 @@ frappe.ui.form.on("Item", {
frappe.set_route('Form', 'Item', new_item.name);
});
- if(frm.doc.has_variants) {
- frm.add_custom_button(__("Item Variant Settings"), function() {
- frappe.set_route("Form", "Item Variant Settings");
- }, __("View"));
- }
-
const stock_exists = (frm.doc.__onload
&& frm.doc.__onload.stock_exists) ? 1 : 0;
diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json
index 524c3d14236..06da8ee9c3e 100644
--- a/erpnext/stock/doctype/item/item.json
+++ b/erpnext/stock/doctype/item/item.json
@@ -645,7 +645,6 @@
},
{
"collapsible": 1,
- "default": "eval:!doc.is_fixed_asset",
"fieldname": "sales_details",
"fieldtype": "Section Break",
"label": "Sales Details",
@@ -992,4 +991,4 @@
"states": [],
"title_field": "item_name",
"track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 3abeecd7424..b2f5fb7d202 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -3,7 +3,7 @@
import copy
import json
-from typing import List
+from typing import Dict, List, Optional
import frappe
from frappe import _
@@ -18,6 +18,7 @@ from frappe.utils import (
now_datetime,
nowtime,
strip,
+ strip_html,
)
from frappe.utils.html_utils import clean_html
@@ -44,13 +45,15 @@ class StockExistsForTemplate(frappe.ValidationError):
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,19 +63,15 @@ class Item(Document):
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
self.item_code = strip(self.item_code)
self.name = self.item_code
- def before_insert(self):
- 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)
@@ -84,7 +83,7 @@ class Item(Document):
if not self.item_name:
self.item_name = self.item_code
- if not self.description:
+ if not strip_html(cstr(self.description)).strip():
self.description = self.item_name
self.validate_uom()
@@ -128,8 +127,8 @@ class Item(Document):
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 +140,27 @@ class Item(Document):
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 +173,30 @@ class Item(Document):
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 +214,21 @@ class Item(Document):
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 +248,7 @@ class Item(Document):
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 +256,7 @@ class Item(Document):
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 +274,7 @@ class Item(Document):
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 +289,31 @@ class Item(Document):
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 +326,32 @@ class Item(Document):
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 +360,39 @@ class Item(Document):
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 +400,30 @@ class Item(Document):
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 +435,8 @@ class Item(Document):
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 +458,11 @@ class Item(Document):
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 +474,54 @@ class Item(Document):
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 += '
'
- msg += ', '.join([get_link_to_form("Stock Reconciliation", d.parent) for d in records]) + "
"
+ msg += "
"
+ msg += (
+ ", ".join([get_link_to_form("Stock Reconciliation", d.parent) for d in records]) + "
"
+ )
- 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 +540,8 @@ class Item(Document):
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 +554,14 @@ class Item(Document):
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 +579,19 @@ class Item(Document):
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 +599,41 @@ class Item(Document):
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 +643,61 @@ class Item(Document):
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 +706,13 @@ class Item(Document):
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 +744,8 @@ class Item(Document):
# 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 +764,18 @@ class Item(Document):
return """
| {0} |
{1} |
-
""".format(title, body)
+ """.format(
+ title, body
+ )
- rows = ''
+ rows = ""
for docname, attr_list in not_included.items():
link = "
{0}".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 = """
{0}
@@ -671,25 +786,37 @@ class Item(Document):
{3}
- """.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 +829,11 @@ class Item(Document):
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 +847,22 @@ class Item(Document):
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 +873,9 @@ class Item(Document):
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)
@@ -755,74 +887,129 @@ class Item(Document):
if self.is_new():
return
- fields = ("has_serial_no", "is_stock_item", "valuation_method", "has_batch_no")
+ restricted_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"
+ values = frappe.db.get_value("Item", self.name, restricted_fields, as_dict=True)
+ if not values:
+ return
- 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))))
+ 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"
+ )
- 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"]
+ changed_fields = [
+ field for field in restricted_fields if cstr(self.get(field)) != cstr(values.get(field))
+ ]
+ if not changed_fields:
+ return
+
+ if linked_doc := self._get_linked_submitted_documents(changed_fields):
+ changed_field_labels = [frappe.bold(self.meta.get_label(f)) for f in changed_fields]
+ msg = _(
+ "As there are existing submitted transactions against item {0}, you can not change the value of {1}."
+ ).format(self.name, ", ".join(changed_field_labels))
+
+ if linked_doc and isinstance(linked_doc, dict):
+ msg += "
"
+ msg += _("Example of a linked document: {0}").format(
+ frappe.get_desk_link(linked_doc.doctype, linked_doc.docname)
+ )
+
+ frappe.throw(msg, title=_("Linked with submitted documents"))
+
+ def _get_linked_submitted_documents(self, changed_fields: List[str]) -> Optional[Dict[str, str]]:
+ 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"]
+ if "is_stock_item" in changed_fields:
+ 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
+ if linked_doc := frappe.db.get_value(
+ "Stock Ledger Entry",
+ {"item_code": self.name, "is_cancelled": 0},
+ ["voucher_no as docname", "voucher_type as doctype"],
+ as_dict=True,
+ ):
+ return linked_doc
- elif frappe.db.get_value(doctype, filters):
- return True
+ elif linked_doc := frappe.db.get_value(
+ doctype,
+ filters,
+ ["parent as docname", "parenttype as doctype"],
+ as_dict=True,
+ ):
+ return linked_doc
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 +1030,13 @@ def validate_cancelled_item(item_code, docstatus=None):
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 +1044,14 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
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 +1059,29 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
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 +1090,25 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
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 +1131,51 @@ def invalidate_item_variants_cache_for_website(doc):
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 +1186,9 @@ def get_item_defaults(item_code, company):
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 +1197,11 @@ def set_item_default(item_code, company, fieldname, value):
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 +1213,36 @@ def get_item_details(item_code, company=None):
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 +1251,10 @@ def get_uom_conv_factor(uom, stock_uom):
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 +1266,12 @@ def get_item_attribute(parent, attribute_value=""):
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 +1282,7 @@ def update_variants(variants, template, publish_progress=True):
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 +1291,25 @@ def set_item_tax_from_hsn_code(item):
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 +1317,3 @@ def get_asset_naming_series():
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 e16f5bbfeb0..897acb74487 100644
--- a/erpnext/stock/doctype/item/item_dashboard.py
+++ b/erpnext/stock/doctype/item/item_dashboard.py
@@ -3,45 +3,35 @@ from frappe import _
def get_data():
return {
- 'heatmap': True,
- 'heatmap_message': _('This is based on stock movement. See {0} for details')\
- .format('
' + _('Stock Ledger') + ''),
- '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(
+ '
' + _("Stock Ledger") + ""
+ ),
+ "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": _("Stock Movement"), "items": ["Stock Entry", "Stock Reconciliation"]},
+ {"label": _("E-commerce"), "items": ["Website Item"]},
+ ],
}
diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py
index 05e6e76e21d..aa0a5490b61 100644
--- a/erpnext/stock/doctype/item/test_item.py
+++ b/erpnext/stock/doctype/item/test_item.py
@@ -30,6 +30,7 @@ from erpnext.stock.get_item_details import get_item_details
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 @@ def make_item(item_code=None, properties=None):
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 @@ def make_item(item_code=None, properties=None):
return item
+
class TestItem(FrappeTestCase):
def setUp(self):
super().setUp()
@@ -98,56 +102,91 @@ class TestItem(FrappeTestCase):
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 @@ class TestItem(FrappeTestCase):
"_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 @@ class TestItem(FrappeTestCase):
"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 @@ class TestItem(FrappeTestCase):
"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 @@ class TestItem(FrappeTestCase):
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 @@ class TestItem(FrappeTestCase):
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 @@ class TestItem(FrappeTestCase):
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 @@ class TestItem(FrappeTestCase):
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 @@ class TestItem(FrappeTestCase):
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 @@ class TestItem(FrappeTestCase):
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 @@ class TestItem(FrappeTestCase):
# 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 @@ class TestItem(FrappeTestCase):
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 @@ class TestItem(FrappeTestCase):
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 @@ class TestItem(FrappeTestCase):
@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 @@ class TestItem(FrappeTestCase):
@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 @@ class TestItem(FrappeTestCase):
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
@@ -692,12 +744,47 @@ class TestItem(FrappeTestCase):
self.assertTrue(get_data(warehouse="_Test Warehouse - _TC"))
self.assertTrue(get_data(item_group="All Item Groups"))
+ def test_empty_description(self):
+ item = make_item(properties={"description": "
"})
+ self.assertEqual(item.description, item.item_name)
+ item.description = ""
+ item.save()
+ self.assertEqual(item.description, item.item_name)
+
+ def test_item_type_field_change(self):
+ """Check if critical fields like `is_stock_item`, `has_batch_no` are not changed if transactions exist."""
+ from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_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
+ from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
+
+ transaction_creators = [
+ lambda i: make_purchase_receipt(item_code=i),
+ lambda i: make_purchase_invoice(item_code=i, update_stock=1),
+ lambda i: make_stock_entry(item_code=i, qty=1, target="_Test Warehouse - _TC"),
+ lambda i: create_delivery_note(item_code=i),
+ ]
+
+ properties = {"has_batch_no": 0, "allow_negative_stock": 1, "valuation_rate": 10}
+ for transaction_creator in transaction_creators:
+ item = make_item(properties=properties)
+ transaction = transaction_creator(item.name)
+ item.has_batch_no = 1
+ self.assertRaises(frappe.ValidationError, item.save)
+
+ transaction.cancel()
+ # should be allowed now
+ item.reload()
+ item.has_batch_no = 1
+ item.save()
+
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 +792,23 @@ def make_item_variant():
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 +822,8 @@ def create_item(item_code, is_stock_item=1, valuation_rate=0, warehouse="_Test W
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 766647b32e5..0f93bb9e95b 100644
--- a/erpnext/stock/doctype/item_alternative/item_alternative.py
+++ b/erpnext/stock/doctype/item_alternative/item_alternative.py
@@ -14,8 +14,7 @@ class ItemAlternative(Document):
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 @@ class ItemAlternative(Document):
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 @@ class ItemAlternative(Document):
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 501c1c1ad3c..32c58c5ae1d 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 @@ class TestItemAlternative(FrappeTestCase):
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=1,
+ 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 5a28a9e231c..391ff06918a 100644
--- a/erpnext/stock/doctype/item_attribute/item_attribute.py
+++ b/erpnext/stock/doctype/item_attribute/item_attribute.py
@@ -14,7 +14,9 @@ from erpnext.controllers.item_variant import (
)
-class ItemAttributeIncrementError(frappe.ValidationError): pass
+class ItemAttributeIncrementError(frappe.ValidationError):
+ pass
+
class ItemAttribute(Document):
def __setup__(self):
@@ -29,11 +31,12 @@ class ItemAttribute(Document):
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 @@ class ItemAttribute(Document):
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 055c22e0c5d..a30f0e999f7 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 @@ class TestItemAttribute(FrappeTestCase):
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.json b/erpnext/stock/doctype/item_barcode/item_barcode.json
index d89ca55a4f3..eef70c95d05 100644
--- a/erpnext/stock/doctype/item_barcode/item_barcode.json
+++ b/erpnext/stock/doctype/item_barcode/item_barcode.json
@@ -1,109 +1,42 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "field:barcode",
- "beta": 0,
- "creation": "2017-12-09 18:54:50.562438",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "autoname": "hash",
+ "creation": "2022-02-11 11:26:22.155183",
+ "doctype": "DocType",
+ "engine": "InnoDB",
+ "field_order": [
+ "barcode",
+ "barcode_type"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "barcode",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 1,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Barcode",
- "length": 0,
- "no_copy": 1,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
+ "fieldname": "barcode",
+ "fieldtype": "Data",
+ "in_global_search": 1,
+ "in_list_view": 1,
+ "label": "Barcode",
+ "no_copy": 1,
"unique": 1
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "barcode_type",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Barcode Type",
- "length": 0,
- "no_copy": 0,
- "options": "\nEAN\nUPC-A",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "fieldname": "barcode_type",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Barcode Type",
+ "options": "\nEAN\nUPC-A"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2018-11-13 06:03:09.814357",
- "modified_by": "Administrator",
- "module": "Stock",
- "name": "Item Barcode",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2022-04-01 05:54:27.314030",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Item Barcode",
+ "naming_rule": "Random",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/item_barcode/item_barcode.py b/erpnext/stock/doctype/item_barcode/item_barcode.py
index 64c39dabde1..c2c042143ea 100644
--- a/erpnext/stock/doctype/item_barcode/item_barcode.py
+++ b/erpnext/stock/doctype/item_barcode/item_barcode.py
@@ -6,4 +6,4 @@ from frappe.model.document import Document
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 469ccd8f2df..b65ba98a8bf 100644
--- a/erpnext/stock/doctype/item_manufacturer/item_manufacturer.py
+++ b/erpnext/stock/doctype/item_manufacturer/item_manufacturer.py
@@ -18,14 +18,17 @@ class ItemManufacturer(Document):
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 @@ class ItemManufacturer(Document):
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 @@ class ItemManufacturer(Document):
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 010e01a78ba..562f7b9e12f 100644
--- a/erpnext/stock/doctype/item_price/item_price.py
+++ b/erpnext/stock/doctype/item_price/item_price.py
@@ -13,7 +13,6 @@ class ItemPriceDuplicateItem(frappe.ValidationError):
class ItemPrice(Document):
-
def validate(self):
self.validate_item()
self.validate_dates()
@@ -32,22 +31,26 @@ class ItemPrice(Document):
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 @@ class ItemPrice(Document):
"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 6ceba3f8d3f..30d933e247d 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 @@ class TestItemPrice(FrappeTestCase):
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 @@ class TestItemPrice(FrappeTestCase):
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 @@ class TestItemPrice(FrappeTestCase):
"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 @@ class TestItemPrice(FrappeTestCase):
"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 @@ class TestItemPrice(FrappeTestCase):
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 @@ class TestItemPrice(FrappeTestCase):
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 @@ class TestItemPrice(FrappeTestCase):
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 @@ class TestItemPrice(FrappeTestCase):
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 be1517eb587..cec5e218cca 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 @@ from frappe.model.document import Document
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
{0} for copying in variants').format(d.field_name))
+ frappe.throw(_("Cannot set the field
{0} 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 7aff95d1e81..b3af309359a 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 @@ class LandedCostVoucher(Document):
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 @@ class LandedCostVoucher(Document):
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 @@ class LandedCostVoucher(Document):
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 += "
" + _("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 += "
" + _(
+ "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 @@ class LandedCostVoucher(Document):
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 @@ class LandedCostVoucher(Document):
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 @@ class LandedCostVoucher(Document):
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}
{0} has submitted Assets. Remove Item
{1} from table to continue.').format(
- item.receipt_document, item.item_code, item.receipt_document_type))
+ frappe.throw(
+ _(
+ "{2}
{0} has submitted Assets. Remove Item
{1} 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 6dc4fee5697..1af99534516 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 @@ class TestLandedCostVoucher(FrappeTestCase):
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 @@ class TestLandedCostVoucher(FrappeTestCase):
# 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 @@ class TestLandedCostVoucher(FrappeTestCase):
"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 @@ class TestLandedCostVoucher(FrappeTestCase):
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 @@ class TestLandedCostVoucher(FrappeTestCase):
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 @@ class TestLandedCostVoucher(FrappeTestCase):
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 @@ class TestLandedCostVoucher(FrappeTestCase):
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 @@ class TestLandedCostVoucher(FrappeTestCase):
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 @@ class TestLandedCostVoucher(FrappeTestCase):
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 @@ class TestLandedCostVoucher(FrappeTestCase):
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 @@ class TestLandedCostVoucher(FrappeTestCase):
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 @@ class TestLandedCostVoucher(FrappeTestCase):
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 @@ def create_landed_cost_voucher(receipt_document_type, receipt_document, company,
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 @@ def create_landed_cost_voucher(receipt_document_type, receipt_document, company,
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 @@ def distribute_landed_cost_on_items(lcv):
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 66323478c83..e176b28b85a 100644
--- a/erpnext/stock/doctype/manufacturer/test_manufacturer.py
+++ b/erpnext/stock/doctype/manufacturer/test_manufacturer.py
@@ -5,5 +5,6 @@ import unittest
# 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 49fefae550c..a70ff171a9b 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.manufacturing.doctype.work_order.work_order import get_item_details
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 @@ class MaterialRequest(BuyingController):
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 @@ class MaterialRequest(BuyingController):
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 @@ class MaterialRequest(BuyingController):
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 @@ class MaterialRequest(BuyingController):
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 @@ class MaterialRequest(BuyingController):
# 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 @@ class MaterialRequest(BuyingController):
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,88 @@ class MaterialRequest(BuyingController):
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 +290,55 @@ def update_completed_and_requested_qty(stock_entry, method):
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 +351,7 @@ def make_purchase_order(source_name, target_doc=None, args=None):
# 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 +359,65 @@ def make_purchase_order(source_name, target_doc=None, args=None):
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 +428,59 @@ def make_purchase_order_based_on_supplier(source_name, target_doc=None, args=Non
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 +492,8 @@ def get_material_requests_based_on_supplier(doctype, txt, searchfield, start, pa
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 +504,16 @@ def get_material_requests_based_on_supplier(doctype, txt, searchfield, start, pa
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 +522,63 @@ def get_default_supplier_query(doctype, txt, searchfield, start, page_len, filte
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 +592,7 @@ def make_stock_entry(source_name, target_doc=None):
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 +601,119 @@ def make_stock_entry(source_name, target_doc=None):
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")), '
' + ', '.join(work_orders_list)))
+ msgprint(
+ _("The following {0} were created: {1}").format(
+ frappe.bold(_("Work Orders")), "
" + ", ".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:
{0}")
- .format(new_line_sep(errors)))
+ frappe.throw(
+ _("Work Order cannot be created for following reason:
{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 c1ce0a93e21..b073e6a22ee 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 @@ from frappe import _
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 866f3ab2d57..78af1532ea8 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 @@ class TestMaterialRequest(FrappeTestCase):
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 @@ class TestMaterialRequest(FrappeTestCase):
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 @@ class TestMaterialRequest(FrappeTestCase):
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 @@ class TestMaterialRequest(FrappeTestCase):
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 @@ class TestMaterialRequest(FrappeTestCase):
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 @@ class TestMaterialRequest(FrappeTestCase):
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 @@ class TestMaterialRequest(FrappeTestCase):
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 @@ class TestMaterialRequest(FrappeTestCase):
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 @@ class TestMaterialRequest(FrappeTestCase):
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 @@ class TestMaterialRequest(FrappeTestCase):
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 @@ class TestMaterialRequest(FrappeTestCase):
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 @@ class TestMaterialRequest(FrappeTestCase):
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 @@ class TestMaterialRequest(FrappeTestCase):
# 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 @@ class TestMaterialRequest(FrappeTestCase):
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 @@ class TestMaterialRequest(FrappeTestCase):
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 @@ class TestMaterialRequest(FrappeTestCase):
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 @@ class TestMaterialRequest(FrappeTestCase):
# 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 @@ class TestMaterialRequest(FrappeTestCase):
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 @@ class TestMaterialRequest(FrappeTestCase):
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 @@ class TestMaterialRequest(FrappeTestCase):
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 @@ class TestMaterialRequest(FrappeTestCase):
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 @@ class TestMaterialRequest(FrappeTestCase):
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 @@ class TestMaterialRequest(FrappeTestCase):
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 @@ class TestMaterialRequest(FrappeTestCase):
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 @@ class TestMaterialRequest(FrappeTestCase):
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 @@ class TestMaterialRequest(FrappeTestCase):
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 @@ class TestMaterialRequest(FrappeTestCase):
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 @@ class TestMaterialRequest(FrappeTestCase):
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 @@ class TestMaterialRequest(FrappeTestCase):
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 @@ class TestMaterialRequest(FrappeTestCase):
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 @@ class TestMaterialRequest(FrappeTestCase):
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 32407d0fb09..08c9ed27427 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 @@ from frappe.model.document import Document
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 f9c00c59bac..026dd4e122a 100644
--- a/erpnext/stock/doctype/packed_item/packed_item.py
+++ b/erpnext/stock/doctype/packed_item/packed_item.py
@@ -23,7 +23,9 @@ def make_packing_list(doc):
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 @@ def make_packing_list(doc):
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 @@ def make_packing_list(doc):
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 @@ def get_indexed_packed_items_table(doc):
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 @@ def reset_packing_list(doc):
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 @@ def add_packed_item_row(doc, packing_item, main_item_row, packed_items_table, re
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 @@ def update_packed_item_basic_data(main_item_row, pi_row, packing_item, item_data
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 @@ def update_packed_item_stock_data(main_item_row, pi_row, packing_item, item_data
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 @@ def update_packed_item_price_data(pi_row, item_data, doc):
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 @@ def update_product_bundle_rate(parent_items_price, pi_row):
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 @@ def set_product_bundle_rate_amount(doc, parent_items_price):
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 @@ def get_items_from_product_bundle(row):
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 5f1b9542d6a..fe1b0d9f792 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 @@ from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
class TestPackedItem(FrappeTestCase):
"Test impact on Packed Items table in various scenarios."
+
@classmethod
def setUpClass(cls) -> None:
super().setUpClass()
@@ -39,8 +40,7 @@ class TestPackedItem(FrappeTestCase):
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 @@ class TestPackedItem(FrappeTestCase):
"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 @@ class TestPackedItem(FrappeTestCase):
"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 @@ class TestPackedItem(FrappeTestCase):
"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 @@ class TestPackedItem(FrappeTestCase):
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 @@ class TestPackedItem(FrappeTestCase):
# 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 @@ class TestPackedItem(FrappeTestCase):
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 @@ class TestPackedItem(FrappeTestCase):
"warehouse": self.warehouse,
"qty": 1,
"rate": 100,
- }
+ },
]
so = make_sales_order(item_list=item_list, warehouse=self.warehouse)
@@ -224,7 +219,7 @@ class TestPackedItem(FrappeTestCase):
"warehouse": self.warehouse,
"qty": 1,
"rate": 100,
- }
+ },
]
so = make_sales_order(item_list=item_list, warehouse=self.warehouse)
@@ -246,11 +241,10 @@ class TestPackedItem(FrappeTestCase):
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 b092862415a..e9ccf5fc779 100644
--- a/erpnext/stock/doctype/packing_slip/packing_slip.py
+++ b/erpnext/stock/doctype/packing_slip/packing_slip.py
@@ -10,14 +10,13 @@ from frappe.utils import cint, flt
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 @@ class PackingSlip(Document):
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 @@ class PackingSlip(Document):
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 @@ class PackingSlip(Document):
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 @@ class PackingSlip(Document):
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 @@ class PackingSlip(Document):
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 @@ class PackingSlip(Document):
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 3a496866cf1..33d7745c628 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -19,6 +19,7 @@ from erpnext.stock.get_item_details import get_conversion_factor
# TODO: Prioritize SO or WO group warehouse
+
class PickList(Document):
def validate(self):
self.validate_for_qty()
@@ -27,9 +28,14 @@ class PickList(Document):
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:
- frappe.throw("Row " + str(location.idx) + " has been picked already!")
+ 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 #{}: item {} has been picked already.").format(location.idx, location.item_code)
+ )
def before_submit(self):
for item in self.locations:
@@ -39,44 +45,61 @@ class PickList(Document):
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 location in self.get('locations'):
- if location.sales_order_item:
- self.update_so(location.sales_order_item,0,location.item_code)
+ # 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 {}. Check if there is any other pick list created for {}"
+ ).format(item_code, 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 +109,26 @@ class PickList(Document):
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 +136,28 @@ class PickList(Document):
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 +184,9 @@ class PickList(Document):
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 +198,7 @@ class PickList(Document):
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 +222,47 @@ def validate_item_locations(pick_list):
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 +272,69 @@ def get_items_with_location_and_quantity(item_doc, item_location_map, docstatus)
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 +342,17 @@ def get_available_item_locations_for_serialized_item(item_code, from_warehouses,
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 +373,94 @@ def get_available_item_locations_for_batched_item(item_code, from_warehouses, re
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,68 +468,74 @@ def create_delivery_note(source_name, target_doc=None):
# 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)
if dn_item:
+ dn_item.pick_list_item = location.name
dn_item.warehouse = location.warehouse
dn_item.qty = flt(location.picked_qty) / (flt(location.conversion_factor) or 1)
dn_item.batch_no = location.batch_no
@@ -471,7 +546,7 @@ def map_pl_locations(pick_list,item_mapper,delivery_note,sales_order = None):
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 +554,17 @@ def create_stock_entry(pick_list):
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 +574,11 @@ def create_stock_entry(pick_list):
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 +594,27 @@ def get_pending_work_orders(doctype, txt, searchfield, start, page_length, filte
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 +623,37 @@ def get_item_details(item_code, uom=None):
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 +662,11 @@ def update_stock_entry_based_on_work_order(pick_list, stock_entry):
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 +680,36 @@ def update_stock_entry_based_on_work_order(pick_list, stock_entry):
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
@@ -637,4 +721,4 @@ def update_common_item_properties(item, location):
item.material_request = location.material_request
item.serial_no = location.serial_no
item.batch_no = location.batch_no
- item.material_request_item = location.material_request_item
\ No newline at end of file
+ item.material_request_item = location.material_request_item
diff --git a/erpnext/stock/doctype/pick_list/pick_list_dashboard.py b/erpnext/stock/doctype/pick_list/pick_list_dashboard.py
index ec3047e98fe..92e57bed220 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 f60104c09ac..ec5011b93d6 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 @@ from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import (
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 @@ class TestPickList(FrappeTestCase):
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 @@ class TestPickList(FrappeTestCase):
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 @@ class TestPickList(FrappeTestCase):
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 @@ class TestPickList(FrappeTestCase):
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 @@ class TestPickList(FrappeTestCase):
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 @@ class TestPickList(FrappeTestCase):
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 @@ class TestPickList(FrappeTestCase):
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 @@ class TestPickList(FrappeTestCase):
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,120 @@ class TestPickList(FrappeTestCase):
_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)
+ self.assertEqual(dn_item.pick_list_item, pick_list.locations[dn_item.idx - 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 8a3172e9e22..554055fd839 100644
--- a/erpnext/stock/doctype/price_list/price_list.py
+++ b/erpnext/stock/doctype/price_list/price_list.py
@@ -31,9 +31,11 @@ class PriceList(Document):
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 @@ class PriceList(Document):
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 b8218b942e7..93660930c79 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 @@ import frappe
# 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.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
index 0182ed55a18..51ec598f726 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
@@ -200,7 +200,7 @@ erpnext.stock.PurchaseReceiptController = class PurchaseReceiptController extend
cur_frm.add_custom_button(__('Reopen'), this.reopen_purchase_receipt, __("Status"))
}
- this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted==="Yes");
+ this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted);
}
make_purchase_invoice() {
@@ -298,10 +298,10 @@ cur_frm.fields_dict['items'].grid.get_field('bom').get_query = function(doc, cdt
frappe.provide("erpnext.buying");
frappe.ui.form.on("Purchase Receipt", "is_subcontracted", function(frm) {
- if (frm.doc.is_subcontracted === "Yes") {
+ if (frm.doc.is_subcontracted) {
erpnext.buying.get_default_bom(frm);
}
- frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted==="Yes");
+ frm.toggle_reqd("supplier_warehouse", frm.doc.is_subcontracted);
});
frappe.ui.form.on('Purchase Receipt Item', {
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
index 6d4b4a19bd2..6e5f6f5b529 100755
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
@@ -437,17 +437,16 @@
"fieldtype": "Column Break"
},
{
- "default": "No",
+ "default": "0",
"fieldname": "is_subcontracted",
- "fieldtype": "Select",
- "label": "Raw Materials Consumed",
+ "fieldtype": "Check",
+ "label": "Is Subcontracted",
"oldfieldname": "is_subcontracted",
"oldfieldtype": "Select",
- "options": "No\nYes",
"print_hide": 1
},
{
- "depends_on": "eval:doc.is_subcontracted==\"Yes\"",
+ "depends_on": "eval:doc.is_subcontracted",
"fieldname": "supplier_warehouse",
"fieldtype": "Link",
"label": "Supplier Warehouse",
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 4bf37fee2cf..1e1c0b9f7c7 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.buying.utils import check_on_hold_or_closed_status
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 @@ class PurchaseReceipt(BuyingController):
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 @@ class PurchaseReceipt(BuyingController):
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 @@ class PurchaseReceipt(BuyingController):
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 @@ class PurchaseReceipt(BuyingController):
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 @@ class PurchaseReceipt(BuyingController):
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 @@ class PurchaseReceipt(BuyingController):
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 @@ class PurchaseReceipt(BuyingController):
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 @@ class PurchaseReceipt(BuyingController):
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 @@ class PurchaseReceipt(BuyingController):
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 @@ class PurchaseReceipt(BuyingController):
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 @@ class PurchaseReceipt(BuyingController):
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 @@ class PurchaseReceipt(BuyingController):
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 @@ class PurchaseReceipt(BuyingController):
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 @@ class PurchaseReceipt(BuyingController):
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 @@ class PurchaseReceipt(BuyingController):
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 @@ class PurchaseReceipt(BuyingController):
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 @@ class PurchaseReceipt(BuyingController):
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 @@ class PurchaseReceipt(BuyingController):
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 @@ class PurchaseReceipt(BuyingController):
# 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 @@ class PurchaseReceipt(BuyingController):
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 @@ class PurchaseReceipt(BuyingController):
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 @@ class PurchaseReceipt(BuyingController):
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 @@ class PurchaseReceipt(BuyingController):
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 @@ class PurchaseReceipt(BuyingController):
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 @@ class PurchaseReceipt(BuyingController):
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 @@ class PurchaseReceipt(BuyingController):
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 @@ class PurchaseReceipt(BuyingController):
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 @@ class PurchaseReceipt(BuyingController):
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 @@ def update_billed_amount_based_on_po(po_detail, update_modified=True):
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 @@ def update_billing_percentage(pr_doc, update_modified=True):
# 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 @@ def update_billing_percentage(pr_doc, update_modified=True):
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 @@ def make_purchase_invoice(source_name, target_doc=None):
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 make_purchase_invoice(source_name, target_doc=None):
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 @@ def make_purchase_invoice(source_name, target_doc=None):
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 @@ def update_purchase_receipt_status(docname, status):
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 @@ def get_item_account_wise_additional_cost(purchase_document):
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 @@ def get_item_account_wise_additional_cost(purchase_document):
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 bdc5435988d..06ba9365561 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 @@ from frappe import _
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 0017fa7ee11..f3faba4f8d5 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 @@ class TestPurchaseReceipt(FrappeTestCase):
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 @@ class TestPurchaseReceipt(FrappeTestCase):
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 @@ class TestPurchaseReceipt(FrappeTestCase):
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"
- )
- old_template_in_supplier = frappe.db.get_value(
- "Supplier",
- "_Test Supplier",
- "payment_terms"
+ "Payment Terms Template", "_Test Payment Terms Template For Purchase Invoice"
)
+ 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 @@ class TestPurchaseReceipt(FrappeTestCase):
# 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 @@ class TestPurchaseReceipt(FrappeTestCase):
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 @@ class TestPurchaseReceipt(FrappeTestCase):
"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 @@ class TestPurchaseReceipt(FrappeTestCase):
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 @@ class TestPurchaseReceipt(FrappeTestCase):
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 @@ class TestPurchaseReceipt(FrappeTestCase):
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 @@ class TestPurchaseReceipt(FrappeTestCase):
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 @@ class TestPurchaseReceipt(FrappeTestCase):
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 @@ class TestPurchaseReceipt(FrappeTestCase):
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 @@ class TestPurchaseReceipt(FrappeTestCase):
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 @@ class TestPurchaseReceipt(FrappeTestCase):
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
- )
- pr = make_purchase_receipt(
- item_code="_Test FG Item", qty=10,
- rate=500, is_subcontracted="Yes"
+ 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=1)
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 @@ class TestPurchaseReceipt(FrappeTestCase):
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,
- is_subcontracted="Yes",
+ qty=10,
+ rate=0,
+ is_subcontracted=1,
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 @@ class TestPurchaseReceipt(FrappeTestCase):
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 @@ class TestPurchaseReceipt(FrappeTestCase):
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=1,
+ 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 @@ class TestPurchaseReceipt(FrappeTestCase):
"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 @@ class TestPurchaseReceipt(FrappeTestCase):
"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 @@ class TestPurchaseReceipt(FrappeTestCase):
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 @@ class TestPurchaseReceipt(FrappeTestCase):
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 @@ class TestPurchaseReceipt(FrappeTestCase):
# 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 @@ class TestPurchaseReceipt(FrappeTestCase):
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 @@ class TestPurchaseReceipt(FrappeTestCase):
# 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 @@ class TestPurchaseReceipt(FrappeTestCase):
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 @@ class TestPurchaseReceipt(FrappeTestCase):
# 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 @@ class TestPurchaseReceipt(FrappeTestCase):
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 @@ class TestPurchaseReceipt(FrappeTestCase):
{
"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,6 +647,44 @@ class TestPurchaseReceipt(FrappeTestCase):
return_pr.cancel()
pr.cancel()
+ def test_purchase_receipt_for_rejected_gle_without_accepted_warehouse(self):
+ from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse
+
+ 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",
+ ).name
+
+ pr = make_purchase_receipt(
+ company="_Test Company with perpetual inventory",
+ warehouse="Stores - TCP1",
+ received_qty=2,
+ rejected_qty=2,
+ rejected_warehouse=rejected_warehouse,
+ do_not_save=True,
+ )
+
+ pr.items[0].qty = 0.0
+ pr.items[0].warehouse = ""
+ pr.submit()
+
+ actual_qty = frappe.db.get_value(
+ "Stock Ledger Entry",
+ {
+ "voucher_type": "Purchase Receipt",
+ "voucher_no": pr.name,
+ "warehouse": pr.items[0].rejected_warehouse,
+ "is_cancelled": 0,
+ },
+ "actual_qty",
+ )
+
+ self.assertEqual(actual_qty, 2)
+ self.assertFalse(pr.items[0].warehouse)
+ pr.cancel()
def test_purchase_return_for_serialized_items(self):
def _check_serial_no_values(serial_no, field_values):
@@ -710,24 +698,22 @@ class TestPurchaseReceipt(FrappeTestCase):
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 +721,12 @@ class TestPurchaseReceipt(FrappeTestCase):
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 +734,7 @@ class TestPurchaseReceipt(FrappeTestCase):
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 +750,16 @@ class TestPurchaseReceipt(FrappeTestCase):
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 +821,15 @@ class TestPurchaseReceipt(FrappeTestCase):
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 +846,9 @@ class TestPurchaseReceipt(FrappeTestCase):
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 +856,52 @@ class TestPurchaseReceipt(FrappeTestCase):
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 +911,18 @@ class TestPurchaseReceipt(FrappeTestCase):
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 +942,27 @@ class TestPurchaseReceipt(FrappeTestCase):
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 +970,24 @@ class TestPurchaseReceipt(FrappeTestCase):
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 +1003,7 @@ class TestPurchaseReceipt(FrappeTestCase):
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 +1020,17 @@ class TestPurchaseReceipt(FrappeTestCase):
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 +1039,7 @@ class TestPurchaseReceipt(FrappeTestCase):
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 +1053,25 @@ class TestPurchaseReceipt(FrappeTestCase):
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 +1083,45 @@ class TestPurchaseReceipt(FrappeTestCase):
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 +1134,6 @@ class TestPurchaseReceipt(FrappeTestCase):
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 +1148,57 @@ class TestPurchaseReceipt(FrappeTestCase):
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=1,
+ 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 +1210,8 @@ class TestPurchaseReceipt(FrappeTestCase):
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 +1231,11 @@ class TestPurchaseReceipt(FrappeTestCase):
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 +1254,16 @@ class TestPurchaseReceipt(FrappeTestCase):
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 +1287,14 @@ class TestPurchaseReceipt(FrappeTestCase):
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 +1306,16 @@ class TestPurchaseReceipt(FrappeTestCase):
# 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 +1337,7 @@ class TestPurchaseReceipt(FrappeTestCase):
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 +1364,9 @@ class TestPurchaseReceipt(FrappeTestCase):
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 +1387,112 @@ class TestPurchaseReceipt(FrappeTestCase):
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")
@@ -1525,7 +1504,7 @@ def make_purchase_receipt(**args):
pr.set_posting_time = 1
pr.company = args.company or "_Test Company"
pr.supplier = args.supplier or "_Test Supplier"
- pr.is_subcontracted = args.is_subcontracted or "No"
+ pr.is_subcontracted = args.is_subcontracted or 0
pr.supplier_warehouse = args.supplier_warehouse or "_Test Warehouse 1 - _TC"
pr.currency = args.currency or "INR"
pr.is_return = args.is_return
@@ -1537,28 +1516,34 @@ def make_purchase_receipt(**args):
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 +1559,44 @@ def make_purchase_receipt(**args):
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/purchase_receipt/test_records.json b/erpnext/stock/doctype/purchase_receipt/test_records.json
index 724e3d729a2..990ad12b30e 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_records.json
+++ b/erpnext/stock/doctype/purchase_receipt/test_records.json
@@ -92,7 +92,7 @@
"currency": "INR",
"doctype": "Purchase Receipt",
"base_grand_total": 5000.0,
- "is_subcontracted": "Yes",
+ "is_subcontracted": 1,
"base_net_total": 5000.0,
"items": [
{
diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
index e5994b2dd48..03a4201ce5c 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
@@ -648,7 +648,7 @@
},
{
"default": "0",
- "depends_on": "eval:parent.is_subcontracted == 'Yes'",
+ "depends_on": "eval:parent.is_subcontracted",
"fieldname": "include_exploded_items",
"fieldtype": "Check",
"label": "Include Exploded Items",
diff --git a/erpnext/stock/doctype/putaway_rule/putaway_rule.py b/erpnext/stock/doctype/putaway_rule/putaway_rule.py
index 4e472a92dc1..623fbde2b0b 100644
--- a/erpnext/stock/doctype/putaway_rule/putaway_rule.py
+++ b/erpnext/stock/doctype/putaway_rule/putaway_rule.py
@@ -24,11 +24,16 @@ class PutawayRule(Document):
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 @@ class PutawayRule(Document):
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 @@ class PutawayRule(Document):
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 @@ def apply_putaway_rule(doctype, items, company, sync=None, purpose=None):
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 @@ def apply_putaway_rule(doctype, items, company, sync=None, purpose=None):
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 @@ def apply_putaway_rule(doctype, items, company, sync=None, purpose=None):
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 @@ def _items_changed(old, new, doctype: str) -> bool:
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 _items_changed(old, new, doctype: str) -> bool:
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 @@ def get_ordered_putaway_rules(item_code, company, source_warehouse=None):
# 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 @@ def add_row(item, to_allocate, warehouse, updated_table, rule=None, serial_nos=N
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 @@ def add_row(item, to_allocate, warehouse, updated_table, rule=None, serial_nos=N
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:") + "
"
formatted_item_rows = ""
@@ -247,7 +280,9 @@ def show_unassigned_items_message(items_not_accomodated):
formatted_item_rows += """
{0} |
{1} |
- """.format(item_link, frappe.bold(entry[1]))
+ """.format(
+ item_link, frappe.bold(entry[1])
+ )
msg += """
@@ -257,13 +292,17 @@ def show_unassigned_items_message(items_not_accomodated):
{2}
- """.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 0ec812c655a..ab0ca106a8b 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 @@ from erpnext.stock.get_item_details import get_conversion_factor
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 @@ class TestPutawayRule(FrappeTestCase):
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 @@ class TestPutawayRule(FrappeTestCase):
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 @@ class TestPutawayRule(FrappeTestCase):
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 @@ class TestPutawayRule(FrappeTestCase):
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 @@ class TestPutawayRule(FrappeTestCase):
"""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 @@ class TestPutawayRule(FrappeTestCase):
"""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 @@ class TestPutawayRule(FrappeTestCase):
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 @@ class TestPutawayRule(FrappeTestCase):
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 @@ class TestPutawayRule(FrappeTestCase):
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 @@ class TestPutawayRule(FrappeTestCase):
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 @@ class TestPutawayRule(FrappeTestCase):
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 @@ class TestPutawayRule(FrappeTestCase):
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 @@ class TestPutawayRule(FrappeTestCase):
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 @@ class TestPutawayRule(FrappeTestCase):
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 @@ class TestPutawayRule(FrappeTestCase):
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 @@ class TestPutawayRule(FrappeTestCase):
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 @@ def create_putaway_rule(**args):
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 4e3b80aa761..331d3e812b2 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
@@ -18,8 +18,8 @@ class QualityInspection(Document):
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 @@ class QualityInspection(Document):
@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 @@ class QualityInspection(Document):
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 @@ class QualityInspection(Document):
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 @@ class QualityInspection(Document):
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 @@ class QualityInspection(Document):
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 @@ class QualityInspection(Document):
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 @@ class QualityInspection(Document):
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 @@ class QualityInspection(Document):
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 @@ def make_quality_inspection(source_name, target_doc=None):
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 601ca054b53..144f13880b1 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 @@ class TestQualityInspection(FrappeTestCase):
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 @@ class TestQualityInspection(FrappeTestCase):
"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 @@ class TestQualityInspection(FrappeTestCase):
"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 @@ class TestQualityInspection(FrappeTestCase):
"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 @@ class TestQualityInspection(FrappeTestCase):
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 @@ class TestQualityInspection(FrappeTestCase):
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(**args):
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 7f8c871a93d..9b8f5d6378c 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 @@ from frappe.model.document import Document
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 7a0f5d08210..846be0b9bdc 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 @@ from erpnext.stock.utils import get_stock_balance, get_stock_value_on
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.json b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
index 0ba97d59a14..6148e16513c 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
@@ -1,6 +1,6 @@
{
"actions": [],
- "autoname": "REPOST-ITEM-VAL-.######",
+ "autoname": "hash",
"creation": "2022-01-11 15:03:38.273179",
"doctype": "DocType",
"editable_grid": 1,
@@ -177,11 +177,11 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2022-01-18 10:57:33.450907",
+ "modified": "2022-03-30 07:22:48.520266",
"modified_by": "Administrator",
"module": "Stock",
"name": "Repost Item Valuation",
- "naming_rule": "Expression (old style)",
+ "naming_rule": "Random",
"owner": "Administrator",
"permissions": [
{
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 f8ec7846971..54c00d1a21c 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 @@ class RepostItemValuation(Document):
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 @@ class RepostItemValuation(Document):
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:
@@ -61,16 +61,32 @@ class RepostItemValuation(Document):
repost(self)
+ def before_cancel(self):
+ self.check_pending_repost_against_cancelled_transaction()
+
+ def check_pending_repost_against_cancelled_transaction(self):
+ if self.status not in ("Queued", "In Progress"):
+ return
+
+ if not (self.voucher_no and self.voucher_no):
+ return
+
+ transaction_status = frappe.db.get_value(self.voucher_type, self.voucher_no, "docstatus")
+ if transaction_status == 2:
+ msg = _("Cannot cancel as processing of cancelled documents is pending.")
+ msg += "
" + _("Please try again in an hour.")
+ frappe.throw(msg, title=_("Pending processing"))
+
@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 +98,8 @@ class RepostItemValuation(Document):
"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 +110,10 @@ class RepostItemValuation(Document):
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 +123,14 @@ def repost(doc):
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 +140,47 @@ def repost(doc):
message = frappe.message_log.pop()
if traceback:
message += "
" + "Traceback:
" + 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 +194,14 @@ def repost_gl_entries(doc):
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 +209,20 @@ def notify_error_to_stock_managers(doc, traceback):
get_users_with_role("System Manager")
subject = _("Error while reposting item valuation")
- message = (_("Hi,") + "
"
- + _("An error has been appeared while reposting item valuation via {0}")
- .format(get_link_to_form(doc.doctype, doc.name)) + "
"
- + _("Please check the error message and take necessary actions to fix the error and then restart the reposting again.")
+ message = (
+ _("Hi,")
+ + "
"
+ + _("An error has been appeared while reposting item valuation via {0}").format(
+ get_link_to_form(doc.doctype, doc.name)
+ )
+ + "
"
+ + _(
+ "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 +230,8 @@ def repost_entries():
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 +239,19 @@ def repost_entries():
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 78b432d564c..55117ceb2e3 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
@@ -1,20 +1,25 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
-import unittest
import frappe
+from frappe.tests.utils import FrappeTestCase
from frappe.utils import nowdate
from erpnext.controllers.stock_controller import create_item_wise_repost_entries
+from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
from erpnext.stock.doctype.repost_item_valuation.repost_item_valuation import (
in_configured_timeslot,
)
+from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from erpnext.stock.utils import PendingRepostingError
-class TestRepostItemValuation(unittest.TestCase):
+class TestRepostItemValuation(FrappeTestCase):
+ def tearDown(self):
+ frappe.flags.dont_execute_stock_reposts = False
+
def test_repost_time_slot(self):
repost_settings = frappe.get_doc("Stock Reposting Settings")
@@ -153,7 +158,7 @@ class TestRepostItemValuation(unittest.TestCase):
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")
@@ -162,3 +167,22 @@ class TestRepostItemValuation(unittest.TestCase):
self.assertRaises(PendingRepostingError, stock_settings.save)
riv.set_status("Skipped")
+
+ def test_prevention_of_cancelled_transaction_riv(self):
+ frappe.flags.dont_execute_stock_reposts = True
+
+ item = make_item()
+ warehouse = "_Test Warehouse - _TC"
+ old = make_stock_entry(item_code=item.name, to_warehouse=warehouse, qty=2, rate=5)
+ _new = make_stock_entry(item_code=item.name, to_warehouse=warehouse, qty=5, rate=10)
+
+ old.cancel()
+
+ riv = frappe.get_last_doc(
+ "Repost Item Valuation", {"voucher_type": old.doctype, "voucher_no": old.name}
+ )
+ self.assertRaises(frappe.ValidationError, riv.cancel)
+
+ riv.db_set("status", "Skipped")
+ riv.reload()
+ riv.cancel() # it should cancel now
diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index 2808c219ea8..316c897da02 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.controllers.stock_controller import StockController
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 @@ class SerialNo(StockController):
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 @@ class SerialNo(StockController):
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 @@ class SerialNo(StockController):
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 @@ class SerialNo(StockController):
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 @@ class SerialNo(StockController):
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 @@ class SerialNo(StockController):
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 @@ class SerialNo(StockController):
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 @@ class SerialNo(StockController):
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 @@ def validate_serial_no(sle, item_det):
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 @@ def check_serial_no_validity_on_cancel(serial_no, sle):
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 @@ def allow_serial_nos_with_different_item(sle_serial_no, sle):
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 @@ def get_auto_serial_nos(serial_no_series, qty):
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 @@ def auto_make_serial_nos(args):
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 @@ def auto_make_serial_nos(args):
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:
{0}").format(get_items_html(form_links, item_code))
+ message = _("The following serial numbers were created:
{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 '''
+ body = ", ".join(serial_nos)
+ return """
{0}: {1} Serial Numbers
{2}
- '''.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 @@ def clean_serial_no_string(serial_no: str) -> str:
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 @@ def update_args_for_serial_no(serial_no_doc, serial_no, args, is_new=False):
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 @@ def update_serial_nos_after_submit(controller, parentfield):
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 @@ def auto_fetch_serial_number(
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 @@ def get_delivered_serial_nos(serial_nos):
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 @@ def get_pos_reserved_serial_nos(filters):
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 @@ def get_pos_reserved_serial_nos(filters):
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 @@ def fetch_serial_numbers(filters, qty, do_not_include=None):
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 @@ def fetch_serial_numbers(filters, qty, do_not_include=None):
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 7df0a56b7f3..68623fba11e 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.stock_entry.test_stock_entry import make_serialized_i
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 @@ class TestSerialNo(FrappeTestCase):
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 @@ class TestSerialNo(FrappeTestCase):
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 @@ class TestSerialNo(FrappeTestCase):
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 @@ class TestSerialNo(FrappeTestCase):
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 @@ class TestSerialNo(FrappeTestCase):
# 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 @@ class TestSerialNo(FrappeTestCase):
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 @@ class TestSerialNo(FrappeTestCase):
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 @@ class TestSerialNo(FrappeTestCase):
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 @@ class TestSerialNo(FrappeTestCase):
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 @@ class TestSerialNo(FrappeTestCase):
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 @@ class TestSerialNo(FrappeTestCase):
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 @@ class TestSerialNo(FrappeTestCase):
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 @@ class TestSerialNo(FrappeTestCase):
# 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 @@ class TestSerialNo(FrappeTestCase):
# 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 666de57f34f..42a67f42bec 100644
--- a/erpnext/stock/doctype/shipment/shipment.py
+++ b/erpnext/stock/doctype/shipment/shipment.py
@@ -17,22 +17,22 @@ class Shipment(Document):
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 @@ class Shipment(Document):
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 317abb6d03e..ae97e7af361 100644
--- a/erpnext/stock/doctype/shipment/test_shipment.py
+++ b/erpnext/stock/doctype/shipment/test_shipment.py
@@ -13,13 +13,14 @@ class TestShipment(FrappeTestCase):
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 @@ def create_test_delivery_note():
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 @@ def create_test_shipment(delivery_notes = None):
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_contact(customer_name):
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 @@ def create_shipment_item(item_name, company_name):
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.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index 324ca7ac599..1df56ef7b4c 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -214,7 +214,7 @@ frappe.ui.form.on('Stock Entry', {
if (frm.doc.docstatus === 1) {
if (frm.doc.add_to_transit && frm.doc.purpose=='Material Transfer' && frm.doc.per_transferred < 100) {
- frm.add_custom_button('End Transit', function() {
+ frm.add_custom_button(__('End Transit'), function() {
frappe.model.open_mapped_doc({
method: "erpnext.stock.doctype.stock_entry.stock_entry.make_stock_in_entry",
frm: frm
@@ -633,7 +633,7 @@ frappe.ui.form.on('Stock Entry Detail', {
// set allow_zero_valuation_rate to 0 if s_warehouse is selected.
let item = frappe.get_doc(cdt, cdn);
if (item.s_warehouse) {
- item.allow_zero_valuation_rate = 0;
+ frappe.model.set_value(cdt, cdn, "allow_zero_valuation_rate", 0);
}
},
@@ -646,21 +646,6 @@ frappe.ui.form.on('Stock Entry Detail', {
frm.events.calculate_basic_amount(frm, item);
},
- barcode: function(doc, cdt, cdn) {
- var d = locals[cdt][cdn];
- if (d.barcode) {
- frappe.call({
- method: "erpnext.stock.get_item_details.get_item_code",
- args: {"barcode": d.barcode },
- callback: function(r) {
- if (!r.exe){
- frappe.model.set_value(cdt, cdn, "item_code", r.message);
- }
- }
- });
- }
- },
-
uom: function(doc, cdt, cdn) {
var d = locals[cdt][cdn];
if(d.uom && d.item_code){
@@ -793,7 +778,7 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle
return {
"filters": {
"docstatus": 1,
- "is_subcontracted": "Yes",
+ "is_subcontracted": 1,
"company": me.frm.doc.company
}
};
@@ -845,8 +830,8 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle
}
scan_barcode() {
- let transaction_controller= new erpnext.TransactionController({frm:this.frm});
- transaction_controller.scan_barcode();
+ const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm});
+ barcode_scanner.process_scan();
}
on_submit() {
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 99cf4de5de7..1e624714d05 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -38,20 +38,28 @@ from erpnext.stock.utils import get_bin, get_incoming_rate
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 @@ class StockEntry(StockController):
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 @@ class StockEntry(StockController):
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 @@ class StockEntry(StockController):
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 @@ class StockEntry(StockController):
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 @@ class StockEntry(StockController):
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,107 +189,160 @@ class StockEntry(StockController):
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):
for item in self.get("items"):
if not flt(item.qty):
- frappe.throw(_("Row {0}: Qty is mandatory").format(item.idx))
+ frappe.throw(_("Row {0}: Qty is mandatory").format(item.idx), title=_("Zero quantity"))
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)
+ )
+ if not flt(item.transfer_qty):
+ frappe.throw(
+ _("Row {0}: Qty in Stock UOM can not be zero.").format(item.idx), title=_("Zero quantity")
+ )
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 +350,10 @@ class StockEntry(StockController):
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 +371,11 @@ class StockEntry(StockController):
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 +383,53 @@ class StockEntry(StockController):
for d in self.get("items"):
if not d.expense_account:
- frappe.throw(_("Please enter Difference Account or set default Stock Adjustment Account for company {0}")
- .format(frappe.bold(self.company)))
+ frappe.throw(
+ _(
+ "Please enter Difference Account or set default Stock Adjustment Account 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 +446,6 @@ class StockEntry(StockController):
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 +457,26 @@ class StockEntry(StockController):
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 +487,66 @@ class StockEntry(StockController):
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 +558,57 @@ class StockEntry(StockController):
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))
- + '
' + _("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),
+ )
+ + "
"
+ + _("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 +616,8 @@ class StockEntry(StockController):
@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 +634,52 @@ class StockEntry(StockController):
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 +694,21 @@ class StockEntry(StockController):
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 +724,11 @@ class StockEntry(StockController):
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 +760,10 @@ class StockEntry(StockController):
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 +777,120 @@ class StockEntry(StockController):
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 +898,7 @@ class StockEntry(StockController):
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 +916,7 @@ class StockEntry(StockController):
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 +935,17 @@ class StockEntry(StockController):
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 +953,16 @@ class StockEntry(StockController):
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 +970,31 @@ class StockEntry(StockController):
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 +1016,38 @@ class StockEntry(StockController):
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 +1077,55 @@ class StockEntry(StockController):
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 +1134,13 @@ class StockEntry(StockController):
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 +1160,95 @@ class StockEntry(StockController):
@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 +1259,57 @@ class StockEntry(StockController):
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 +1318,20 @@ class StockEntry(StockController):
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 +1340,36 @@ class StockEntry(StockController):
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 +1386,6 @@ class StockEntry(StockController):
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 = ''
if self.pro_doc and self.pro_doc.scrap_warehouse:
item["to_warehouse"] = self.pro_doc.scrap_warehouse
@@ -1136,7 +1398,7 @@ class StockEntry(StockController):
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 +1429,18 @@ class StockEntry(StockController):
"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 +1449,12 @@ class StockEntry(StockController):
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 +1469,7 @@ class StockEntry(StockController):
if not batch_qty:
continue
- if qty <=0:
+ if qty <= 0:
break
fg_qty = batch_qty
@@ -1214,23 +1483,27 @@ class StockEntry(StockController):
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 +1521,10 @@ class StockEntry(StockController):
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 +1538,18 @@ class StockEntry(StockController):
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 +1562,25 @@ class StockEntry(StockController):
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 +1594,7 @@ class StockEntry(StockController):
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 +1605,14 @@ class StockEntry(StockController):
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 +1622,11 @@ class StockEntry(StockController):
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 +1643,24 @@ class StockEntry(StockController):
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 +1669,13 @@ class StockEntry(StockController):
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 +1685,34 @@ class StockEntry(StockController):
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 +1720,23 @@ class StockEntry(StockController):
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 +1744,51 @@ class StockEntry(StockController):
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 +1798,11 @@ class StockEntry(StockController):
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 +1823,7 @@ class StockEntry(StockController):
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 +1842,9 @@ class StockEntry(StockController):
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 +1855,7 @@ class StockEntry(StockController):
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 +1875,7 @@ class StockEntry(StockController):
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 +1884,86 @@ class StockEntry(StockController):
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 [
+ "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 +1971,34 @@ class StockEntry(StockController):
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 +2006,17 @@ class StockEntry(StockController):
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 +2027,7 @@ class StockEntry(StockController):
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 +2035,14 @@ class StockEntry(StockController):
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 +2050,87 @@ class StockEntry(StockController):
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 +2138,9 @@ class StockEntry(StockController):
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 +2165,22 @@ class StockEntry(StockController):
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 +2195,98 @@ class StockEntry(StockController):
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 +2298,10 @@ def get_work_order_details(work_order, company):
"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 +2320,78 @@ def get_operating_cost_per_unit(work_order=None, bom_no=None):
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 +2400,31 @@ def get_uom_details(item_code, uom, qty):
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 +2435,73 @@ def get_warehouse_details(args):
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 +2510,8 @@ def get_supplied_items(purchase_order):
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 17266ad059b..b3df7286eaf 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 @@ import erpnext
@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 @@ def make_stock_entry(**args):
: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 @@ def make_stock_entry(**args):
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 @@ def make_stock_entry(**args):
# 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 @@ def make_stock_entry(**args):
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 54c0e43c5ed..3ccd3420e3a 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -38,51 +38,56 @@ def get_sle(**args):
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):
frappe.db.rollback()
frappe.set_user("Administrator")
- frappe.db.set_value("Manufacturing Settings", None, "material_consumption", "0")
def test_fifo(self):
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
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 +97,48 @@ class TestStockEntry(FrappeTestCase):
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 +149,7 @@ class TestStockEntry(FrappeTestCase):
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 +162,113 @@ class TestStockEntry(FrappeTestCase):
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 +276,33 @@ class TestStockEntry(FrappeTestCase):
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 +319,23 @@ class TestStockEntry(FrappeTestCase):
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 +348,13 @@ class TestStockEntry(FrappeTestCase):
# 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 +363,113 @@ class TestStockEntry(FrappeTestCase):
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 +481,13 @@ class TestStockEntry(FrappeTestCase):
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 +588,7 @@ class TestStockEntry(FrappeTestCase):
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 +618,14 @@ class TestStockEntry(FrappeTestCase):
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 +653,16 @@ class TestStockEntry(FrappeTestCase):
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 +676,15 @@ class TestStockEntry(FrappeTestCase):
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 +692,7 @@ class TestStockEntry(FrappeTestCase):
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 +706,14 @@ class TestStockEntry(FrappeTestCase):
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 +729,24 @@ class TestStockEntry(FrappeTestCase):
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 +759,40 @@ class TestStockEntry(FrappeTestCase):
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)
+ )
+ @change_settings("Manufacturing Settings", {"material_consumption": 1})
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 +804,12 @@ class TestStockEntry(FrappeTestCase):
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 +817,34 @@ class TestStockEntry(FrappeTestCase):
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 +858,29 @@ class TestStockEntry(FrappeTestCase):
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 +890,15 @@ class TestStockEntry(FrappeTestCase):
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 +914,58 @@ class TestStockEntry(FrappeTestCase):
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])
@@ -811,82 +981,64 @@ class TestStockEntry(FrappeTestCase):
repack.insert()
self.assertRaises(frappe.ValidationError, repack.submit)
- # def test_material_consumption(self):
- # frappe.db.set_value("Manufacturing Settings", None, "backflush_raw_materials_based_on", "BOM")
- # frappe.db.set_value("Manufacturing Settings", None, "material_consumption", "0")
-
- # from erpnext.manufacturing.doctype.work_order.work_order \
- # import make_stock_entry as _make_stock_entry
- # bom_no = frappe.db.get_value("BOM", {"item": "_Test FG Item 2",
- # "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 2",
- # "bom_no": bom_no,
- # "qty": 4.0,
- # "stock_uom": "_Test UOM",
- # "wip_warehouse": "_Test Warehouse - _TC",
- # "additional_operating_cost": 1000,
- # "use_multi_level_bom": 1
- # })
- # work_order.insert()
- # work_order.submit()
-
- # make_stock_entry(item_code="_Test Serialized Item With Series", target="_Test Warehouse - _TC", qty=50, basic_rate=100)
- # make_stock_entry(item_code="_Test Item 2", target="_Test Warehouse - _TC", qty=50, basic_rate=20)
-
- # item_quantity = {
- # '_Test Item': 2.0,
- # '_Test Item 2': 12.0,
- # '_Test Serialized Item With Series': 6.0
- # }
-
- # stock_entry = frappe.get_doc(_make_stock_entry(work_order.name, "Material Consumption for Manufacture", 2))
- # for d in stock_entry.get('items'):
- # 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 +1047,61 @@ class TestStockEntry(FrappeTestCase):
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 +1132,15 @@ class TestStockEntry(FrappeTestCase):
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 +1149,14 @@ class TestStockEntry(FrappeTestCase):
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 +1171,11 @@ class TestStockEntry(FrappeTestCase):
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 +1190,49 @@ class TestStockEntry(FrappeTestCase):
# 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 +1242,74 @@ class TestStockEntry(FrappeTestCase):
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 +1319,14 @@ class TestStockEntry(FrappeTestCase):
issue.reload() # reload because reposting current voucher updates rate
self.assertEqual(issue.value_difference, -30)
+ def test_transfer_qty_validation(self):
+ se = make_stock_entry(item_code="_Test Item", do_not_save=True, qty=0.001, rate=100)
+ se.items[0].uom = "Kg"
+ se.items[0].conversion_factor = 0.002
+
+ self.assertRaises(frappe.ValidationError, se.save)
+
+
def make_serialized_item(**args):
args = frappe._dict(args)
se = frappe.copy_doc(test_records[0])
@@ -1183,50 +1356,57 @@ def make_serialized_item(**args):
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 +1417,9 @@ def initialize_records_for_future_negative_sle_test(
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 efd97c04ac6..7258cfbe2c9 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 @@ from frappe.model.document import Document
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.js b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.js
index 42cc7e6cba5..23018aa615b 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.js
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.js
@@ -3,6 +3,6 @@
frappe.ui.form.on('Stock Ledger Entry', {
refresh: function(frm) {
-
+ frm.page.btn_secondary.hide()
}
});
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 2f593041bf2..329cd7da09b 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.accounts.utils import get_fiscal_year
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 @@ class StockLedgerEntry(Document):
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 @@ class StockLedgerEntry(Document):
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 @@ class StockLedgerEntry(Document):
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 @@ class StockLedgerEntry(Document):
# 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,66 @@ class StockLedgerEntry(Document):
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 += "
" + _("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 += "
" + _(
+ "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 += "
" + _("Please contact any of the following users to {} this transaction.")
msg += "
" + "
".join(authorized_users)
frappe.throw(msg, BackDatedStockTransaction, title=_("Backdated Stock Entry"))
+ def on_cancel(self):
+ msg = _("Individual Stock Ledger Entry cannot be cancelled.")
+ msg += "
" + _("Please cancel related transaction.")
+ frappe.throw(msg)
+
+
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 fc579958be3..6561362c3af 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 @@ from erpnext.stock.stock_ledger import get_previous_sle
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 @@ class TestStockLedgerEntry(FrappeTestCase):
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 @@ class TestStockLedgerEntry(FrappeTestCase):
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 @@ class TestStockLedgerEntry(FrappeTestCase):
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 @@ class TestStockLedgerEntry(FrappeTestCase):
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 @@ class TestStockLedgerEntry(FrappeTestCase):
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 @@ class TestStockLedgerEntry(FrappeTestCase):
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 @@ class TestStockLedgerEntry(FrappeTestCase):
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=1,
+ )
self.assertEqual(pr1.items[0].valuation_rate, 120)
@@ -308,8 +448,11 @@ class TestStockLedgerEntry(FrappeTestCase):
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 @@ class TestStockLedgerEntry(FrappeTestCase):
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 @@ class TestStockLedgerEntry(FrappeTestCase):
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 @@ class TestStockLedgerEntry(FrappeTestCase):
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 @@ class TestStockLedgerEntry(FrappeTestCase):
# 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 @@ class TestStockLedgerEntry(FrappeTestCase):
(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 @@ class TestStockLedgerEntry(FrappeTestCase):
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 @@ class TestStockLedgerEntry(FrappeTestCase):
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 @@ class TestStockLedgerEntry(FrappeTestCase):
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 @@ class TestStockLedgerEntry(FrappeTestCase):
)
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 @@ class TestStockLedgerEntry(FrappeTestCase):
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 @@ class TestStockLedgerEntry(FrappeTestCase):
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 @@ class TestStockLedgerEntry(FrappeTestCase):
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 @@ class TestStockLedgerEntry(FrappeTestCase):
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 @@ class TestStockLedgerEntry(FrappeTestCase):
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 @@ class TestStockLedgerEntry(FrappeTestCase):
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 @@ class TestStockLedgerEntry(FrappeTestCase):
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 @@ def create_repack_entry(**args):
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 @@ def create_items():
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 @@ def setup_item_valuation_test(valuation_method="FIFO", suffix=None, use_batchwis
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 @@ def setup_item_valuation_test(valuation_method="FIFO", suffix=None, use_batchwis
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 @@ def setup_item_valuation_test(valuation_method="FIFO", suffix=None, use_batchwis
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 @@ def create_purchase_receipt_entries_for_batchwise_item_valuation_test(pr_entry_l
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 @@ def create_delivery_note_entries_for_batchwise_item_valuation_test(dn_entry_list
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 @@ def create_stock_entry_entries_for_batchwise_item_valuation_test(se_entry_list,
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 @@ def create_stock_entry_entries_for_batchwise_item_valuation_test(se_entry_list,
return ses
+
def get_unique_suffix():
# Used to isolate valuation sensitive
# tests to prevent future tests from failing.
@@ -944,7 +1261,6 @@ def get_unique_suffix():
class TestDeferredNaming(FrappeTestCase):
-
@classmethod
def setUpClass(cls) -> None:
super().setUpClass()
@@ -957,10 +1273,22 @@ class TestDeferredNaming(FrappeTestCase):
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 @@ class TestDeferredNaming(FrappeTestCase):
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 @@ class TestDeferredNaming(FrappeTestCase):
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.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
index 84f65a077e0..05dd105d99d 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
@@ -55,6 +55,25 @@ frappe.ui.form.on("Stock Reconciliation", {
}
},
+ scan_barcode: function(frm) {
+ const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:frm});
+ barcode_scanner.process_scan();
+ },
+
+ scan_mode: function(frm) {
+ if (frm.doc.scan_mode) {
+ frappe.show_alert({
+ message: __("Scan mode enabled, existing quantity will not be fetched."),
+ indicator: "green"
+ });
+ }
+ },
+
+ set_warehouse: function(frm) {
+ let transaction_controller = new erpnext.TransactionController({frm:frm});
+ transaction_controller.autofill_warehouse(frm.doc.items, "warehouse", frm.doc.set_warehouse);
+ },
+
get_items: function(frm) {
let fields = [
{
@@ -148,35 +167,25 @@ frappe.ui.form.on("Stock Reconciliation", {
batch_no: d.batch_no
},
callback: function(r) {
- frappe.model.set_value(cdt, cdn, "qty", r.message.qty);
+ const row = frappe.model.get_doc(cdt, cdn);
+ if (!frm.doc.scan_mode) {
+ frappe.model.set_value(cdt, cdn, "qty", r.message.qty);
+ }
frappe.model.set_value(cdt, cdn, "valuation_rate", r.message.rate);
frappe.model.set_value(cdt, cdn, "current_qty", r.message.qty);
frappe.model.set_value(cdt, cdn, "current_valuation_rate", r.message.rate);
frappe.model.set_value(cdt, cdn, "current_amount", r.message.rate * r.message.qty);
- frappe.model.set_value(cdt, cdn, "amount", r.message.rate * r.message.qty);
+ frappe.model.set_value(cdt, cdn, "amount", row.qty * row.valuation_rate);
frappe.model.set_value(cdt, cdn, "current_serial_no", r.message.serial_nos);
- if (frm.doc.purpose == "Stock Reconciliation") {
+ if (frm.doc.purpose == "Stock Reconciliation" && !frm.doc.scan_mode) {
frappe.model.set_value(cdt, cdn, "serial_no", r.message.serial_nos);
}
}
});
}
},
- set_item_code: function(doc, cdt, cdn) {
- var d = frappe.model.get_doc(cdt, cdn);
- if (d.barcode) {
- frappe.call({
- method: "erpnext.stock.get_item_details.get_item_code",
- args: {"barcode": d.barcode },
- callback: function(r) {
- if (!r.exe){
- frappe.model.set_value(cdt, cdn, "item_code", r.message);
- }
- }
- });
- }
- },
+
set_amount_quantity: function(doc, cdt, cdn) {
var d = frappe.model.get_doc(cdt, cdn);
if (d.qty & d.valuation_rate) {
@@ -214,13 +223,10 @@ frappe.ui.form.on("Stock Reconciliation", {
});
frappe.ui.form.on("Stock Reconciliation Item", {
- barcode: function(frm, cdt, cdn) {
- frm.events.set_item_code(frm, cdt, cdn);
- },
warehouse: function(frm, cdt, cdn) {
var child = locals[cdt][cdn];
- if (child.batch_no) {
+ if (child.batch_no && !frm.doc.scan_mode) {
frappe.model.set_value(child.cdt, child.cdn, "batch_no", "");
}
@@ -229,7 +235,7 @@ frappe.ui.form.on("Stock Reconciliation Item", {
item_code: function(frm, cdt, cdn) {
var child = locals[cdt][cdn];
- if (child.batch_no) {
+ if (child.batch_no && !frm.doc.scan_mode) {
frappe.model.set_value(cdt, cdn, "batch_no", "");
}
@@ -255,7 +261,14 @@ frappe.ui.form.on("Stock Reconciliation Item", {
const serial_nos = child.serial_no.trim().split('\n');
frappe.model.set_value(cdt, cdn, "qty", serial_nos.length);
}
- }
+ },
+
+ items_add: function(frm, cdt, cdn) {
+ var item = frappe.get_doc(cdt, cdn);
+ if (!item.warehouse && frm.doc.set_warehouse) {
+ frappe.model.set_value(cdt, cdn, "warehouse", frm.doc.set_warehouse);
+ }
+ },
});
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json
index a882a61e5a5..e545b8ea5c3 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json
@@ -14,6 +14,12 @@
"posting_date",
"posting_time",
"set_posting_time",
+ "section_break_8",
+ "set_warehouse",
+ "section_break_22",
+ "scan_barcode",
+ "column_break_12",
+ "scan_mode",
"sb9",
"items",
"section_break_9",
@@ -139,13 +145,44 @@
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "section_break_8",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "scan_barcode",
+ "fieldtype": "Data",
+ "label": "Scan Barcode",
+ "options": "Barcode"
+ },
+ {
+ "default": "0",
+ "description": "Disables auto-fetching of existing quantity",
+ "fieldname": "scan_mode",
+ "fieldtype": "Check",
+ "label": "Scan Mode"
+ },
+ {
+ "fieldname": "set_warehouse",
+ "fieldtype": "Link",
+ "label": "Default Warehouse",
+ "options": "Warehouse"
+ },
+ {
+ "fieldname": "section_break_22",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "column_break_12",
+ "fieldtype": "Column Break"
}
],
"icon": "fa fa-upload-alt",
"idx": 1,
"is_submittable": 1,
"links": [],
- "modified": "2022-02-06 14:28:19.043905",
+ "modified": "2022-03-27 08:57:47.161959",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Reconciliation",
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 82a8c3717dc..5d5a27f1eab 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -1,6 +1,7 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
+from typing import Optional
import frappe
from frappe import _, msgprint
@@ -14,8 +15,13 @@ from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
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 +30,11 @@ class StockReconciliation(StockController):
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 +45,8 @@ class StockReconciliation(StockController):
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 +54,11 @@ class StockReconciliation(StockController):
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 +66,17 @@ class StockReconciliation(StockController):
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 +93,20 @@ class StockReconciliation(StockController):
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 +116,7 @@ class StockReconciliation(StockController):
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 +126,7 @@ class StockReconciliation(StockController):
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 +143,35 @@ class StockReconciliation(StockController):
# 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 +198,9 @@ class StockReconciliation(StockController):
# 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 +213,8 @@ class StockReconciliation(StockController):
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 +230,20 @@ class StockReconciliation(StockController):
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 +253,16 @@ class StockReconciliation(StockController):
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 +284,24 @@ class StockReconciliation(StockController):
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 +309,49 @@ class StockReconciliation(StockController):
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 +361,8 @@ class StockReconciliation(StockController):
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 +373,7 @@ class StockReconciliation(StockController):
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 +381,24 @@ class StockReconciliation(StockController):
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 +426,7 @@ class StockReconciliation(StockController):
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 +439,11 @@ class StockReconciliation(StockController):
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 +456,16 @@ class StockReconciliation(StockController):
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 +478,9 @@ class StockReconciliation(StockController):
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 +488,39 @@ class StockReconciliation(StockController):
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 +532,33 @@ class StockReconciliation(StockController):
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 +568,9 @@ def get_items(warehouse, posting_date, posting_time, company, item_code=None, ig
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 +579,22 @@ def get_items(warehouse, posting_date, posting_time, company, item_code=None, ig
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 +602,11 @@ def get_items(warehouse, posting_date, posting_time, company, item_code=None, ig
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 +619,12 @@ def get_items_for_stock_reco(warehouse, company):
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 +639,50 @@ def get_items_for_stock_reco(warehouse, company):
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,54 +690,79 @@ def get_itemwise_batch(warehouse, posting_date, company, item_code=None):
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: str,
+ warehouse: str,
+ posting_date: str,
+ posting_time: str,
+ batch_no: Optional[str] = None,
+ with_valuation_rate: bool = True,
+):
+ frappe.has_permission("Stock Reconciliation", "write", throw=True)
+
+ item_dict = frappe.get_cached_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
msg = _("Item {} does not exist.").format(item_code)
frappe.throw(msg, title=_("Missing"))
- 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)
+ serial_nos = None
+ has_serial_no = bool(item_dict.get("has_serial_no"))
+ has_batch_no = bool(item_dict.get("has_batch_no"))
- if with_serial_no:
+ if not batch_no and has_batch_no:
+ # Not enough information to fetch data
+ return {"qty": 0, "rate": 0, "serial_nos": None}
+
+ # TODO: fetch only selected batch's values
+ data = get_stock_balance(
+ item_code,
+ warehouse,
+ posting_date,
+ posting_time,
+ with_valuation_rate=with_valuation_rate,
+ with_serial_no=has_serial_no,
+ )
+
+ if has_serial_no:
qty, rate, serial_nos = data
else:
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 e6b252e8562..46a3f2a5b63 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 @@ class TestStockReconciliation(FrappeTestCase):
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 @@ class TestStockReconciliation(FrappeTestCase):
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 @@ class TestStockReconciliation(FrappeTestCase):
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 @@ class TestStockReconciliation(FrappeTestCase):
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 @@ class TestStockReconciliation(FrappeTestCase):
"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 @@ class TestStockReconciliation(FrappeTestCase):
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 @@ class TestStockReconciliation(FrappeTestCase):
"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 @@ class TestStockReconciliation(FrappeTestCase):
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 @@ class TestStockReconciliation(FrappeTestCase):
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 @@ class TestStockReconciliation(FrappeTestCase):
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 @@ class TestStockReconciliation(FrappeTestCase):
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 @@ class TestStockReconciliation(FrappeTestCase):
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 @@ class TestStockReconciliation(FrappeTestCase):
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 @@ class TestStockReconciliation(FrappeTestCase):
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 @@ class TestStockReconciliation(FrappeTestCase):
@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 @@ class TestStockReconciliation(FrappeTestCase):
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 @@ class TestStockReconciliation(FrappeTestCase):
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 @@ class TestStockReconciliation(FrappeTestCase):
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 @@ class TestStockReconciliation(FrappeTestCase):
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 @@ class TestStockReconciliation(FrappeTestCase):
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 @@ class TestStockReconciliation(FrappeTestCase):
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 @@ class TestStockReconciliation(FrappeTestCase):
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 @@ def create_batch_item_with_batch(item_name, batch_id):
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 @@ def create_batch_or_serial_no_items():
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 @@ def create_stock_reconciliation(**args):
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 @@ def create_stock_reconciliation(**args):
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 @@ def set_valuation_method(item_code, valuation_method):
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_reconciliation_item/stock_reconciliation_item.json b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json
index 6bbba051f98..79c2fcc252c 100644
--- a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json
+++ b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json
@@ -16,15 +16,15 @@
"amount",
"allow_zero_valuation_rate",
"serial_no_and_batch_section",
- "serial_no",
- "column_break_11",
"batch_no",
+ "column_break_11",
+ "serial_no",
"section_break_3",
"current_qty",
- "current_serial_no",
+ "current_amount",
"column_break_9",
"current_valuation_rate",
- "current_amount",
+ "current_serial_no",
"section_break_14",
"quantity_difference",
"column_break_16",
@@ -181,7 +181,7 @@
],
"istable": 1,
"links": [],
- "modified": "2021-05-21 12:13:33.041266",
+ "modified": "2022-04-02 04:19:40.380587",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Reconciliation Item",
@@ -190,5 +190,6 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"track_changes": 1
}
\ No newline at end of file
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 bab521d69fc..e0c8ed12e7d 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 @@ from frappe.utils import add_to_date, get_datetime, get_time_str, time_diff_in_h
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 c1293cbf0fa..e592a4be3c6 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.py
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.py
@@ -16,24 +16,40 @@ from erpnext.stock.utils import check_pending_reposting
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 @@ class StockSettings(Document):
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 @@ class StockSettings(Document):
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 13496718ead..974e16339b7 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 @@ class TestStockSettings(FrappeTestCase):
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 = 'Drawing No. 07-xxx-PO132
1800 x 1685 x 750
All parts made of Marine Ply
Top w/ Corian dd
CO, CS, VIP Day Cabin
'
- )).insert()
+ item = frappe.get_doc(
+ dict(
+ doctype="Item",
+ item_code="Item for description test",
+ item_group="Products",
+ description='Drawing No. 07-xxx-PO132
1800 x 1685 x 750
All parts made of Marine Ply
Top w/ Corian dd
CO, CS, VIP Day Cabin
',
+ )
+ ).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, 'Drawing No. 07-xxx-PO132
1800 x 1685 x 750
All parts made of Marine Ply
Top w/ Corian dd
CO, CS, VIP Day Cabin
')
+ self.assertEqual(
+ item.description,
+ "Drawing No. 07-xxx-PO132
1800 x 1685 x 750
All parts made of Marine Ply
Top w/ Corian dd
CO, CS, VIP Day Cabin
",
+ )
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 = 'Drawing No. 07-xxx-PO132
1800 x 1685 x 750
All parts made of Marine Ply
Top w/ Corian dd
CO, CS, VIP Day Cabin
'
- )).insert()
+ item = frappe.get_doc(
+ dict(
+ doctype="Item",
+ item_code="Item for description test",
+ item_group="Products",
+ description='Drawing No. 07-xxx-PO132
1800 x 1685 x 750
All parts made of Marine Ply
Top w/ Corian dd
CO, CS, VIP Day Cabin
',
+ )
+ ).insert()
- self.assertEqual(item.description, 'Drawing No. 07-xxx-PO132
1800 x 1685 x 750
All parts made of Marine Ply
Top w/ Corian dd
CO, CS, VIP Day Cabin
')
+ self.assertEqual(
+ item.description,
+ "Drawing No. 07-xxx-PO132
1800 x 1685 x 750
All parts made of Marine Ply
Top w/ Corian dd
CO, CS, VIP Day Cabin
",
+ )
item.delete()
diff --git a/erpnext/stock/doctype/warehouse/test_warehouse.py b/erpnext/stock/doctype/warehouse/test_warehouse.py
index 08d7c993521..5a7228a5068 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.item.test_item import create_item
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,23 +27,37 @@ class TestWarehouse(FrappeTestCase):
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)
self.assertEqual(child_warehouse.is_group, 0)
+ def test_naming(self):
+ company = "Wind Power LLC"
+ warehouse_name = "Named Warehouse - WP"
+ wh = frappe.get_doc(doctype="Warehouse", warehouse_name=warehouse_name, company=company).insert()
+ self.assertEqual(wh.name, warehouse_name)
+
+ warehouse_name = "Unnamed Warehouse"
+ wh = frappe.get_doc(doctype="Warehouse", warehouse_name=warehouse_name, company=company).insert()
+ self.assertIn(warehouse_name, wh.name)
+
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 +67,14 @@ class TestWarehouse(FrappeTestCase):
# 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 +102,7 @@ class TestWarehouse(FrappeTestCase):
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 +123,46 @@ def create_warehouse(warehouse_name, properties=None, company=None):
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 4c7f41dcb5e..3b18a9ac26f 100644
--- a/erpnext/stock/doctype/warehouse/warehouse.py
+++ b/erpnext/stock/doctype/warehouse/warehouse.py
@@ -14,23 +14,26 @@ from erpnext.stock import get_warehouse_account
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
+ return
+
+ 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 +46,19 @@ class Warehouse(NestedSet):
# 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 +103,24 @@ class Warehouse(NestedSet):
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 +131,20 @@ def get_children(doctype, parent=None, company=None, is_root=False):
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 +152,30 @@ def get_warehouse_wise_stock_value(company):
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 +183,37 @@ def add_node():
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 9bb41b9dbbe..d3a230e3d89 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -23,31 +23,38 @@ from erpnext.stock.doctype.item.item import get_item_defaults, get_uom_conv_fact
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": 0/1,
+ "ignore_pricing_rule": 0/1
+ "project": ""
+ "set_warehouse": ""
+ }
"""
args = process_args(args)
@@ -61,16 +68,21 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru
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 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru
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 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru
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)
+ if args.get("is_subcontracted"):
+ 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 remove_standard_fields(details):
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,15 +158,18 @@ def update_stock(args, out):
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:
reserved_so = get_so_reservation_for_item(args)
out.serial_no = get_serial_no(out, args.serial_no, sales_order=reserved_so)
+ if not out.serial_no:
+ out.pop("serial_no", None)
+
def set_valuation_rate(out, args):
if frappe.db.exists("Product Bundle", args.item_code, cache=True):
@@ -156,13 +177,14 @@ def set_valuation_rate(out, args):
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 +207,13 @@ def process_args(args):
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,50 +233,51 @@ def validate_item_details(args, item):
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):
throw(_("Item {0} is a template, please select one of its variants").format(item.name))
elif args.transaction_type == "buying" and args.doctype != "Material Request":
- if args.get("is_subcontracted") == "Yes" and item.is_sub_contracted_item != 1:
+ if args.get("is_subcontracted") and item.is_sub_contracted_item != 1:
throw(_("Item {0} must be a Sub-contracted Item").format(item.name))
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": 0/1,
+ "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 +292,98 @@ def get_basic_details(args, item, overwrite_warehouse=True):
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 +392,31 @@ def get_basic_details(args, item, overwrite_warehouse=True):
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 +429,58 @@ def get_basic_details(args, item, overwrite_warehouse=True):
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 +489,14 @@ def get_item_warehouse(item, args, overwrite_warehouse, defaults=None):
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 +505,13 @@ def get_barcode_data(items_list):
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 +520,7 @@ def get_barcode_data(items_list):
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 +546,29 @@ def get_item_tax_info(company, tax_category, item_codes, item_rates=None, item_t
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 +581,7 @@ def get_item_tax_template(args, item, out):
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 +589,43 @@ def _get_item_tax_template(args, taxes, out=None, for_validate=False):
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 +633,17 @@ def _get_item_tax_template(args, taxes, out=None, for_validate=False):
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 +655,7 @@ def get_item_tax_map(company, item_tax_template, as_json=True):
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 +674,68 @@ def calculate_service_end_date(args, item=None):
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 +748,26 @@ def get_default_cost_center(args, item=None, item_group=None, brand=None, compan
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 +775,7 @@ def get_price_list_rate(args, item_doc, out=None):
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 +797,72 @@ def get_price_list_rate(args, item_doc, out=None):
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 +878,42 @@ def get_item_price(args, item_code, ignore_party=False):
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 +926,15 @@ def get_price_list_rate_for(args, item_code):
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 +942,19 @@ def get_price_list_rate_for(args, item_code):
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 +967,62 @@ def check_packing_list(price_list_rate_name, desired_qty, item_code):
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 +1035,16 @@ def get_party_item_code(args, item_doc, out):
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 +1056,89 @@ def get_pos_profile_item_details(company, args, pos_profile=None, update_data=Fa
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 +1146,94 @@ def get_conversion_factor(item_code, uom):
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 +1243,23 @@ def apply_price_list(args, as_doc=False):
: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 +1279,10 @@ def apply_price_list(args, as_doc=False):
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 +1290,25 @@ def apply_price_list(args, as_doc=False):
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 +1319,38 @@ def get_price_list_currency_and_exchange_rate(args):
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 +1359,57 @@ def get_valuation_rate(item_code, company, warehouse=None):
# 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 +1426,68 @@ def update_party_blanket_order(args, out):
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/page/warehouse_capacity_summary/warehouse_capacity_summary.js b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.js
index ea27dd251da..61927f51a82 100644
--- a/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.js
+++ b/erpnext/stock/page/warehouse_capacity_summary/warehouse_capacity_summary.js
@@ -68,7 +68,7 @@ frappe.pages['warehouse-capacity-summary'].on_page_load = function(wrapper) {
options: [
{fieldname: 'stock_capacity', label: __('Capacity (Stock UOM)')},
{fieldname: 'percent_occupied', label: __('% Occupied')},
- {fieldname: 'actual_qty', label: __('Balance Qty (Stock ')}
+ {fieldname: 'actual_qty', label: __('Balance Qty (Stock)')}
]
},
change: function(sort_by, sort_order) {
diff --git a/erpnext/stock/reorder_item.py b/erpnext/stock/reorder_item.py
index 21f2573a279..ee151b7517c 100644
--- a/erpnext/stock/reorder_item.py
+++ b/erpnext/stock/reorder_item.py
@@ -13,22 +13,29 @@ import erpnext
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 @@ def _reorder_item():
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 @@ def _reorder_item():
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 @@ def _reorder_item():
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 @@ def get_item_warehouse_projected_qty(items_to_consider):
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 @@ def create_material_request(material_requests):
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 @@ def create_material_request(material_requests):
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 @@ def create_material_request(material_requests):
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,43 @@ def create_material_request(material_requests):
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,") + "
" + _("An error occured for certain Items while creating Material Requests based on Re-order level. \
- Please rectify these issues :") + "
"
+ content = (
+ _("Dear System Manager,")
+ + "
"
+ + _(
+ "An error occured for certain Items while creating Material Requests based on Re-order level. Please rectify these issues :"
+ )
+ + "
"
+ )
for exception in exceptions_list:
exception = json.loads(exception)
- error_message = """{0}
""".format(_(exception.get("message")))
+ error_message = """{0}
""".format(
+ _(exception.get("message"))
+ )
content += error_message
content += _("Regards,") + "
" + _("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 87097c72fa4..3d9b0461977 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 @@ from frappe.utils import cint, getdate
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 @@ def execute(filters=None):
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 @@ def get_conditions(filters):
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 @@ def get_item_warehouse_batch_map(filters, float_precision):
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 @@ def get_item_warehouse_batch_map(filters, float_precision):
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 9b21deabcd4..8a13300dc83 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 @@ from frappe.utils import cint, flt, getdate
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 @@ def execute(filters=None):
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 execute(filters=None):
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 @@ def get_conditions(filters):
# 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 @@ def get_item_warehouse_batch_map(filters, float_precision):
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 a22b2248676..56a65c3c308 100644
--- a/erpnext/stock/report/bom_search/bom_search.py
+++ b/erpnext/stock/report/bom_search/bom_search.py
@@ -3,6 +3,7 @@
import frappe
+from frappe import _
def execute(filters=None):
@@ -10,11 +11,13 @@ def execute(filters=None):
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 +32,13 @@ def execute(filters=None):
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 058af77aa21..4642a535b63 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 validate_filters(filters: Filters) -> None:
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 @@ def get_data(filters: Filters) -> Data:
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 @@ def get_filtered_entries(filters: Filters) -> FilteredEntries:
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_filtered_entries(filters: Filters) -> FilteredEntries:
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_stock_value_difference_list(filtered_entries: FilteredEntries) -> SVDLis
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 @@ def get_leveled_dict() -> OrderedDict:
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 get_leveled_dict() -> OrderedDict:
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 assign_item_groups_to_svd_list(svd_list: SVDList) -> None:
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 4ec36ea417f..9df24d65596 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 import _
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 @@ class DelayedItemReport(object):
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 @@ class DelayedItemReport(object):
`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 @@ class DelayedItemReport(object):
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 26090ab8ffb..197218d7ff4 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 @@ def execute(filters=None):
return columns, data
+
class DelayedOrderReport(DelayedItemReport):
def run(self):
return self.get_columns(), self.get_data(consolidated=True) or []
@@ -33,60 +34,48 @@ class DelayedOrderReport(DelayedItemReport):
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 b7ac7ff6a53..7a1b8c0cee9 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 @@ from erpnext.controllers.trends import get_columns, get_data
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 @@ def execute(filters=None):
return conditions["columns"], data, None, chart_data
+
def get_chart_data(data, filters):
if not data:
return []
@@ -27,7 +29,7 @@ def get_chart_data(data, filters):
# 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 @@ def get_chart_data(data, filters):
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 6aa12ac2c99..bcc213905d4 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 @@ def execute(filters=None):
data = get_data(filters)
return columns, data
+
def get_data(filters):
data = get_stock_ledger_entries(filters)
itewise_balance_qty = {}
@@ -23,6 +24,7 @@ def get_data(filters):
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 @@ def validate_data(itewise_balance_qty):
return res
+
def get_incorrect_data(data):
balance_qty = 0.0
for row in data:
@@ -45,67 +48,84 @@ def get_incorrect_data(data):
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 be8597dfed3..78c69616230 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 @@ def execute(filters=None):
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 @@ def get_data(filters):
return data
+
def prepare_serial_nos(data):
serial_no_wise_data = {}
for row in data:
@@ -37,13 +39,16 @@ def prepare_serial_nos(data):
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 @@ def get_incorrect_serial_nos(serial_nos_data):
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 28e6cb2d275..23e3c8a97f5 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 @@ from erpnext.stock.utils import get_stock_value_on
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 @@ def get_unsync_date(filters):
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 @@ def get_data(report_filters):
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 @@ def get_data(report_filters):
= %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 @@ def get_data(report_filters):
return result
+
def get_columns(filters):
return [
{
@@ -83,60 +96,43 @@ def get_columns(filters):
"fieldname": "name",
"fieldtype": "Link",
"options": "Stock Ledger Entry",
- "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"
+ "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": _("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"
- },
- {
- "label": _("Stock Value"),
- "fieldname": "stock_value",
- "fieldtype": "Currency",
- "width": "120"
+ "width": "150",
},
+ {"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 65af9f51cdf..15218e63a87 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 @@ from frappe import _
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 @@ def get_columns():
"fieldname": "item_code",
"fieldtype": "Link",
"options": "Item",
- "width": 120
- },
- {
- "label": _("Item Name"),
- "fieldname": "item_name",
- "fieldtype": "Data",
- "width": 120
- },
- {
- "label": _("Brand"),
- "fieldname": "brand",
- "fieldtype": "Data",
- "width": 100
+ "width": 120,
},
+ {"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
- },
- {
- "label": _("Buying Rate"),
- "fieldname": "buying_rate",
- "fieldtype": "Currency",
- "width": 120
+ "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 @@ def get_item_price_qty_data(filters):
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 @@ def get_item_price_qty_data(filters):
return result
+
def get_price_map(price_list_names, buying=0, selling=0):
price_map = {}
@@ -137,14 +126,12 @@ def get_price_map(price_list_names, buying=0, selling=0):
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 0d0e8d22924..87f1a42e2b2 100644
--- a/erpnext/stock/report/item_prices/item_prices.py
+++ b/erpnext/stock/report/item_prices/item_prices.py
@@ -8,7 +8,8 @@ from frappe.utils import flt
def execute(filters=None):
- if not filters: filters = {}
+ if not filters:
+ filters = {}
columns = get_columns(filters)
conditions = get_condition(filters)
@@ -19,64 +20,95 @@ def execute(filters=None):
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 @@ def get_last_purchase_rate():
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 30c761421fd..03a3a6a0b83 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 @@ def execute(filters=None):
return columns, data, None, chart_data
+
def get_conditions(filters):
conditions = ""
@@ -28,8 +29,10 @@ def get_conditions(filters):
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 @@ def get_data(conditions, filters):
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 @@ def get_chart_data(data):
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 @@ def get_columns():
"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 10cef70215b..e3a2a65d8fe 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 @@ def execute(filters=None):
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 @@ def get_data(item):
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 @@ def get_data(item):
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
- },
- {
- "fieldname": "current_stock",
- "label": _("Current Stock"),
- "fieldtype": "Float",
- "width": 120
- },
- {
- "fieldname": "in_production",
- "label": _("In Production"),
- "fieldtype": "Float",
- "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": "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 @@ def get_open_sales_orders_count(variants_list):
return order_count_map
+
def get_stock_details_map(variant_list):
stock_details = frappe.db.get_all(
"Bin",
@@ -160,10 +148,8 @@ def get_stock_details_map(variant_list):
"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 @@ def get_stock_details_map(variant_list):
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 @@ def get_buying_price_map(variant_list):
"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 @@ def get_buying_price_map(variant_list):
return buying_price_map
+
def get_selling_price_map(variant_list):
selling = frappe.db.get_all(
"Item Price",
@@ -203,11 +188,8 @@ def get_selling_price_map(variant_list):
"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 @@ def get_selling_price_map(variant_list):
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 cfa1e474c7b..f308e9e41f1 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 @@ from frappe.utils import flt, getdate
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 @@ def execute(filters=None):
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 @@ def get_consumed_items(condition):
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 @@ def get_consumed_items(condition):
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 @@ def get_delivered_items(condition):
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 d9adceddf1d..854875a0532 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 @@ def execute(filters=None):
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 execute(filters=None):
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 @@ def get_items(filters):
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 @@ def get_items(filters):
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 @@ def get_items(filters):
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 @@ def get_stock_ledger_entries(filters, items):
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 @@ def get_stock_ledger_entries(filters, items):
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 @@ def get_sle_conditions(filters):
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 97384427fa4..fe2d55a3913 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 @@ from erpnext.controllers.trends import get_columns, get_data
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 @@ def execute(filters=None):
return conditions["columns"], data, None, chart_data
+
def get_chart_data(data, filters):
if not data:
return []
@@ -27,7 +29,7 @@ def get_chart_data(data, filters):
# 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 @@ def get_chart_data(data, filters):
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 80ec848e5b6..e439f51dd69 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 @@ def execute(filters=None):
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 7ca40033edb..1956238331e 100644
--- a/erpnext/stock/report/stock_ageing/stock_ageing.py
+++ b/erpnext/stock/report/stock_ageing/stock_ageing.py
@@ -25,6 +25,7 @@ def execute(filters: Filters = None) -> Tuple:
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 @@ def format_report_data(filters: Filters, item_details: Dict, to_date: str) -> Li
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 @@ def get_average_age(fifo_queue: List, to_date: str) -> float:
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 @@ def get_range_age(filters: Filters, fifo_queue: List, to_date: str, item_dict: D
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 @@ def get_columns(filters: Filters) -> List[Dict]:
"fieldname": "item_code",
"fieldtype": "Link",
"options": "Item",
- "width": 100
- },
- {
- "label": _("Item Name"),
- "fieldname": "item_name",
- "fieldtype": "Data",
- "width": 100
- },
- {
- "label": _("Description"),
- "fieldname": "description",
- "fieldtype": "Data",
- "width": 200
+ "width": 100,
},
+ {"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 @@ def get_chart_data(data: List, filters: Filters) -> Dict:
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 @@ def get_chart_data(data: List, filters: Filters) -> Dict:
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 @@ class FIFOSlots:
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 @@ class FIFOSlots:
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 @@ class FIFOSlots:
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 @@ class FIFOSlots:
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 @@ class FIFOSlots:
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 @@ class FIFOSlots:
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 @@ class FIFOSlots:
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 @@ class FIFOSlots:
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 ca963b74863..fb363606233 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 @@ from erpnext.stock.report.stock_ageing.stock_ageing import FIFOSlots, format_rep
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 @@ class TestStockAgeing(FrappeTestCase):
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 @@ class TestStockAgeing(FrappeTestCase):
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 @@ class TestStockAgeing(FrappeTestCase):
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 @@ class TestStockAgeing(FrappeTestCase):
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 @@ class TestStockAgeing(FrappeTestCase):
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 @@ class TestStockAgeing(FrappeTestCase):
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 @@ class TestStockAgeing(FrappeTestCase):
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 @@ class TestStockAgeing(FrappeTestCase):
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 @@ class TestStockAgeing(FrappeTestCase):
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 @@ class TestStockAgeing(FrappeTestCase):
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 @@ class TestStockAgeing(FrappeTestCase):
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 @@ class TestStockAgeing(FrappeTestCase):
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 @@ class TestStockAgeing(FrappeTestCase):
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 @@ class TestStockAgeing(FrappeTestCase):
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 @@ class TestStockAgeing(FrappeTestCase):
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 ddc831037bf..da0776b9a84 100644
--- a/erpnext/stock/report/stock_analytics/stock_analytics.py
+++ b/erpnext/stock/report/stock_analytics/stock_analytics.py
@@ -25,84 +25,64 @@ def execute(filters=None):
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 round_down_to_nearest_frequency(date: str, frequency: str) -> datetime.datet
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_period(posting_date, filters):
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 @@ def get_periodic_data(entry, filters):
# 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 @@ def get_data(filters):
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 6fd3fe7da48..99f820ecac6 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 @@ from erpnext.stock.doctype.warehouse.warehouse import get_warehouses_based_on_ac
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 @@ def get_data(report_filters):
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 @@ def get_gl_data(report_filters, filters):
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 @@ def get_gl_data(report_filters, filters):
return voucher_wise_gl_data
+
def get_columns(filters):
return [
{
@@ -88,46 +108,29 @@ def get_columns(filters):
"fieldname": "name",
"fieldtype": "Link",
"options": "Stock Ledger Entry",
- "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"
+ "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": _("Voucher No"),
"fieldname": "voucher_no",
"fieldtype": "Dynamic Link",
"options": "voucher_type",
- "width": "110"
- },
- {
- "label": _("Stock Value"),
- "fieldname": "stock_value",
- "fieldtype": "Currency",
- "width": "120"
+ "width": "110",
},
+ {"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 24f47c19468..afbc6fe249d 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.py
+++ b/erpnext/stock/report/stock_balance/stock_balance.py
@@ -16,9 +16,10 @@ from erpnext.stock.utils import add_additional_uom_columns, is_reposting_item_va
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 @@ def execute(filters=None):
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 @@ def execute(filters=None):
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 @@ def execute(filters=None):
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 @@ def execute(filters=None):
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 @@ def get_conditions(filters):
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 @@ def get_stock_ledger_entries(filters, items):
`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 @@ def get_item_warehouse_map(filters, sle):
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 @@ def get_item_warehouse_map(filters, sle):
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 @@ def get_item_warehouse_map(filters, sle):
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 @@ def filter_items_with_no_transactions(iwb_map, float_precision):
return iwb_map
+
def get_items(filters):
"Get items based on item code, item group or brand."
conditions = []
@@ -245,15 +357,17 @@ def get_items(filters):
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 @@ def get_item_details(items, sle, filters):
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 @@ def get_item_details(items, sle, filters):
%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 9fde47e0610..409e2386578 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.py
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.py
@@ -42,19 +42,13 @@ def execute(filters=None):
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 @@ def execute(filters=None):
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 @@ def update_available_serial_nos(available_serial_nos, sle):
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 @@ def get_stock_ledger_entries(filters, items):
{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 @@ def get_items(filters):
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 @@ def get_item_details(items, sl_entries, include_uom):
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 @@ def get_item_details(items, sl_entries, include_uom):
{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 @@ def get_opening_balance(filters, columns, sl_entries):
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 @@ def get_opening_balance(filters, columns, sl_entries):
"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_opening_balance(filters, columns, sl_entries):
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 163b2057c94..f93bd663db5 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 @@ class TestStockLedgerReeport(FrappeTestCase):
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 @@ class TestStockLedgerReeport(FrappeTestCase):
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 1ba2482935c..837c4a6d15c 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
@@ -4,6 +4,7 @@
import json
import frappe
+from frappe import _
SLE_FIELDS = (
"name",
@@ -40,11 +41,7 @@ def get_stock_ledger_entries(filters):
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 +59,7 @@ def add_invariant_check_fields(sles):
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 +87,16 @@ def add_invariant_check_fields(sles):
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
@@ -107,157 +106,155 @@ def get_columns():
{
"fieldname": "name",
"fieldtype": "Link",
- "label": "Stock Ledger Entry",
+ "label": _("Stock Ledger Entry"),
"options": "Stock Ledger Entry",
},
{
"fieldname": "posting_date",
"fieldtype": "Date",
- "label": "Posting Date",
+ "label": _("Posting Date"),
},
{
"fieldname": "posting_time",
"fieldtype": "Time",
- "label": "Posting Time",
+ "label": _("Posting Time"),
},
{
"fieldname": "creation",
"fieldtype": "Datetime",
- "label": "Creation",
+ "label": _("Creation"),
},
{
"fieldname": "voucher_type",
"fieldtype": "Link",
- "label": "Voucher Type",
+ "label": _("Voucher Type"),
"options": "DocType",
},
{
"fieldname": "voucher_no",
"fieldtype": "Dynamic Link",
- "label": "Voucher No",
+ "label": _("Voucher No"),
"options": "voucher_type",
},
{
"fieldname": "batch_no",
"fieldtype": "Link",
- "label": "Batch",
+ "label": _("Batch"),
"options": "Batch",
},
{
"fieldname": "use_batchwise_valuation",
"fieldtype": "Check",
- "label": "Batchwise Valuation",
+ "label": _("Batchwise Valuation"),
},
{
"fieldname": "actual_qty",
"fieldtype": "Float",
- "label": "Qty Change",
+ "label": _("Qty Change"),
},
{
"fieldname": "incoming_rate",
"fieldtype": "Float",
- "label": "Incoming Rate",
+ "label": _("Incoming Rate"),
},
{
"fieldname": "consumption_rate",
"fieldtype": "Float",
- "label": "Consumption Rate",
+ "label": _("Consumption Rate"),
},
{
"fieldname": "qty_after_transaction",
"fieldtype": "Float",
- "label": "(A) Qty After Transaction",
+ "label": _("(A) Qty After Transaction"),
},
{
"fieldname": "expected_qty_after_transaction",
"fieldtype": "Float",
- "label": "(B) Expected Qty After Transaction",
+ "label": _("(B) Expected Qty After Transaction"),
},
{
"fieldname": "difference_in_qty",
"fieldtype": "Float",
- "label": "A - B",
+ "label": _("A - B"),
},
{
"fieldname": "stock_queue",
"fieldtype": "Data",
- "label": "FIFO/LIFO Queue",
+ "label": _("FIFO/LIFO Queue"),
},
-
{
"fieldname": "fifo_queue_qty",
"fieldtype": "Float",
- "label": "(C) Total qty in queue",
+ "label": _("(C) Total qty in queue"),
},
{
"fieldname": "fifo_qty_diff",
"fieldtype": "Float",
- "label": "A - C",
+ "label": _("A - C"),
},
{
"fieldname": "stock_value",
"fieldtype": "Float",
- "label": "(D) Balance Stock Value",
+ "label": _("(D) Balance Stock Value"),
},
{
"fieldname": "fifo_stock_value",
"fieldtype": "Float",
- "label": "(E) Balance Stock Value in Queue",
+ "label": _("(E) Balance Stock Value in Queue"),
},
{
"fieldname": "fifo_value_diff",
"fieldtype": "Float",
- "label": "D - E",
+ "label": _("D - E"),
},
{
"fieldname": "stock_value_difference",
"fieldtype": "Float",
- "label": "(F) Stock Value Difference",
+ "label": _("(F) Stock Value Difference"),
},
{
"fieldname": "stock_value_from_diff",
"fieldtype": "Float",
- "label": "Balance Stock Value using (F)",
+ "label": _("Balance Stock Value using (F)"),
},
{
"fieldname": "diff_value_diff",
"fieldtype": "Float",
- "label": "K - D",
+ "label": _("K - D"),
},
{
"fieldname": "fifo_stock_diff",
"fieldtype": "Float",
- "label": "(G) Stock Value difference (FIFO queue)",
+ "label": _("(G) Stock Value difference (FIFO queue)"),
},
{
"fieldname": "fifo_difference_diff",
"fieldtype": "Float",
- "label": "F - G",
+ "label": _("F - G"),
},
{
"fieldname": "valuation_rate",
"fieldtype": "Float",
- "label": "(H) Valuation Rate",
+ "label": _("(H) Valuation Rate"),
},
{
"fieldname": "fifo_valuation_rate",
"fieldtype": "Float",
- "label": "(I) Valuation Rate as per FIFO",
+ "label": _("(I) Valuation Rate as per FIFO"),
},
-
{
"fieldname": "fifo_valuation_diff",
"fieldtype": "Float",
- "label": "H - I",
+ "label": _("H - I"),
},
{
"fieldname": "balance_value_by_qty",
"fieldtype": "Float",
- "label": "(J) Valuation = Value (D) ÷ Qty (A)",
+ "label": _("(J) Valuation = Value (D) ÷ Qty (A)"),
},
{
"fieldname": "valuation_diff",
"fieldtype": "Float",
- "label": "H - J",
+ "label": _("H - J"),
},
]
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 a28b75250bf..49e797d6a30 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 @@ def execute(filters=None):
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 @@ def execute(filters=None):
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 @@ def execute(filters=None):
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 @@ def get_item_map(item_code, include_uom):
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 a7b48356b8d..70f04da4753 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 @@ def execute(filters=None):
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 @@ def get_columns():
"fieldname": "item_code",
"fieldtype": "Link",
"options": "Item",
- "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
+ "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},
]
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 d1748ed24b1..5430fe6969d 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 @@ def execute(filters=None):
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 @@ def execute(filters=None):
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 @@ def get_suppliers_details(filters):
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 76c20798bfb..55b910432a6 100644
--- a/erpnext/stock/report/test_reports.py
+++ b/erpnext/stock/report/test_reports.py
@@ -43,8 +43,18 @@ REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [
},
),
("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 @@ REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [
("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 6f27558b887..21529da2a12 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 @@ def execute(filters=None):
return columns, stock
+
def get_columns():
columns = [
_("Company") + ":Link/Company:250",
@@ -26,13 +27,16 @@ def get_columns():
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 @@ def get_total_stock(filters):
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 @@ def get_total_stock(filters):
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 22bdb891988..a54373f364c 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 @@ from erpnext.stock.utils import is_reposting_item_valuation_in_progress
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 @@ def execute(filters=None):
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 @@ def execute(filters=None):
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 @@ def execute(filters=None):
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 @@ def validate_filters(filters):
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 @@ def get_warehouse_list(filters):
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 62017e41593..e05d1c3a29f 100644
--- a/erpnext/stock/stock_balance.py
+++ b/erpnext/stock/stock_balance.py
@@ -15,16 +15,20 @@ def repost(only_actual=False, allow_negative_stock=False, allow_zero_rate=False,
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 @@ def repost(only_actual=False, allow_negative_stock=False, allow_zero_rate=False,
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 @@ def repost_stock(item_code, warehouse, allow_zero_rate=False,
"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 @@ def get_reserved_qty(item_code, warehouse):
) 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 @@ def update_bin_qty(item_code, warehouse, qty_dict=None):
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 @@ def set_stock_balance_as_per_serial_no(item_code=None, posting_date=None, postin
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 1aaaad2dff8..b7fd65bda88 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -22,28 +22,32 @@ from erpnext.stock.utils import (
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 @@ def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_vouc
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 @@ def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_vouc
# 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 @@ def repost_current_voucher(args, allow_negative_stock=False, via_landed_cost_vou
# 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 @@ def validate_serial_no(sle):
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.''' + '
- ')
+ 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."""
+ + "
- "
+ )
- msg += '
- '.join(vouchers)
- msg += '
'
+ msg += " - ".join(vouchers)
+ msg += "
"
- 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.status = "Skipped"
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 @@ def repost_future_sle(args=None, voucher_type=None, voucher_no=None, allow_negat
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 @@ def repost_future_sle(args=None, voucher_type=None, voucher_no=None, allow_negat
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 @@ class update_entries_after(object):
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 @@ class update_entries_after(object):
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 @@ class update_entries_after(object):
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 @@ class update_entries_after(object):
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 @@ class update_entries_after(object):
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 @@ class update_entries_after(object):
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 @@ class update_entries_after(object):
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 @@ class update_entries_after(object):
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 @@ class update_entries_after(object):
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 @@ class update_entries_after(object):
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 @@ class update_entries_after(object):
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 @@ class update_entries_after(object):
# 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"):
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 @@ class update_entries_after(object):
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 @@ class update_entries_after(object):
)
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 @@ class update_entries_after(object):
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 @@ class update_entries_after(object):
# 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 @@ class update_entries_after(object):
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 @@ class update_entries_after(object):
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 @@ class update_entries_after(object):
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 @@ class update_entries_after(object):
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 @@ class update_entries_after(object):
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 @@ class update_entries_after(object):
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 @@ class update_entries_after(object):
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 @@ class update_entries_after(object):
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 @@ def get_previous_sle_of_current_voucher(args, exclude_current_voucher=False):
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 @@ def get_previous_sle_of_current_voucher(args, exclude_current_voucher=False):
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 @@ def get_stock_ledger_entries(previous_sle, operator=None,
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 @@ def get_stock_ledger_entries(previous_sle, operator=None,
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 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no,
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 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no,
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 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no,
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 += "
" + _("Here are the options to proceed:")
- solutions = "" + _("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) + ""
- solutions += "" + _("If not, you can Cancel / Submit this entry") + " {0} ".format(frappe.bold("after")) + _("performing either one below:") + ""
+ solutions = (
+ ""
+ + _(
+ "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)
+ + ""
+ )
+ solutions += (
+ ""
+ + _("If not, you can Cancel / Submit this entry")
+ + " {0} ".format(frappe.bold("after"))
+ + _("performing either one below:")
+ + ""
+ )
sub_solutions = "- " + _("Create an incoming stock transaction for the Item.") + "
"
sub_solutions += "- " + _("Mention Valuation Rate in the Item master.") + "
"
msg = message + solutions + sub_solutions + ""
@@ -1054,6 +1281,7 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no,
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 @@ def update_qty_in_future_sle(args, allow_negative_stock=False):
# 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 @@ def update_qty_in_future_sle(args, allow_negative_stock=False):
)
)
{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 @@ def get_stock_reco_qty_shift(args):
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 @@ def get_stock_reco_qty_shift(args):
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 @@ def get_next_stock_reco(args):
)
)
limit 1
- """, args, as_dict=1)
+ """,
+ args,
+ as_dict=1,
+ )
+
def get_datetime_limit_condition(detail):
return f"""
@@ -1143,6 +1384,7 @@ def get_datetime_limit_condition(detail):
)
)"""
+
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 @@ def validate_negative_qty_in_future_sle(args, allow_negative_stock=False):
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.throw(message, NegativeStockError, title=_('Insufficient Stock'))
+ 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"))
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 @@ def get_future_sle_with_negative_qty(args):
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 @@ def get_future_sle_with_negative_batch_qty(args):
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_utils.py b/erpnext/stock/tests/test_utils.py
new file mode 100644
index 00000000000..9ee0c9f3b5a
--- /dev/null
+++ b/erpnext/stock/tests/test_utils.py
@@ -0,0 +1,31 @@
+import frappe
+from frappe.tests.utils import FrappeTestCase
+
+from erpnext.stock.doctype.item.test_item import make_item
+from erpnext.stock.utils import scan_barcode
+
+
+class TestStockUtilities(FrappeTestCase):
+ def test_barcode_scanning(self):
+ simple_item = make_item(properties={"barcodes": [{"barcode": "12399"}]})
+ self.assertEqual(scan_barcode("12399")["item_code"], simple_item.name)
+
+ batch_item = make_item(properties={"has_batch_no": 1, "create_new_batch": 1})
+ batch = frappe.get_doc(doctype="Batch", item=batch_item.name).insert()
+
+ batch_scan = scan_barcode(batch.name)
+ self.assertEqual(batch_scan["item_code"], batch_item.name)
+ self.assertEqual(batch_scan["batch_no"], batch.name)
+ self.assertEqual(batch_scan["has_batch_no"], 1)
+ self.assertEqual(batch_scan["has_serial_no"], 0)
+
+ serial_item = make_item(properties={"has_serial_no": 1})
+ serial = frappe.get_doc(
+ doctype="Serial No", item_code=serial_item.name, serial_no=frappe.generate_hash()
+ ).insert()
+
+ serial_scan = scan_barcode(serial.name)
+ self.assertEqual(serial_scan["item_code"], serial_item.name)
+ self.assertEqual(serial_scan["serial_no"], serial.name)
+ self.assertEqual(serial_scan["has_batch_no"], 0)
+ self.assertEqual(serial_scan["has_serial_no"], 1)
diff --git a/erpnext/stock/tests/test_valuation.py b/erpnext/stock/tests/test_valuation.py
index b64ff8e28c8..e60c1caac34 100644
--- a/erpnext/stock/tests/test_valuation.py
+++ b/erpnext/stock/tests/test_valuation.py
@@ -16,7 +16,6 @@ stock_queue_generator = st.lists(st.tuples(qty_gen, value_gen), min_size=10)
class TestFIFOValuation(unittest.TestCase):
-
def setUp(self):
self.queue = FIFOValuation([])
@@ -29,7 +28,9 @@ class TestFIFOValuation(unittest.TestCase):
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,14 +56,13 @@ class TestFIFOValuation(unittest.TestCase):
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]])
- # XXX
- self.queue.remove_stock(1, 10)
+ self.queue.remove_stock(1)
self.assertTotalQty(-2)
+ self.assertEqual(self.queue, [[-2, 5]])
self.queue.add_stock(2, 10)
self.assertTotalQty(0)
@@ -75,7 +75,6 @@ class TestFIFOValuation(unittest.TestCase):
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 @@ class TestFIFOValuation(unittest.TestCase):
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)
@@ -95,7 +93,7 @@ class TestFIFOValuation(unittest.TestCase):
self.queue.remove_stock(3, 20)
self.assertEqual(self.queue, [[1, 10], [5, 20]])
- def test_collapsing_of_queue(self):
+ def test_queue_with_unknown_rate(self):
self.queue.add_stock(1, 1)
self.queue.add_stock(1, 2)
self.queue.add_stock(1, 3)
@@ -104,8 +102,7 @@ class TestFIFOValuation(unittest.TestCase):
self.assertTotalValue(10)
self.queue.remove_stock(3, 1)
- # XXX
- self.assertEqual(self.queue, [[1, 7]])
+ self.assertEqual(self.queue, [[1, 4]])
def test_rounding_off(self):
self.queue.add_stock(1.0, 1.0)
@@ -143,7 +140,9 @@ class TestFIFOValuation(unittest.TestCase):
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,15 +163,42 @@ class TestFIFOValuation(unittest.TestCase):
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)
self.assertTotalValue(total_value)
+ @given(stock_queue_generator, st.floats(min_value=0.1, max_value=1e6))
+ def test_fifo_qty_value_nonneg_hypothesis_with_outgoing_rate(self, stock_queue, outgoing_rate):
+ self.queue = FIFOValuation([])
+ total_qty = 0.0
+ total_value = 0.0
+
+ for qty, rate in stock_queue:
+ # don't allow negative stock
+ if qty == 0 or total_qty + qty < 0 or abs(qty) < 0.1:
+ continue
+ if qty > 0:
+ self.queue.add_stock(qty, rate)
+ total_qty += qty
+ total_value += qty * rate
+ else:
+ qty = abs(qty)
+ consumed = self.queue.remove_stock(qty, outgoing_rate)
+ 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)
+ self.assertGreaterEqual(total_value, 0)
+
class TestLIFOValuation(unittest.TestCase):
-
def setUp(self):
self.stack = LIFOValuation([])
@@ -185,7 +211,9 @@ class TestLIFOValuation(unittest.TestCase):
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 +276,6 @@ class TestLIFOValuation(unittest.TestCase):
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 +290,9 @@ class TestLIFOValuation(unittest.TestCase):
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 +313,15 @@ class TestLIFOValuation(unittest.TestCase):
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 +341,9 @@ class TestLIFOValuationSLE(FrappeTestCase):
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 +355,6 @@ class TestLIFOValuationSLE(FrappeTestCase):
if total_qty > 0:
self.assertEqual(stock_queue, expected_queue)
-
def test_lifo_values(self):
in1 = self._make_stock_entry(1, 1)
@@ -340,7 +373,7 @@ class TestLIFOValuationSLE(FrappeTestCase):
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 e20538928e4..d40218e1439 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -3,6 +3,7 @@
import json
+from typing import Dict, Optional
import frappe
from frappe import _
@@ -12,8 +13,13 @@ import erpnext
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 +32,27 @@ def get_stock_value_from_bin(warehouse=None, item_code=None):
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 +74,19 @@ def get_stock_value_on(warehouse=None, posting_date=None, item_code=None):
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 +95,32 @@ def get_stock_value_on(warehouse=None, posting_date=None, item_code=None):
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 +129,41 @@ def get_stock_balance(item_code, warehouse, posting_date=None, posting_time=None
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 +172,15 @@ def get_serial_nos_data_after_transactions(args):
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 +197,48 @@ def get_latest_stock_qty(item_code, warehouse=None):
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 +254,7 @@ def _create_bin(item_code, warehouse):
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 +263,21 @@ def get_incoming_rate(args, raise_error_if_no_rate=True):
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 +285,62 @@ def get_incoming_rate(args, raise_error_if_no_rate=True):
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 +359,11 @@ def _get_fifo_lifo_rate(previous_stock_queue, qty, method):
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 +379,29 @@ def get_valid_serial_nos(sr_nos, qty=0, item_code=''):
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 +419,14 @@ def update_included_uom_in_report(columns, result, include_uom, conversion_facto
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 +438,13 @@ def update_included_uom_in_report(columns, result, include_uom, conversion_facto
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 +453,17 @@ def update_included_uom_in_report(columns, result, include_uom, conversion_facto
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 +472,128 @@ def add_additional_uom_columns(columns, result, include_uom, conversion_factors)
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"]],
"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)
+
+
+@frappe.whitelist()
+def scan_barcode(search_value: str) -> Dict[str, Optional[str]]:
+
+ # search barcode no
+ barcode_data = frappe.db.get_value(
+ "Item Barcode",
+ {"barcode": search_value},
+ ["barcode", "parent as item_code"],
+ as_dict=True,
+ )
+ if barcode_data:
+ return _update_item_info(barcode_data)
+
+ # search serial no
+ serial_no_data = frappe.db.get_value(
+ "Serial No",
+ search_value,
+ ["name as serial_no", "item_code", "batch_no"],
+ as_dict=True,
+ )
+ if serial_no_data:
+ return _update_item_info(serial_no_data)
+
+ # search batch no
+ batch_no_data = frappe.db.get_value(
+ "Batch",
+ search_value,
+ ["name as batch_no", "item as item_code"],
+ as_dict=True,
+ )
+ if batch_no_data:
+ return _update_item_info(batch_no_data)
+
+ return {}
+
+
+def _update_item_info(scan_result: Dict[str, Optional[str]]) -> Dict[str, Optional[str]]:
+ if item_code := scan_result.get("item_code"):
+ if item_info := frappe.get_cached_value(
+ "Item",
+ item_code,
+ ["has_batch_no", "has_serial_no"],
+ as_dict=True,
+ ):
+ scan_result.update(item_info)
+ return scan_result
diff --git a/erpnext/stock/valuation.py b/erpnext/stock/valuation.py
index e2bd1ad4dfe..35f4f12235d 100644
--- a/erpnext/stock/valuation.py
+++ b/erpnext/stock/valuation.py
@@ -11,7 +11,6 @@ RATE = 1
class BinWiseValuation(ABC):
-
@abstractmethod
def add_stock(self, qty: float, rate: float) -> None:
pass
@@ -61,7 +60,7 @@ class FIFOValuation(BinWiseValuation):
# 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 +73,9 @@ class FIFOValuation(BinWiseValuation):
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 +100,12 @@ class FIFOValuation(BinWiseValuation):
"""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:
@@ -122,13 +121,9 @@ class FIFOValuation(BinWiseValuation):
index = idx
break
- # If no entry found with outgoing rate, collapse queue
+ # If no entry found with outgoing rate, consume as per FIFO
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]]
- consumed_bins.append([qty, outgoing_rate])
- break
+ index = 0
else:
index = 0
@@ -169,7 +164,7 @@ class LIFOValuation(BinWiseValuation):
# 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 +177,11 @@ class LIFOValuation(BinWiseValuation):
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 +200,18 @@ class LIFOValuation(BinWiseValuation):
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 +248,7 @@ def round_off_if_near_zero(number: float, precision: int = 7) -> float:
"""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)
diff --git a/erpnext/support/__init__.py b/erpnext/support/__init__.py
index ac23ede49ef..7b6845d2fd1 100644
--- a/erpnext/support/__init__.py
+++ b/erpnext/support/__init__.py
@@ -1,5 +1,5 @@
install_docs = [
- {'doctype':'Role', 'role_name':'Support Team', 'name':'Support Team'},
- {'doctype':'Role', 'role_name':'Maintenance User', 'name':'Maintenance User'},
- {'doctype':'Role', 'role_name':'Maintenance Manager', 'name':'Maintenance Manager'}
+ {"doctype": "Role", "role_name": "Support Team", "name": "Support Team"},
+ {"doctype": "Role", "role_name": "Maintenance User", "name": "Maintenance User"},
+ {"doctype": "Role", "role_name": "Maintenance Manager", "name": "Maintenance Manager"},
]
diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py
index e211e24c402..08a06b19b43 100644
--- a/erpnext/support/doctype/issue/issue.py
+++ b/erpnext/support/doctype/issue/issue.py
@@ -50,23 +50,26 @@ class Issue(Document):
self.customer = contact.get_link_for("Customer")
if not self.company:
- self.company = frappe.db.get_value("Lead", self.lead, "company") or \
- frappe.db.get_default("Company")
+ self.company = frappe.db.get_value("Lead", self.lead, "company") or frappe.db.get_default(
+ "Company"
+ )
def create_communication(self):
communication = frappe.new_doc("Communication")
- communication.update({
- "communication_type": "Communication",
- "communication_medium": "Email",
- "sent_or_received": "Received",
- "email_status": "Open",
- "subject": self.subject,
- "sender": self.raised_by,
- "content": self.description,
- "status": "Linked",
- "reference_doctype": "Issue",
- "reference_name": self.name
- })
+ communication.update(
+ {
+ "communication_type": "Communication",
+ "communication_medium": "Email",
+ "sent_or_received": "Received",
+ "email_status": "Open",
+ "subject": self.subject,
+ "sender": self.raised_by,
+ "content": self.description,
+ "status": "Linked",
+ "reference_doctype": "Issue",
+ "reference_name": self.name,
+ }
+ )
communication.ignore_permissions = True
communication.ignore_mandatory = True
communication.save()
@@ -97,23 +100,31 @@ class Issue(Document):
# Replicate linked Communications
# TODO: get all communications in timeline before this, and modify them to append them to new doc
comm_to_split_from = frappe.get_doc("Communication", communication_id)
- communications = frappe.get_all("Communication",
- filters={"reference_doctype": "Issue",
+ communications = frappe.get_all(
+ "Communication",
+ filters={
+ "reference_doctype": "Issue",
"reference_name": comm_to_split_from.reference_name,
- "creation": (">=", comm_to_split_from.creation)})
+ "creation": (">=", comm_to_split_from.creation),
+ },
+ )
for communication in communications:
doc = frappe.get_doc("Communication", communication.name)
doc.reference_name = replicated_issue.name
doc.save(ignore_permissions=True)
- frappe.get_doc({
- "doctype": "Comment",
- "comment_type": "Info",
- "reference_doctype": "Issue",
- "reference_name": replicated_issue.name,
- "content": " - Split the Issue from {1}".format(self.name, frappe.bold(self.name)),
- }).insert(ignore_permissions=True)
+ frappe.get_doc(
+ {
+ "doctype": "Comment",
+ "comment_type": "Info",
+ "reference_doctype": "Issue",
+ "reference_name": replicated_issue.name,
+ "content": " - Split the Issue from {1}".format(
+ self.name, frappe.bold(self.name)
+ ),
+ }
+ ).insert(ignore_permissions=True)
return replicated_issue.name
@@ -121,6 +132,7 @@ class Issue(Document):
self.db_set("resolution_time", None)
self.db_set("user_resolution_time", None)
+
def get_list_context(context=None):
return {
"title": _("Issues"),
@@ -128,7 +140,7 @@ def get_list_context(context=None):
"row_template": "templates/includes/issue_row.html",
"show_sidebar": True,
"show_search": True,
- "no_breadcrumbs": True
+ "no_breadcrumbs": True,
}
@@ -145,7 +157,8 @@ def get_issue_list(doctype, txt, filters, limit_start, limit_page_length=20, ord
ignore_permissions = False
if is_website_user():
- if not filters: filters = {}
+ if not filters:
+ filters = {}
if customer:
filters["customer"] = customer
@@ -154,7 +167,9 @@ def get_issue_list(doctype, txt, filters, limit_start, limit_page_length=20, ord
ignore_permissions = True
- return get_list(doctype, txt, filters, limit_start, limit_page_length, ignore_permissions=ignore_permissions)
+ return get_list(
+ doctype, txt, filters, limit_start, limit_page_length, ignore_permissions=ignore_permissions
+ )
@frappe.whitelist()
@@ -163,16 +178,24 @@ def set_multiple_status(names, status):
for name in json.loads(names):
frappe.db.set_value("Issue", name, "status", status)
+
@frappe.whitelist()
def set_status(name, status):
frappe.db.set_value("Issue", name, "status", status)
+
def auto_close_tickets():
"""Auto-close replied support tickets after 7 days"""
- auto_close_after_days = frappe.db.get_value("Support Settings", "Support Settings", "close_issue_after_days") or 7
+ auto_close_after_days = (
+ frappe.db.get_value("Support Settings", "Support Settings", "close_issue_after_days") or 7
+ )
- issues = frappe.db.sql(""" select name from tabIssue where status='Replied' and
- modified 1 pm, Response Time -> 4 hrs, Resolution Time -> 6 hrs
frappe.flags.current_time = get_datetime("2021-11-01 13:00")
- issue = make_issue(frappe.flags.current_time, index=1, issue_type='Critical') # Applies 24hr working time SLA
+ issue = make_issue(
+ frappe.flags.current_time, index=1, issue_type="Critical"
+ ) # Applies 24hr working time SLA
create_communication(issue.name, "test@example.com", "Received", frappe.flags.current_time)
- self.assertEquals(issue.agreement_status, 'First Response Due')
+ self.assertEquals(issue.agreement_status, "First Response Due")
self.assertEquals(issue.response_by, get_datetime("2021-11-01 17:00"))
self.assertEquals(issue.resolution_by, get_datetime("2021-11-01 19:00"))
@@ -158,9 +162,9 @@ class TestIssue(TestSetUp):
frappe.flags.current_time = get_datetime("2021-11-01 14:00")
create_communication(issue.name, "test@admin.com", "Sent", frappe.flags.current_time)
issue.reload()
- issue.status = 'Replied'
+ issue.status = "Replied"
issue.save()
- self.assertEquals(issue.agreement_status, 'Resolution Due')
+ self.assertEquals(issue.agreement_status, "Resolution Due")
self.assertEquals(issue.on_hold_since, frappe.flags.current_time)
self.assertEquals(issue.first_responded_on, frappe.flags.current_time)
@@ -168,7 +172,7 @@ class TestIssue(TestSetUp):
frappe.flags.current_time = get_datetime("2021-11-01 15:00")
create_communication(issue.name, "test@example.com", "Received", frappe.flags.current_time)
issue.reload()
- self.assertEquals(issue.status, 'Open')
+ self.assertEquals(issue.status, "Open")
# Hold Time + 1 Hrs
self.assertEquals(issue.total_hold_time, 3600)
# Resolution By should increase by one hrs
@@ -178,19 +182,19 @@ class TestIssue(TestSetUp):
frappe.flags.current_time = get_datetime("2021-11-01 16:00")
create_communication(issue.name, "test@admin.com", "Sent", frappe.flags.current_time)
issue.reload()
- issue.status = 'Replied'
+ issue.status = "Replied"
issue.save()
- self.assertEquals(issue.agreement_status, 'Resolution Due')
+ self.assertEquals(issue.agreement_status, "Resolution Due")
# Customer Closed → 10 pm
frappe.flags.current_time = get_datetime("2021-11-01 22:00")
- issue.status = 'Closed'
+ issue.status = "Closed"
issue.save()
# Hold Time + 6 Hrs
self.assertEquals(issue.total_hold_time, 3600 + 21600)
# Resolution By should increase by 6 hrs
self.assertEquals(issue.resolution_by, get_datetime("2021-11-02 02:00"))
- self.assertEquals(issue.agreement_status, 'Fulfilled')
+ self.assertEquals(issue.agreement_status, "Fulfilled")
self.assertEquals(issue.resolution_date, frappe.flags.current_time)
# Customer Open → 3 am i.e after resolution by is crossed
@@ -201,15 +205,15 @@ class TestIssue(TestSetUp):
self.assertEquals(issue.total_hold_time, 3600 + 21600 + 18000)
# Resolution By should increase by 5 hrs
self.assertEquals(issue.resolution_by, get_datetime("2021-11-02 07:00"))
- self.assertEquals(issue.agreement_status, 'Resolution Due')
+ self.assertEquals(issue.agreement_status, "Resolution Due")
self.assertFalse(issue.resolution_date)
# We Closed → 4 am, SLA should be Fulfilled
frappe.flags.current_time = get_datetime("2021-11-02 04:00")
- issue.status = 'Closed'
+ issue.status = "Closed"
issue.save()
self.assertEquals(issue.resolution_by, get_datetime("2021-11-02 07:00"))
- self.assertEquals(issue.agreement_status, 'Fulfilled')
+ self.assertEquals(issue.agreement_status, "Fulfilled")
self.assertEquals(issue.resolution_date, frappe.flags.current_time)
def test_recording_of_assignment_on_first_reponse_failure(self):
@@ -219,11 +223,7 @@ class TestIssue(TestSetUp):
issue = make_issue(frappe.flags.current_time, index=1)
create_communication(issue.name, "test@example.com", "Received", frappe.flags.current_time)
- add_assignment({
- 'doctype': issue.doctype,
- 'name': issue.name,
- 'assign_to': ['test@admin.com']
- })
+ add_assignment({"doctype": issue.doctype, "name": issue.name, "assign_to": ["test@admin.com"]})
issue.reload()
# send a reply failing response SLA
@@ -232,12 +232,15 @@ class TestIssue(TestSetUp):
# assert if a new timeline item has been added
# to record the assignment
- comment = frappe.db.exists('Comment', {
- 'reference_doctype': 'Issue',
- 'reference_name': issue.name,
- 'comment_type': 'Assigned',
- 'content': _('First Response SLA Failed by {}').format('test')
- })
+ comment = frappe.db.exists(
+ "Comment",
+ {
+ "reference_doctype": "Issue",
+ "reference_name": issue.name,
+ "comment_type": "Assigned",
+ "content": _("First Response SLA Failed by {}").format("test"),
+ },
+ )
self.assertTrue(comment)
def test_agreement_status_on_response(self):
@@ -245,7 +248,7 @@ class TestIssue(TestSetUp):
issue = make_issue(frappe.flags.current_time, index=1)
create_communication(issue.name, "test@example.com", "Received", frappe.flags.current_time)
- self.assertTrue(issue.status == 'Open')
+ self.assertTrue(issue.status == "Open")
# send a reply within response SLA
frappe.flags.current_time = get_datetime("2021-11-02 11:00")
@@ -253,7 +256,8 @@ class TestIssue(TestSetUp):
issue.reload()
self.assertEquals(issue.first_responded_on, frappe.flags.current_time)
- self.assertEquals(issue.agreement_status, 'Resolution Due')
+ self.assertEquals(issue.agreement_status, "Resolution Due")
+
class TestFirstResponseTime(TestSetUp):
# working hours used in all cases: Mon-Fri, 10am to 6pm
@@ -262,209 +266,268 @@ class TestFirstResponseTime(TestSetUp):
# issue creation and first response are on the same day
def test_first_response_time_case1(self):
"""
- Test frt when issue creation and first response are during working hours on the same day.
+ Test frt when issue creation and first response are during working hours on the same day.
"""
- issue = create_issue_and_communication(get_datetime("06-28-2021 11:00"), get_datetime("06-28-2021 12:00"))
+ issue = create_issue_and_communication(
+ get_datetime("06-28-2021 11:00"), get_datetime("06-28-2021 12:00")
+ )
self.assertEqual(issue.first_response_time, 3600.0)
def test_first_response_time_case2(self):
"""
- Test frt when issue creation was during working hours, but first response is sent after working hours on the same day.
+ Test frt when issue creation was during working hours, but first response is sent after working hours on the same day.
"""
- issue = create_issue_and_communication(get_datetime("06-28-2021 12:00"), get_datetime("06-28-2021 20:00"))
+ issue = create_issue_and_communication(
+ get_datetime("06-28-2021 12:00"), get_datetime("06-28-2021 20:00")
+ )
self.assertEqual(issue.first_response_time, 21600.0)
def test_first_response_time_case3(self):
"""
- Test frt when issue creation was before working hours but first response is sent during working hours on the same day.
+ Test frt when issue creation was before working hours but first response is sent during working hours on the same day.
"""
- issue = create_issue_and_communication(get_datetime("06-28-2021 6:00"), get_datetime("06-28-2021 12:00"))
+ issue = create_issue_and_communication(
+ get_datetime("06-28-2021 6:00"), get_datetime("06-28-2021 12:00")
+ )
self.assertEqual(issue.first_response_time, 7200.0)
def test_first_response_time_case4(self):
"""
- Test frt when both issue creation and first response were after working hours on the same day.
+ Test frt when both issue creation and first response were after working hours on the same day.
"""
- issue = create_issue_and_communication(get_datetime("06-28-2021 19:00"), get_datetime("06-28-2021 20:00"))
+ issue = create_issue_and_communication(
+ get_datetime("06-28-2021 19:00"), get_datetime("06-28-2021 20:00")
+ )
self.assertEqual(issue.first_response_time, 1.0)
def test_first_response_time_case5(self):
"""
- Test frt when both issue creation and first response are on the same day, but it's not a work day.
+ Test frt when both issue creation and first response are on the same day, but it's not a work day.
"""
- issue = create_issue_and_communication(get_datetime("06-27-2021 10:00"), get_datetime("06-27-2021 11:00"))
+ issue = create_issue_and_communication(
+ get_datetime("06-27-2021 10:00"), get_datetime("06-27-2021 11:00")
+ )
self.assertEqual(issue.first_response_time, 1.0)
# issue creation and first response are on consecutive days
def test_first_response_time_case6(self):
"""
- Test frt when the issue was created before working hours and the first response is also sent before working hours, but on the next day.
+ Test frt when the issue was created before working hours and the first response is also sent before working hours, but on the next day.
"""
- issue = create_issue_and_communication(get_datetime("06-28-2021 6:00"), get_datetime("06-29-2021 6:00"))
+ issue = create_issue_and_communication(
+ get_datetime("06-28-2021 6:00"), get_datetime("06-29-2021 6:00")
+ )
self.assertEqual(issue.first_response_time, 28800.0)
def test_first_response_time_case7(self):
"""
- Test frt when the issue was created before working hours and the first response is sent during working hours, but on the next day.
+ Test frt when the issue was created before working hours and the first response is sent during working hours, but on the next day.
"""
- issue = create_issue_and_communication(get_datetime("06-28-2021 6:00"), get_datetime("06-29-2021 11:00"))
+ issue = create_issue_and_communication(
+ get_datetime("06-28-2021 6:00"), get_datetime("06-29-2021 11:00")
+ )
self.assertEqual(issue.first_response_time, 32400.0)
def test_first_response_time_case8(self):
"""
- Test frt when the issue was created before working hours and the first response is sent after working hours, but on the next day.
+ Test frt when the issue was created before working hours and the first response is sent after working hours, but on the next day.
"""
- issue = create_issue_and_communication(get_datetime("06-28-2021 6:00"), get_datetime("06-29-2021 20:00"))
+ issue = create_issue_and_communication(
+ get_datetime("06-28-2021 6:00"), get_datetime("06-29-2021 20:00")
+ )
self.assertEqual(issue.first_response_time, 57600.0)
def test_first_response_time_case9(self):
"""
- Test frt when the issue was created before working hours and the first response is sent on the next day, which is not a work day.
+ Test frt when the issue was created before working hours and the first response is sent on the next day, which is not a work day.
"""
- issue = create_issue_and_communication(get_datetime("06-25-2021 6:00"), get_datetime("06-26-2021 11:00"))
+ issue = create_issue_and_communication(
+ get_datetime("06-25-2021 6:00"), get_datetime("06-26-2021 11:00")
+ )
self.assertEqual(issue.first_response_time, 28800.0)
def test_first_response_time_case10(self):
"""
- Test frt when the issue was created during working hours and the first response is sent before working hours, but on the next day.
+ Test frt when the issue was created during working hours and the first response is sent before working hours, but on the next day.
"""
- issue = create_issue_and_communication(get_datetime("06-28-2021 12:00"), get_datetime("06-29-2021 6:00"))
+ issue = create_issue_and_communication(
+ get_datetime("06-28-2021 12:00"), get_datetime("06-29-2021 6:00")
+ )
self.assertEqual(issue.first_response_time, 21600.0)
def test_first_response_time_case11(self):
"""
- Test frt when the issue was created during working hours and the first response is also sent during working hours, but on the next day.
+ Test frt when the issue was created during working hours and the first response is also sent during working hours, but on the next day.
"""
- issue = create_issue_and_communication(get_datetime("06-28-2021 12:00"), get_datetime("06-29-2021 11:00"))
+ issue = create_issue_and_communication(
+ get_datetime("06-28-2021 12:00"), get_datetime("06-29-2021 11:00")
+ )
self.assertEqual(issue.first_response_time, 25200.0)
def test_first_response_time_case12(self):
"""
- Test frt when the issue was created during working hours and the first response is sent after working hours, but on the next day.
+ Test frt when the issue was created during working hours and the first response is sent after working hours, but on the next day.
"""
- issue = create_issue_and_communication(get_datetime("06-28-2021 12:00"), get_datetime("06-29-2021 20:00"))
+ issue = create_issue_and_communication(
+ get_datetime("06-28-2021 12:00"), get_datetime("06-29-2021 20:00")
+ )
self.assertEqual(issue.first_response_time, 50400.0)
def test_first_response_time_case13(self):
"""
- Test frt when the issue was created during working hours and the first response is sent on the next day, which is not a work day.
+ Test frt when the issue was created during working hours and the first response is sent on the next day, which is not a work day.
"""
- issue = create_issue_and_communication(get_datetime("06-25-2021 12:00"), get_datetime("06-26-2021 11:00"))
+ issue = create_issue_and_communication(
+ get_datetime("06-25-2021 12:00"), get_datetime("06-26-2021 11:00")
+ )
self.assertEqual(issue.first_response_time, 21600.0)
def test_first_response_time_case14(self):
"""
- Test frt when the issue was created after working hours and the first response is sent before working hours, but on the next day.
+ Test frt when the issue was created after working hours and the first response is sent before working hours, but on the next day.
"""
- issue = create_issue_and_communication(get_datetime("06-28-2021 20:00"), get_datetime("06-29-2021 6:00"))
+ issue = create_issue_and_communication(
+ get_datetime("06-28-2021 20:00"), get_datetime("06-29-2021 6:00")
+ )
self.assertEqual(issue.first_response_time, 1.0)
def test_first_response_time_case15(self):
"""
- Test frt when the issue was created after working hours and the first response is sent during working hours, but on the next day.
+ Test frt when the issue was created after working hours and the first response is sent during working hours, but on the next day.
"""
- issue = create_issue_and_communication(get_datetime("06-28-2021 20:00"), get_datetime("06-29-2021 11:00"))
+ issue = create_issue_and_communication(
+ get_datetime("06-28-2021 20:00"), get_datetime("06-29-2021 11:00")
+ )
self.assertEqual(issue.first_response_time, 3600.0)
def test_first_response_time_case16(self):
"""
- Test frt when the issue was created after working hours and the first response is also sent after working hours, but on the next day.
+ Test frt when the issue was created after working hours and the first response is also sent after working hours, but on the next day.
"""
- issue = create_issue_and_communication(get_datetime("06-28-2021 20:00"), get_datetime("06-29-2021 20:00"))
+ issue = create_issue_and_communication(
+ get_datetime("06-28-2021 20:00"), get_datetime("06-29-2021 20:00")
+ )
self.assertEqual(issue.first_response_time, 28800.0)
def test_first_response_time_case17(self):
"""
- Test frt when the issue was created after working hours and the first response is sent on the next day, which is not a work day.
+ Test frt when the issue was created after working hours and the first response is sent on the next day, which is not a work day.
"""
- issue = create_issue_and_communication(get_datetime("06-25-2021 20:00"), get_datetime("06-26-2021 11:00"))
+ issue = create_issue_and_communication(
+ get_datetime("06-25-2021 20:00"), get_datetime("06-26-2021 11:00")
+ )
self.assertEqual(issue.first_response_time, 1.0)
# issue creation and first response are a few days apart
def test_first_response_time_case18(self):
"""
- Test frt when the issue was created before working hours and the first response is also sent before working hours, but after a few days.
+ Test frt when the issue was created before working hours and the first response is also sent before working hours, but after a few days.
"""
- issue = create_issue_and_communication(get_datetime("06-28-2021 6:00"), get_datetime("07-01-2021 6:00"))
+ issue = create_issue_and_communication(
+ get_datetime("06-28-2021 6:00"), get_datetime("07-01-2021 6:00")
+ )
self.assertEqual(issue.first_response_time, 86400.0)
def test_first_response_time_case19(self):
"""
- Test frt when the issue was created before working hours and the first response is sent during working hours, but after a few days.
+ Test frt when the issue was created before working hours and the first response is sent during working hours, but after a few days.
"""
- issue = create_issue_and_communication(get_datetime("06-28-2021 6:00"), get_datetime("07-01-2021 11:00"))
+ issue = create_issue_and_communication(
+ get_datetime("06-28-2021 6:00"), get_datetime("07-01-2021 11:00")
+ )
self.assertEqual(issue.first_response_time, 90000.0)
def test_first_response_time_case20(self):
"""
- Test frt when the issue was created before working hours and the first response is sent after working hours, but after a few days.
+ Test frt when the issue was created before working hours and the first response is sent after working hours, but after a few days.
"""
- issue = create_issue_and_communication(get_datetime("06-28-2021 6:00"), get_datetime("07-01-2021 20:00"))
+ issue = create_issue_and_communication(
+ get_datetime("06-28-2021 6:00"), get_datetime("07-01-2021 20:00")
+ )
self.assertEqual(issue.first_response_time, 115200.0)
def test_first_response_time_case21(self):
"""
- Test frt when the issue was created before working hours and the first response is sent after a few days, on a holiday.
+ Test frt when the issue was created before working hours and the first response is sent after a few days, on a holiday.
"""
- issue = create_issue_and_communication(get_datetime("06-25-2021 6:00"), get_datetime("06-27-2021 11:00"))
+ issue = create_issue_and_communication(
+ get_datetime("06-25-2021 6:00"), get_datetime("06-27-2021 11:00")
+ )
self.assertEqual(issue.first_response_time, 28800.0)
def test_first_response_time_case22(self):
"""
- Test frt when the issue was created during working hours and the first response is sent before working hours, but after a few days.
+ Test frt when the issue was created during working hours and the first response is sent before working hours, but after a few days.
"""
- issue = create_issue_and_communication(get_datetime("06-28-2021 12:00"), get_datetime("07-01-2021 6:00"))
+ issue = create_issue_and_communication(
+ get_datetime("06-28-2021 12:00"), get_datetime("07-01-2021 6:00")
+ )
self.assertEqual(issue.first_response_time, 79200.0)
def test_first_response_time_case23(self):
"""
- Test frt when the issue was created during working hours and the first response is also sent during working hours, but after a few days.
+ Test frt when the issue was created during working hours and the first response is also sent during working hours, but after a few days.
"""
- issue = create_issue_and_communication(get_datetime("06-28-2021 12:00"), get_datetime("07-01-2021 11:00"))
+ issue = create_issue_and_communication(
+ get_datetime("06-28-2021 12:00"), get_datetime("07-01-2021 11:00")
+ )
self.assertEqual(issue.first_response_time, 82800.0)
def test_first_response_time_case24(self):
"""
- Test frt when the issue was created during working hours and the first response is sent after working hours, but after a few days.
+ Test frt when the issue was created during working hours and the first response is sent after working hours, but after a few days.
"""
- issue = create_issue_and_communication(get_datetime("06-28-2021 12:00"), get_datetime("07-01-2021 20:00"))
+ issue = create_issue_and_communication(
+ get_datetime("06-28-2021 12:00"), get_datetime("07-01-2021 20:00")
+ )
self.assertEqual(issue.first_response_time, 108000.0)
def test_first_response_time_case25(self):
"""
- Test frt when the issue was created during working hours and the first response is sent after a few days, on a holiday.
+ Test frt when the issue was created during working hours and the first response is sent after a few days, on a holiday.
"""
- issue = create_issue_and_communication(get_datetime("06-25-2021 12:00"), get_datetime("06-27-2021 11:00"))
+ issue = create_issue_and_communication(
+ get_datetime("06-25-2021 12:00"), get_datetime("06-27-2021 11:00")
+ )
self.assertEqual(issue.first_response_time, 21600.0)
def test_first_response_time_case26(self):
"""
- Test frt when the issue was created after working hours and the first response is sent before working hours, but after a few days.
+ Test frt when the issue was created after working hours and the first response is sent before working hours, but after a few days.
"""
- issue = create_issue_and_communication(get_datetime("06-28-2021 20:00"), get_datetime("07-01-2021 6:00"))
+ issue = create_issue_and_communication(
+ get_datetime("06-28-2021 20:00"), get_datetime("07-01-2021 6:00")
+ )
self.assertEqual(issue.first_response_time, 57600.0)
def test_first_response_time_case27(self):
"""
- Test frt when the issue was created after working hours and the first response is sent during working hours, but after a few days.
+ Test frt when the issue was created after working hours and the first response is sent during working hours, but after a few days.
"""
- issue = create_issue_and_communication(get_datetime("06-28-2021 20:00"), get_datetime("07-01-2021 11:00"))
+ issue = create_issue_and_communication(
+ get_datetime("06-28-2021 20:00"), get_datetime("07-01-2021 11:00")
+ )
self.assertEqual(issue.first_response_time, 61200.0)
def test_first_response_time_case28(self):
"""
- Test frt when the issue was created after working hours and the first response is also sent after working hours, but after a few days.
+ Test frt when the issue was created after working hours and the first response is also sent after working hours, but after a few days.
"""
- issue = create_issue_and_communication(get_datetime("06-28-2021 20:00"), get_datetime("07-01-2021 20:00"))
+ issue = create_issue_and_communication(
+ get_datetime("06-28-2021 20:00"), get_datetime("07-01-2021 20:00")
+ )
self.assertEqual(issue.first_response_time, 86400.0)
def test_first_response_time_case29(self):
"""
- Test frt when the issue was created after working hours and the first response is sent after a few days, on a holiday.
+ Test frt when the issue was created after working hours and the first response is sent after a few days, on a holiday.
"""
- issue = create_issue_and_communication(get_datetime("06-25-2021 20:00"), get_datetime("06-27-2021 11:00"))
+ issue = create_issue_and_communication(
+ get_datetime("06-25-2021 20:00"), get_datetime("06-27-2021 11:00")
+ )
self.assertEqual(issue.first_response_time, 1.0)
+
def create_issue_and_communication(issue_creation, first_responded_on):
issue = make_issue(issue_creation, index=1)
sender = create_user("test@admin.com")
@@ -474,25 +537,28 @@ def create_issue_and_communication(issue_creation, first_responded_on):
return issue
+
def make_issue(creation=None, customer=None, index=0, priority=None, issue_type=None):
- if issue_type and not frappe.db.exists('Issue Type', issue_type):
- doc = frappe.new_doc('Issue Type')
+ if issue_type and not frappe.db.exists("Issue Type", issue_type):
+ doc = frappe.new_doc("Issue Type")
doc.name = issue_type
doc.insert()
- issue = frappe.get_doc({
- "doctype": "Issue",
- "subject": "Service Level Agreement Issue {0}".format(index),
- "customer": customer,
- "raised_by": "test@example.com",
- "description": "Service Level Agreement Issue",
- "issue_type": issue_type,
- "priority": priority,
- "creation": creation,
- "opening_date": creation,
- "service_level_agreement_creation": creation,
- "company": "_Test Company"
- }).insert(ignore_permissions=True)
+ issue = frappe.get_doc(
+ {
+ "doctype": "Issue",
+ "subject": "Service Level Agreement Issue {0}".format(index),
+ "customer": customer,
+ "raised_by": "test@example.com",
+ "description": "Service Level Agreement Issue",
+ "issue_type": issue_type,
+ "priority": priority,
+ "creation": creation,
+ "opening_date": creation,
+ "service_level_agreement_creation": creation,
+ "company": "_Test Company",
+ }
+ ).insert(ignore_permissions=True)
return issue
@@ -503,45 +569,50 @@ def create_customer(name, customer_group, territory):
create_territory(territory)
if not frappe.db.exists("Customer", {"customer_name": name}):
- frappe.get_doc({
- "doctype": "Customer",
- "customer_name": name,
- "customer_group": customer_group,
- "territory": territory
- }).insert(ignore_permissions=True)
+ frappe.get_doc(
+ {
+ "doctype": "Customer",
+ "customer_name": name,
+ "customer_group": customer_group,
+ "territory": territory,
+ }
+ ).insert(ignore_permissions=True)
def create_customer_group(customer_group):
if not frappe.db.exists("Customer Group", {"customer_group_name": customer_group}):
- frappe.get_doc({
- "doctype": "Customer Group",
- "customer_group_name": customer_group
- }).insert(ignore_permissions=True)
+ frappe.get_doc({"doctype": "Customer Group", "customer_group_name": customer_group}).insert(
+ ignore_permissions=True
+ )
def create_territory(territory):
if not frappe.db.exists("Territory", {"territory_name": territory}):
- frappe.get_doc({
- "doctype": "Territory",
- "territory_name": territory,
- }).insert(ignore_permissions=True)
+ frappe.get_doc(
+ {
+ "doctype": "Territory",
+ "territory_name": territory,
+ }
+ ).insert(ignore_permissions=True)
def create_communication(reference_name, sender, sent_or_received, creation):
- communication = frappe.get_doc({
- "doctype": "Communication",
- "communication_type": "Communication",
- "communication_medium": "Email",
- "sent_or_received": sent_or_received,
- "email_status": "Open",
- "subject": "Test Issue",
- "sender": sender,
- "content": "Test",
- "status": "Linked",
- "reference_doctype": "Issue",
- "creation": creation,
- "reference_name": reference_name
- })
+ communication = frappe.get_doc(
+ {
+ "doctype": "Communication",
+ "communication_type": "Communication",
+ "communication_medium": "Email",
+ "sent_or_received": sent_or_received,
+ "email_status": "Open",
+ "subject": "Test Issue",
+ "sender": sender,
+ "content": "Test",
+ "status": "Linked",
+ "reference_doctype": "Issue",
+ "creation": creation,
+ "reference_name": reference_name,
+ }
+ )
communication.save()
diff --git a/erpnext/support/doctype/issue_priority/test_issue_priority.py b/erpnext/support/doctype/issue_priority/test_issue_priority.py
index d2b1415d33a..c30540b9271 100644
--- a/erpnext/support/doctype/issue_priority/test_issue_priority.py
+++ b/erpnext/support/doctype/issue_priority/test_issue_priority.py
@@ -7,7 +7,6 @@ import frappe
class TestIssuePriority(unittest.TestCase):
-
def test_priorities(self):
make_priorities()
priorities = frappe.get_list("Issue Priority")
@@ -15,14 +14,13 @@ class TestIssuePriority(unittest.TestCase):
for priority in priorities:
self.assertIn(priority.name, ["Low", "Medium", "High"])
+
def make_priorities():
insert_priority("Low")
insert_priority("Medium")
insert_priority("High")
+
def insert_priority(name):
if not frappe.db.exists("Issue Priority", name):
- frappe.get_doc({
- "doctype": "Issue Priority",
- "name": name
- }).insert(ignore_permissions=True)
+ frappe.get_doc({"doctype": "Issue Priority", "name": name}).insert(ignore_permissions=True)
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
index 526b6aa249e..e49f212f10f 100644
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
+++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
@@ -42,16 +42,24 @@ class ServiceLevelAgreement(Document):
for priority in self.priorities:
# Check if response and resolution time is set for every priority
if not priority.response_time:
- frappe.throw(_("Set Response Time for Priority {0} in row {1}.").format(priority.priority, priority.idx))
+ frappe.throw(
+ _("Set Response Time for Priority {0} in row {1}.").format(priority.priority, priority.idx)
+ )
if self.apply_sla_for_resolution:
if not priority.resolution_time:
- frappe.throw(_("Set Response Time for Priority {0} in row {1}.").format(priority.priority, priority.idx))
+ frappe.throw(
+ _("Set Response Time for Priority {0} in row {1}.").format(priority.priority, priority.idx)
+ )
response = priority.response_time
resolution = priority.resolution_time
if response > resolution:
- frappe.throw(_("Response Time for {0} priority in row {1} can't be greater than Resolution Time.").format(priority.priority, priority.idx))
+ frappe.throw(
+ _("Response Time for {0} priority in row {1} can't be greater than Resolution Time.").format(
+ priority.priority, priority.idx
+ )
+ )
priorities.append(priority.priority)
@@ -74,9 +82,14 @@ class ServiceLevelAgreement(Document):
support_days.append(support_and_resolution.workday)
support_and_resolution.idx = week.index(support_and_resolution.workday) + 1
- if to_timedelta(support_and_resolution.start_time) >= to_timedelta(support_and_resolution.end_time):
- frappe.throw(_("Start Time can't be greater than or equal to End Time for {0}.").format(
- support_and_resolution.workday))
+ if to_timedelta(support_and_resolution.start_time) >= to_timedelta(
+ support_and_resolution.end_time
+ ):
+ frappe.throw(
+ _("Start Time can't be greater than or equal to End Time for {0}.").format(
+ support_and_resolution.workday
+ )
+ )
# Check for repeated workday
if not len(set(support_days)) == len(support_days):
@@ -84,51 +97,76 @@ class ServiceLevelAgreement(Document):
frappe.throw(_("Workday {0} has been repeated.").format(repeated_days))
def validate_doc(self):
- if self.enabled and self.document_type == "Issue" \
- and not frappe.db.get_single_value("Support Settings", "track_service_level_agreement"):
- frappe.throw(_("{0} is not enabled in {1}").format(frappe.bold("Track Service Level Agreement"),
- get_link_to_form("Support Settings", "Support Settings")))
+ if (
+ self.enabled
+ and self.document_type == "Issue"
+ and not frappe.db.get_single_value("Support Settings", "track_service_level_agreement")
+ ):
+ frappe.throw(
+ _("{0} is not enabled in {1}").format(
+ frappe.bold("Track Service Level Agreement"),
+ get_link_to_form("Support Settings", "Support Settings"),
+ )
+ )
- if self.default_service_level_agreement and frappe.db.exists("Service Level Agreement", {
- "document_type": self.document_type,
- "default_service_level_agreement": "1",
- "name": ["!=", self.name]
- }):
- frappe.throw(_("Default Service Level Agreement for {0} already exists.").format(self.document_type))
+ if self.default_service_level_agreement and frappe.db.exists(
+ "Service Level Agreement",
+ {
+ "document_type": self.document_type,
+ "default_service_level_agreement": "1",
+ "name": ["!=", self.name],
+ },
+ ):
+ frappe.throw(
+ _("Default Service Level Agreement for {0} already exists.").format(self.document_type)
+ )
if self.start_date and self.end_date:
self.validate_from_to_dates(self.start_date, self.end_date)
- if self.entity_type and self.entity and frappe.db.exists("Service Level Agreement", {
- "entity_type": self.entity_type,
- "entity": self.entity,
- "name": ["!=", self.name]
- }):
- frappe.throw(_("Service Level Agreement for {0} {1} already exists.").format(
- frappe.bold(self.entity_type), frappe.bold(self.entity)))
+ if (
+ self.entity_type
+ and self.entity
+ and frappe.db.exists(
+ "Service Level Agreement",
+ {"entity_type": self.entity_type, "entity": self.entity, "name": ["!=", self.name]},
+ )
+ ):
+ frappe.throw(
+ _("Service Level Agreement for {0} {1} already exists.").format(
+ frappe.bold(self.entity_type), frappe.bold(self.entity)
+ )
+ )
def validate_selected_doctype(self):
invalid_doctypes = list(frappe.model.core_doctypes_list)
- invalid_doctypes.extend(['Cost Center', 'Company'])
- valid_document_types = frappe.get_all('DocType', {
- 'issingle': 0,
- 'istable': 0,
- 'is_submittable': 0,
- 'name': ['not in', invalid_doctypes],
- 'module': ['not in', ["Email", "Core", "Custom", "Event Streaming", "Social", "Data Migration", "Geo", "Desk"]]
- }, pluck="name")
+ invalid_doctypes.extend(["Cost Center", "Company"])
+ valid_document_types = frappe.get_all(
+ "DocType",
+ {
+ "issingle": 0,
+ "istable": 0,
+ "is_submittable": 0,
+ "name": ["not in", invalid_doctypes],
+ "module": [
+ "not in",
+ ["Email", "Core", "Custom", "Event Streaming", "Social", "Data Migration", "Geo", "Desk"],
+ ],
+ },
+ pluck="name",
+ )
if self.document_type not in valid_document_types:
- frappe.throw(
- msg=_("Please select valid document type."),
- title=_("Invalid Document Type")
- )
+ frappe.throw(msg=_("Please select valid document type."), title=_("Invalid Document Type"))
def validate_status_field(self):
meta = frappe.get_meta(self.document_type)
if not meta.get_field("status"):
- frappe.throw(_("The Document Type {0} must have a Status field to configure Service Level Agreement").format(
- frappe.bold(self.document_type)))
+ frappe.throw(
+ _(
+ "The Document Type {0} must have a Status field to configure Service Level Agreement"
+ ).format(frappe.bold(self.document_type))
+ )
def validate_condition(self):
temp_doc = frappe.new_doc(self.document_type)
@@ -141,11 +179,13 @@ class ServiceLevelAgreement(Document):
def get_service_level_agreement_priority(self, priority):
priority = frappe.get_doc("Service Level Priority", {"priority": priority, "parent": self.name})
- return frappe._dict({
- "priority": priority.priority,
- "response_time": priority.response_time,
- "resolution_time": priority.resolution_time
- })
+ return frappe._dict(
+ {
+ "priority": priority.priority,
+ "response_time": priority.response_time,
+ "resolution_time": priority.resolution_time,
+ }
+ )
def before_insert(self):
# no need to set up SLA fields for Issue dt as they are standard fields in Issue
@@ -176,46 +216,50 @@ class ServiceLevelAgreement(Document):
if not meta.has_field(field.get("fieldname")):
last_index += 1
- frappe.get_doc({
- "doctype": "DocField",
- "idx": last_index,
- "parenttype": "DocType",
- "parentfield": "fields",
- "parent": self.document_type,
- "label": field.get("label"),
- "fieldname": field.get("fieldname"),
- "fieldtype": field.get("fieldtype"),
- "collapsible": field.get("collapsible"),
- "options": field.get("options"),
- "read_only": field.get("read_only"),
- "hidden": field.get("hidden"),
- "description": field.get("description"),
- "default": field.get("default"),
- }).insert(ignore_permissions=True)
+ frappe.get_doc(
+ {
+ "doctype": "DocField",
+ "idx": last_index,
+ "parenttype": "DocType",
+ "parentfield": "fields",
+ "parent": self.document_type,
+ "label": field.get("label"),
+ "fieldname": field.get("fieldname"),
+ "fieldtype": field.get("fieldtype"),
+ "collapsible": field.get("collapsible"),
+ "options": field.get("options"),
+ "read_only": field.get("read_only"),
+ "hidden": field.get("hidden"),
+ "description": field.get("description"),
+ "default": field.get("default"),
+ }
+ ).insert(ignore_permissions=True)
else:
existing_field = meta.get_field(field.get("fieldname"))
self.reset_field_properties(existing_field, "DocField", field)
# to update meta and modified timestamp
- frappe.get_doc('DocType', self.document_type).save(ignore_permissions=True)
+ frappe.get_doc("DocType", self.document_type).save(ignore_permissions=True)
def create_custom_fields(self, meta, service_level_agreement_fields):
for field in service_level_agreement_fields:
if not meta.has_field(field.get("fieldname")):
- frappe.get_doc({
- "doctype": "Custom Field",
- "dt": self.document_type,
- "label": field.get("label"),
- "fieldname": field.get("fieldname"),
- "fieldtype": field.get("fieldtype"),
- "insert_after": "append",
- "collapsible": field.get("collapsible"),
- "options": field.get("options"),
- "read_only": field.get("read_only"),
- "hidden": field.get("hidden"),
- "description": field.get("description"),
- "default": field.get("default"),
- }).insert(ignore_permissions=True)
+ frappe.get_doc(
+ {
+ "doctype": "Custom Field",
+ "dt": self.document_type,
+ "label": field.get("label"),
+ "fieldname": field.get("fieldname"),
+ "fieldtype": field.get("fieldtype"),
+ "insert_after": "append",
+ "collapsible": field.get("collapsible"),
+ "options": field.get("options"),
+ "read_only": field.get("read_only"),
+ "hidden": field.get("hidden"),
+ "description": field.get("description"),
+ "default": field.get("default"),
+ }
+ ).insert(ignore_permissions=True)
else:
existing_field = meta.get_field(field.get("fieldname"))
self.reset_field_properties(existing_field, "Custom Field", field)
@@ -236,57 +280,73 @@ class ServiceLevelAgreement(Document):
def check_agreement_status():
- service_level_agreements = frappe.get_all("Service Level Agreement", filters=[
- {"enabled": 1},
- {"default_service_level_agreement": 0}
- ], fields=["name"])
+ service_level_agreements = frappe.get_all(
+ "Service Level Agreement",
+ filters=[{"enabled": 1}, {"default_service_level_agreement": 0}],
+ fields=["name"],
+ )
for service_level_agreement in service_level_agreements:
doc = frappe.get_doc("Service Level Agreement", service_level_agreement.name)
if doc.end_date and getdate(doc.end_date) < getdate(frappe.utils.getdate()):
frappe.db.set_value("Service Level Agreement", service_level_agreement.name, "enabled", 0)
+
def get_active_service_level_agreement_for(doc):
if not frappe.db.get_single_value("Support Settings", "track_service_level_agreement"):
return
filters = [
- ["Service Level Agreement", "document_type", "=", doc.get('doctype')],
- ["Service Level Agreement", "enabled", "=", 1]
+ ["Service Level Agreement", "document_type", "=", doc.get("doctype")],
+ ["Service Level Agreement", "enabled", "=", 1],
]
- if doc.get('priority'):
- filters.append(["Service Level Priority", "priority", "=", doc.get('priority')])
+ if doc.get("priority"):
+ filters.append(["Service Level Priority", "priority", "=", doc.get("priority")])
or_filters = []
- if doc.get('service_level_agreement'):
+ if doc.get("service_level_agreement"):
or_filters = [
- ["Service Level Agreement", "name", "=", doc.get('service_level_agreement')],
+ ["Service Level Agreement", "name", "=", doc.get("service_level_agreement")],
]
- customer = doc.get('customer')
+ customer = doc.get("customer")
if customer:
- or_filters.extend([
- ["Service Level Agreement", "entity", "in", [customer] + get_customer_group(customer) + get_customer_territory(customer)],
- ["Service Level Agreement", "entity_type", "is", "not set"]
- ])
- else:
- or_filters.append(
- ["Service Level Agreement", "entity_type", "is", "not set"]
+ or_filters.extend(
+ [
+ [
+ "Service Level Agreement",
+ "entity",
+ "in",
+ [customer] + get_customer_group(customer) + get_customer_territory(customer),
+ ],
+ ["Service Level Agreement", "entity_type", "is", "not set"],
+ ]
)
+ else:
+ or_filters.append(["Service Level Agreement", "entity_type", "is", "not set"])
- default_sla_filter = filters + [["Service Level Agreement", "default_service_level_agreement", "=", 1]]
- default_sla = frappe.get_all("Service Level Agreement", filters=default_sla_filter,
- fields=["name", "default_priority", "apply_sla_for_resolution", "condition"])
+ default_sla_filter = filters + [
+ ["Service Level Agreement", "default_service_level_agreement", "=", 1]
+ ]
+ default_sla = frappe.get_all(
+ "Service Level Agreement",
+ filters=default_sla_filter,
+ fields=["name", "default_priority", "apply_sla_for_resolution", "condition"],
+ )
filters += [["Service Level Agreement", "default_service_level_agreement", "=", 0]]
- agreements = frappe.get_all("Service Level Agreement", filters=filters, or_filters=or_filters,
- fields=["name", "default_priority", "apply_sla_for_resolution", "condition"])
+ agreements = frappe.get_all(
+ "Service Level Agreement",
+ filters=filters,
+ or_filters=or_filters,
+ fields=["name", "default_priority", "apply_sla_for_resolution", "condition"],
+ )
# check if the current document on which SLA is to be applied fulfills all the conditions
filtered_agreements = []
for agreement in agreements:
- condition = agreement.get('condition')
+ condition = agreement.get("condition")
if not condition or (condition and frappe.safe_eval(condition, None, get_context(doc))):
filtered_agreements.append(agreement)
@@ -295,8 +355,14 @@ def get_active_service_level_agreement_for(doc):
return filtered_agreements[0] if filtered_agreements else None
+
def get_context(doc):
- return {"doc": doc.as_dict(), "nowdate": nowdate, "frappe": frappe._dict(utils=get_safe_globals().get("frappe").get("utils"))}
+ return {
+ "doc": doc.as_dict(),
+ "nowdate": nowdate,
+ "frappe": frappe._dict(utils=get_safe_globals().get("frappe").get("utils")),
+ }
+
def get_customer_group(customer):
customer_groups = []
@@ -325,22 +391,33 @@ def get_service_level_agreement_filters(doctype, name, customer=None):
filters = [
["Service Level Agreement", "document_type", "=", doctype],
- ["Service Level Agreement", "enabled", "=", 1]
+ ["Service Level Agreement", "enabled", "=", 1],
]
- or_filters = [
- ["Service Level Agreement", "default_service_level_agreement", "=", 1]
- ]
+ or_filters = [["Service Level Agreement", "default_service_level_agreement", "=", 1]]
if customer:
# Include SLA with No Entity and Entity Type
or_filters.append(
- ["Service Level Agreement", "entity", "in", [""] + [customer] + get_customer_group(customer) + get_customer_territory(customer)]
+ [
+ "Service Level Agreement",
+ "entity",
+ "in",
+ [""] + [customer] + get_customer_group(customer) + get_customer_territory(customer),
+ ]
)
return {
- "priority": [priority.priority for priority in frappe.get_all("Service Level Priority", filters={"parent": name}, fields=["priority"])],
- "service_level_agreements": [d.name for d in frappe.get_all("Service Level Agreement", filters=filters, or_filters=or_filters)]
+ "priority": [
+ priority.priority
+ for priority in frappe.get_all(
+ "Service Level Priority", filters={"parent": name}, fields=["priority"]
+ )
+ ],
+ "service_level_agreements": [
+ d.name
+ for d in frappe.get_all("Service Level Agreement", filters=filters, or_filters=or_filters)
+ ],
}
@@ -366,7 +443,9 @@ def get_documents_with_active_service_level_agreement():
def set_documents_with_active_service_level_agreement():
- active = [sla.document_type for sla in frappe.get_all("Service Level Agreement", fields=["document_type"])]
+ active = [
+ sla.document_type for sla in frappe.get_all("Service Level Agreement", fields=["document_type"])
+ ]
frappe.cache().hset("service_level_agreement", "active", active)
return active
@@ -414,7 +493,7 @@ def process_sla(doc, sla):
def handle_status_change(doc, apply_sla_for_resolution):
now_time = frappe.flags.current_time or now_datetime(doc.get("owner"))
- prev_status = frappe.db.get_value(doc.doctype, doc.name, 'status')
+ prev_status = frappe.db.get_value(doc.doctype, doc.name, "status")
hold_statuses = get_hold_statuses(doc.service_level_agreement)
fulfillment_statuses = get_fulfillment_statuses(doc.service_level_agreement)
@@ -429,9 +508,9 @@ def handle_status_change(doc, apply_sla_for_resolution):
return status not in hold_statuses and status not in fulfillment_statuses
def set_first_response():
- if doc.meta.has_field("first_responded_on") and not doc.get('first_responded_on'):
+ if doc.meta.has_field("first_responded_on") and not doc.get("first_responded_on"):
doc.first_responded_on = now_time
- if get_datetime(doc.get('first_responded_on')) > get_datetime(doc.get('response_by')):
+ if get_datetime(doc.get("first_responded_on")) > get_datetime(doc.get("response_by")):
record_assigned_users_on_failure(doc)
def calculate_hold_hours():
@@ -444,7 +523,7 @@ def handle_status_change(doc, apply_sla_for_resolution):
doc.total_hold_time = (doc.total_hold_time or 0) + current_hold_hours
doc.on_hold_since = None
- if ((is_open_status(prev_status) and not is_open_status(doc.status)) or doc.flags.on_first_reply):
+ if (is_open_status(prev_status) and not is_open_status(doc.status)) or doc.flags.on_first_reply:
set_first_response()
# Open to Replied
@@ -492,22 +571,28 @@ def handle_status_change(doc, apply_sla_for_resolution):
def get_fulfillment_statuses(service_level_agreement):
- return [entry.status for entry in frappe.db.get_all("SLA Fulfilled On Status", filters={
- "parent": service_level_agreement
- }, fields=["status"])]
+ return [
+ entry.status
+ for entry in frappe.db.get_all(
+ "SLA Fulfilled On Status", filters={"parent": service_level_agreement}, fields=["status"]
+ )
+ ]
def get_hold_statuses(service_level_agreement):
- return [entry.status for entry in frappe.db.get_all("Pause SLA On Status", filters={
- "parent": service_level_agreement
- }, fields=["status"])]
+ return [
+ entry.status
+ for entry in frappe.db.get_all(
+ "Pause SLA On Status", filters={"parent": service_level_agreement}, fields=["status"]
+ )
+ ]
def update_response_and_resolution_metrics(doc, apply_sla_for_resolution):
priority = get_response_and_resolution_duration(doc)
start_date_time = get_datetime(doc.get("service_level_agreement_creation") or doc.creation)
set_response_by(doc, start_date_time, priority)
- if apply_sla_for_resolution and not doc.get('on_hold_since'): # resolution_by is reset if on hold
+ if apply_sla_for_resolution and not doc.get("on_hold_since"): # resolution_by is reset if on hold
set_resolution_by(doc, start_date_time, priority)
@@ -526,9 +611,13 @@ def get_expected_time_for(parameter, service_level, start_date_time):
current_weekday = weekdays[current_date_time.weekday()]
if not is_holiday(current_date_time, holidays) and current_weekday in support_days:
- if getdate(current_date_time) == getdate(start_date_time) \
- and get_time_in_timedelta(current_date_time.time()) > support_days[current_weekday].start_time:
- start_time = current_date_time - datetime(current_date_time.year, current_date_time.month, current_date_time.day)
+ if (
+ getdate(current_date_time) == getdate(start_date_time)
+ and get_time_in_timedelta(current_date_time.time()) > support_days[current_weekday].start_time
+ ):
+ start_time = current_date_time - datetime(
+ current_date_time.year, current_date_time.month, current_date_time.day
+ )
else:
start_time = support_days[current_weekday].start_time
@@ -572,10 +661,12 @@ def get_allotted_seconds(parameter, service_level):
def get_support_days(service_level):
support_days = {}
for service in service_level.get("support_and_resolution"):
- support_days[service.workday] = frappe._dict({
- "start_time": service.start_time,
- "end_time": service.end_time,
- })
+ support_days[service.workday] = frappe._dict(
+ {
+ "start_time": service.start_time,
+ "end_time": service.end_time,
+ }
+ )
return support_days
@@ -588,15 +679,20 @@ def set_resolution_time(doc):
if not doc.meta.has_field("user_resolution_time"):
return
- communications = frappe.get_all("Communication", filters={
- "reference_doctype": doc.doctype,
- "reference_name": doc.name
- }, fields=["sent_or_received", "name", "creation"], order_by="creation")
+ communications = frappe.get_all(
+ "Communication",
+ filters={"reference_doctype": doc.doctype, "reference_name": doc.name},
+ fields=["sent_or_received", "name", "creation"],
+ order_by="creation",
+ )
pending_time = []
for i in range(len(communications)):
- if communications[i].sent_or_received == "Received" and communications[i-1].sent_or_received == "Sent":
- wait_time = time_diff_in_seconds(communications[i].creation, communications[i-1].creation)
+ if (
+ communications[i].sent_or_received == "Received"
+ and communications[i - 1].sent_or_received == "Sent"
+ ):
+ wait_time = time_diff_in_seconds(communications[i].creation, communications[i - 1].creation)
if wait_time > 0:
pending_time.append(wait_time)
@@ -606,25 +702,35 @@ def set_resolution_time(doc):
def change_service_level_agreement_and_priority(self):
- if self.service_level_agreement and frappe.db.exists("Issue", self.name) and \
- frappe.db.get_single_value("Support Settings", "track_service_level_agreement"):
+ if (
+ self.service_level_agreement
+ and frappe.db.exists("Issue", self.name)
+ and frappe.db.get_single_value("Support Settings", "track_service_level_agreement")
+ ):
if not self.priority == frappe.db.get_value("Issue", self.name, "priority"):
- self.set_response_and_resolution_time(priority=self.priority, service_level_agreement=self.service_level_agreement)
+ self.set_response_and_resolution_time(
+ priority=self.priority, service_level_agreement=self.service_level_agreement
+ )
frappe.msgprint(_("Priority has been changed to {0}.").format(self.priority))
- if not self.service_level_agreement == frappe.db.get_value("Issue", self.name, "service_level_agreement"):
- self.set_response_and_resolution_time(priority=self.priority, service_level_agreement=self.service_level_agreement)
- frappe.msgprint(_("Service Level Agreement has been changed to {0}.").format(self.service_level_agreement))
+ if not self.service_level_agreement == frappe.db.get_value(
+ "Issue", self.name, "service_level_agreement"
+ ):
+ self.set_response_and_resolution_time(
+ priority=self.priority, service_level_agreement=self.service_level_agreement
+ )
+ frappe.msgprint(
+ _("Service Level Agreement has been changed to {0}.").format(self.service_level_agreement)
+ )
def get_response_and_resolution_duration(doc):
sla = frappe.get_doc("Service Level Agreement", doc.service_level_agreement)
priority = sla.get_service_level_agreement_priority(doc.priority)
- priority.update({
- "support_and_resolution": sla.support_and_resolution,
- "holiday_list": sla.holiday_list
- })
+ priority.update(
+ {"support_and_resolution": sla.support_and_resolution, "holiday_list": sla.holiday_list}
+ )
return priority
@@ -632,14 +738,16 @@ def reset_service_level_agreement(doc, reason, user):
if not frappe.db.get_single_value("Support Settings", "allow_resetting_service_level_agreement"):
frappe.throw(_("Allow Resetting Service Level Agreement from Support Settings."))
- frappe.get_doc({
- "doctype": "Comment",
- "comment_type": "Info",
- "reference_doctype": doc.doctype,
- "reference_name": doc.name,
- "comment_email": user,
- "content": " resetted Service Level Agreement - {0}".format(_(reason)),
- }).insert(ignore_permissions=True)
+ frappe.get_doc(
+ {
+ "doctype": "Comment",
+ "comment_type": "Info",
+ "reference_doctype": doc.doctype,
+ "reference_name": doc.name,
+ "comment_email": user,
+ "content": " resetted Service Level Agreement - {0}".format(_(reason)),
+ }
+ ).insert(ignore_permissions=True)
doc.service_level_agreement_creation = now_datetime(doc.get("owner"))
doc.save()
@@ -665,28 +773,30 @@ def on_communication_update(doc, status):
if not parent:
return
- if not parent.meta.has_field('service_level_agreement'):
+ if not parent.meta.has_field("service_level_agreement"):
return
if (
- doc.sent_or_received == "Received" # a reply is received
- and parent.get('status') == 'Open' # issue status is set as open from communication.py
+ doc.sent_or_received == "Received" # a reply is received
+ and parent.get("status") == "Open" # issue status is set as open from communication.py
and parent.get_doc_before_save()
- and parent.get('status') != parent._doc_before_save.get('status') # status changed
+ and parent.get("status") != parent._doc_before_save.get("status") # status changed
):
# undo the status change in db
# since prev status is fetched from db
frappe.db.set_value(
- parent.doctype, parent.name,
- 'status', parent._doc_before_save.get('status'),
- update_modified=False
+ parent.doctype,
+ parent.name,
+ "status",
+ parent._doc_before_save.get("status"),
+ update_modified=False,
)
elif (
- doc.sent_or_received == "Sent" # a reply is sent
- and parent.get('first_responded_on') # first_responded_on is set from communication.py
+ doc.sent_or_received == "Sent" # a reply is sent
+ and parent.get("first_responded_on") # first_responded_on is set from communication.py
and parent.get_doc_before_save()
- and not parent._doc_before_save.get('first_responded_on') # first_responded_on was not set
+ and not parent._doc_before_save.get("first_responded_on") # first_responded_on was not set
):
# reset first_responded_on since it will be handled/set later on
parent.first_responded_on = None
@@ -695,7 +805,9 @@ def on_communication_update(doc, status):
else:
return
- for_resolution = frappe.db.get_value('Service Level Agreement', parent.service_level_agreement, 'apply_sla_for_resolution')
+ for_resolution = frappe.db.get_value(
+ "Service Level Agreement", parent.service_level_agreement, "apply_sla_for_resolution"
+ )
handle_status_change(parent, for_resolution)
update_response_and_resolution_metrics(parent, for_resolution)
@@ -705,36 +817,42 @@ def on_communication_update(doc, status):
def reset_expected_response_and_resolution(doc):
- if doc.meta.has_field("first_responded_on") and not doc.get('first_responded_on'):
+ if doc.meta.has_field("first_responded_on") and not doc.get("first_responded_on"):
doc.response_by = None
- if doc.meta.has_field("resolution_by") and not doc.get('resolution_date'):
+ if doc.meta.has_field("resolution_by") and not doc.get("resolution_date"):
doc.resolution_by = None
def set_response_by(doc, start_date_time, priority):
if doc.meta.has_field("response_by"):
- doc.response_by = get_expected_time_for(parameter="response", service_level=priority, start_date_time=start_date_time)
- if doc.meta.has_field("total_hold_time") and doc.get('total_hold_time') and not doc.get('first_responded_on'):
- doc.response_by = add_to_date(doc.response_by, seconds=round(doc.get('total_hold_time')))
+ doc.response_by = get_expected_time_for(
+ parameter="response", service_level=priority, start_date_time=start_date_time
+ )
+ if (
+ doc.meta.has_field("total_hold_time")
+ and doc.get("total_hold_time")
+ and not doc.get("first_responded_on")
+ ):
+ doc.response_by = add_to_date(doc.response_by, seconds=round(doc.get("total_hold_time")))
def set_resolution_by(doc, start_date_time, priority):
if doc.meta.has_field("resolution_by"):
- doc.resolution_by = get_expected_time_for(parameter="resolution", service_level=priority, start_date_time=start_date_time)
- if doc.meta.has_field("total_hold_time") and doc.get('total_hold_time'):
- doc.resolution_by = add_to_date(doc.resolution_by, seconds=round(doc.get('total_hold_time')))
+ doc.resolution_by = get_expected_time_for(
+ parameter="resolution", service_level=priority, start_date_time=start_date_time
+ )
+ if doc.meta.has_field("total_hold_time") and doc.get("total_hold_time"):
+ doc.resolution_by = add_to_date(doc.resolution_by, seconds=round(doc.get("total_hold_time")))
def record_assigned_users_on_failure(doc):
assigned_users = doc.get_assigned_users()
if assigned_users:
from frappe.utils import get_fullname
- assigned_users = ', '.join((get_fullname(user) for user in assigned_users))
- message = _('First Response SLA Failed by {}').format(assigned_users)
- doc.add_comment(
- comment_type='Assigned',
- text=message
- )
+
+ assigned_users = ", ".join((get_fullname(user) for user in assigned_users))
+ message = _("First Response SLA Failed by {}").format(assigned_users)
+ doc.add_comment(comment_type="Assigned", text=message)
def get_service_level_agreement_fields():
@@ -743,71 +861,57 @@ def get_service_level_agreement_fields():
"collapsible": 1,
"fieldname": "service_level_section",
"fieldtype": "Section Break",
- "label": "Service Level"
+ "label": "Service Level",
},
{
"fieldname": "service_level_agreement",
"fieldtype": "Link",
"label": "Service Level Agreement",
- "options": "Service Level Agreement"
- },
- {
- "fieldname": "priority",
- "fieldtype": "Link",
- "label": "Priority",
- "options": "Issue Priority"
- },
- {
- "fieldname": "response_by",
- "fieldtype": "Datetime",
- "label": "Response By",
- "read_only": 1
+ "options": "Service Level Agreement",
},
+ {"fieldname": "priority", "fieldtype": "Link", "label": "Priority", "options": "Issue Priority"},
+ {"fieldname": "response_by", "fieldtype": "Datetime", "label": "Response By", "read_only": 1},
{
"fieldname": "first_responded_on",
"fieldtype": "Datetime",
"label": "First Responded On",
"no_copy": 1,
- "read_only": 1
+ "read_only": 1,
},
{
"fieldname": "on_hold_since",
"fieldtype": "Datetime",
"hidden": 1,
"label": "On Hold Since",
- "read_only": 1
+ "read_only": 1,
},
{
"fieldname": "total_hold_time",
"fieldtype": "Duration",
"label": "Total Hold Time",
- "read_only": 1
- },
- {
- "fieldname": "cb",
- "fieldtype": "Column Break",
- "read_only": 1
+ "read_only": 1,
},
+ {"fieldname": "cb", "fieldtype": "Column Break", "read_only": 1},
{
"default": "First Response Due",
"fieldname": "agreement_status",
"fieldtype": "Select",
"label": "Service Level Agreement Status",
"options": "First Response Due\nResolution Due\nFulfilled\nFailed",
- "read_only": 1
+ "read_only": 1,
},
{
"fieldname": "resolution_by",
"fieldtype": "Datetime",
"label": "Resolution By",
- "read_only": 1
+ "read_only": 1,
},
{
"fieldname": "service_level_agreement_creation",
"fieldtype": "Datetime",
"hidden": 1,
"label": "Service Level Agreement Creation",
- "read_only": 1
+ "read_only": 1,
},
{
"depends_on": "eval:!doc.__islocal",
@@ -815,8 +919,8 @@ def get_service_level_agreement_fields():
"fieldtype": "Datetime",
"label": "Resolution Date",
"no_copy": 1,
- "read_only": 1
- }
+ "read_only": 1,
+ },
]
@@ -826,21 +930,21 @@ def update_agreement_status_on_custom_status(doc):
def update_agreement_status(doc, apply_sla_for_resolution):
- if (doc.meta.has_field("agreement_status")):
+ if doc.meta.has_field("agreement_status"):
# if SLA is applied for resolution check for response and resolution, else only response
if apply_sla_for_resolution:
- if doc.meta.has_field("first_responded_on") and not doc.get('first_responded_on'):
+ if doc.meta.has_field("first_responded_on") and not doc.get("first_responded_on"):
doc.agreement_status = "First Response Due"
- elif doc.meta.has_field("resolution_date") and not doc.get('resolution_date'):
+ elif doc.meta.has_field("resolution_date") and not doc.get("resolution_date"):
doc.agreement_status = "Resolution Due"
- elif get_datetime(doc.get('resolution_date')) <= get_datetime(doc.get('resolution_by')):
+ elif get_datetime(doc.get("resolution_date")) <= get_datetime(doc.get("resolution_by")):
doc.agreement_status = "Fulfilled"
else:
doc.agreement_status = "Failed"
else:
- if doc.meta.has_field("first_responded_on") and not doc.get('first_responded_on'):
+ if doc.meta.has_field("first_responded_on") and not doc.get("first_responded_on"):
doc.agreement_status = "First Response Due"
- elif get_datetime(doc.get('first_responded_on')) <= get_datetime(doc.get('response_by')):
+ elif get_datetime(doc.get("first_responded_on")) <= get_datetime(doc.get("response_by")):
doc.agreement_status = "Fulfilled"
else:
doc.agreement_status = "Failed"
@@ -853,6 +957,7 @@ def is_holiday(date, holidays):
def get_time_in_timedelta(time):
"""Converts datetime.time(10, 36, 55, 961454) to datetime.timedelta(seconds=38215)."""
import datetime
+
return datetime.timedelta(hours=time.hour, minutes=time.minute, seconds=time.second)
@@ -865,7 +970,7 @@ def convert_utc_to_user_timezone(utc_timestamp, user):
from pytz import UnknownTimeZoneError, timezone
user_tz = get_tz(user)
- utcnow = timezone('UTC').localize(utc_timestamp)
+ utcnow = timezone("UTC").localize(utc_timestamp)
try:
return utcnow.astimezone(timezone(user_tz))
except UnknownTimeZoneError:
@@ -884,11 +989,7 @@ def get_user_time(user, to_string=False):
@frappe.whitelist()
def get_sla_doctypes():
doctypes = []
- data = frappe.get_all('Service Level Agreement',
- {'enabled': 1},
- ['document_type'],
- distinct=1
- )
+ data = frappe.get_all("Service Level Agreement", {"enabled": 1}, ["document_type"], distinct=1)
for entry in data:
doctypes.append(entry.document_type)
diff --git a/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py
index a34124fba20..4e00138906d 100644
--- a/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py
+++ b/erpnext/support/doctype/service_level_agreement/test_service_level_agreement.py
@@ -20,51 +20,122 @@ class TestServiceLevelAgreement(unittest.TestCase):
def test_service_level_agreement(self):
# Default Service Level Agreement
- create_default_service_level_agreement = create_service_level_agreement(default_service_level_agreement=1,
- holiday_list="__Test Holiday List", entity_type=None, entity=None, response_time=14400, resolution_time=21600)
+ create_default_service_level_agreement = create_service_level_agreement(
+ default_service_level_agreement=1,
+ holiday_list="__Test Holiday List",
+ entity_type=None,
+ entity=None,
+ response_time=14400,
+ resolution_time=21600,
+ )
- get_default_service_level_agreement = get_service_level_agreement(default_service_level_agreement=1)
+ get_default_service_level_agreement = get_service_level_agreement(
+ default_service_level_agreement=1
+ )
- self.assertEqual(create_default_service_level_agreement.name, get_default_service_level_agreement.name)
- self.assertEqual(create_default_service_level_agreement.entity_type, get_default_service_level_agreement.entity_type)
- self.assertEqual(create_default_service_level_agreement.entity, get_default_service_level_agreement.entity)
- self.assertEqual(create_default_service_level_agreement.default_service_level_agreement, get_default_service_level_agreement.default_service_level_agreement)
+ self.assertEqual(
+ create_default_service_level_agreement.name, get_default_service_level_agreement.name
+ )
+ self.assertEqual(
+ create_default_service_level_agreement.entity_type,
+ get_default_service_level_agreement.entity_type,
+ )
+ self.assertEqual(
+ create_default_service_level_agreement.entity, get_default_service_level_agreement.entity
+ )
+ self.assertEqual(
+ create_default_service_level_agreement.default_service_level_agreement,
+ get_default_service_level_agreement.default_service_level_agreement,
+ )
# Service Level Agreement for Customer
customer = create_customer()
- create_customer_service_level_agreement = create_service_level_agreement(default_service_level_agreement=0,
- holiday_list="__Test Holiday List", entity_type="Customer", entity=customer,
- response_time=7200, resolution_time=10800)
- get_customer_service_level_agreement = get_service_level_agreement(entity_type="Customer", entity=customer)
+ create_customer_service_level_agreement = create_service_level_agreement(
+ default_service_level_agreement=0,
+ holiday_list="__Test Holiday List",
+ entity_type="Customer",
+ entity=customer,
+ response_time=7200,
+ resolution_time=10800,
+ )
+ get_customer_service_level_agreement = get_service_level_agreement(
+ entity_type="Customer", entity=customer
+ )
- self.assertEqual(create_customer_service_level_agreement.name, get_customer_service_level_agreement.name)
- self.assertEqual(create_customer_service_level_agreement.entity_type, get_customer_service_level_agreement.entity_type)
- self.assertEqual(create_customer_service_level_agreement.entity, get_customer_service_level_agreement.entity)
- self.assertEqual(create_customer_service_level_agreement.default_service_level_agreement, get_customer_service_level_agreement.default_service_level_agreement)
+ self.assertEqual(
+ create_customer_service_level_agreement.name, get_customer_service_level_agreement.name
+ )
+ self.assertEqual(
+ create_customer_service_level_agreement.entity_type,
+ get_customer_service_level_agreement.entity_type,
+ )
+ self.assertEqual(
+ create_customer_service_level_agreement.entity, get_customer_service_level_agreement.entity
+ )
+ self.assertEqual(
+ create_customer_service_level_agreement.default_service_level_agreement,
+ get_customer_service_level_agreement.default_service_level_agreement,
+ )
# Service Level Agreement for Customer Group
customer_group = create_customer_group()
- create_customer_group_service_level_agreement = create_service_level_agreement(default_service_level_agreement=0,
- holiday_list="__Test Holiday List", entity_type="Customer Group", entity=customer_group,
- response_time=7200, resolution_time=10800)
- get_customer_group_service_level_agreement = get_service_level_agreement(entity_type="Customer Group", entity=customer_group)
+ create_customer_group_service_level_agreement = create_service_level_agreement(
+ default_service_level_agreement=0,
+ holiday_list="__Test Holiday List",
+ entity_type="Customer Group",
+ entity=customer_group,
+ response_time=7200,
+ resolution_time=10800,
+ )
+ get_customer_group_service_level_agreement = get_service_level_agreement(
+ entity_type="Customer Group", entity=customer_group
+ )
- self.assertEqual(create_customer_group_service_level_agreement.name, get_customer_group_service_level_agreement.name)
- self.assertEqual(create_customer_group_service_level_agreement.entity_type, get_customer_group_service_level_agreement.entity_type)
- self.assertEqual(create_customer_group_service_level_agreement.entity, get_customer_group_service_level_agreement.entity)
- self.assertEqual(create_customer_group_service_level_agreement.default_service_level_agreement, get_customer_group_service_level_agreement.default_service_level_agreement)
+ self.assertEqual(
+ create_customer_group_service_level_agreement.name,
+ get_customer_group_service_level_agreement.name,
+ )
+ self.assertEqual(
+ create_customer_group_service_level_agreement.entity_type,
+ get_customer_group_service_level_agreement.entity_type,
+ )
+ self.assertEqual(
+ create_customer_group_service_level_agreement.entity,
+ get_customer_group_service_level_agreement.entity,
+ )
+ self.assertEqual(
+ create_customer_group_service_level_agreement.default_service_level_agreement,
+ get_customer_group_service_level_agreement.default_service_level_agreement,
+ )
# Service Level Agreement for Territory
territory = create_territory()
- create_territory_service_level_agreement = create_service_level_agreement(default_service_level_agreement=0,
+ create_territory_service_level_agreement = create_service_level_agreement(
+ default_service_level_agreement=0,
holiday_list="__Test Holiday List",
- entity_type="Territory", entity=territory, response_time=7200, resolution_time=10800)
- get_territory_service_level_agreement = get_service_level_agreement(entity_type="Territory", entity=territory)
+ entity_type="Territory",
+ entity=territory,
+ response_time=7200,
+ resolution_time=10800,
+ )
+ get_territory_service_level_agreement = get_service_level_agreement(
+ entity_type="Territory", entity=territory
+ )
- self.assertEqual(create_territory_service_level_agreement.name, get_territory_service_level_agreement.name)
- self.assertEqual(create_territory_service_level_agreement.entity_type, get_territory_service_level_agreement.entity_type)
- self.assertEqual(create_territory_service_level_agreement.entity, get_territory_service_level_agreement.entity)
- self.assertEqual(create_territory_service_level_agreement.default_service_level_agreement, get_territory_service_level_agreement.default_service_level_agreement)
+ self.assertEqual(
+ create_territory_service_level_agreement.name, get_territory_service_level_agreement.name
+ )
+ self.assertEqual(
+ create_territory_service_level_agreement.entity_type,
+ get_territory_service_level_agreement.entity_type,
+ )
+ self.assertEqual(
+ create_territory_service_level_agreement.entity, get_territory_service_level_agreement.entity
+ )
+ self.assertEqual(
+ create_territory_service_level_agreement.default_service_level_agreement,
+ get_territory_service_level_agreement.default_service_level_agreement,
+ )
def test_custom_field_creation_for_sla_on_standard_dt(self):
# Default Service Level Agreement
@@ -72,10 +143,12 @@ class TestServiceLevelAgreement(unittest.TestCase):
lead_sla = create_service_level_agreement(
default_service_level_agreement=1,
holiday_list="__Test Holiday List",
- entity_type=None, entity=None,
- response_time=14400, resolution_time=21600,
+ entity_type=None,
+ entity=None,
+ response_time=14400,
+ resolution_time=21600,
doctype=doctype,
- sla_fulfilled_on=[{"status": "Converted"}]
+ sla_fulfilled_on=[{"status": "Converted"}],
)
# check default SLA for lead
@@ -86,27 +159,35 @@ class TestServiceLevelAgreement(unittest.TestCase):
sla_fields = get_service_level_agreement_fields()
for field in sla_fields:
- self.assertTrue(frappe.db.exists("Custom Field", {"dt": doctype, "fieldname": field.get("fieldname")}))
+ self.assertTrue(
+ frappe.db.exists("Custom Field", {"dt": doctype, "fieldname": field.get("fieldname")})
+ )
def test_docfield_creation_for_sla_on_custom_dt(self):
doctype = create_custom_doctype()
sla = create_service_level_agreement(
default_service_level_agreement=1,
holiday_list="__Test Holiday List",
- entity_type=None, entity=None,
- response_time=14400, resolution_time=21600,
- doctype=doctype.name
+ entity_type=None,
+ entity=None,
+ response_time=14400,
+ resolution_time=21600,
+ doctype=doctype.name,
)
# check default SLA for custom dt
- default_sla = get_service_level_agreement(default_service_level_agreement=1, doctype=doctype.name)
+ default_sla = get_service_level_agreement(
+ default_service_level_agreement=1, doctype=doctype.name
+ )
self.assertEqual(sla.name, default_sla.name)
# check SLA docfields created
sla_fields = get_service_level_agreement_fields()
for field in sla_fields:
- self.assertTrue(frappe.db.exists("DocField", {"fieldname": field.get("fieldname"), "parent": doctype.name}))
+ self.assertTrue(
+ frappe.db.exists("DocField", {"fieldname": field.get("fieldname"), "parent": doctype.name})
+ )
def test_sla_application(self):
# Default Service Level Agreement
@@ -114,10 +195,12 @@ class TestServiceLevelAgreement(unittest.TestCase):
lead_sla = create_service_level_agreement(
default_service_level_agreement=1,
holiday_list="__Test Holiday List",
- entity_type=None, entity=None,
- response_time=14400, resolution_time=21600,
+ entity_type=None,
+ entity=None,
+ response_time=14400,
+ resolution_time=21600,
doctype=doctype,
- sla_fulfilled_on=[{"status": "Converted"}]
+ sla_fulfilled_on=[{"status": "Converted"}],
)
# make lead with default SLA
@@ -130,21 +213,23 @@ class TestServiceLevelAgreement(unittest.TestCase):
frappe.flags.current_time = datetime.datetime(2019, 3, 4, 15, 0)
lead.reload()
- lead.status = 'Converted'
+ lead.status = "Converted"
lead.save()
- self.assertEqual(lead.agreement_status, 'Fulfilled')
+ self.assertEqual(lead.agreement_status, "Fulfilled")
def test_hold_time(self):
doctype = "Lead"
create_service_level_agreement(
default_service_level_agreement=1,
holiday_list="__Test Holiday List",
- entity_type=None, entity=None,
- response_time=14400, resolution_time=21600,
+ entity_type=None,
+ entity=None,
+ response_time=14400,
+ resolution_time=21600,
doctype=doctype,
sla_fulfilled_on=[{"status": "Converted"}],
- pause_sla_on=[{"status": "Replied"}]
+ pause_sla_on=[{"status": "Replied"}],
)
creation = datetime.datetime(2020, 3, 4, 4, 0)
@@ -152,7 +237,7 @@ class TestServiceLevelAgreement(unittest.TestCase):
frappe.flags.current_time = datetime.datetime(2020, 3, 4, 4, 15)
lead.reload()
- lead.status = 'Replied'
+ lead.status = "Replied"
lead.save()
lead.reload()
@@ -160,7 +245,7 @@ class TestServiceLevelAgreement(unittest.TestCase):
frappe.flags.current_time = datetime.datetime(2020, 3, 4, 5, 5)
lead.reload()
- lead.status = 'Converted'
+ lead.status = "Converted"
lead.save()
lead.reload()
@@ -172,12 +257,13 @@ class TestServiceLevelAgreement(unittest.TestCase):
create_service_level_agreement(
default_service_level_agreement=1,
holiday_list="__Test Holiday List",
- entity_type=None, entity=None,
+ entity_type=None,
+ entity=None,
response_time=14400,
doctype=doctype,
sla_fulfilled_on=[{"status": "Replied"}],
pause_sla_on=[],
- apply_sla_for_resolution=0
+ apply_sla_for_resolution=0,
)
creation = datetime.datetime(2019, 3, 4, 12, 0)
@@ -187,22 +273,23 @@ class TestServiceLevelAgreement(unittest.TestCase):
# failed with response time only
frappe.flags.current_time = datetime.datetime(2019, 3, 4, 16, 5)
lead.reload()
- lead.status = 'Replied'
+ lead.status = "Replied"
lead.save()
lead.reload()
- self.assertEqual(lead.agreement_status, 'Failed')
+ self.assertEqual(lead.agreement_status, "Failed")
def test_fulfilled_sla_for_response_only(self):
doctype = "Lead"
lead_sla = create_service_level_agreement(
default_service_level_agreement=1,
holiday_list="__Test Holiday List",
- entity_type=None, entity=None,
+ entity_type=None,
+ entity=None,
response_time=14400,
doctype=doctype,
sla_fulfilled_on=[{"status": "Replied"}],
- apply_sla_for_resolution=0
+ apply_sla_for_resolution=0,
)
# fulfilled with response time only
@@ -214,11 +301,11 @@ class TestServiceLevelAgreement(unittest.TestCase):
frappe.flags.current_time = datetime.datetime(2019, 3, 4, 15, 30)
lead.reload()
- lead.status = 'Replied'
+ lead.status = "Replied"
lead.save()
lead.reload()
- self.assertEqual(lead.agreement_status, 'Fulfilled')
+ self.assertEqual(lead.agreement_status, "Fulfilled")
def test_service_level_agreement_filters(self):
doctype = "Lead"
@@ -226,29 +313,30 @@ class TestServiceLevelAgreement(unittest.TestCase):
default_service_level_agreement=0,
doctype=doctype,
holiday_list="__Test Holiday List",
- entity_type=None, entity=None,
+ entity_type=None,
+ entity=None,
condition='doc.source == "Test Source"',
response_time=14400,
sla_fulfilled_on=[{"status": "Replied"}],
- apply_sla_for_resolution=0
+ apply_sla_for_resolution=0,
)
creation = datetime.datetime(2019, 3, 4, 12, 0)
lead = make_lead(creation=creation, index=4)
- applied_sla = frappe.db.get_value('Lead', lead.name, 'service_level_agreement')
+ applied_sla = frappe.db.get_value("Lead", lead.name, "service_level_agreement")
self.assertFalse(applied_sla)
- source = frappe.get_doc(doctype='Lead Source', source_name='Test Source')
+ source = frappe.get_doc(doctype="Lead Source", source_name="Test Source")
source.insert(ignore_if_duplicate=True)
lead.source = "Test Source"
lead.save()
- applied_sla = frappe.db.get_value('Lead', lead.name, 'service_level_agreement')
+ applied_sla = frappe.db.get_value("Lead", lead.name, "service_level_agreement")
self.assertEqual(applied_sla, lead_sla.name)
# check if SLA is removed if condition fails
lead.reload()
lead.source = None
lead.save()
- applied_sla = frappe.db.get_value('Lead', lead.name, 'service_level_agreement')
+ applied_sla = frappe.db.get_value("Lead", lead.name, "service_level_agreement")
self.assertFalse(applied_sla)
def tearDown(self):
@@ -256,130 +344,150 @@ class TestServiceLevelAgreement(unittest.TestCase):
frappe.delete_doc("Service Level Agreement", d.name, force=1)
-def get_service_level_agreement(default_service_level_agreement=None, entity_type=None, entity=None, doctype="Issue"):
+def get_service_level_agreement(
+ default_service_level_agreement=None, entity_type=None, entity=None, doctype="Issue"
+):
if default_service_level_agreement:
- filters = {"default_service_level_agreement": default_service_level_agreement, "document_type": doctype}
+ filters = {
+ "default_service_level_agreement": default_service_level_agreement,
+ "document_type": doctype,
+ }
else:
filters = {"entity_type": entity_type, "entity": entity}
service_level_agreement = frappe.get_doc("Service Level Agreement", filters)
return service_level_agreement
-def create_service_level_agreement(default_service_level_agreement, holiday_list, response_time, entity_type,
- entity, resolution_time=0, doctype="Issue", condition="", sla_fulfilled_on=[], pause_sla_on=[], apply_sla_for_resolution=1,
- service_level=None, start_time="10:00:00", end_time="18:00:00"):
+
+def create_service_level_agreement(
+ default_service_level_agreement,
+ holiday_list,
+ response_time,
+ entity_type,
+ entity,
+ resolution_time=0,
+ doctype="Issue",
+ condition="",
+ sla_fulfilled_on=[],
+ pause_sla_on=[],
+ apply_sla_for_resolution=1,
+ service_level=None,
+ start_time="10:00:00",
+ end_time="18:00:00",
+):
make_holiday_list()
make_priorities()
if not sla_fulfilled_on:
- sla_fulfilled_on = [
- {"status": "Resolved"},
- {"status": "Closed"}
- ]
+ sla_fulfilled_on = [{"status": "Resolved"}, {"status": "Closed"}]
pause_sla_on = [{"status": "Replied"}] if doctype == "Issue" else pause_sla_on
- service_level_agreement = frappe._dict({
- "doctype": "Service Level Agreement",
- "enabled": 1,
- "document_type": doctype,
- "service_level": service_level or "__Test {} SLA".format(entity_type if entity_type else "Default"),
- "default_service_level_agreement": default_service_level_agreement,
- "condition": condition,
- "default_priority": "Medium",
- "holiday_list": holiday_list,
- "entity_type": entity_type,
- "entity": entity,
- "start_date": frappe.utils.getdate(),
- "end_date": frappe.utils.add_to_date(frappe.utils.getdate(), days=100),
- "apply_sla_for_resolution": apply_sla_for_resolution,
- "priorities": [
- {
- "priority": "Low",
- "response_time": response_time,
- "resolution_time": resolution_time,
- },
- {
- "priority": "Medium",
- "response_time": response_time,
- "default_priority": 1,
- "resolution_time": resolution_time,
- },
- {
- "priority": "High",
- "response_time": response_time,
- "resolution_time": resolution_time,
- }
- ],
- "sla_fulfilled_on": sla_fulfilled_on,
- "pause_sla_on": pause_sla_on,
- "support_and_resolution": [
- {
- "workday": "Monday",
- "start_time": start_time,
- "end_time": end_time,
- },
- {
- "workday": "Tuesday",
- "start_time": start_time,
- "end_time": end_time,
- },
- {
- "workday": "Wednesday",
- "start_time": start_time,
- "end_time": end_time,
- },
- {
- "workday": "Thursday",
- "start_time": start_time,
- "end_time": end_time,
- },
- {
- "workday": "Friday",
- "start_time": start_time,
- "end_time": end_time,
- }
- ]
- })
+ service_level_agreement = frappe._dict(
+ {
+ "doctype": "Service Level Agreement",
+ "enabled": 1,
+ "document_type": doctype,
+ "service_level": service_level
+ or "__Test {} SLA".format(entity_type if entity_type else "Default"),
+ "default_service_level_agreement": default_service_level_agreement,
+ "condition": condition,
+ "default_priority": "Medium",
+ "holiday_list": holiday_list,
+ "entity_type": entity_type,
+ "entity": entity,
+ "start_date": frappe.utils.getdate(),
+ "end_date": frappe.utils.add_to_date(frappe.utils.getdate(), days=100),
+ "apply_sla_for_resolution": apply_sla_for_resolution,
+ "priorities": [
+ {
+ "priority": "Low",
+ "response_time": response_time,
+ "resolution_time": resolution_time,
+ },
+ {
+ "priority": "Medium",
+ "response_time": response_time,
+ "default_priority": 1,
+ "resolution_time": resolution_time,
+ },
+ {
+ "priority": "High",
+ "response_time": response_time,
+ "resolution_time": resolution_time,
+ },
+ ],
+ "sla_fulfilled_on": sla_fulfilled_on,
+ "pause_sla_on": pause_sla_on,
+ "support_and_resolution": [
+ {
+ "workday": "Monday",
+ "start_time": start_time,
+ "end_time": end_time,
+ },
+ {
+ "workday": "Tuesday",
+ "start_time": start_time,
+ "end_time": end_time,
+ },
+ {
+ "workday": "Wednesday",
+ "start_time": start_time,
+ "end_time": end_time,
+ },
+ {
+ "workday": "Thursday",
+ "start_time": start_time,
+ "end_time": end_time,
+ },
+ {
+ "workday": "Friday",
+ "start_time": start_time,
+ "end_time": end_time,
+ },
+ ],
+ }
+ )
filters = {
"default_service_level_agreement": service_level_agreement.default_service_level_agreement,
- "service_level": service_level_agreement.service_level
+ "service_level": service_level_agreement.service_level,
}
if not default_service_level_agreement:
- filters.update({
- "entity_type": entity_type,
- "entity": entity
- })
+ filters.update({"entity_type": entity_type, "entity": entity})
sla = frappe.db.exists("Service Level Agreement", filters)
if sla:
frappe.delete_doc("Service Level Agreement", sla, force=1)
- return frappe.get_doc(service_level_agreement).insert(ignore_permissions=True, ignore_if_duplicate=True)
+ return frappe.get_doc(service_level_agreement).insert(
+ ignore_permissions=True, ignore_if_duplicate=True
+ )
def create_customer():
- customer = frappe.get_doc({
- "doctype": "Customer",
- "customer_name": "_Test Customer",
- "customer_group": "Commercial",
- "customer_type": "Individual",
- "territory": "Rest Of The World"
- })
+ customer = frappe.get_doc(
+ {
+ "doctype": "Customer",
+ "customer_name": "_Test Customer",
+ "customer_group": "Commercial",
+ "customer_type": "Individual",
+ "territory": "Rest Of The World",
+ }
+ )
if not frappe.db.exists("Customer", "_Test Customer"):
customer.insert(ignore_permissions=True)
return customer.name
else:
return frappe.db.exists("Customer", "_Test Customer")
+
def create_customer_group():
- customer_group = frappe.get_doc({
- "doctype": "Customer Group",
- "customer_group_name": "_Test SLA Customer Group"
- })
+ customer_group = frappe.get_doc(
+ {"doctype": "Customer Group", "customer_group_name": "_Test SLA Customer Group"}
+ )
if not frappe.db.exists("Customer Group", {"customer_group_name": "_Test SLA Customer Group"}):
customer_group.insert()
@@ -387,11 +495,14 @@ def create_customer_group():
else:
return frappe.db.exists("Customer Group", {"customer_group_name": "_Test SLA Customer Group"})
+
def create_territory():
- territory = frappe.get_doc({
- "doctype": "Territory",
- "territory_name": "_Test SLA Territory",
- })
+ territory = frappe.get_doc(
+ {
+ "doctype": "Territory",
+ "territory_name": "_Test SLA Territory",
+ }
+ )
if not frappe.db.exists("Territory", {"territory_name": "_Test SLA Territory"}):
territory.insert()
@@ -399,102 +510,116 @@ def create_territory():
else:
return frappe.db.exists("Territory", {"territory_name": "_Test SLA Territory"})
+
def create_service_level_agreements_for_issues():
- create_service_level_agreement(default_service_level_agreement=1, holiday_list="__Test Holiday List",
- entity_type=None, entity=None, response_time=14400, resolution_time=21600)
+ create_service_level_agreement(
+ default_service_level_agreement=1,
+ holiday_list="__Test Holiday List",
+ entity_type=None,
+ entity=None,
+ response_time=14400,
+ resolution_time=21600,
+ )
create_customer()
- create_service_level_agreement(default_service_level_agreement=0, holiday_list="__Test Holiday List",
- entity_type="Customer", entity="_Test Customer", response_time=7200, resolution_time=10800)
+ create_service_level_agreement(
+ default_service_level_agreement=0,
+ holiday_list="__Test Holiday List",
+ entity_type="Customer",
+ entity="_Test Customer",
+ response_time=7200,
+ resolution_time=10800,
+ )
create_customer_group()
- create_service_level_agreement(default_service_level_agreement=0, holiday_list="__Test Holiday List",
- entity_type="Customer Group", entity="_Test SLA Customer Group", response_time=7200, resolution_time=10800)
+ create_service_level_agreement(
+ default_service_level_agreement=0,
+ holiday_list="__Test Holiday List",
+ entity_type="Customer Group",
+ entity="_Test SLA Customer Group",
+ response_time=7200,
+ resolution_time=10800,
+ )
create_territory()
- create_service_level_agreement(default_service_level_agreement=0, holiday_list="__Test Holiday List",
- entity_type="Territory", entity="_Test SLA Territory", response_time=7200, resolution_time=10800)
+ create_service_level_agreement(
+ default_service_level_agreement=0,
+ holiday_list="__Test Holiday List",
+ entity_type="Territory",
+ entity="_Test SLA Territory",
+ response_time=7200,
+ resolution_time=10800,
+ )
create_service_level_agreement(
- default_service_level_agreement=0, holiday_list="__Test Holiday List",
- entity_type=None, entity=None, response_time=14400, resolution_time=21600,
- service_level="24-hour-SLA", start_time="00:00:00", end_time="23:59:59",
- condition="doc.issue_type == 'Critical'"
+ default_service_level_agreement=0,
+ holiday_list="__Test Holiday List",
+ entity_type=None,
+ entity=None,
+ response_time=14400,
+ resolution_time=21600,
+ service_level="24-hour-SLA",
+ start_time="00:00:00",
+ end_time="23:59:59",
+ condition="doc.issue_type == 'Critical'",
)
+
def make_holiday_list():
holiday_list = frappe.db.exists("Holiday List", "__Test Holiday List")
if not holiday_list:
- holiday_list = frappe.get_doc({
- "doctype": "Holiday List",
- "holiday_list_name": "__Test Holiday List",
- "from_date": "2019-01-01",
- "to_date": "2019-12-31",
- "holidays": [
- {
- "description": "Test Holiday 1",
- "holiday_date": "2019-03-05"
- },
- {
- "description": "Test Holiday 2",
- "holiday_date": "2019-03-07"
- },
- {
- "description": "Test Holiday 3",
- "holiday_date": "2019-02-11"
- },
- ]
- }).insert()
+ holiday_list = frappe.get_doc(
+ {
+ "doctype": "Holiday List",
+ "holiday_list_name": "__Test Holiday List",
+ "from_date": "2019-01-01",
+ "to_date": "2019-12-31",
+ "holidays": [
+ {"description": "Test Holiday 1", "holiday_date": "2019-03-05"},
+ {"description": "Test Holiday 2", "holiday_date": "2019-03-07"},
+ {"description": "Test Holiday 3", "holiday_date": "2019-02-11"},
+ ],
+ }
+ ).insert()
+
def create_custom_doctype():
if not frappe.db.exists("DocType", "Test SLA on Custom Dt"):
- doc = frappe.get_doc({
+ doc = frappe.get_doc(
+ {
"doctype": "DocType",
"module": "Support",
"custom": 1,
"fields": [
- {
- "label": "Date",
- "fieldname": "date",
- "fieldtype": "Date"
- },
- {
- "label": "Description",
- "fieldname": "desc",
- "fieldtype": "Long Text"
- },
- {
- "label": "Email ID",
- "fieldname": "email_id",
- "fieldtype": "Link",
- "options": "Customer"
- },
+ {"label": "Date", "fieldname": "date", "fieldtype": "Date"},
+ {"label": "Description", "fieldname": "desc", "fieldtype": "Long Text"},
+ {"label": "Email ID", "fieldname": "email_id", "fieldtype": "Link", "options": "Customer"},
{
"label": "Status",
"fieldname": "status",
"fieldtype": "Select",
- "options": "Open\nReplied\nClosed"
- }
+ "options": "Open\nReplied\nClosed",
+ },
],
- "permissions": [{
- "role": "System Manager",
- "read": 1,
- "write": 1
- }],
+ "permissions": [{"role": "System Manager", "read": 1, "write": 1}],
"name": "Test SLA on Custom Dt",
- })
+ }
+ )
doc.insert()
return doc
else:
return frappe.get_doc("DocType", "Test SLA on Custom Dt")
+
def make_lead(creation=None, index=0):
- return frappe.get_doc({
- "doctype": "Lead",
- "email_id": "test_lead1@example{0}.com".format(index),
- "lead_name": "_Test Lead {0}".format(index),
- "status": "Open",
- "creation": creation,
- "service_level_agreement_creation": creation,
- "priority": "Medium"
- }).insert(ignore_permissions=True)
+ return frappe.get_doc(
+ {
+ "doctype": "Lead",
+ "email_id": "test_lead1@example{0}.com".format(index),
+ "lead_name": "_Test Lead {0}".format(index),
+ "status": "Open",
+ "creation": creation,
+ "service_level_agreement_creation": creation,
+ "priority": "Medium",
+ }
+ ).insert(ignore_permissions=True)
diff --git a/erpnext/support/doctype/warranty_claim/test_warranty_claim.py b/erpnext/support/doctype/warranty_claim/test_warranty_claim.py
index f022d55a4b1..19e23493fe5 100644
--- a/erpnext/support/doctype/warranty_claim/test_warranty_claim.py
+++ b/erpnext/support/doctype/warranty_claim/test_warranty_claim.py
@@ -5,7 +5,8 @@ import unittest
import frappe
-test_records = frappe.get_test_records('Warranty Claim')
+test_records = frappe.get_test_records("Warranty Claim")
+
class TestWarrantyClaim(unittest.TestCase):
pass
diff --git a/erpnext/support/doctype/warranty_claim/warranty_claim.py b/erpnext/support/doctype/warranty_claim/warranty_claim.py
index 87e95410264..5e2ea067a86 100644
--- a/erpnext/support/doctype/warranty_claim/warranty_claim.py
+++ b/erpnext/support/doctype/warranty_claim/warranty_claim.py
@@ -2,7 +2,6 @@
# License: GNU General Public License v3. See license.txt
-
import frappe
from frappe import _, session
from frappe.utils import now_datetime
@@ -15,27 +14,33 @@ class WarrantyClaim(TransactionBase):
return _("{0}: From {1}").format(self.status, self.customer_name)
def validate(self):
- if session['user'] != 'Guest' and not self.customer:
+ if session["user"] != "Guest" and not self.customer:
frappe.throw(_("Customer is required"))
- if self.status=="Closed" and not self.resolution_date and \
- frappe.db.get_value("Warranty Claim", self.name, "status")!="Closed":
+ if (
+ self.status == "Closed"
+ and not self.resolution_date
+ and frappe.db.get_value("Warranty Claim", self.name, "status") != "Closed"
+ ):
self.resolution_date = now_datetime()
def on_cancel(self):
- lst = frappe.db.sql("""select t1.name
+ lst = frappe.db.sql(
+ """select t1.name
from `tabMaintenance Visit` t1, `tabMaintenance Visit Purpose` t2
where t2.parent = t1.name and t2.prevdoc_docname = %s and t1.docstatus!=2""",
- (self.name))
+ (self.name),
+ )
if lst:
- lst1 = ','.join(x[0] for x in lst)
+ lst1 = ",".join(x[0] for x in lst)
frappe.throw(_("Cancel Material Visit {0} before cancelling this Warranty Claim").format(lst1))
else:
- frappe.db.set(self, 'status', 'Cancelled')
+ frappe.db.set(self, "status", "Cancelled")
def on_update(self):
pass
+
@frappe.whitelist()
def make_maintenance_visit(source_name, target_doc=None):
from frappe.model.mapper import get_mapped_doc, map_child_doc
@@ -44,25 +49,25 @@ def make_maintenance_visit(source_name, target_doc=None):
target_doc.prevdoc_doctype = source_parent.doctype
target_doc.prevdoc_docname = source_parent.name
- visit = frappe.db.sql("""select t1.name
+ visit = frappe.db.sql(
+ """select t1.name
from `tabMaintenance Visit` t1, `tabMaintenance Visit Purpose` t2
where t2.parent=t1.name and t2.prevdoc_docname=%s
- and t1.docstatus=1 and t1.completion_status='Fully Completed'""", source_name)
+ and t1.docstatus=1 and t1.completion_status='Fully Completed'""",
+ source_name,
+ )
if not visit:
- target_doc = get_mapped_doc("Warranty Claim", source_name, {
- "Warranty Claim": {
- "doctype": "Maintenance Visit",
- "field_map": {}
- }
- }, target_doc)
+ target_doc = get_mapped_doc(
+ "Warranty Claim",
+ source_name,
+ {"Warranty Claim": {"doctype": "Maintenance Visit", "field_map": {}}},
+ target_doc,
+ )
source_doc = frappe.get_doc("Warranty Claim", source_name)
if source_doc.get("item_code"):
- table_map = {
- "doctype": "Maintenance Visit Purpose",
- "postprocess": _update_links
- }
+ table_map = {"doctype": "Maintenance Visit Purpose", "postprocess": _update_links}
map_child_doc(source_doc, target_doc, table_map, source_doc)
return target_doc
diff --git a/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.py b/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.py
index 2ab0fb88a7f..57fa7bf2b7b 100644
--- a/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.py
+++ b/erpnext/support/report/first_response_time_for_issues/first_response_time_for_issues.py
@@ -3,25 +3,22 @@
import frappe
+from frappe import _
def execute(filters=None):
columns = [
+ {"fieldname": "creation_date", "label": _("Date"), "fieldtype": "Date", "width": 300},
{
- 'fieldname': 'creation_date',
- 'label': 'Date',
- 'fieldtype': 'Date',
- 'width': 300
- },
- {
- 'fieldname': 'first_response_time',
- 'fieldtype': 'Duration',
- 'label': 'First Response Time',
- 'width': 300
+ "fieldname": "first_response_time",
+ "fieldtype": "Duration",
+ "label": _("First Response Time"),
+ "width": 300,
},
]
- data = frappe.db.sql('''
+ data = frappe.db.sql(
+ """
SELECT
date(creation) as creation_date,
avg(first_response_time) as avg_response_time
@@ -31,6 +28,8 @@ def execute(filters=None):
and first_response_time > 0
GROUP BY creation_date
ORDER BY creation_date desc
- ''', (filters.from_date, filters.to_date))
+ """,
+ (filters.from_date, filters.to_date),
+ )
return columns, data
diff --git a/erpnext/support/report/issue_analytics/issue_analytics.py b/erpnext/support/report/issue_analytics/issue_analytics.py
index 056f2e0b41a..00ba25a6a97 100644
--- a/erpnext/support/report/issue_analytics/issue_analytics.py
+++ b/erpnext/support/report/issue_analytics/issue_analytics.py
@@ -14,6 +14,7 @@ from erpnext.accounts.utils import get_fiscal_year
def execute(filters=None):
return IssueAnalytics(filters).run()
+
class IssueAnalytics(object):
def __init__(self, filters=None):
"""Issue Analytics Report"""
@@ -30,101 +31,98 @@ class IssueAnalytics(object):
def get_columns(self):
self.columns = []
- if self.filters.based_on == 'Customer':
- self.columns.append({
- 'label': _('Customer'),
- 'options': 'Customer',
- 'fieldname': 'customer',
- 'fieldtype': 'Link',
- 'width': 200
- })
+ if self.filters.based_on == "Customer":
+ self.columns.append(
+ {
+ "label": _("Customer"),
+ "options": "Customer",
+ "fieldname": "customer",
+ "fieldtype": "Link",
+ "width": 200,
+ }
+ )
- elif self.filters.based_on == 'Assigned To':
- self.columns.append({
- 'label': _('User'),
- 'fieldname': 'user',
- 'fieldtype': 'Link',
- 'options': 'User',
- 'width': 200
- })
+ elif self.filters.based_on == "Assigned To":
+ self.columns.append(
+ {"label": _("User"), "fieldname": "user", "fieldtype": "Link", "options": "User", "width": 200}
+ )
- elif self.filters.based_on == 'Issue Type':
- self.columns.append({
- 'label': _('Issue Type'),
- 'fieldname': 'issue_type',
- 'fieldtype': 'Link',
- 'options': 'Issue Type',
- 'width': 200
- })
+ elif self.filters.based_on == "Issue Type":
+ self.columns.append(
+ {
+ "label": _("Issue Type"),
+ "fieldname": "issue_type",
+ "fieldtype": "Link",
+ "options": "Issue Type",
+ "width": 200,
+ }
+ )
- elif self.filters.based_on == 'Issue Priority':
- self.columns.append({
- 'label': _('Issue Priority'),
- 'fieldname': 'priority',
- 'fieldtype': 'Link',
- 'options': 'Issue Priority',
- 'width': 200
- })
+ elif self.filters.based_on == "Issue Priority":
+ self.columns.append(
+ {
+ "label": _("Issue Priority"),
+ "fieldname": "priority",
+ "fieldtype": "Link",
+ "options": "Issue Priority",
+ "width": 200,
+ }
+ )
for end_date in self.periodic_daterange:
period = self.get_period(end_date)
- self.columns.append({
- 'label': _(period),
- 'fieldname': scrub(period),
- 'fieldtype': 'Int',
- 'width': 120
- })
+ self.columns.append(
+ {"label": _(period), "fieldname": scrub(period), "fieldtype": "Int", "width": 120}
+ )
- self.columns.append({
- 'label': _('Total'),
- 'fieldname': 'total',
- 'fieldtype': 'Int',
- 'width': 120
- })
+ self.columns.append(
+ {"label": _("Total"), "fieldname": "total", "fieldtype": "Int", "width": 120}
+ )
def get_data(self):
self.get_issues()
self.get_rows()
def get_period(self, date):
- months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
+ months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
- if self.filters.range == 'Weekly':
- period = 'Week ' + str(date.isocalendar()[1])
- elif self.filters.range == 'Monthly':
+ if self.filters.range == "Weekly":
+ period = "Week " + str(date.isocalendar()[1])
+ elif self.filters.range == "Monthly":
period = str(months[date.month - 1])
- elif self.filters.range == 'Quarterly':
- period = 'Quarter ' + str(((date.month - 1) // 3) + 1)
+ elif self.filters.range == "Quarterly":
+ period = "Quarter " + str(((date.month - 1) // 3) + 1)
else:
year = get_fiscal_year(date, self.filters.company)
period = str(year[0])
- if getdate(self.filters.from_date).year != getdate(self.filters.to_date).year and self.filters.range != 'Yearly':
- period += ' ' + str(date.year)
+ if (
+ getdate(self.filters.from_date).year != getdate(self.filters.to_date).year
+ and self.filters.range != "Yearly"
+ ):
+ period += " " + str(date.year)
return period
def get_period_date_ranges(self):
from dateutil.relativedelta import MO, relativedelta
+
from_date, to_date = getdate(self.filters.from_date), getdate(self.filters.to_date)
- increment = {
- 'Monthly': 1,
- 'Quarterly': 3,
- 'Half-Yearly': 6,
- 'Yearly': 12
- }.get(self.filters.range, 1)
+ increment = {"Monthly": 1, "Quarterly": 3, "Half-Yearly": 6, "Yearly": 12}.get(
+ self.filters.range, 1
+ )
- if self.filters.range in ['Monthly', 'Quarterly']:
+ if self.filters.range in ["Monthly", "Quarterly"]:
from_date = from_date.replace(day=1)
- elif self.filters.range == 'Yearly':
+ elif self.filters.range == "Yearly":
from_date = get_fiscal_year(from_date)[1]
else:
from_date = from_date + relativedelta(from_date, weekday=MO(-1))
self.periodic_daterange = []
for dummy in range(1, 53):
- if self.filters.range == 'Weekly':
+ if self.filters.range == "Weekly":
period_end_date = add_days(from_date, 6)
else:
period_end_date = add_to_date(from_date, months=increment, days=-1)
@@ -141,25 +139,26 @@ class IssueAnalytics(object):
def get_issues(self):
filters = self.get_common_filters()
self.field_map = {
- 'Customer': 'customer',
- 'Issue Type': 'issue_type',
- 'Issue Priority': 'priority',
- 'Assigned To': '_assign'
+ "Customer": "customer",
+ "Issue Type": "issue_type",
+ "Issue Priority": "priority",
+ "Assigned To": "_assign",
}
- self.entries = frappe.db.get_all('Issue',
- fields=[self.field_map.get(self.filters.based_on), 'name', 'opening_date'],
- filters=filters
+ self.entries = frappe.db.get_all(
+ "Issue",
+ fields=[self.field_map.get(self.filters.based_on), "name", "opening_date"],
+ filters=filters,
)
def get_common_filters(self):
filters = {}
- filters['opening_date'] = ('between', [self.filters.from_date, self.filters.to_date])
+ filters["opening_date"] = ("between", [self.filters.from_date, self.filters.to_date])
- if self.filters.get('assigned_to'):
- filters['_assign'] = ('like', '%' + self.filters.get('assigned_to') + '%')
+ if self.filters.get("assigned_to"):
+ filters["_assign"] = ("like", "%" + self.filters.get("assigned_to") + "%")
- for entry in ['company', 'status', 'priority', 'customer', 'project']:
+ for entry in ["company", "status", "priority", "customer", "project"]:
if self.filters.get(entry):
filters[entry] = self.filters.get(entry)
@@ -170,14 +169,14 @@ class IssueAnalytics(object):
self.get_periodic_data()
for entity, period_data in self.issue_periodic_data.items():
- if self.filters.based_on == 'Customer':
- row = {'customer': entity}
- elif self.filters.based_on == 'Assigned To':
- row = {'user': entity}
- elif self.filters.based_on == 'Issue Type':
- row = {'issue_type': entity}
- elif self.filters.based_on == 'Issue Priority':
- row = {'priority': entity}
+ if self.filters.based_on == "Customer":
+ row = {"customer": entity}
+ elif self.filters.based_on == "Assigned To":
+ row = {"user": entity}
+ elif self.filters.based_on == "Issue Type":
+ row = {"issue_type": entity}
+ elif self.filters.based_on == "Issue Priority":
+ row = {"priority": entity}
total = 0
for end_date in self.periodic_daterange:
@@ -186,7 +185,7 @@ class IssueAnalytics(object):
row[scrub(period)] = amount
total += amount
- row['total'] = total
+ row["total"] = total
self.data.append(row)
@@ -194,9 +193,9 @@ class IssueAnalytics(object):
self.issue_periodic_data = frappe._dict()
for d in self.entries:
- period = self.get_period(d.get('opening_date'))
+ period = self.get_period(d.get("opening_date"))
- if self.filters.based_on == 'Assigned To':
+ if self.filters.based_on == "Assigned To":
if d._assign:
for entry in json.loads(d._assign):
self.issue_periodic_data.setdefault(entry, frappe._dict()).setdefault(period, 0.0)
@@ -206,18 +205,12 @@ class IssueAnalytics(object):
field = self.field_map.get(self.filters.based_on)
value = d.get(field)
if not value:
- value = _('Not Specified')
+ value = _("Not Specified")
self.issue_periodic_data.setdefault(value, frappe._dict()).setdefault(period, 0.0)
self.issue_periodic_data[value][period] += 1
def get_chart_data(self):
length = len(self.columns)
- labels = [d.get('label') for d in self.columns[1:length-1]]
- self.chart = {
- 'data': {
- 'labels': labels,
- 'datasets': []
- },
- 'type': 'line'
- }
+ labels = [d.get("label") for d in self.columns[1 : length - 1]]
+ self.chart = {"data": {"labels": labels, "datasets": []}, "type": "line"}
diff --git a/erpnext/support/report/issue_analytics/test_issue_analytics.py b/erpnext/support/report/issue_analytics/test_issue_analytics.py
index ba4dc54887a..169392e5e92 100644
--- a/erpnext/support/report/issue_analytics/test_issue_analytics.py
+++ b/erpnext/support/report/issue_analytics/test_issue_analytics.py
@@ -10,7 +10,8 @@ from erpnext.support.doctype.service_level_agreement.test_service_level_agreemen
)
from erpnext.support.report.issue_analytics.issue_analytics import execute
-months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
+months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
+
class TestIssueAnalytics(unittest.TestCase):
@classmethod
@@ -23,8 +24,8 @@ class TestIssueAnalytics(unittest.TestCase):
self.current_month = str(months[current_month_date.month - 1]).lower()
self.last_month = str(months[last_month_date.month - 1]).lower()
if current_month_date.year != last_month_date.year:
- self.current_month += '_' + str(current_month_date.year)
- self.last_month += '_' + str(last_month_date.year)
+ self.current_month += "_" + str(current_month_date.year)
+ self.last_month += "_" + str(last_month_date.year)
def test_issue_analytics(self):
create_service_level_agreements_for_issues()
@@ -38,146 +39,88 @@ class TestIssueAnalytics(unittest.TestCase):
def compare_result_for_customer(self):
filters = {
- 'company': '_Test Company',
- 'based_on': 'Customer',
- 'from_date': add_months(getdate(), -1),
- 'to_date': getdate(),
- 'range': 'Monthly'
+ "company": "_Test Company",
+ "based_on": "Customer",
+ "from_date": add_months(getdate(), -1),
+ "to_date": getdate(),
+ "range": "Monthly",
}
report = execute(filters)
expected_data = [
- {
- 'customer': '__Test Customer 2',
- self.last_month: 1.0,
- self.current_month: 0.0,
- 'total': 1.0
- },
- {
- 'customer': '__Test Customer 1',
- self.last_month: 0.0,
- self.current_month: 1.0,
- 'total': 1.0
- },
- {
- 'customer': '__Test Customer',
- self.last_month: 1.0,
- self.current_month: 1.0,
- 'total': 2.0
- }
+ {"customer": "__Test Customer 2", self.last_month: 1.0, self.current_month: 0.0, "total": 1.0},
+ {"customer": "__Test Customer 1", self.last_month: 0.0, self.current_month: 1.0, "total": 1.0},
+ {"customer": "__Test Customer", self.last_month: 1.0, self.current_month: 1.0, "total": 2.0},
]
- self.assertEqual(expected_data, report[1]) # rows
- self.assertEqual(len(report[0]), 4) # cols
+ self.assertEqual(expected_data, report[1]) # rows
+ self.assertEqual(len(report[0]), 4) # cols
def compare_result_for_issue_type(self):
filters = {
- 'company': '_Test Company',
- 'based_on': 'Issue Type',
- 'from_date': add_months(getdate(), -1),
- 'to_date': getdate(),
- 'range': 'Monthly'
+ "company": "_Test Company",
+ "based_on": "Issue Type",
+ "from_date": add_months(getdate(), -1),
+ "to_date": getdate(),
+ "range": "Monthly",
}
report = execute(filters)
expected_data = [
- {
- 'issue_type': 'Discomfort',
- self.last_month: 1.0,
- self.current_month: 0.0,
- 'total': 1.0
- },
- {
- 'issue_type': 'Service Request',
- self.last_month: 0.0,
- self.current_month: 1.0,
- 'total': 1.0
- },
- {
- 'issue_type': 'Bug',
- self.last_month: 1.0,
- self.current_month: 1.0,
- 'total': 2.0
- }
+ {"issue_type": "Discomfort", self.last_month: 1.0, self.current_month: 0.0, "total": 1.0},
+ {"issue_type": "Service Request", self.last_month: 0.0, self.current_month: 1.0, "total": 1.0},
+ {"issue_type": "Bug", self.last_month: 1.0, self.current_month: 1.0, "total": 2.0},
]
- self.assertEqual(expected_data, report[1]) # rows
- self.assertEqual(len(report[0]), 4) # cols
+ self.assertEqual(expected_data, report[1]) # rows
+ self.assertEqual(len(report[0]), 4) # cols
def compare_result_for_issue_priority(self):
filters = {
- 'company': '_Test Company',
- 'based_on': 'Issue Priority',
- 'from_date': add_months(getdate(), -1),
- 'to_date': getdate(),
- 'range': 'Monthly'
+ "company": "_Test Company",
+ "based_on": "Issue Priority",
+ "from_date": add_months(getdate(), -1),
+ "to_date": getdate(),
+ "range": "Monthly",
}
report = execute(filters)
expected_data = [
- {
- 'priority': 'Medium',
- self.last_month: 1.0,
- self.current_month: 1.0,
- 'total': 2.0
- },
- {
- 'priority': 'Low',
- self.last_month: 1.0,
- self.current_month: 0.0,
- 'total': 1.0
- },
- {
- 'priority': 'High',
- self.last_month: 0.0,
- self.current_month: 1.0,
- 'total': 1.0
- }
+ {"priority": "Medium", self.last_month: 1.0, self.current_month: 1.0, "total": 2.0},
+ {"priority": "Low", self.last_month: 1.0, self.current_month: 0.0, "total": 1.0},
+ {"priority": "High", self.last_month: 0.0, self.current_month: 1.0, "total": 1.0},
]
- self.assertEqual(expected_data, report[1]) # rows
- self.assertEqual(len(report[0]), 4) # cols
+ self.assertEqual(expected_data, report[1]) # rows
+ self.assertEqual(len(report[0]), 4) # cols
def compare_result_for_assignment(self):
filters = {
- 'company': '_Test Company',
- 'based_on': 'Assigned To',
- 'from_date': add_months(getdate(), -1),
- 'to_date': getdate(),
- 'range': 'Monthly'
+ "company": "_Test Company",
+ "based_on": "Assigned To",
+ "from_date": add_months(getdate(), -1),
+ "to_date": getdate(),
+ "range": "Monthly",
}
report = execute(filters)
expected_data = [
- {
- 'user': 'test@example.com',
- self.last_month: 1.0,
- self.current_month: 1.0,
- 'total': 2.0
- },
- {
- 'user': 'test1@example.com',
- self.last_month: 2.0,
- self.current_month: 1.0,
- 'total': 3.0
- }
+ {"user": "test@example.com", self.last_month: 1.0, self.current_month: 1.0, "total": 2.0},
+ {"user": "test1@example.com", self.last_month: 2.0, self.current_month: 1.0, "total": 3.0},
]
- self.assertEqual(expected_data, report[1]) # rows
- self.assertEqual(len(report[0]), 4) # cols
+ self.assertEqual(expected_data, report[1]) # rows
+ self.assertEqual(len(report[0]), 4) # cols
def create_issue_types():
- for entry in ['Bug', 'Service Request', 'Discomfort']:
- if not frappe.db.exists('Issue Type', entry):
- frappe.get_doc({
- 'doctype': 'Issue Type',
- '__newname': entry
- }).insert()
+ for entry in ["Bug", "Service Request", "Discomfort"]:
+ if not frappe.db.exists("Issue Type", entry):
+ frappe.get_doc({"doctype": "Issue Type", "__newname": entry}).insert()
def create_records():
@@ -189,29 +132,15 @@ def create_records():
last_month_date = add_months(current_month_date, -1)
issue = make_issue(current_month_date, "__Test Customer", 2, "High", "Bug")
- add_assignment({
- "assign_to": ["test@example.com"],
- "doctype": "Issue",
- "name": issue.name
- })
+ add_assignment({"assign_to": ["test@example.com"], "doctype": "Issue", "name": issue.name})
issue = make_issue(last_month_date, "__Test Customer", 2, "Low", "Bug")
- add_assignment({
- "assign_to": ["test1@example.com"],
- "doctype": "Issue",
- "name": issue.name
- })
+ add_assignment({"assign_to": ["test1@example.com"], "doctype": "Issue", "name": issue.name})
issue = make_issue(current_month_date, "__Test Customer 1", 2, "Medium", "Service Request")
- add_assignment({
- "assign_to": ["test1@example.com"],
- "doctype": "Issue",
- "name": issue.name
- })
+ add_assignment({"assign_to": ["test1@example.com"], "doctype": "Issue", "name": issue.name})
issue = make_issue(last_month_date, "__Test Customer 2", 2, "Medium", "Discomfort")
- add_assignment({
- "assign_to": ["test@example.com", "test1@example.com"],
- "doctype": "Issue",
- "name": issue.name
- })
+ add_assignment(
+ {"assign_to": ["test@example.com", "test1@example.com"], "doctype": "Issue", "name": issue.name}
+ )
diff --git a/erpnext/support/report/issue_summary/issue_summary.py b/erpnext/support/report/issue_summary/issue_summary.py
index 67fe345d5fe..c80ce88222d 100644
--- a/erpnext/support/report/issue_summary/issue_summary.py
+++ b/erpnext/support/report/issue_summary/issue_summary.py
@@ -12,6 +12,7 @@ from frappe.utils import flt
def execute(filters=None):
return IssueSummary(filters).run()
+
class IssueSummary(object):
def __init__(self, filters=None):
self.filters = frappe._dict(filters or {})
@@ -27,83 +28,78 @@ class IssueSummary(object):
def get_columns(self):
self.columns = []
- if self.filters.based_on == 'Customer':
- self.columns.append({
- 'label': _('Customer'),
- 'options': 'Customer',
- 'fieldname': 'customer',
- 'fieldtype': 'Link',
- 'width': 200
- })
+ if self.filters.based_on == "Customer":
+ self.columns.append(
+ {
+ "label": _("Customer"),
+ "options": "Customer",
+ "fieldname": "customer",
+ "fieldtype": "Link",
+ "width": 200,
+ }
+ )
- elif self.filters.based_on == 'Assigned To':
- self.columns.append({
- 'label': _('User'),
- 'fieldname': 'user',
- 'fieldtype': 'Link',
- 'options': 'User',
- 'width': 200
- })
+ elif self.filters.based_on == "Assigned To":
+ self.columns.append(
+ {"label": _("User"), "fieldname": "user", "fieldtype": "Link", "options": "User", "width": 200}
+ )
- elif self.filters.based_on == 'Issue Type':
- self.columns.append({
- 'label': _('Issue Type'),
- 'fieldname': 'issue_type',
- 'fieldtype': 'Link',
- 'options': 'Issue Type',
- 'width': 200
- })
+ elif self.filters.based_on == "Issue Type":
+ self.columns.append(
+ {
+ "label": _("Issue Type"),
+ "fieldname": "issue_type",
+ "fieldtype": "Link",
+ "options": "Issue Type",
+ "width": 200,
+ }
+ )
- elif self.filters.based_on == 'Issue Priority':
- self.columns.append({
- 'label': _('Issue Priority'),
- 'fieldname': 'priority',
- 'fieldtype': 'Link',
- 'options': 'Issue Priority',
- 'width': 200
- })
+ elif self.filters.based_on == "Issue Priority":
+ self.columns.append(
+ {
+ "label": _("Issue Priority"),
+ "fieldname": "priority",
+ "fieldtype": "Link",
+ "options": "Issue Priority",
+ "width": 200,
+ }
+ )
- self.statuses = ['Open', 'Replied', 'On Hold', 'Resolved', 'Closed']
+ self.statuses = ["Open", "Replied", "On Hold", "Resolved", "Closed"]
for status in self.statuses:
- self.columns.append({
- 'label': _(status),
- 'fieldname': scrub(status),
- 'fieldtype': 'Int',
- 'width': 80
- })
+ self.columns.append(
+ {"label": _(status), "fieldname": scrub(status), "fieldtype": "Int", "width": 80}
+ )
- self.columns.append({
- 'label': _('Total Issues'),
- 'fieldname': 'total_issues',
- 'fieldtype': 'Int',
- 'width': 100
- })
+ self.columns.append(
+ {"label": _("Total Issues"), "fieldname": "total_issues", "fieldtype": "Int", "width": 100}
+ )
self.sla_status_map = {
- 'SLA Failed': 'failed',
- 'SLA Fulfilled': 'fulfilled',
- 'First Response Due': 'first_response_due',
- 'Resolution Due': 'resolution_due'
+ "SLA Failed": "failed",
+ "SLA Fulfilled": "fulfilled",
+ "First Response Due": "first_response_due",
+ "Resolution Due": "resolution_due",
}
for label, fieldname in self.sla_status_map.items():
- self.columns.append({
- 'label': _(label),
- 'fieldname': fieldname,
- 'fieldtype': 'Int',
- 'width': 100
- })
+ self.columns.append(
+ {"label": _(label), "fieldname": fieldname, "fieldtype": "Int", "width": 100}
+ )
- self.metrics = ['Avg First Response Time', 'Avg Response Time', 'Avg Hold Time',
- 'Avg Resolution Time', 'Avg User Resolution Time']
+ self.metrics = [
+ "Avg First Response Time",
+ "Avg Response Time",
+ "Avg Hold Time",
+ "Avg Resolution Time",
+ "Avg User Resolution Time",
+ ]
for metric in self.metrics:
- self.columns.append({
- 'label': _(metric),
- 'fieldname': scrub(metric),
- 'fieldtype': 'Duration',
- 'width': 170
- })
+ self.columns.append(
+ {"label": _(metric), "fieldname": scrub(metric), "fieldtype": "Duration", "width": 170}
+ )
def get_data(self):
self.get_issues()
@@ -112,26 +108,37 @@ class IssueSummary(object):
def get_issues(self):
filters = self.get_common_filters()
self.field_map = {
- 'Customer': 'customer',
- 'Issue Type': 'issue_type',
- 'Issue Priority': 'priority',
- 'Assigned To': '_assign'
+ "Customer": "customer",
+ "Issue Type": "issue_type",
+ "Issue Priority": "priority",
+ "Assigned To": "_assign",
}
- self.entries = frappe.db.get_all('Issue',
- fields=[self.field_map.get(self.filters.based_on), 'name', 'opening_date', 'status', 'avg_response_time',
- 'first_response_time', 'total_hold_time', 'user_resolution_time', 'resolution_time', 'agreement_status'],
- filters=filters
+ self.entries = frappe.db.get_all(
+ "Issue",
+ fields=[
+ self.field_map.get(self.filters.based_on),
+ "name",
+ "opening_date",
+ "status",
+ "avg_response_time",
+ "first_response_time",
+ "total_hold_time",
+ "user_resolution_time",
+ "resolution_time",
+ "agreement_status",
+ ],
+ filters=filters,
)
def get_common_filters(self):
filters = {}
- filters['opening_date'] = ('between', [self.filters.from_date, self.filters.to_date])
+ filters["opening_date"] = ("between", [self.filters.from_date, self.filters.to_date])
- if self.filters.get('assigned_to'):
- filters['_assign'] = ('like', '%' + self.filters.get('assigned_to') + '%')
+ if self.filters.get("assigned_to"):
+ filters["_assign"] = ("like", "%" + self.filters.get("assigned_to") + "%")
- for entry in ['company', 'status', 'priority', 'customer', 'project']:
+ for entry in ["company", "status", "priority", "customer", "project"]:
if self.filters.get(entry):
filters[entry] = self.filters.get(entry)
@@ -142,20 +149,20 @@ class IssueSummary(object):
self.get_summary_data()
for entity, data in self.issue_summary_data.items():
- if self.filters.based_on == 'Customer':
- row = {'customer': entity}
- elif self.filters.based_on == 'Assigned To':
- row = {'user': entity}
- elif self.filters.based_on == 'Issue Type':
- row = {'issue_type': entity}
- elif self.filters.based_on == 'Issue Priority':
- row = {'priority': entity}
+ if self.filters.based_on == "Customer":
+ row = {"customer": entity}
+ elif self.filters.based_on == "Assigned To":
+ row = {"user": entity}
+ elif self.filters.based_on == "Issue Type":
+ row = {"issue_type": entity}
+ elif self.filters.based_on == "Issue Priority":
+ row = {"priority": entity}
for status in self.statuses:
count = flt(data.get(status, 0.0))
row[scrub(status)] = count
- row['total_issues'] = data.get('total_issues', 0.0)
+ row["total_issues"] = data.get("total_issues", 0.0)
for sla_status in self.sla_status_map.values():
value = flt(data.get(sla_status), 0.0)
@@ -174,36 +181,41 @@ class IssueSummary(object):
status = d.status
agreement_status = scrub(d.agreement_status)
- if self.filters.based_on == 'Assigned To':
+ if self.filters.based_on == "Assigned To":
if d._assign:
for entry in json.loads(d._assign):
self.issue_summary_data.setdefault(entry, frappe._dict()).setdefault(status, 0.0)
self.issue_summary_data.setdefault(entry, frappe._dict()).setdefault(agreement_status, 0.0)
- self.issue_summary_data.setdefault(entry, frappe._dict()).setdefault('total_issues', 0.0)
+ self.issue_summary_data.setdefault(entry, frappe._dict()).setdefault("total_issues", 0.0)
self.issue_summary_data[entry][status] += 1
self.issue_summary_data[entry][agreement_status] += 1
- self.issue_summary_data[entry]['total_issues'] += 1
+ self.issue_summary_data[entry]["total_issues"] += 1
else:
field = self.field_map.get(self.filters.based_on)
value = d.get(field)
if not value:
- value = _('Not Specified')
+ value = _("Not Specified")
self.issue_summary_data.setdefault(value, frappe._dict()).setdefault(status, 0.0)
self.issue_summary_data.setdefault(value, frappe._dict()).setdefault(agreement_status, 0.0)
- self.issue_summary_data.setdefault(value, frappe._dict()).setdefault('total_issues', 0.0)
+ self.issue_summary_data.setdefault(value, frappe._dict()).setdefault("total_issues", 0.0)
self.issue_summary_data[value][status] += 1
self.issue_summary_data[value][agreement_status] += 1
- self.issue_summary_data[value]['total_issues'] += 1
+ self.issue_summary_data[value]["total_issues"] += 1
self.get_metrics_data()
def get_metrics_data(self):
issues = []
- metrics_list = ['avg_response_time', 'avg_first_response_time', 'avg_hold_time',
- 'avg_resolution_time', 'avg_user_resolution_time']
+ metrics_list = [
+ "avg_response_time",
+ "avg_first_response_time",
+ "avg_hold_time",
+ "avg_resolution_time",
+ "avg_user_resolution_time",
+ ]
for entry in self.entries:
issues.append(entry.name)
@@ -211,7 +223,7 @@ class IssueSummary(object):
field = self.field_map.get(self.filters.based_on)
if issues:
- if self.filters.based_on == 'Assigned To':
+ if self.filters.based_on == "Assigned To":
assignment_map = frappe._dict()
for d in self.entries:
if d._assign:
@@ -219,11 +231,15 @@ class IssueSummary(object):
for metric in metrics_list:
self.issue_summary_data.setdefault(entry, frappe._dict()).setdefault(metric, 0.0)
- self.issue_summary_data[entry]['avg_response_time'] += d.get('avg_response_time') or 0.0
- self.issue_summary_data[entry]['avg_first_response_time'] += d.get('first_response_time') or 0.0
- self.issue_summary_data[entry]['avg_hold_time'] += d.get('total_hold_time') or 0.0
- self.issue_summary_data[entry]['avg_resolution_time'] += d.get('resolution_time') or 0.0
- self.issue_summary_data[entry]['avg_user_resolution_time'] += d.get('user_resolution_time') or 0.0
+ self.issue_summary_data[entry]["avg_response_time"] += d.get("avg_response_time") or 0.0
+ self.issue_summary_data[entry]["avg_first_response_time"] += (
+ d.get("first_response_time") or 0.0
+ )
+ self.issue_summary_data[entry]["avg_hold_time"] += d.get("total_hold_time") or 0.0
+ self.issue_summary_data[entry]["avg_resolution_time"] += d.get("resolution_time") or 0.0
+ self.issue_summary_data[entry]["avg_user_resolution_time"] += (
+ d.get("user_resolution_time") or 0.0
+ )
if not assignment_map.get(entry):
assignment_map[entry] = 0
@@ -234,7 +250,8 @@ class IssueSummary(object):
self.issue_summary_data[entry][metric] /= flt(assignment_map.get(entry))
else:
- data = frappe.db.sql("""
+ data = frappe.db.sql(
+ """
SELECT
{0}, AVG(first_response_time) as avg_frt,
AVG(avg_response_time) as avg_resp_time,
@@ -245,21 +262,30 @@ class IssueSummary(object):
WHERE
name IN %(issues)s
GROUP BY {0}
- """.format(field), {'issues': issues}, as_dict=1)
+ """.format(
+ field
+ ),
+ {"issues": issues},
+ as_dict=1,
+ )
for entry in data:
value = entry.get(field)
if not value:
- value = _('Not Specified')
+ value = _("Not Specified")
for metric in metrics_list:
self.issue_summary_data.setdefault(value, frappe._dict()).setdefault(metric, 0.0)
- self.issue_summary_data[value]['avg_response_time'] = entry.get('avg_resp_time') or 0.0
- self.issue_summary_data[value]['avg_first_response_time'] = entry.get('avg_frt') or 0.0
- self.issue_summary_data[value]['avg_hold_time'] = entry.get('avg_hold_time') or 0.0
- self.issue_summary_data[value]['avg_resolution_time'] = entry.get('avg_resolution_time') or 0.0
- self.issue_summary_data[value]['avg_user_resolution_time'] = entry.get('avg_user_resolution_time') or 0.0
+ self.issue_summary_data[value]["avg_response_time"] = entry.get("avg_resp_time") or 0.0
+ self.issue_summary_data[value]["avg_first_response_time"] = entry.get("avg_frt") or 0.0
+ self.issue_summary_data[value]["avg_hold_time"] = entry.get("avg_hold_time") or 0.0
+ self.issue_summary_data[value]["avg_resolution_time"] = (
+ entry.get("avg_resolution_time") or 0.0
+ )
+ self.issue_summary_data[value]["avg_user_resolution_time"] = (
+ entry.get("avg_user_resolution_time") or 0.0
+ )
def get_chart_data(self):
self.chart = []
@@ -273,47 +299,30 @@ class IssueSummary(object):
entity = self.filters.based_on
entity_field = self.field_map.get(entity)
- if entity == 'Assigned To':
- entity_field = 'user'
+ if entity == "Assigned To":
+ entity_field = "user"
for entry in self.data:
labels.append(entry.get(entity_field))
- open_issues.append(entry.get('open'))
- replied_issues.append(entry.get('replied'))
- on_hold_issues.append(entry.get('on_hold'))
- resolved_issues.append(entry.get('resolved'))
- closed_issues.append(entry.get('closed'))
+ open_issues.append(entry.get("open"))
+ replied_issues.append(entry.get("replied"))
+ on_hold_issues.append(entry.get("on_hold"))
+ resolved_issues.append(entry.get("resolved"))
+ closed_issues.append(entry.get("closed"))
self.chart = {
- 'data': {
- 'labels': labels[:30],
- 'datasets': [
- {
- 'name': 'Open',
- 'values': open_issues[:30]
- },
- {
- 'name': 'Replied',
- 'values': replied_issues[:30]
- },
- {
- 'name': 'On Hold',
- 'values': on_hold_issues[:30]
- },
- {
- 'name': 'Resolved',
- 'values': resolved_issues[:30]
- },
- {
- 'name': 'Closed',
- 'values': closed_issues[:30]
- }
- ]
+ "data": {
+ "labels": labels[:30],
+ "datasets": [
+ {"name": "Open", "values": open_issues[:30]},
+ {"name": "Replied", "values": replied_issues[:30]},
+ {"name": "On Hold", "values": on_hold_issues[:30]},
+ {"name": "Resolved", "values": resolved_issues[:30]},
+ {"name": "Closed", "values": closed_issues[:30]},
+ ],
},
- 'type': 'bar',
- 'barOptions': {
- 'stacked': True
- }
+ "type": "bar",
+ "barOptions": {"stacked": True},
}
def get_report_summary(self):
@@ -326,41 +335,41 @@ class IssueSummary(object):
closed = 0
for entry in self.data:
- open_issues += entry.get('open')
- replied += entry.get('replied')
- on_hold += entry.get('on_hold')
- resolved += entry.get('resolved')
- closed += entry.get('closed')
+ open_issues += entry.get("open")
+ replied += entry.get("replied")
+ on_hold += entry.get("on_hold")
+ resolved += entry.get("resolved")
+ closed += entry.get("closed")
self.report_summary = [
{
- 'value': open_issues,
- 'indicator': 'Red',
- 'label': _('Open'),
- 'datatype': 'Int',
+ "value": open_issues,
+ "indicator": "Red",
+ "label": _("Open"),
+ "datatype": "Int",
},
{
- 'value': replied,
- 'indicator': 'Grey',
- 'label': _('Replied'),
- 'datatype': 'Int',
+ "value": replied,
+ "indicator": "Grey",
+ "label": _("Replied"),
+ "datatype": "Int",
},
{
- 'value': on_hold,
- 'indicator': 'Grey',
- 'label': _('On Hold'),
- 'datatype': 'Int',
+ "value": on_hold,
+ "indicator": "Grey",
+ "label": _("On Hold"),
+ "datatype": "Int",
},
{
- 'value': resolved,
- 'indicator': 'Green',
- 'label': _('Resolved'),
- 'datatype': 'Int',
+ "value": resolved,
+ "indicator": "Green",
+ "label": _("Resolved"),
+ "datatype": "Int",
},
{
- 'value': closed,
- 'indicator': 'Green',
- 'label': _('Closed'),
- 'datatype': 'Int',
- }
+ "value": closed,
+ "indicator": "Green",
+ "label": _("Closed"),
+ "datatype": "Int",
+ },
]
diff --git a/erpnext/support/report/support_hour_distribution/support_hour_distribution.py b/erpnext/support/report/support_hour_distribution/support_hour_distribution.py
index 6b2098f4a8e..54967213af6 100644
--- a/erpnext/support/report/support_hour_distribution/support_hour_distribution.py
+++ b/erpnext/support/report/support_hour_distribution/support_hour_distribution.py
@@ -7,34 +7,36 @@ from frappe import _
from frappe.utils import add_to_date, get_datetime, getdate
time_slots = {
- '12AM - 3AM': '00:00:00-03:00:00',
- '3AM - 6AM': '03:00:00-06:00:00',
- '6AM - 9AM': '06:00:00-09:00:00',
- '9AM - 12PM': '09:00:00-12:00:00',
- '12PM - 3PM': '12:00:00-15:00:00',
- '3PM - 6PM': '15:00:00-18:00:00',
- '6PM - 9PM': '18:00:00-21:00:00',
- '9PM - 12AM': '21:00:00-23:00:00'
+ "12AM - 3AM": "00:00:00-03:00:00",
+ "3AM - 6AM": "03:00:00-06:00:00",
+ "6AM - 9AM": "06:00:00-09:00:00",
+ "9AM - 12PM": "09:00:00-12:00:00",
+ "12PM - 3PM": "12:00:00-15:00:00",
+ "3PM - 6PM": "15:00:00-18:00:00",
+ "6PM - 9PM": "18:00:00-21:00:00",
+ "9PM - 12AM": "21:00:00-23:00:00",
}
+
def execute(filters=None):
columns, data = [], []
- if not filters.get('periodicity'):
- filters['periodicity'] = 'Daily'
+ if not filters.get("periodicity"):
+ filters["periodicity"] = "Daily"
columns = get_columns()
data, timeslot_wise_count = get_data(filters)
chart = get_chart_data(timeslot_wise_count)
return columns, data, None, chart
+
def get_data(filters):
start_date = getdate(filters.from_date)
data = []
time_slot_wise_total_count = {}
- while(start_date <= getdate(filters.to_date)):
- hours_count = {'date': start_date}
+ while start_date <= getdate(filters.to_date):
+ hours_count = {"date": start_date}
for key, value in time_slots.items():
- start_time, end_time = value.split('-')
+ start_time, end_time = value.split("-")
start_time = get_datetime("{0} {1}".format(start_date.strftime("%Y-%m-%d"), start_time))
end_time = get_datetime("{0} {1}".format(start_date.strftime("%Y-%m-%d"), end_time))
hours_count[key] = get_hours_count(start_time, end_time)
@@ -47,49 +49,57 @@ def get_data(filters):
return data, time_slot_wise_total_count
+
def get_hours_count(start_time, end_time):
- data = frappe.db.sql(""" select count(*) from `tabIssue` where creation
- between %(start_time)s and %(end_time)s""", {
- 'start_time': start_time,
- 'end_time': end_time
- }, as_list=1) or []
+ data = (
+ frappe.db.sql(
+ """ select count(*) from `tabIssue` where creation
+ between %(start_time)s and %(end_time)s""",
+ {"start_time": start_time, "end_time": end_time},
+ as_list=1,
+ )
+ or []
+ )
return data[0][0] if len(data) > 0 else 0
-def get_columns():
- columns = [{
- "fieldname": "date",
- "label": _("Date"),
- "fieldtype": "Date",
- "width": 100
- }]
- for label in ['12AM - 3AM', '3AM - 6AM', '6AM - 9AM',
- '9AM - 12PM', '12PM - 3PM', '3PM - 6PM', '6PM - 9PM', '9PM - 12AM']:
- columns.append({
- "fieldname": label,
- "label": _(label),
- "fieldtype": "Data",
- "width": 120
- })
+def get_columns():
+ columns = [{"fieldname": "date", "label": _("Date"), "fieldtype": "Date", "width": 100}]
+
+ for label in [
+ "12AM - 3AM",
+ "3AM - 6AM",
+ "6AM - 9AM",
+ "9AM - 12PM",
+ "12PM - 3PM",
+ "3PM - 6PM",
+ "6PM - 9PM",
+ "9PM - 12AM",
+ ]:
+ columns.append({"fieldname": label, "label": _(label), "fieldtype": "Data", "width": 120})
return columns
+
def get_chart_data(timeslot_wise_count):
total_count = []
- timeslots = ['12AM - 3AM', '3AM - 6AM', '6AM - 9AM',
- '9AM - 12PM', '12PM - 3PM', '3PM - 6PM', '6PM - 9PM', '9PM - 12AM']
+ timeslots = [
+ "12AM - 3AM",
+ "3AM - 6AM",
+ "6AM - 9AM",
+ "9AM - 12PM",
+ "12PM - 3PM",
+ "3PM - 6PM",
+ "6PM - 9PM",
+ "9PM - 12AM",
+ ]
datasets = []
for data in timeslots:
total_count.append(timeslot_wise_count.get(data, 0))
- datasets.append({'values': total_count})
+ datasets.append({"values": total_count})
- chart = {
- "data": {
- 'labels': timeslots,
- 'datasets': datasets
- }
- }
+ chart = {"data": {"labels": timeslots, "datasets": datasets}}
chart["type"] = "line"
return chart
diff --git a/erpnext/telephony/doctype/call_log/call_log.py b/erpnext/telephony/doctype/call_log/call_log.py
index 0c24484bdfb..1c88883abce 100644
--- a/erpnext/telephony/doctype/call_log/call_log.py
+++ b/erpnext/telephony/doctype/call_log/call_log.py
@@ -11,8 +11,8 @@ from frappe.model.document import Document
from erpnext.crm.doctype.lead.lead import get_lead_with_phone_number
from erpnext.crm.doctype.utils import get_scheduled_employees_for_popup, strip_number
-END_CALL_STATUSES = ['No Answer', 'Completed', 'Busy', 'Failed']
-ONGOING_CALL_STATUSES = ['Ringing', 'In Progress']
+END_CALL_STATUSES = ["No Answer", "Completed", "Busy", "Failed"]
+ONGOING_CALL_STATUSES = ["Ringing", "In Progress"]
class CallLog(Document):
@@ -20,18 +20,17 @@ class CallLog(Document):
deduplicate_dynamic_links(self)
def before_insert(self):
- """Add lead(third party person) links to the document.
- """
- lead_number = self.get('from') if self.is_incoming_call() else self.get('to')
+ """Add lead(third party person) links to the document."""
+ lead_number = self.get("from") if self.is_incoming_call() else self.get("to")
lead_number = strip_number(lead_number)
contact = get_contact_with_phone_number(strip_number(lead_number))
if contact:
- self.add_link(link_type='Contact', link_name=contact)
+ self.add_link(link_type="Contact", link_name=contact)
lead = get_lead_with_phone_number(lead_number)
if lead:
- self.add_link(link_type='Lead', link_name=lead)
+ self.add_link(link_type="Lead", link_name=lead)
def after_insert(self):
self.trigger_call_popup()
@@ -39,29 +38,29 @@ class CallLog(Document):
def on_update(self):
def _is_call_missed(doc_before_save, doc_after_save):
# FIXME: This works for Exotel but not for all telepony providers
- return doc_before_save.to != doc_after_save.to and doc_after_save.status not in END_CALL_STATUSES
+ return (
+ doc_before_save.to != doc_after_save.to and doc_after_save.status not in END_CALL_STATUSES
+ )
def _is_call_ended(doc_before_save, doc_after_save):
return doc_before_save.status not in END_CALL_STATUSES and self.status in END_CALL_STATUSES
doc_before_save = self.get_doc_before_save()
- if not doc_before_save: return
+ if not doc_before_save:
+ return
if _is_call_missed(doc_before_save, self):
- frappe.publish_realtime('call_{id}_missed'.format(id=self.id), self)
+ frappe.publish_realtime("call_{id}_missed".format(id=self.id), self)
self.trigger_call_popup()
if _is_call_ended(doc_before_save, self):
- frappe.publish_realtime('call_{id}_ended'.format(id=self.id), self)
+ frappe.publish_realtime("call_{id}_ended".format(id=self.id), self)
def is_incoming_call(self):
- return self.type == 'Incoming'
+ return self.type == "Incoming"
def add_link(self, link_type, link_name):
- self.append('links', {
- 'link_doctype': link_type,
- 'link_name': link_name
- })
+ self.append("links", {"link_doctype": link_type, "link_name": link_name})
def trigger_call_popup(self):
if self.is_incoming_call():
@@ -72,53 +71,63 @@ class CallLog(Document):
emails = set(scheduled_employees).intersection(employee_emails)
if frappe.conf.developer_mode:
- self.add_comment(text=f"""
+ self.add_comment(
+ text=f"""
Scheduled Employees: {scheduled_employees}
Matching Employee: {employee_emails}
Show Popup To: {emails}
- """)
+ """
+ )
if employee_emails and not emails:
self.add_comment(text=_("No employee was scheduled for call popup"))
for email in emails:
- frappe.publish_realtime('show_call_popup', self, user=email)
+ frappe.publish_realtime("show_call_popup", self, user=email)
@frappe.whitelist()
def add_call_summary(call_log, summary):
- doc = frappe.get_doc('Call Log', call_log)
- doc.add_comment('Comment', frappe.bold(_('Call Summary')) + '
' + summary)
+ doc = frappe.get_doc("Call Log", call_log)
+ doc.add_comment("Comment", frappe.bold(_("Call Summary")) + "
" + summary)
+
def get_employees_with_number(number):
number = strip_number(number)
- if not number: return []
+ if not number:
+ return []
- employee_emails = frappe.cache().hget('employees_with_number', number)
- if employee_emails: return employee_emails
+ employee_emails = frappe.cache().hget("employees_with_number", number)
+ if employee_emails:
+ return employee_emails
- employees = frappe.get_all('Employee', filters={
- 'cell_number': ['like', '%{}%'.format(number)],
- 'user_id': ['!=', '']
- }, fields=['user_id'])
+ employees = frappe.get_all(
+ "Employee",
+ filters={"cell_number": ["like", "%{}%".format(number)], "user_id": ["!=", ""]},
+ fields=["user_id"],
+ )
employee_emails = [employee.user_id for employee in employees]
- frappe.cache().hset('employees_with_number', number, employee_emails)
+ frappe.cache().hset("employees_with_number", number, employee_emails)
return employee_emails
+
def link_existing_conversations(doc, state):
"""
Called from hooks on creation of Contact or Lead to link all the existing conversations.
"""
- if doc.doctype != 'Contact': return
+ if doc.doctype != "Contact":
+ return
try:
numbers = [d.phone for d in doc.phone_nos]
for number in numbers:
number = strip_number(number)
- if not number: continue
- logs = frappe.db.sql_list("""
+ if not number:
+ continue
+ logs = frappe.db.sql_list(
+ """
SELECT cl.name FROM `tabCall Log` cl
LEFT JOIN `tabDynamic Link` dl
ON cl.name = dl.parent
@@ -131,44 +140,42 @@ def link_existing_conversations(doc, state):
ELSE 0
END
)=0
- """, dict(
- phone_number='%{}'.format(number),
- docname=doc.name,
- doctype = doc.doctype
- )
+ """,
+ dict(phone_number="%{}".format(number), docname=doc.name, doctype=doc.doctype),
)
for log in logs:
- call_log = frappe.get_doc('Call Log', log)
+ call_log = frappe.get_doc("Call Log", log)
call_log.add_link(link_type=doc.doctype, link_name=doc.name)
call_log.save(ignore_permissions=True)
frappe.db.commit()
except Exception:
- frappe.log_error(title=_('Error during caller information update'))
+ frappe.log_error(title=_("Error during caller information update"))
+
def get_linked_call_logs(doctype, docname):
# content will be shown in timeline
- logs = frappe.get_all('Dynamic Link', fields=['parent'], filters={
- 'parenttype': 'Call Log',
- 'link_doctype': doctype,
- 'link_name': docname
- })
+ logs = frappe.get_all(
+ "Dynamic Link",
+ fields=["parent"],
+ filters={"parenttype": "Call Log", "link_doctype": doctype, "link_name": docname},
+ )
logs = set([log.parent for log in logs])
- logs = frappe.get_all('Call Log', fields=['*'], filters={
- 'name': ['in', logs]
- })
+ logs = frappe.get_all("Call Log", fields=["*"], filters={"name": ["in", logs]})
timeline_contents = []
for log in logs:
log.show_call_button = 0
- timeline_contents.append({
- 'icon': 'call',
- 'is_card': True,
- 'creation': log.creation,
- 'template': 'call_link',
- 'template_data': log
- })
+ timeline_contents.append(
+ {
+ "icon": "call",
+ "is_card": True,
+ "creation": log.creation,
+ "template": "call_link",
+ "template_data": log,
+ }
+ )
return timeline_contents
diff --git a/erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.py b/erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.py
index 08e244d8897..5edf81df736 100644
--- a/erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.py
+++ b/erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.py
@@ -20,35 +20,38 @@ class IncomingCallSettings(Document):
self.validate_call_schedule_overlaps(self.call_handling_schedule)
def validate_call_schedule_timeslot(self, schedule: list):
- """ Make sure that to time slot is ahead of from time slot.
- """
+ """Make sure that to time slot is ahead of from time slot."""
errors = []
for record in schedule:
from_time = self.time_to_seconds(record.from_time)
to_time = self.time_to_seconds(record.to_time)
if from_time >= to_time:
errors.append(
- _('Call Schedule Row {0}: To time slot should always be ahead of From time slot.').format(record.idx)
+ _("Call Schedule Row {0}: To time slot should always be ahead of From time slot.").format(
+ record.idx
+ )
)
if errors:
- frappe.throw('
'.join(errors))
+ frappe.throw("
".join(errors))
def validate_call_schedule_overlaps(self, schedule: list):
- """Check if any time slots are overlapped in a day schedule.
- """
+ """Check if any time slots are overlapped in a day schedule."""
week_days = set([each.day_of_week for each in schedule])
for day in week_days:
- timeslots = [(record.from_time, record.to_time) for record in schedule if record.day_of_week==day]
+ timeslots = [
+ (record.from_time, record.to_time) for record in schedule if record.day_of_week == day
+ ]
# convert time in timeslot into an integer represents number of seconds
timeslots = sorted(map(lambda seq: tuple(map(self.time_to_seconds, seq)), timeslots))
- if len(timeslots) < 2: continue
+ if len(timeslots) < 2:
+ continue
for i in range(1, len(timeslots)):
- if self.check_timeslots_overlap(timeslots[i-1], timeslots[i]):
- frappe.throw(_('Please fix overlapping time slots for {0}.').format(day))
+ if self.check_timeslots_overlap(timeslots[i - 1], timeslots[i]):
+ frappe.throw(_("Please fix overlapping time slots for {0}.").format(day))
@staticmethod
def check_timeslots_overlap(ts1: Tuple[int, int], ts2: Tuple[int, int]) -> bool:
@@ -58,7 +61,6 @@ class IncomingCallSettings(Document):
@staticmethod
def time_to_seconds(time: str) -> int:
- """Convert time string of format HH:MM:SS into seconds
- """
+ """Convert time string of format HH:MM:SS into seconds"""
date_time = datetime.strptime(time, "%H:%M:%S")
return date_time - datetime(1900, 1, 1)
diff --git a/erpnext/templates/includes/projects/project_row.html b/erpnext/templates/includes/projects/project_row.html
index a256fbd677b..686637a2014 100644
--- a/erpnext/templates/includes/projects/project_row.html
+++ b/erpnext/templates/includes/projects/project_row.html
@@ -1,11 +1,11 @@
{% if doc.status == "Open" %}
-
+
-
+
{{ doc.project_name }}
@@ -25,7 +25,7 @@
{% if doc["_assign"] %}
{% set assigned_users = json.loads(doc["_assign"])%}
-
+
{% for user in assigned_users %}
{% set user_details = frappe
.db
@@ -46,7 +46,7 @@
{% endfor %}
{% endif %}
-
+
{{ frappe.utils.pretty_date(doc.modified) }}
diff --git a/erpnext/templates/pages/courses.html b/erpnext/templates/pages/courses.html
deleted file mode 100644
index 6592f7a2e5c..00000000000
--- a/erpnext/templates/pages/courses.html
+++ /dev/null
@@ -1,11 +0,0 @@
-{% extends "templates/web.html" %}
-
-{% block header %}
-
About
-{% endblock %}
-
-{% block page_content %}
-
-
{{ intro }}
-
-{% endblock %}
diff --git a/erpnext/templates/pages/courses.py b/erpnext/templates/pages/courses.py
deleted file mode 100644
index 6051e60aa30..00000000000
--- a/erpnext/templates/pages/courses.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-
-import frappe
-
-
-def get_context(context):
- course = frappe.get_doc('Course', frappe.form_dict.course)
- sidebar_title = course.name
-
- context.no_cache = 1
- context.show_sidebar = True
- course = frappe.get_doc('Course', frappe.form_dict.course)
- course.has_permission('read')
- context.doc = course
- context.sidebar_title = sidebar_title
- context.intro = course.course_intro
diff --git a/erpnext/templates/pages/help.py b/erpnext/templates/pages/help.py
index 6a83fc8b57a..19993ee9b13 100644
--- a/erpnext/templates/pages/help.py
+++ b/erpnext/templates/pages/help.py
@@ -25,21 +25,19 @@ def get_context(context):
else:
context.issues = []
+
def get_forum_posts(s):
- response = requests.get(s.forum_url + '/' + s.get_latest_query)
+ response = requests.get(s.forum_url + "/" + s.get_latest_query)
response.raise_for_status()
response_json = response.json()
- topics_data = {} # it will actually be an array
- key_list = s.response_key_list.split(',')
+ topics_data = {} # it will actually be an array
+ key_list = s.response_key_list.split(",")
for key in key_list:
topics_data = response_json.get(key) if not topics_data else topics_data.get(key)
for topic in topics_data:
- topic["link"] = s.forum_url + '/' + s.post_route_string + '/' + str(topic.get(s.post_route_key))
+ topic["link"] = s.forum_url + "/" + s.post_route_string + "/" + str(topic.get(s.post_route_key))
- post_params = {
- "title": s.post_title_key,
- "description": s.post_description_key
- }
+ post_params = {"title": s.post_title_key, "description": s.post_description_key}
return topics_data, post_params
diff --git a/erpnext/templates/pages/home.py b/erpnext/templates/pages/home.py
index d08e81b9e62..47fb89dea31 100644
--- a/erpnext/templates/pages/home.py
+++ b/erpnext/templates/pages/home.py
@@ -6,46 +6,51 @@ import frappe
no_cache = 1
+
def get_context(context):
- homepage = frappe.get_doc('Homepage')
+ homepage = frappe.get_cached_doc("Homepage")
for item in homepage.products:
- route = frappe.db.get_value('Website Item', {"item_code": item.item_code}, 'route')
+ route = frappe.db.get_value("Website Item", {"item_code": item.item_code}, "route")
if route:
- item.route = '/' + route
+ item.route = "/" + route
homepage.title = homepage.title or homepage.company
context.title = homepage.title
context.homepage = homepage
- if homepage.hero_section_based_on == 'Homepage Section' and homepage.hero_section:
- homepage.hero_section_doc = frappe.get_doc('Homepage Section', homepage.hero_section)
+ if homepage.hero_section_based_on == "Homepage Section" and homepage.hero_section:
+ homepage.hero_section_doc = frappe.get_cached_doc("Homepage Section", homepage.hero_section)
if homepage.slideshow:
- doc = frappe.get_doc('Website Slideshow', homepage.slideshow)
+ doc = frappe.get_cached_doc("Website Slideshow", homepage.slideshow)
context.slideshow = homepage.slideshow
context.slideshow_header = doc.header
context.slides = doc.slideshow_items
- context.blogs = frappe.get_all('Blog Post',
- fields=['title', 'blogger', 'blog_intro', 'route'],
- filters={
- 'published': 1
- },
- order_by='modified desc',
- limit=3
+ context.blogs = frappe.get_all(
+ "Blog Post",
+ fields=["title", "blogger", "blog_intro", "route"],
+ filters={"published": 1},
+ order_by="modified desc",
+ limit=3,
)
# filter out homepage section which is used as hero section
- homepage_hero_section = homepage.hero_section_based_on == 'Homepage Section' and homepage.hero_section
- homepage_sections = frappe.get_all('Homepage Section',
- filters=[['name', '!=', homepage_hero_section]] if homepage_hero_section else None,
- order_by='section_order asc'
+ homepage_hero_section = (
+ homepage.hero_section_based_on == "Homepage Section" and homepage.hero_section
)
- context.homepage_sections = [frappe.get_doc('Homepage Section', name) for name in homepage_sections]
+ homepage_sections = frappe.get_all(
+ "Homepage Section",
+ filters=[["name", "!=", homepage_hero_section]] if homepage_hero_section else None,
+ order_by="section_order asc",
+ )
+ context.homepage_sections = [
+ frappe.get_cached_doc("Homepage Section", name) for name in homepage_sections
+ ]
context.metatags = context.metatags or frappe._dict({})
context.metatags.image = homepage.hero_image or None
context.metatags.description = homepage.description or None
- context.explore_link = '/all-products'
+ context.explore_link = "/all-products"
diff --git a/erpnext/templates/pages/integrations/gocardless_checkout.py b/erpnext/templates/pages/integrations/gocardless_checkout.py
index bbdbf1ddf97..280f67f16b9 100644
--- a/erpnext/templates/pages/integrations/gocardless_checkout.py
+++ b/erpnext/templates/pages/integrations/gocardless_checkout.py
@@ -14,8 +14,18 @@ from erpnext.erpnext_integrations.doctype.gocardless_settings.gocardless_setting
no_cache = 1
-expected_keys = ('amount', 'title', 'description', 'reference_doctype', 'reference_docname',
- 'payer_name', 'payer_email', 'order_id', 'currency')
+expected_keys = (
+ "amount",
+ "title",
+ "description",
+ "reference_doctype",
+ "reference_docname",
+ "payer_name",
+ "payer_email",
+ "order_id",
+ "currency",
+)
+
def get_context(context):
context.no_cache = 1
@@ -25,17 +35,22 @@ def get_context(context):
for key in expected_keys:
context[key] = frappe.form_dict[key]
- context['amount'] = flt(context['amount'])
+ context["amount"] = flt(context["amount"])
gateway_controller = get_gateway_controller(context.reference_docname)
- context['header_img'] = frappe.db.get_value("GoCardless Settings", gateway_controller, "header_img")
+ context["header_img"] = frappe.db.get_value(
+ "GoCardless Settings", gateway_controller, "header_img"
+ )
else:
- frappe.redirect_to_message(_('Some information is missing'),
- _('Looks like someone sent you to an incomplete URL. Please ask them to look into it.'))
+ frappe.redirect_to_message(
+ _("Some information is missing"),
+ _("Looks like someone sent you to an incomplete URL. Please ask them to look into it."),
+ )
frappe.local.flags.redirect_location = frappe.local.response.location
raise frappe.Redirect
+
@frappe.whitelist(allow_guest=True)
def check_mandate(data, reference_doctype, reference_docname):
data = json.loads(data)
@@ -59,23 +74,27 @@ def check_mandate(data, reference_doctype, reference_docname):
prefilled_customer.update({"email": frappe.session.user})
else:
- prefilled_customer = {
- "company_name": payer.name,
- "email": frappe.session.user
- }
+ prefilled_customer = {"company_name": payer.name, "email": frappe.session.user}
- success_url = get_url("./integrations/gocardless_confirmation?reference_doctype=" + reference_doctype + "&reference_docname=" + reference_docname)
+ success_url = get_url(
+ "./integrations/gocardless_confirmation?reference_doctype="
+ + reference_doctype
+ + "&reference_docname="
+ + reference_docname
+ )
try:
- redirect_flow = client.redirect_flows.create(params={
- "description": _("Pay {0} {1}").format(data['amount'], data['currency']),
- "session_token": frappe.session.user,
- "success_redirect_url": success_url,
- "prefilled_customer": prefilled_customer
- })
+ redirect_flow = client.redirect_flows.create(
+ params={
+ "description": _("Pay {0} {1}").format(data["amount"], data["currency"]),
+ "session_token": frappe.session.user,
+ "success_redirect_url": success_url,
+ "prefilled_customer": prefilled_customer,
+ }
+ )
return {"redirect_to": redirect_flow.redirect_url}
except Exception as e:
frappe.log_error(e, "GoCardless Payment Error")
- return {"redirect_to": '/integrations/payment-failed'}
+ return {"redirect_to": "/integrations/payment-failed"}
diff --git a/erpnext/templates/pages/integrations/gocardless_confirmation.py b/erpnext/templates/pages/integrations/gocardless_confirmation.py
index a6c3e714947..cab532a5303 100644
--- a/erpnext/templates/pages/integrations/gocardless_confirmation.py
+++ b/erpnext/templates/pages/integrations/gocardless_confirmation.py
@@ -11,7 +11,8 @@ from erpnext.erpnext_integrations.doctype.gocardless_settings.gocardless_setting
no_cache = 1
-expected_keys = ('redirect_flow_id', 'reference_doctype', 'reference_docname')
+expected_keys = ("redirect_flow_id", "reference_doctype", "reference_docname")
+
def get_context(context):
context.no_cache = 1
@@ -22,11 +23,14 @@ def get_context(context):
context[key] = frappe.form_dict[key]
else:
- frappe.redirect_to_message(_('Some information is missing'),
- _('Looks like someone sent you to an incomplete URL. Please ask them to look into it.'))
+ frappe.redirect_to_message(
+ _("Some information is missing"),
+ _("Looks like someone sent you to an incomplete URL. Please ask them to look into it."),
+ )
frappe.local.flags.redirect_location = frappe.local.response.location
raise frappe.Redirect
+
@frappe.whitelist(allow_guest=True)
def confirm_payment(redirect_flow_id, reference_doctype, reference_docname):
@@ -34,15 +38,15 @@ def confirm_payment(redirect_flow_id, reference_doctype, reference_docname):
try:
redirect_flow = client.redirect_flows.complete(
- redirect_flow_id,
- params={
- "session_token": frappe.session.user
- })
+ redirect_flow_id, params={"session_token": frappe.session.user}
+ )
confirmation_url = redirect_flow.confirmation_url
- gocardless_success_page = frappe.get_hooks('gocardless_success_page')
+ gocardless_success_page = frappe.get_hooks("gocardless_success_page")
if gocardless_success_page:
- confirmation_url = frappe.get_attr(gocardless_success_page[-1])(reference_doctype, reference_docname)
+ confirmation_url = frappe.get_attr(gocardless_success_page[-1])(
+ reference_doctype, reference_docname
+ )
data = {
"mandate": redirect_flow.links.mandate,
@@ -50,7 +54,7 @@ def confirm_payment(redirect_flow_id, reference_doctype, reference_docname):
"redirect_to": confirmation_url,
"redirect_message": "Mandate successfully created",
"reference_doctype": reference_doctype,
- "reference_docname": reference_docname
+ "reference_docname": reference_docname,
}
try:
@@ -65,29 +69,38 @@ def confirm_payment(redirect_flow_id, reference_doctype, reference_docname):
except Exception as e:
frappe.log_error(e, "GoCardless Payment Error")
- return {"redirect_to": '/integrations/payment-failed'}
+ return {"redirect_to": "/integrations/payment-failed"}
def create_mandate(data):
data = frappe._dict(data)
frappe.logger().debug(data)
- mandate = data.get('mandate')
+ mandate = data.get("mandate")
if frappe.db.exists("GoCardless Mandate", mandate):
return
else:
- reference_doc = frappe.db.get_value(data.get('reference_doctype'), data.get('reference_docname'), ["reference_doctype", "reference_name"], as_dict=1)
- erpnext_customer = frappe.db.get_value(reference_doc.reference_doctype, reference_doc.reference_name, ["customer_name"], as_dict=1)
+ reference_doc = frappe.db.get_value(
+ data.get("reference_doctype"),
+ data.get("reference_docname"),
+ ["reference_doctype", "reference_name"],
+ as_dict=1,
+ )
+ erpnext_customer = frappe.db.get_value(
+ reference_doc.reference_doctype, reference_doc.reference_name, ["customer_name"], as_dict=1
+ )
try:
- frappe.get_doc({
- "doctype": "GoCardless Mandate",
- "mandate": mandate,
- "customer": erpnext_customer.customer_name,
- "gocardless_customer": data.get('customer')
- }).insert(ignore_permissions=True)
+ frappe.get_doc(
+ {
+ "doctype": "GoCardless Mandate",
+ "mandate": mandate,
+ "customer": erpnext_customer.customer_name,
+ "gocardless_customer": data.get("customer"),
+ }
+ ).insert(ignore_permissions=True)
except Exception:
frappe.log_error(frappe.get_traceback())
diff --git a/erpnext/templates/pages/material_request_info.py b/erpnext/templates/pages/material_request_info.py
index 65d4427e118..301ca01cfce 100644
--- a/erpnext/templates/pages/material_request_info.py
+++ b/erpnext/templates/pages/material_request_info.py
@@ -20,17 +20,23 @@ def get_context(context):
if not frappe.has_website_permission(context.doc):
frappe.throw(_("Not Permitted"), frappe.PermissionError)
- default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=frappe.form_dict.doctype), "value")
+ default_print_format = frappe.db.get_value(
+ "Property Setter",
+ dict(property="default_print_format", doc_type=frappe.form_dict.doctype),
+ "value",
+ )
if default_print_format:
context.print_format = default_print_format
else:
context.print_format = "Standard"
context.doc.items = get_more_items_info(context.doc.items, context.doc.name)
+
def get_more_items_info(items, material_request):
for item in items:
- item.customer_provided = frappe.get_value('Item', item.item_code, 'is_customer_provided_item')
- item.work_orders = frappe.db.sql("""
+ item.customer_provided = frappe.get_value("Item", item.item_code, "is_customer_provided_item")
+ item.work_orders = frappe.db.sql(
+ """
select
wo.name, wo.status, wo_item.consumed_qty
from
@@ -41,9 +47,16 @@ def get_more_items_info(items, material_request):
and wo_item.parent=wo.name
and wo.status not in ('Completed', 'Cancelled', 'Stopped')
order by
- wo.name asc""", item.item_code, as_dict=1)
- item.delivered_qty = flt(frappe.db.sql("""select sum(transfer_qty)
+ wo.name asc""",
+ item.item_code,
+ as_dict=1,
+ )
+ item.delivered_qty = flt(
+ frappe.db.sql(
+ """select sum(transfer_qty)
from `tabStock Entry Detail` where material_request = %s
and item_code = %s and docstatus = 1""",
- (material_request, item.item_code))[0][0])
+ (material_request, item.item_code),
+ )[0][0]
+ )
return items
diff --git a/erpnext/templates/pages/order.py b/erpnext/templates/pages/order.py
index 712b141defe..3e6d57a02b1 100644
--- a/erpnext/templates/pages/order.py
+++ b/erpnext/templates/pages/order.py
@@ -19,12 +19,17 @@ def get_context(context):
context.parents = frappe.form_dict.parents
context.title = frappe.form_dict.name
- context.payment_ref = frappe.db.get_value("Payment Request",
- {"reference_name": frappe.form_dict.name}, "name")
+ context.payment_ref = frappe.db.get_value(
+ "Payment Request", {"reference_name": frappe.form_dict.name}, "name"
+ )
context.enabled_checkout = frappe.get_doc("E Commerce Settings").enable_checkout
- default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=frappe.form_dict.doctype), "value")
+ default_print_format = frappe.db.get_value(
+ "Property Setter",
+ dict(property="default_print_format", doc_type=frappe.form_dict.doctype),
+ "value",
+ )
if default_print_format:
context.print_format = default_print_format
else:
@@ -34,15 +39,23 @@ def get_context(context):
frappe.throw(_("Not Permitted"), frappe.PermissionError)
# check for the loyalty program of the customer
- customer_loyalty_program = frappe.db.get_value("Customer", context.doc.customer, "loyalty_program")
+ customer_loyalty_program = frappe.db.get_value(
+ "Customer", context.doc.customer, "loyalty_program"
+ )
if customer_loyalty_program:
from erpnext.accounts.doctype.loyalty_program.loyalty_program import (
get_loyalty_program_details_with_points,
)
- loyalty_program_details = get_loyalty_program_details_with_points(context.doc.customer, customer_loyalty_program)
+
+ loyalty_program_details = get_loyalty_program_details_with_points(
+ context.doc.customer, customer_loyalty_program
+ )
context.available_loyalty_points = int(loyalty_program_details.get("loyalty_points"))
+
def get_attachments(dt, dn):
- return frappe.get_all("File",
- fields=["name", "file_name", "file_url", "is_private"],
- filters = {"attached_to_name": dn, "attached_to_doctype": dt, "is_private":0})
+ return frappe.get_all(
+ "File",
+ fields=["name", "file_name", "file_url", "is_private"],
+ filters={"attached_to_name": dn, "attached_to_doctype": dt, "is_private": 0},
+ )
diff --git a/erpnext/templates/pages/partners.py b/erpnext/templates/pages/partners.py
index e4043ea8b96..8a49504ff04 100644
--- a/erpnext/templates/pages/partners.py
+++ b/erpnext/templates/pages/partners.py
@@ -6,11 +6,12 @@ import frappe
page_title = "Partners"
-def get_context(context):
- partners = frappe.db.sql("""select * from `tabSales Partner`
- where show_in_website=1 order by name asc""", as_dict=True)
- return {
- "partners": partners,
- "title": page_title
- }
+def get_context(context):
+ partners = frappe.db.sql(
+ """select * from `tabSales Partner`
+ where show_in_website=1 order by name asc""",
+ as_dict=True,
+ )
+
+ return {"partners": partners, "title": page_title}
diff --git a/erpnext/templates/pages/product_search.py b/erpnext/templates/pages/product_search.py
index 237adf99f5d..3ed056f55e7 100644
--- a/erpnext/templates/pages/product_search.py
+++ b/erpnext/templates/pages/product_search.py
@@ -1,6 +1,8 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
+import json
+
import frappe
from frappe.utils import cint, cstr
from redisearch import AutoCompleter, Client, Query
@@ -9,7 +11,7 @@ from erpnext.e_commerce.redisearch_utils import (
WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE,
WEBSITE_ITEM_INDEX,
WEBSITE_ITEM_NAME_AUTOCOMPLETE,
- is_search_module_loaded,
+ is_redisearch_enabled,
make_key,
)
from erpnext.e_commerce.shopping_cart.product_info import set_product_info_for_website
@@ -17,9 +19,11 @@ from erpnext.setup.doctype.item_group.item_group import get_item_for_list_in_htm
no_cache = 1
+
def get_context(context):
context.show_search = True
+
@frappe.whitelist(allow_guest=True)
def get_product_list(search=None, start=0, limit=12):
data = get_product_data(search, start, limit)
@@ -29,6 +33,7 @@ def get_product_list(search=None, start=0, limit=12):
return [get_item_for_list_in_html(r) for r in data]
+
def get_product_data(search=None, start=0, limit=12):
# limit = 12 because we show 12 items in the grid view
# base query
@@ -53,7 +58,8 @@ def get_product_data(search=None, start=0, limit=12):
# order by
query += """ ORDER BY ranking desc, modified desc limit %s, %s""" % (cint(start), cint(limit))
- return frappe.db.sql(query, {"search": search}, as_dict=1) # nosemgrep
+ return frappe.db.sql(query, {"search": search}, as_dict=1) # nosemgrep
+
@frappe.whitelist(allow_guest=True)
def search(query):
@@ -62,15 +68,16 @@ def search(query):
return {
"product_results": product_results.get("results") or [],
- "category_results": category_results.get("results") or []
+ "category_results": category_results.get("results") or [],
}
+
@frappe.whitelist(allow_guest=True)
def product_search(query, limit=10, fuzzy_search=True):
search_results = {"from_redisearch": True, "results": []}
- if not is_search_module_loaded():
- # Redisearch module not loaded
+ if not is_redisearch_enabled():
+ # Redisearch module not enabled
search_results["from_redisearch"] = False
search_results["results"] = get_product_data(query, 0, limit)
return search_results
@@ -81,12 +88,12 @@ def product_search(query, limit=10, fuzzy_search=True):
red = frappe.cache()
query = clean_up_query(query)
+ # TODO: Check perf/correctness with Suggestions & Query vs only Query
+ # TODO: Use Levenshtein Distance in Query (max=3)
ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=red)
client = Client(make_key(WEBSITE_ITEM_INDEX), conn=red)
suggestions = ac.get_suggestions(
- query,
- num=limit,
- fuzzy= fuzzy_search and len(query) > 3 # Fuzzy on length < 3 can be real slow
+ query, num=limit, fuzzy=fuzzy_search and len(query) > 3 # Fuzzy on length < 3 can be real slow
)
# Build a query
@@ -98,40 +105,44 @@ def product_search(query, limit=10, fuzzy_search=True):
q = Query(query_string)
results = client.search(q)
- search_results['results'] = list(map(convert_to_dict, results.docs))
- search_results['results'] = sorted(search_results['results'], key=lambda k: frappe.utils.cint(k['ranking']), reverse=True)
+ search_results["results"] = list(map(convert_to_dict, results.docs))
+ search_results["results"] = sorted(
+ search_results["results"], key=lambda k: frappe.utils.cint(k["ranking"]), reverse=True
+ )
return search_results
+
def clean_up_query(query):
- return ''.join(c for c in query if c.isalnum() or c.isspace())
+ return "".join(c for c in query if c.isalnum() or c.isspace())
+
def convert_to_dict(redis_search_doc):
return redis_search_doc.__dict__
+
@frappe.whitelist(allow_guest=True)
def get_category_suggestions(query):
search_results = {"results": []}
- if not is_search_module_loaded():
- # Redisearch module not loaded, query db
+ if not is_redisearch_enabled():
+ # Redisearch module not enabled, query db
categories = frappe.db.get_all(
"Item Group",
- filters={
- "name": ["like", "%{0}%".format(query)],
- "show_in_website": 1
- },
- fields=["name", "route"]
+ filters={"name": ["like", "%{0}%".format(query)], "show_in_website": 1},
+ fields=["name", "route"],
)
- search_results['results'] = categories
+ search_results["results"] = categories
return search_results
if not query:
return search_results
ac = AutoCompleter(make_key(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE), conn=frappe.cache())
- suggestions = ac.get_suggestions(query, num=10)
+ suggestions = ac.get_suggestions(query, num=10, with_payloads=True)
- search_results['results'] = [s.string for s in suggestions]
+ results = [json.loads(s.payload) for s in suggestions]
- return search_results
\ No newline at end of file
+ search_results["results"] = results
+
+ return search_results
diff --git a/erpnext/templates/pages/projects.py b/erpnext/templates/pages/projects.py
index 16aa4390f1f..4b3089b2916 100644
--- a/erpnext/templates/pages/projects.py
+++ b/erpnext/templates/pages/projects.py
@@ -6,21 +6,28 @@ import frappe
def get_context(context):
- project_user = frappe.db.get_value("Project User", {"parent": frappe.form_dict.project, "user": frappe.session.user} , ["user", "view_attachments"], as_dict= True)
- if frappe.session.user != 'Administrator' and (not project_user or frappe.session.user == 'Guest'):
+ project_user = frappe.db.get_value(
+ "Project User",
+ {"parent": frappe.form_dict.project, "user": frappe.session.user},
+ ["user", "view_attachments"],
+ as_dict=True,
+ )
+ if frappe.session.user != "Administrator" and (
+ not project_user or frappe.session.user == "Guest"
+ ):
raise frappe.PermissionError
context.no_cache = 1
context.show_sidebar = True
- project = frappe.get_doc('Project', frappe.form_dict.project)
+ project = frappe.get_doc("Project", frappe.form_dict.project)
- project.has_permission('read')
+ project.has_permission("read")
- project.tasks = get_tasks(project.name, start=0, item_status='open',
- search=frappe.form_dict.get("search"))
+ project.tasks = get_tasks(
+ project.name, start=0, item_status="open", search=frappe.form_dict.get("search")
+ )
- project.timesheets = get_timesheets(project.name, start=0,
- search=frappe.form_dict.get("search"))
+ project.timesheets = get_timesheets(project.name, start=0, search=frappe.form_dict.get("search"))
if project_user and project_user.view_attachments:
project.attachments = get_attachments(project.name)
@@ -32,9 +39,22 @@ def get_tasks(project, start=0, search=None, item_status=None):
filters = {"project": project}
if search:
filters["subject"] = ("like", "%{0}%".format(search))
- tasks = frappe.get_all("Task", filters=filters,
- fields=["name", "subject", "status", "modified", "_assign", "exp_end_date", "is_group", "parent_task"],
- limit_start=start, limit_page_length=10)
+ tasks = frappe.get_all(
+ "Task",
+ filters=filters,
+ fields=[
+ "name",
+ "subject",
+ "status",
+ "modified",
+ "_assign",
+ "exp_end_date",
+ "is_group",
+ "parent_task",
+ ],
+ limit_start=start,
+ limit_page_length=10,
+ )
task_nest = []
for task in tasks:
if task.is_group:
@@ -44,36 +64,59 @@ def get_tasks(project, start=0, search=None, item_status=None):
task_nest.append(task)
return list(filter(lambda x: not x.parent_task, tasks))
+
@frappe.whitelist()
def get_task_html(project, start=0, item_status=None):
- return frappe.render_template("erpnext/templates/includes/projects/project_tasks.html",
- {"doc": {
- "name": project,
- "project_name": project,
- "tasks": get_tasks(project, start, item_status=item_status)}
- }, is_path=True)
+ return frappe.render_template(
+ "erpnext/templates/includes/projects/project_tasks.html",
+ {
+ "doc": {
+ "name": project,
+ "project_name": project,
+ "tasks": get_tasks(project, start, item_status=item_status),
+ }
+ },
+ is_path=True,
+ )
+
def get_timesheets(project, start=0, search=None):
filters = {"project": project}
if search:
filters["activity_type"] = ("like", "%{0}%".format(search))
- timesheets = frappe.get_all('Timesheet Detail', filters=filters,
- fields=['project','activity_type','from_time','to_time','parent'],
- limit_start=start, limit_page_length=10)
+ timesheets = frappe.get_all(
+ "Timesheet Detail",
+ filters=filters,
+ fields=["project", "activity_type", "from_time", "to_time", "parent"],
+ limit_start=start,
+ limit_page_length=10,
+ )
for timesheet in timesheets:
- info = frappe.get_all('Timesheet', filters={"name": timesheet.parent},
- fields=['name','status','modified','modified_by'],
- limit_start=start, limit_page_length=10)
+ info = frappe.get_all(
+ "Timesheet",
+ filters={"name": timesheet.parent},
+ fields=["name", "status", "modified", "modified_by"],
+ limit_start=start,
+ limit_page_length=10,
+ )
if len(info):
timesheet.update(info[0])
return timesheets
+
@frappe.whitelist()
def get_timesheet_html(project, start=0):
- return frappe.render_template("erpnext/templates/includes/projects/project_timesheets.html",
- {"doc": {"timesheets": get_timesheets(project, start)}}, is_path=True)
+ return frappe.render_template(
+ "erpnext/templates/includes/projects/project_timesheets.html",
+ {"doc": {"timesheets": get_timesheets(project, start)}},
+ is_path=True,
+ )
+
def get_attachments(project):
- return frappe.get_all('File', filters= {"attached_to_name": project, "attached_to_doctype": 'Project', "is_private":0},
- fields=['file_name','file_url', 'file_size'])
+ return frappe.get_all(
+ "File",
+ filters={"attached_to_name": project, "attached_to_doctype": "Project", "is_private": 0},
+ fields=["file_name", "file_url", "file_size"],
+ )
diff --git a/erpnext/templates/pages/regional/india/update_gstin.py b/erpnext/templates/pages/regional/india/update_gstin.py
index 95b8f72d882..6939fe41a84 100644
--- a/erpnext/templates/pages/regional/india/update_gstin.py
+++ b/erpnext/templates/pages/regional/india/update_gstin.py
@@ -11,12 +11,12 @@ def get_context(context):
except frappe.ValidationError:
context.invalid_gstin = 1
- party_type = 'Customer'
- party_name = frappe.db.get_value('Customer', party)
+ party_type = "Customer"
+ party_name = frappe.db.get_value("Customer", party)
if not party_name:
- party_type = 'Supplier'
- party_name = frappe.db.get_value('Supplier', party)
+ party_type = "Supplier"
+ party_name = frappe.db.get_value("Supplier", party)
if not party_name:
context.not_found = 1
@@ -29,10 +29,10 @@ def get_context(context):
def update_gstin(context):
dirty = False
for key, value in frappe.form_dict.items():
- if key != 'party':
- address_name = frappe.get_value('Address', key)
+ if key != "party":
+ address_name = frappe.get_value("Address", key)
if address_name:
- address = frappe.get_doc('Address', address_name)
+ address = frappe.get_doc("Address", address_name)
address.gstin = value.upper()
address.save(ignore_permissions=True)
dirty = True
diff --git a/erpnext/templates/pages/rfq.py b/erpnext/templates/pages/rfq.py
index 0afd46cac90..4b836424911 100644
--- a/erpnext/templates/pages/rfq.py
+++ b/erpnext/templates/pages/rfq.py
@@ -20,40 +20,59 @@ def get_context(context):
update_supplier_details(context)
context["title"] = frappe.form_dict.name
+
def get_supplier():
doctype = frappe.form_dict.doctype
- parties_doctype = 'Request for Quotation Supplier' if doctype == 'Request for Quotation' else doctype
+ parties_doctype = (
+ "Request for Quotation Supplier" if doctype == "Request for Quotation" else doctype
+ )
customers, suppliers = get_customers_suppliers(parties_doctype, frappe.session.user)
- return suppliers[0] if suppliers else ''
+ return suppliers[0] if suppliers else ""
+
def check_supplier_has_docname_access(supplier):
status = True
- if frappe.form_dict.name not in frappe.db.sql_list("""select parent from `tabRequest for Quotation Supplier`
- where supplier = %s""", (supplier,)):
+ if frappe.form_dict.name not in frappe.db.sql_list(
+ """select parent from `tabRequest for Quotation Supplier`
+ where supplier = %s""",
+ (supplier,),
+ ):
status = False
return status
+
def unauthorized_user(supplier):
status = check_supplier_has_docname_access(supplier) or False
if status == False:
frappe.throw(_("Not Permitted"), frappe.PermissionError)
+
def update_supplier_details(context):
supplier_doc = frappe.get_doc("Supplier", context.doc.supplier)
- context.doc.currency = supplier_doc.default_currency or frappe.get_cached_value('Company', context.doc.company, "default_currency")
- context.doc.currency_symbol = frappe.db.get_value("Currency", context.doc.currency, "symbol", cache=True)
- context.doc.number_format = frappe.db.get_value("Currency", context.doc.currency, "number_format", cache=True)
- context.doc.buying_price_list = supplier_doc.default_price_list or ''
+ context.doc.currency = supplier_doc.default_currency or frappe.get_cached_value(
+ "Company", context.doc.company, "default_currency"
+ )
+ context.doc.currency_symbol = frappe.db.get_value(
+ "Currency", context.doc.currency, "symbol", cache=True
+ )
+ context.doc.number_format = frappe.db.get_value(
+ "Currency", context.doc.currency, "number_format", cache=True
+ )
+ context.doc.buying_price_list = supplier_doc.default_price_list or ""
+
def get_link_quotation(supplier, rfq):
- quotation = frappe.db.sql(""" select distinct `tabSupplier Quotation Item`.parent as name,
+ quotation = frappe.db.sql(
+ """ select distinct `tabSupplier Quotation Item`.parent as name,
`tabSupplier Quotation`.status, `tabSupplier Quotation`.transaction_date from
`tabSupplier Quotation Item`, `tabSupplier Quotation` where `tabSupplier Quotation`.docstatus < 2 and
`tabSupplier Quotation Item`.request_for_quotation =%(name)s and
`tabSupplier Quotation Item`.parent = `tabSupplier Quotation`.name and
`tabSupplier Quotation`.supplier = %(supplier)s order by `tabSupplier Quotation`.creation desc""",
- {'name': rfq, 'supplier': supplier}, as_dict=1)
+ {"name": rfq, "supplier": supplier},
+ as_dict=1,
+ )
for data in quotation:
data.transaction_date = formatdate(data.transaction_date)
diff --git a/erpnext/templates/pages/search_help.py b/erpnext/templates/pages/search_help.py
index 1ef3942cbc9..a6877ce9abd 100644
--- a/erpnext/templates/pages/search_help.py
+++ b/erpnext/templates/pages/search_help.py
@@ -11,17 +11,18 @@ def get_context(context):
context.no_cache = 1
if frappe.form_dict.q:
query = str(utils.escape(sanitize_html(frappe.form_dict.q)))
- context.title = _('Help Results for')
+ context.title = _("Help Results for")
context.query = query
- context.route = '/search_help'
+ context.route = "/search_help"
d = frappe._dict()
d.results_sections = get_help_results_sections(query)
context.update(d)
else:
- context.title = _('Docs Search')
+ context.title = _("Docs Search")
-@frappe.whitelist(allow_guest = True)
+
+@frappe.whitelist(allow_guest=True)
def get_help_results_sections(text):
out = []
settings = frappe.get_doc("Support Settings", "Support Settings")
@@ -40,63 +41,72 @@ def get_help_results_sections(text):
if results:
# Add section
- out.append({
- "title": api.source_name,
- "results": results
- })
+ out.append({"title": api.source_name, "results": results})
return out
+
def get_response(api, text):
- response = requests.get(api.base_url + '/' + api.query_route, data={
- api.search_term_param_name: text
- })
+ response = requests.get(
+ api.base_url + "/" + api.query_route, data={api.search_term_param_name: text}
+ )
response.raise_for_status()
return response.json()
+
def get_topics_data(api, response_json):
if not response_json:
response_json = {}
- topics_data = {} # it will actually be an array
- key_list = api.response_result_key_path.split(',')
+ topics_data = {} # it will actually be an array
+ key_list = api.response_result_key_path.split(",")
for key in key_list:
topics_data = response_json.get(key) if not topics_data else topics_data.get(key)
return topics_data or []
+
def prepare_api_results(api, topics_data):
if not topics_data:
topics_data = []
results = []
for topic in topics_data:
- route = api.base_url + '/' + (api.post_route + '/' if api.post_route else "")
- for key in api.post_route_key_list.split(','):
+ route = api.base_url + "/" + (api.post_route + "/" if api.post_route else "")
+ for key in api.post_route_key_list.split(","):
route += str(topic[key])
- results.append(frappe._dict({
- 'title': topic[api.post_title_key],
- 'preview': html2text(topic[api.post_description_key]),
- 'route': route
- }))
+ results.append(
+ frappe._dict(
+ {
+ "title": topic[api.post_title_key],
+ "preview": html2text(topic[api.post_description_key]),
+ "route": route,
+ }
+ )
+ )
return results[:5]
+
def prepare_doctype_results(api, raw):
results = []
for r in raw:
prepared_result = {}
- parts = r["content"].split(' ||| ')
+ parts = r["content"].split(" ||| ")
for part in parts:
- pair = part.split(' : ', 1)
+ pair = part.split(" : ", 1)
prepared_result[pair[0]] = pair[1]
- results.append(frappe._dict({
- 'title': prepared_result[api.result_title_field],
- 'preview': prepared_result[api.result_preview_field],
- 'route': prepared_result[api.result_route_field]
- }))
+ results.append(
+ frappe._dict(
+ {
+ "title": prepared_result[api.result_title_field],
+ "preview": prepared_result[api.result_preview_field],
+ "route": prepared_result[api.result_route_field],
+ }
+ )
+ )
return results
diff --git a/erpnext/templates/pages/task_info.py b/erpnext/templates/pages/task_info.py
index d1a70e14c3b..66b775a9178 100644
--- a/erpnext/templates/pages/task_info.py
+++ b/erpnext/templates/pages/task_info.py
@@ -4,9 +4,12 @@ import frappe
def get_context(context):
context.no_cache = 1
- task = frappe.get_doc('Task', frappe.form_dict.task)
+ task = frappe.get_doc("Task", frappe.form_dict.task)
- context.comments = frappe.get_all('Communication', filters={'reference_name': task.name, 'comment_type': 'comment'},
- fields=['subject', 'sender_full_name', 'communication_date'])
+ context.comments = frappe.get_all(
+ "Communication",
+ filters={"reference_name": task.name, "comment_type": "comment"},
+ fields=["subject", "sender_full_name", "communication_date"],
+ )
context.doc = task
diff --git a/erpnext/templates/pages/timelog_info.py b/erpnext/templates/pages/timelog_info.py
index db61e7e44b5..3f0ec3738b2 100644
--- a/erpnext/templates/pages/timelog_info.py
+++ b/erpnext/templates/pages/timelog_info.py
@@ -4,6 +4,6 @@ import frappe
def get_context(context):
context.no_cache = 1
- timelog = frappe.get_doc('Time Log', frappe.form_dict.timelog)
+ timelog = frappe.get_doc("Time Log", frappe.form_dict.timelog)
context.doc = timelog
diff --git a/erpnext/templates/pages/wishlist.py b/erpnext/templates/pages/wishlist.py
index 72ee34e157e..d70f27c9d9d 100644
--- a/erpnext/templates/pages/wishlist.py
+++ b/erpnext/templates/pages/wishlist.py
@@ -23,31 +23,33 @@ def get_context(context):
context.settings = settings
context.no_cache = 1
+
def get_stock_availability(item_code, warehouse):
stock_qty = frappe.utils.flt(
- frappe.db.get_value("Bin",
- {
- "item_code": item_code,
- "warehouse": warehouse
- },
- "actual_qty")
+ frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "actual_qty")
)
return bool(stock_qty)
+
def get_wishlist_items():
if not frappe.db.exists("Wishlist", frappe.session.user):
return []
return frappe.db.get_all(
"Wishlist Item",
- filters={
- "parent": frappe.session.user
- },
+ filters={"parent": frappe.session.user},
fields=[
- "web_item_name", "item_code", "item_name",
- "website_item", "warehouse",
- "image", "item_group", "route"
- ])
+ "web_item_name",
+ "item_code",
+ "item_name",
+ "website_item",
+ "warehouse",
+ "image",
+ "item_group",
+ "route",
+ ],
+ )
+
def set_stock_price_details(items, settings, selling_price_list):
for item in items:
@@ -55,17 +57,15 @@ def set_stock_price_details(items, settings, selling_price_list):
item.available = get_stock_availability(item.item_code, item.get("warehouse"))
price_details = get_price(
- item.item_code,
- selling_price_list,
- settings.default_customer_group,
- settings.company
+ item.item_code, selling_price_list, settings.default_customer_group, settings.company
)
if price_details:
- item.formatted_price = price_details.get('formatted_price')
- item.formatted_mrp = price_details.get('formatted_mrp')
+ item.formatted_price = price_details.get("formatted_price")
+ item.formatted_mrp = price_details.get("formatted_mrp")
if item.formatted_mrp:
- item.discount = price_details.get('formatted_discount_percent') or \
- price_details.get('formatted_discount_rate')
+ item.discount = price_details.get("formatted_discount_percent") or price_details.get(
+ "formatted_discount_rate"
+ )
- return items
\ No newline at end of file
+ return items
diff --git a/erpnext/templates/utils.py b/erpnext/templates/utils.py
index 9f46e6a99ed..4295188dc0b 100644
--- a/erpnext/templates/utils.py
+++ b/erpnext/templates/utils.py
@@ -8,31 +8,35 @@ import frappe
@frappe.whitelist(allow_guest=True)
def send_message(subject="Website Query", message="", sender="", status="Open"):
from frappe.www.contact import send_message as website_send_message
+
lead = customer = None
website_send_message(subject, message, sender)
- customer = frappe.db.sql("""select distinct dl.link_name from `tabDynamic Link` dl
+ customer = frappe.db.sql(
+ """select distinct dl.link_name from `tabDynamic Link` dl
left join `tabContact` c on dl.parent=c.name where dl.link_doctype='Customer'
- and c.email_id = %s""", sender)
+ and c.email_id = %s""",
+ sender,
+ )
if not customer:
- lead = frappe.db.get_value('Lead', dict(email_id=sender))
+ lead = frappe.db.get_value("Lead", dict(email_id=sender))
if not lead:
- new_lead = frappe.get_doc(dict(
- doctype='Lead',
- email_id = sender,
- lead_name = sender.split('@')[0].title()
- )).insert(ignore_permissions=True)
+ new_lead = frappe.get_doc(
+ dict(doctype="Lead", email_id=sender, lead_name=sender.split("@")[0].title())
+ ).insert(ignore_permissions=True)
- opportunity = frappe.get_doc(dict(
- doctype ='Opportunity',
- opportunity_from = 'Customer' if customer else 'Lead',
- status = 'Open',
- title = subject,
- contact_email = sender,
- to_discuss = message
- ))
+ opportunity = frappe.get_doc(
+ dict(
+ doctype="Opportunity",
+ opportunity_from="Customer" if customer else "Lead",
+ status="Open",
+ title=subject,
+ contact_email=sender,
+ to_discuss=message,
+ )
+ )
if customer:
opportunity.party_name = customer[0][0]
@@ -43,15 +47,17 @@ def send_message(subject="Website Query", message="", sender="", status="Open"):
opportunity.insert(ignore_permissions=True)
- comm = frappe.get_doc({
- "doctype":"Communication",
- "subject": subject,
- "content": message,
- "sender": sender,
- "sent_or_received": "Received",
- 'reference_doctype': 'Opportunity',
- 'reference_name': opportunity.name
- })
+ comm = frappe.get_doc(
+ {
+ "doctype": "Communication",
+ "subject": subject,
+ "content": message,
+ "sender": sender,
+ "sent_or_received": "Received",
+ "reference_doctype": "Opportunity",
+ "reference_name": opportunity.name,
+ }
+ )
comm.insert(ignore_permissions=True)
return "okay"
diff --git a/erpnext/tests/__init__.py b/erpnext/tests/__init__.py
index a504340d409..dc37472c4c6 100644
--- a/erpnext/tests/__init__.py
+++ b/erpnext/tests/__init__.py
@@ -1 +1 @@
-global_test_dependencies = ['User', 'Company', 'Item']
+global_test_dependencies = ["User", "Company", "Item"]
diff --git a/erpnext/tests/test_init.py b/erpnext/tests/test_init.py
index 61849726efe..6fbfbf46899 100644
--- a/erpnext/tests/test_init.py
+++ b/erpnext/tests/test_init.py
@@ -4,7 +4,8 @@ import frappe
from erpnext import encode_company_abbr
-test_records = frappe.get_test_records('Company')
+test_records = frappe.get_test_records("Company")
+
class TestInit(unittest.TestCase):
def test_encode_company_abbr(self):
@@ -12,23 +13,30 @@ class TestInit(unittest.TestCase):
abbr = "NFECT"
names = [
- "Warehouse Name", "ERPNext Foundation India", "Gold - Member - {a}".format(a=abbr),
- " - {a}".format(a=abbr), "ERPNext - Foundation - India",
+ "Warehouse Name",
+ "ERPNext Foundation India",
+ "Gold - Member - {a}".format(a=abbr),
+ " - {a}".format(a=abbr),
+ "ERPNext - Foundation - India",
"ERPNext Foundation India - {a}".format(a=abbr),
- "No-Space-{a}".format(a=abbr), "- Warehouse"
+ "No-Space-{a}".format(a=abbr),
+ "- Warehouse",
]
expected_names = [
- "Warehouse Name - {a}".format(a=abbr), "ERPNext Foundation India - {a}".format(a=abbr),
- "Gold - Member - {a}".format(a=abbr), " - {a}".format(a=abbr),
+ "Warehouse Name - {a}".format(a=abbr),
+ "ERPNext Foundation India - {a}".format(a=abbr),
+ "Gold - Member - {a}".format(a=abbr),
+ " - {a}".format(a=abbr),
"ERPNext - Foundation - India - {a}".format(a=abbr),
- "ERPNext Foundation India - {a}".format(a=abbr), "No-Space-{a} - {a}".format(a=abbr),
- "- Warehouse - {a}".format(a=abbr)
+ "ERPNext Foundation India - {a}".format(a=abbr),
+ "No-Space-{a} - {a}".format(a=abbr),
+ "- Warehouse - {a}".format(a=abbr),
]
for i in range(len(names)):
enc_name = encode_company_abbr(names[i], abbr=abbr)
self.assertTrue(
enc_name == expected_names[i],
- "{enc} is not same as {exp}".format(enc=enc_name, exp=expected_names[i])
+ "{enc} is not same as {exp}".format(enc=enc_name, exp=expected_names[i]),
)
diff --git a/erpnext/tests/test_notifications.py b/erpnext/tests/test_notifications.py
index 669bf6f3d92..0f391956309 100644
--- a/erpnext/tests/test_notifications.py
+++ b/erpnext/tests/test_notifications.py
@@ -10,9 +10,9 @@ from frappe.desk import notifications
class TestNotifications(unittest.TestCase):
def test_get_notifications_for_targets(self):
- '''
- Test notification config entries for targets as percentages
- '''
+ """
+ Test notification config entries for targets as percentages
+ """
company = frappe.get_all("Company")[0]
frappe.db.set_value("Company", company.name, "monthly_sales_target", 10000)
@@ -21,7 +21,7 @@ class TestNotifications(unittest.TestCase):
config = notifications.get_notification_config()
doc_target_percents = notifications.get_notifications_for_targets(config, {})
- self.assertEqual(doc_target_percents['Company'][company.name], 10)
+ self.assertEqual(doc_target_percents["Company"][company.name], 10)
frappe.db.set_value("Company", company.name, "monthly_sales_target", 2000)
frappe.db.set_value("Company", company.name, "total_monthly_sales", 0)
@@ -29,4 +29,4 @@ class TestNotifications(unittest.TestCase):
config = notifications.get_notification_config()
doc_target_percents = notifications.get_notifications_for_targets(config, {})
- self.assertEqual(doc_target_percents['Company'][company.name], 0)
+ self.assertEqual(doc_target_percents["Company"][company.name], 0)
diff --git a/erpnext/tests/test_point_of_sale.py b/erpnext/tests/test_point_of_sale.py
index 38f2c16d939..7267d4a1d5e 100644
--- a/erpnext/tests/test_point_of_sale.py
+++ b/erpnext/tests/test_point_of_sale.py
@@ -14,11 +14,11 @@ from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
class TestPointOfSale(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
- frappe.db.savepoint('before_test_point_of_sale')
+ frappe.db.savepoint("before_test_point_of_sale")
@classmethod
def tearDownClass(cls) -> None:
- frappe.db.rollback(save_point='before_test_point_of_sale')
+ frappe.db.rollback(save_point="before_test_point_of_sale")
def test_item_search(self):
"""
diff --git a/erpnext/tests/test_regional.py b/erpnext/tests/test_regional.py
index 10d62ce69f2..abeecee4e74 100644
--- a/erpnext/tests/test_regional.py
+++ b/erpnext/tests/test_regional.py
@@ -7,15 +7,16 @@ import erpnext
@erpnext.allow_regional
def test_method():
- return 'original'
+ return "original"
+
class TestInit(unittest.TestCase):
def test_regional_overrides(self):
- frappe.flags.country = 'India'
- self.assertEqual(test_method(), 'overridden')
+ frappe.flags.country = "India"
+ self.assertEqual(test_method(), "overridden")
- frappe.flags.country = 'Maldives'
- self.assertEqual(test_method(), 'original')
+ frappe.flags.country = "Maldives"
+ self.assertEqual(test_method(), "original")
- frappe.flags.country = 'France'
- self.assertEqual(test_method(), 'overridden')
+ frappe.flags.country = "France"
+ self.assertEqual(test_method(), "overridden")
diff --git a/erpnext/tests/test_search.py b/erpnext/tests/test_search.py
index c169458b8ca..ffe9a5ae541 100644
--- a/erpnext/tests/test_search.py
+++ b/erpnext/tests/test_search.py
@@ -8,12 +8,11 @@ class TestSearch(unittest.TestCase):
# Search for the word "cond", part of the word "conduire" (Lead) in french.
def test_contact_search_in_foreign_language(self):
try:
- frappe.local.lang = 'fr'
- output = filter_dynamic_link_doctypes("DocType", "cond", "name", 0, 20, {
- 'fieldtype': 'HTML',
- 'fieldname': 'contact_html'
- })
- result = [['found' for x in y if x=="Lead"] for y in output]
- self.assertTrue(['found'] in result)
+ frappe.local.lang = "fr"
+ output = filter_dynamic_link_doctypes(
+ "DocType", "cond", "name", 0, 20, {"fieldtype": "HTML", "fieldname": "contact_html"}
+ )
+ result = [["found" for x in y if x == "Lead"] for y in output]
+ self.assertTrue(["found"] in result)
finally:
- frappe.local.lang = 'en'
+ frappe.local.lang = "en"
diff --git a/erpnext/tests/test_subcontracting.py b/erpnext/tests/test_subcontracting.py
index fccfd0d1cf7..bf12181c527 100644
--- a/erpnext/tests/test_subcontracting.py
+++ b/erpnext/tests/test_subcontracting.py
@@ -25,35 +25,43 @@ class TestSubcontracting(unittest.TestCase):
make_bom_for_subcontracted_items()
def test_po_with_bom(self):
- '''
- - Set backflush based on BOM
- - Create subcontracted PO for the item Subcontracted Item SA1 and add same item two times.
- - Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
- - Create purchase receipt against the PO and check serial nos and batch no.
- '''
+ """
+ - Set backflush based on BOM
+ - Create subcontracted PO for the item Subcontracted Item SA1 and add same item two times.
+ - Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
+ - Create purchase receipt against the PO and check serial nos and batch no.
+ """
- set_backflush_based_on('BOM')
- item_code = 'Subcontracted Item SA1'
- items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 5, 'rate': 100},
- {'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 6, 'rate': 100}]
+ set_backflush_based_on("BOM")
+ item_code = "Subcontracted Item SA1"
+ items = [
+ {"warehouse": "_Test Warehouse - _TC", "item_code": item_code, "qty": 5, "rate": 100},
+ {"warehouse": "_Test Warehouse - _TC", "item_code": item_code, "qty": 6, "rate": 100},
+ ]
- rm_items = [{'item_code': 'Subcontracted SRM Item 1', 'qty': 5},
- {'item_code': 'Subcontracted SRM Item 2', 'qty': 5},
- {'item_code': 'Subcontracted SRM Item 3', 'qty': 5},
- {'item_code': 'Subcontracted SRM Item 1', 'qty': 6},
- {'item_code': 'Subcontracted SRM Item 2', 'qty': 6},
- {'item_code': 'Subcontracted SRM Item 3', 'qty': 6}
+ rm_items = [
+ {"item_code": "Subcontracted SRM Item 1", "qty": 5},
+ {"item_code": "Subcontracted SRM Item 2", "qty": 5},
+ {"item_code": "Subcontracted SRM Item 3", "qty": 5},
+ {"item_code": "Subcontracted SRM Item 1", "qty": 6},
+ {"item_code": "Subcontracted SRM Item 2", "qty": 6},
+ {"item_code": "Subcontracted SRM Item 3", "qty": 6},
]
itemwise_details = make_stock_in_entry(rm_items=rm_items)
- po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
- supplier_warehouse="_Test Warehouse 1 - _TC")
+ po = create_purchase_order(
+ rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC"
+ )
for d in rm_items:
- d['po_detail'] = po.items[0].name if d.get('qty') == 5 else po.items[1].name
+ d["po_detail"] = po.items[0].name if d.get("qty") == 5 else po.items[1].name
- make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
- rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+ make_stock_transfer_entry(
+ po_no=po.name,
+ main_item_code=item_code,
+ rm_items=rm_items,
+ itemwise_details=copy.deepcopy(itemwise_details),
+ )
pr1 = make_purchase_receipt(po.name)
pr1.submit()
@@ -61,43 +69,58 @@ class TestSubcontracting(unittest.TestCase):
for key, value in get_supplied_items(pr1).items():
transferred_detais = itemwise_details.get(key)
- for field in ['qty', 'serial_no', 'batch_no']:
+ for field in ["qty", "serial_no", "batch_no"]:
if value.get(field):
transfer, consumed = (transferred_detais.get(field), value.get(field))
- if field == 'serial_no':
+ if field == "serial_no":
transfer, consumed = (sorted(transfer), sorted(consumed))
self.assertEqual(transfer, consumed)
def test_po_with_material_transfer(self):
- '''
- - Set backflush based on Material Transfer
- - Create subcontracted PO for the item Subcontracted Item SA1 and Subcontracted Item SA5.
- - Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
- - Transfer extra item Subcontracted SRM Item 4 for the subcontract item Subcontracted Item SA5.
- - Create partial purchase receipt against the PO and check serial nos and batch no.
- '''
+ """
+ - Set backflush based on Material Transfer
+ - Create subcontracted PO for the item Subcontracted Item SA1 and Subcontracted Item SA5.
+ - Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
+ - Transfer extra item Subcontracted SRM Item 4 for the subcontract item Subcontracted Item SA5.
+ - Create partial purchase receipt against the PO and check serial nos and batch no.
+ """
- set_backflush_based_on('Material Transferred for Subcontract')
- items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': 'Subcontracted Item SA1', 'qty': 5, 'rate': 100},
- {'warehouse': '_Test Warehouse - _TC', 'item_code': 'Subcontracted Item SA5', 'qty': 6, 'rate': 100}]
+ set_backflush_based_on("Material Transferred for Subcontract")
+ items = [
+ {
+ "warehouse": "_Test Warehouse - _TC",
+ "item_code": "Subcontracted Item SA1",
+ "qty": 5,
+ "rate": 100,
+ },
+ {
+ "warehouse": "_Test Warehouse - _TC",
+ "item_code": "Subcontracted Item SA5",
+ "qty": 6,
+ "rate": 100,
+ },
+ ]
- rm_items = [{'item_code': 'Subcontracted SRM Item 1', 'qty': 5, 'main_item_code': 'Subcontracted Item SA1'},
- {'item_code': 'Subcontracted SRM Item 2', 'qty': 5, 'main_item_code': 'Subcontracted Item SA1'},
- {'item_code': 'Subcontracted SRM Item 3', 'qty': 5, 'main_item_code': 'Subcontracted Item SA1'},
- {'item_code': 'Subcontracted SRM Item 5', 'qty': 6, 'main_item_code': 'Subcontracted Item SA5'},
- {'item_code': 'Subcontracted SRM Item 4', 'qty': 6, 'main_item_code': 'Subcontracted Item SA5'}
+ rm_items = [
+ {"item_code": "Subcontracted SRM Item 1", "qty": 5, "main_item_code": "Subcontracted Item SA1"},
+ {"item_code": "Subcontracted SRM Item 2", "qty": 5, "main_item_code": "Subcontracted Item SA1"},
+ {"item_code": "Subcontracted SRM Item 3", "qty": 5, "main_item_code": "Subcontracted Item SA1"},
+ {"item_code": "Subcontracted SRM Item 5", "qty": 6, "main_item_code": "Subcontracted Item SA5"},
+ {"item_code": "Subcontracted SRM Item 4", "qty": 6, "main_item_code": "Subcontracted Item SA5"},
]
itemwise_details = make_stock_in_entry(rm_items=rm_items)
- po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
- supplier_warehouse="_Test Warehouse 1 - _TC")
+ po = create_purchase_order(
+ rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC"
+ )
for d in rm_items:
- d['po_detail'] = po.items[0].name if d.get('qty') == 5 else po.items[1].name
+ d["po_detail"] = po.items[0].name if d.get("qty") == 5 else po.items[1].name
- make_stock_transfer_entry(po_no = po.name,
- rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+ make_stock_transfer_entry(
+ po_no=po.name, rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details)
+ )
pr1 = make_purchase_receipt(po.name)
pr1.remove(pr1.items[1])
@@ -106,7 +129,7 @@ class TestSubcontracting(unittest.TestCase):
for key, value in get_supplied_items(pr1).items():
transferred_detais = itemwise_details.get(key)
- for field in ['qty', 'serial_no', 'batch_no']:
+ for field in ["qty", "serial_no", "batch_no"]:
if value.get(field):
self.assertEqual(value.get(field), transferred_detais.get(field))
@@ -116,36 +139,51 @@ class TestSubcontracting(unittest.TestCase):
for key, value in get_supplied_items(pr2).items():
transferred_detais = itemwise_details.get(key)
- for field in ['qty', 'serial_no', 'batch_no']:
+ for field in ["qty", "serial_no", "batch_no"]:
if value.get(field):
self.assertEqual(value.get(field), transferred_detais.get(field))
def test_subcontract_with_same_components_different_fg(self):
- '''
- - Set backflush based on Material Transfer
- - Create subcontracted PO for the item Subcontracted Item SA2 and Subcontracted Item SA3.
- - Transfer the components from Stores to Supplier warehouse with serial nos.
- - Transfer extra qty of components for the item Subcontracted Item SA2.
- - Create partial purchase receipt against the PO and check serial nos and batch no.
- '''
+ """
+ - Set backflush based on Material Transfer
+ - Create subcontracted PO for the item Subcontracted Item SA2 and Subcontracted Item SA3.
+ - Transfer the components from Stores to Supplier warehouse with serial nos.
+ - Transfer extra qty of components for the item Subcontracted Item SA2.
+ - Create partial purchase receipt against the PO and check serial nos and batch no.
+ """
- set_backflush_based_on('Material Transferred for Subcontract')
- items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': 'Subcontracted Item SA2', 'qty': 5, 'rate': 100},
- {'warehouse': '_Test Warehouse - _TC', 'item_code': 'Subcontracted Item SA3', 'qty': 6, 'rate': 100}]
+ set_backflush_based_on("Material Transferred for Subcontract")
+ items = [
+ {
+ "warehouse": "_Test Warehouse - _TC",
+ "item_code": "Subcontracted Item SA2",
+ "qty": 5,
+ "rate": 100,
+ },
+ {
+ "warehouse": "_Test Warehouse - _TC",
+ "item_code": "Subcontracted Item SA3",
+ "qty": 6,
+ "rate": 100,
+ },
+ ]
- rm_items = [{'item_code': 'Subcontracted SRM Item 2', 'qty': 6, 'main_item_code': 'Subcontracted Item SA2'},
- {'item_code': 'Subcontracted SRM Item 2', 'qty': 6, 'main_item_code': 'Subcontracted Item SA3'}
+ rm_items = [
+ {"item_code": "Subcontracted SRM Item 2", "qty": 6, "main_item_code": "Subcontracted Item SA2"},
+ {"item_code": "Subcontracted SRM Item 2", "qty": 6, "main_item_code": "Subcontracted Item SA3"},
]
itemwise_details = make_stock_in_entry(rm_items=rm_items)
- po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
- supplier_warehouse="_Test Warehouse 1 - _TC")
+ po = create_purchase_order(
+ rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC"
+ )
for d in rm_items:
- d['po_detail'] = po.items[0].name if d.get('qty') == 5 else po.items[1].name
+ d["po_detail"] = po.items[0].name if d.get("qty") == 5 else po.items[1].name
- make_stock_transfer_entry(po_no = po.name,
- rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+ make_stock_transfer_entry(
+ po_no=po.name, rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details)
+ )
pr1 = make_purchase_receipt(po.name)
pr1.items[0].qty = 3
@@ -155,7 +193,7 @@ class TestSubcontracting(unittest.TestCase):
for key, value in get_supplied_items(pr1).items():
transferred_detais = itemwise_details.get(key)
self.assertEqual(value.qty, 4)
- self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get('serial_no')[0:4]))
+ self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[0:4]))
pr2 = make_purchase_receipt(po.name)
pr2.items[0].qty = 2
@@ -166,7 +204,7 @@ class TestSubcontracting(unittest.TestCase):
transferred_detais = itemwise_details.get(key)
self.assertEqual(value.qty, 2)
- self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get('serial_no')[4:6]))
+ self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[4:6]))
pr3 = make_purchase_receipt(po.name)
pr3.submit()
@@ -174,85 +212,104 @@ class TestSubcontracting(unittest.TestCase):
transferred_detais = itemwise_details.get(key)
self.assertEqual(value.qty, 6)
- self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get('serial_no')[6:12]))
+ self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[6:12]))
def test_return_non_consumed_materials(self):
- '''
- - Set backflush based on Material Transfer
- - Create subcontracted PO for the item Subcontracted Item SA2.
- - Transfer the components from Stores to Supplier warehouse with serial nos.
- - Transfer extra qty of component for the subcontracted item Subcontracted Item SA2.
- - Create purchase receipt for full qty against the PO and change the qty of raw material.
- - After that return the non consumed material back to the store from supplier's warehouse.
- '''
+ """
+ - Set backflush based on Material Transfer
+ - Create subcontracted PO for the item Subcontracted Item SA2.
+ - Transfer the components from Stores to Supplier warehouse with serial nos.
+ - Transfer extra qty of component for the subcontracted item Subcontracted Item SA2.
+ - Create purchase receipt for full qty against the PO and change the qty of raw material.
+ - After that return the non consumed material back to the store from supplier's warehouse.
+ """
- set_backflush_based_on('Material Transferred for Subcontract')
- items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': 'Subcontracted Item SA2', 'qty': 5, 'rate': 100}]
- rm_items = [{'item_code': 'Subcontracted SRM Item 2', 'qty': 6, 'main_item_code': 'Subcontracted Item SA2'}]
+ set_backflush_based_on("Material Transferred for Subcontract")
+ items = [
+ {
+ "warehouse": "_Test Warehouse - _TC",
+ "item_code": "Subcontracted Item SA2",
+ "qty": 5,
+ "rate": 100,
+ }
+ ]
+ rm_items = [
+ {"item_code": "Subcontracted SRM Item 2", "qty": 6, "main_item_code": "Subcontracted Item SA2"}
+ ]
itemwise_details = make_stock_in_entry(rm_items=rm_items)
- po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
- supplier_warehouse="_Test Warehouse 1 - _TC")
+ po = create_purchase_order(
+ rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC"
+ )
for d in rm_items:
- d['po_detail'] = po.items[0].name
+ d["po_detail"] = po.items[0].name
- make_stock_transfer_entry(po_no = po.name,
- rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+ make_stock_transfer_entry(
+ po_no=po.name, rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details)
+ )
pr1 = make_purchase_receipt(po.name)
pr1.save()
pr1.supplied_items[0].consumed_qty = 5
- pr1.supplied_items[0].serial_no = '\n'.join(sorted(
- itemwise_details.get('Subcontracted SRM Item 2').get('serial_no')[0:5]
- ))
+ pr1.supplied_items[0].serial_no = "\n".join(
+ sorted(itemwise_details.get("Subcontracted SRM Item 2").get("serial_no")[0:5])
+ )
pr1.submit()
for key, value in get_supplied_items(pr1).items():
transferred_detais = itemwise_details.get(key)
self.assertEqual(value.qty, 5)
- self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get('serial_no')[0:5]))
+ self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[0:5]))
po.load_from_db()
self.assertEqual(po.supplied_items[0].consumed_qty, 5)
doc = get_materials_from_supplier(po.name, [d.name for d in po.supplied_items])
self.assertEqual(doc.items[0].qty, 1)
- self.assertEqual(doc.items[0].s_warehouse, '_Test Warehouse 1 - _TC')
- self.assertEqual(doc.items[0].t_warehouse, '_Test Warehouse - _TC')
- self.assertEqual(get_serial_nos(doc.items[0].serial_no),
- itemwise_details.get(doc.items[0].item_code)['serial_no'][5:6])
+ self.assertEqual(doc.items[0].s_warehouse, "_Test Warehouse 1 - _TC")
+ self.assertEqual(doc.items[0].t_warehouse, "_Test Warehouse - _TC")
+ self.assertEqual(
+ get_serial_nos(doc.items[0].serial_no),
+ itemwise_details.get(doc.items[0].item_code)["serial_no"][5:6],
+ )
def test_item_with_batch_based_on_bom(self):
- '''
- - Set backflush based on BOM
- - Create subcontracted PO for the item Subcontracted Item SA4 (has batch no).
- - Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
- - Transfer the components in multiple batches.
- - Create the 3 purchase receipt against the PO and split Subcontracted Items into two batches.
- - Keep the qty as 2 for Subcontracted Item in the purchase receipt.
- '''
+ """
+ - Set backflush based on BOM
+ - Create subcontracted PO for the item Subcontracted Item SA4 (has batch no).
+ - Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
+ - Transfer the components in multiple batches.
+ - Create the 3 purchase receipt against the PO and split Subcontracted Items into two batches.
+ - Keep the qty as 2 for Subcontracted Item in the purchase receipt.
+ """
- set_backflush_based_on('BOM')
- item_code = 'Subcontracted Item SA4'
- items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
+ set_backflush_based_on("BOM")
+ item_code = "Subcontracted Item SA4"
+ items = [{"warehouse": "_Test Warehouse - _TC", "item_code": item_code, "qty": 10, "rate": 100}]
- rm_items = [{'item_code': 'Subcontracted SRM Item 1', 'qty': 10},
- {'item_code': 'Subcontracted SRM Item 2', 'qty': 10},
- {'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
- {'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
- {'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
- {'item_code': 'Subcontracted SRM Item 3', 'qty': 1}
+ rm_items = [
+ {"item_code": "Subcontracted SRM Item 1", "qty": 10},
+ {"item_code": "Subcontracted SRM Item 2", "qty": 10},
+ {"item_code": "Subcontracted SRM Item 3", "qty": 3},
+ {"item_code": "Subcontracted SRM Item 3", "qty": 3},
+ {"item_code": "Subcontracted SRM Item 3", "qty": 3},
+ {"item_code": "Subcontracted SRM Item 3", "qty": 1},
]
itemwise_details = make_stock_in_entry(rm_items=rm_items)
- po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
- supplier_warehouse="_Test Warehouse 1 - _TC")
+ po = create_purchase_order(
+ rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC"
+ )
for d in rm_items:
- d['po_detail'] = po.items[0].name
+ d["po_detail"] = po.items[0].name
- make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
- rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+ make_stock_transfer_entry(
+ po_no=po.name,
+ main_item_code=item_code,
+ rm_items=rm_items,
+ itemwise_details=copy.deepcopy(itemwise_details),
+ )
pr1 = make_purchase_receipt(po.name)
pr1.items[0].qty = 2
@@ -281,37 +338,43 @@ class TestSubcontracting(unittest.TestCase):
self.assertEqual(value.qty, 2)
def test_item_with_batch_based_on_material_transfer(self):
- '''
- - Set backflush based on Material Transferred for Subcontract
- - Create subcontracted PO for the item Subcontracted Item SA4 (has batch no).
- - Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
- - Transfer the components in multiple batches with extra 2 qty for the batched item.
- - Create the 3 purchase receipt against the PO and split Subcontracted Items into two batches.
- - Keep the qty as 2 for Subcontracted Item in the purchase receipt.
- - In the first purchase receipt the batched raw materials will be consumed 2 extra qty.
- '''
+ """
+ - Set backflush based on Material Transferred for Subcontract
+ - Create subcontracted PO for the item Subcontracted Item SA4 (has batch no).
+ - Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
+ - Transfer the components in multiple batches with extra 2 qty for the batched item.
+ - Create the 3 purchase receipt against the PO and split Subcontracted Items into two batches.
+ - Keep the qty as 2 for Subcontracted Item in the purchase receipt.
+ - In the first purchase receipt the batched raw materials will be consumed 2 extra qty.
+ """
- set_backflush_based_on('Material Transferred for Subcontract')
- item_code = 'Subcontracted Item SA4'
- items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
+ set_backflush_based_on("Material Transferred for Subcontract")
+ item_code = "Subcontracted Item SA4"
+ items = [{"warehouse": "_Test Warehouse - _TC", "item_code": item_code, "qty": 10, "rate": 100}]
- rm_items = [{'item_code': 'Subcontracted SRM Item 1', 'qty': 10},
- {'item_code': 'Subcontracted SRM Item 2', 'qty': 10},
- {'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
- {'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
- {'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
- {'item_code': 'Subcontracted SRM Item 3', 'qty': 3}
+ rm_items = [
+ {"item_code": "Subcontracted SRM Item 1", "qty": 10},
+ {"item_code": "Subcontracted SRM Item 2", "qty": 10},
+ {"item_code": "Subcontracted SRM Item 3", "qty": 3},
+ {"item_code": "Subcontracted SRM Item 3", "qty": 3},
+ {"item_code": "Subcontracted SRM Item 3", "qty": 3},
+ {"item_code": "Subcontracted SRM Item 3", "qty": 3},
]
itemwise_details = make_stock_in_entry(rm_items=rm_items)
- po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
- supplier_warehouse="_Test Warehouse 1 - _TC")
+ po = create_purchase_order(
+ rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC"
+ )
for d in rm_items:
- d['po_detail'] = po.items[0].name
+ d["po_detail"] = po.items[0].name
- make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
- rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+ make_stock_transfer_entry(
+ po_no=po.name,
+ main_item_code=item_code,
+ rm_items=rm_items,
+ itemwise_details=copy.deepcopy(itemwise_details),
+ )
pr1 = make_purchase_receipt(po.name)
pr1.items[0].qty = 2
@@ -320,7 +383,7 @@ class TestSubcontracting(unittest.TestCase):
pr1.submit()
for key, value in get_supplied_items(pr1).items():
- qty = 4 if key != 'Subcontracted SRM Item 3' else 6
+ qty = 4 if key != "Subcontracted SRM Item 3" else 6
self.assertEqual(value.qty, qty)
pr1 = make_purchase_receipt(po.name)
@@ -341,30 +404,35 @@ class TestSubcontracting(unittest.TestCase):
self.assertEqual(value.qty, 2)
def test_partial_transfer_serial_no_components_based_on_material_transfer(self):
- '''
- - Set backflush based on Material Transferred for Subcontract
- - Create subcontracted PO for the item Subcontracted Item SA2.
- - Transfer the partial components from Stores to Supplier warehouse with serial nos.
- - Create partial purchase receipt against the PO and change the qty manually.
- - Transfer the remaining components from Stores to Supplier warehouse with serial nos.
- - Create purchase receipt for remaining qty against the PO and change the qty manually.
- '''
+ """
+ - Set backflush based on Material Transferred for Subcontract
+ - Create subcontracted PO for the item Subcontracted Item SA2.
+ - Transfer the partial components from Stores to Supplier warehouse with serial nos.
+ - Create partial purchase receipt against the PO and change the qty manually.
+ - Transfer the remaining components from Stores to Supplier warehouse with serial nos.
+ - Create purchase receipt for remaining qty against the PO and change the qty manually.
+ """
- set_backflush_based_on('Material Transferred for Subcontract')
- item_code = 'Subcontracted Item SA2'
- items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
+ set_backflush_based_on("Material Transferred for Subcontract")
+ item_code = "Subcontracted Item SA2"
+ items = [{"warehouse": "_Test Warehouse - _TC", "item_code": item_code, "qty": 10, "rate": 100}]
- rm_items = [{'item_code': 'Subcontracted SRM Item 2', 'qty': 5}]
+ rm_items = [{"item_code": "Subcontracted SRM Item 2", "qty": 5}]
itemwise_details = make_stock_in_entry(rm_items=rm_items)
- po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
- supplier_warehouse="_Test Warehouse 1 - _TC")
+ po = create_purchase_order(
+ rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC"
+ )
for d in rm_items:
- d['po_detail'] = po.items[0].name
+ d["po_detail"] = po.items[0].name
- make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
- rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+ make_stock_transfer_entry(
+ po_no=po.name,
+ main_item_code=item_code,
+ rm_items=rm_items,
+ itemwise_details=copy.deepcopy(itemwise_details),
+ )
pr1 = make_purchase_receipt(po.name)
pr1.items[0].qty = 5
@@ -377,7 +445,9 @@ class TestSubcontracting(unittest.TestCase):
pr1.load_from_db()
pr1.supplied_items[0].consumed_qty = 5
- pr1.supplied_items[0].serial_no = '\n'.join(itemwise_details[pr1.supplied_items[0].rm_item_code]['serial_no'])
+ pr1.supplied_items[0].serial_no = "\n".join(
+ itemwise_details[pr1.supplied_items[0].rm_item_code]["serial_no"]
+ )
pr1.save()
pr1.submit()
@@ -388,10 +458,14 @@ class TestSubcontracting(unittest.TestCase):
itemwise_details = make_stock_in_entry(rm_items=rm_items)
for d in rm_items:
- d['po_detail'] = po.items[0].name
+ d["po_detail"] = po.items[0].name
- make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
- rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+ make_stock_transfer_entry(
+ po_no=po.name,
+ main_item_code=item_code,
+ rm_items=rm_items,
+ itemwise_details=copy.deepcopy(itemwise_details),
+ )
pr1 = make_purchase_receipt(po.name)
pr1.submit()
@@ -402,67 +476,77 @@ class TestSubcontracting(unittest.TestCase):
self.assertEqual(sorted(value.serial_no), sorted(details.serial_no))
def test_incorrect_serial_no_components_based_on_material_transfer(self):
- '''
- - Set backflush based on Material Transferred for Subcontract
- - Create subcontracted PO for the item Subcontracted Item SA2.
- - Transfer the serialized componenets to the supplier.
- - Create purchase receipt and change the serial no which is not transferred.
- - System should throw the error and not allowed to save the purchase receipt.
- '''
+ """
+ - Set backflush based on Material Transferred for Subcontract
+ - Create subcontracted PO for the item Subcontracted Item SA2.
+ - Transfer the serialized componenets to the supplier.
+ - Create purchase receipt and change the serial no which is not transferred.
+ - System should throw the error and not allowed to save the purchase receipt.
+ """
- set_backflush_based_on('Material Transferred for Subcontract')
- item_code = 'Subcontracted Item SA2'
- items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
+ set_backflush_based_on("Material Transferred for Subcontract")
+ item_code = "Subcontracted Item SA2"
+ items = [{"warehouse": "_Test Warehouse - _TC", "item_code": item_code, "qty": 10, "rate": 100}]
- rm_items = [{'item_code': 'Subcontracted SRM Item 2', 'qty': 10}]
+ rm_items = [{"item_code": "Subcontracted SRM Item 2", "qty": 10}]
itemwise_details = make_stock_in_entry(rm_items=rm_items)
- po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
- supplier_warehouse="_Test Warehouse 1 - _TC")
+ po = create_purchase_order(
+ rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC"
+ )
for d in rm_items:
- d['po_detail'] = po.items[0].name
+ d["po_detail"] = po.items[0].name
- make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
- rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+ make_stock_transfer_entry(
+ po_no=po.name,
+ main_item_code=item_code,
+ rm_items=rm_items,
+ itemwise_details=copy.deepcopy(itemwise_details),
+ )
pr1 = make_purchase_receipt(po.name)
pr1.save()
- pr1.supplied_items[0].serial_no = 'ABCD'
+ pr1.supplied_items[0].serial_no = "ABCD"
self.assertRaises(frappe.ValidationError, pr1.save)
pr1.delete()
def test_partial_transfer_batch_based_on_material_transfer(self):
- '''
- - Set backflush based on Material Transferred for Subcontract
- - Create subcontracted PO for the item Subcontracted Item SA6.
- - Transfer the partial components from Stores to Supplier warehouse with batch.
- - Create partial purchase receipt against the PO and change the qty manually.
- - Transfer the remaining components from Stores to Supplier warehouse with batch.
- - Create purchase receipt for remaining qty against the PO and change the qty manually.
- '''
+ """
+ - Set backflush based on Material Transferred for Subcontract
+ - Create subcontracted PO for the item Subcontracted Item SA6.
+ - Transfer the partial components from Stores to Supplier warehouse with batch.
+ - Create partial purchase receipt against the PO and change the qty manually.
+ - Transfer the remaining components from Stores to Supplier warehouse with batch.
+ - Create purchase receipt for remaining qty against the PO and change the qty manually.
+ """
- set_backflush_based_on('Material Transferred for Subcontract')
- item_code = 'Subcontracted Item SA6'
- items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
+ set_backflush_based_on("Material Transferred for Subcontract")
+ item_code = "Subcontracted Item SA6"
+ items = [{"warehouse": "_Test Warehouse - _TC", "item_code": item_code, "qty": 10, "rate": 100}]
- rm_items = [{'item_code': 'Subcontracted SRM Item 3', 'qty': 5}]
+ rm_items = [{"item_code": "Subcontracted SRM Item 3", "qty": 5}]
itemwise_details = make_stock_in_entry(rm_items=rm_items)
- po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
- supplier_warehouse="_Test Warehouse 1 - _TC")
+ po = create_purchase_order(
+ rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC"
+ )
for d in rm_items:
- d['po_detail'] = po.items[0].name
+ d["po_detail"] = po.items[0].name
- make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
- rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+ make_stock_transfer_entry(
+ po_no=po.name,
+ main_item_code=item_code,
+ rm_items=rm_items,
+ itemwise_details=copy.deepcopy(itemwise_details),
+ )
pr1 = make_purchase_receipt(po.name)
pr1.items[0].qty = 5
pr1.save()
- transferred_batch_no = ''
+ transferred_batch_no = ""
for key, value in get_supplied_items(pr1).items():
details = itemwise_details.get(key)
self.assertEqual(value.qty, 3)
@@ -482,10 +566,14 @@ class TestSubcontracting(unittest.TestCase):
itemwise_details = make_stock_in_entry(rm_items=rm_items)
for d in rm_items:
- d['po_detail'] = po.items[0].name
+ d["po_detail"] = po.items[0].name
- make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
- rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+ make_stock_transfer_entry(
+ po_no=po.name,
+ main_item_code=item_code,
+ rm_items=rm_items,
+ itemwise_details=copy.deepcopy(itemwise_details),
+ )
pr1 = make_purchase_receipt(po.name)
pr1.submit()
@@ -495,55 +583,60 @@ class TestSubcontracting(unittest.TestCase):
self.assertEqual(value.qty, details.qty)
self.assertEqual(value.batch_no, details.batch_no)
-
def test_item_with_batch_based_on_material_transfer_for_purchase_invoice(self):
- '''
- - Set backflush based on Material Transferred for Subcontract
- - Create subcontracted PO for the item Subcontracted Item SA4 (has batch no).
- - Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
- - Transfer the components in multiple batches with extra 2 qty for the batched item.
- - Create the 3 purchase receipt against the PO and split Subcontracted Items into two batches.
- - Keep the qty as 2 for Subcontracted Item in the purchase receipt.
- - In the first purchase receipt the batched raw materials will be consumed 2 extra qty.
- '''
+ """
+ - Set backflush based on Material Transferred for Subcontract
+ - Create subcontracted PO for the item Subcontracted Item SA4 (has batch no).
+ - Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
+ - Transfer the components in multiple batches with extra 2 qty for the batched item.
+ - Create the 3 purchase receipt against the PO and split Subcontracted Items into two batches.
+ - Keep the qty as 2 for Subcontracted Item in the purchase receipt.
+ - In the first purchase receipt the batched raw materials will be consumed 2 extra qty.
+ """
- set_backflush_based_on('Material Transferred for Subcontract')
- item_code = 'Subcontracted Item SA4'
- items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
+ set_backflush_based_on("Material Transferred for Subcontract")
+ item_code = "Subcontracted Item SA4"
+ items = [{"warehouse": "_Test Warehouse - _TC", "item_code": item_code, "qty": 10, "rate": 100}]
- rm_items = [{'item_code': 'Subcontracted SRM Item 1', 'qty': 10},
- {'item_code': 'Subcontracted SRM Item 2', 'qty': 10},
- {'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
- {'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
- {'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
- {'item_code': 'Subcontracted SRM Item 3', 'qty': 3}
+ rm_items = [
+ {"item_code": "Subcontracted SRM Item 1", "qty": 10},
+ {"item_code": "Subcontracted SRM Item 2", "qty": 10},
+ {"item_code": "Subcontracted SRM Item 3", "qty": 3},
+ {"item_code": "Subcontracted SRM Item 3", "qty": 3},
+ {"item_code": "Subcontracted SRM Item 3", "qty": 3},
+ {"item_code": "Subcontracted SRM Item 3", "qty": 3},
]
itemwise_details = make_stock_in_entry(rm_items=rm_items)
- po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
- supplier_warehouse="_Test Warehouse 1 - _TC")
+ po = create_purchase_order(
+ rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC"
+ )
for d in rm_items:
- d['po_detail'] = po.items[0].name
+ d["po_detail"] = po.items[0].name
- make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
- rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+ make_stock_transfer_entry(
+ po_no=po.name,
+ main_item_code=item_code,
+ rm_items=rm_items,
+ itemwise_details=copy.deepcopy(itemwise_details),
+ )
pr1 = make_purchase_invoice(po.name)
pr1.update_stock = 1
pr1.items[0].qty = 2
- pr1.items[0].expense_account = 'Stock Adjustment - _TC'
+ pr1.items[0].expense_account = "Stock Adjustment - _TC"
add_second_row_in_pr(pr1)
pr1.save()
pr1.submit()
for key, value in get_supplied_items(pr1).items():
- qty = 4 if key != 'Subcontracted SRM Item 3' else 6
+ qty = 4 if key != "Subcontracted SRM Item 3" else 6
self.assertEqual(value.qty, qty)
pr1 = make_purchase_invoice(po.name)
pr1.update_stock = 1
- pr1.items[0].expense_account = 'Stock Adjustment - _TC'
+ pr1.items[0].expense_account = "Stock Adjustment - _TC"
pr1.items[0].qty = 2
add_second_row_in_pr(pr1)
pr1.save()
@@ -555,43 +648,50 @@ class TestSubcontracting(unittest.TestCase):
pr1 = make_purchase_invoice(po.name)
pr1.update_stock = 1
pr1.items[0].qty = 2
- pr1.items[0].expense_account = 'Stock Adjustment - _TC'
+ pr1.items[0].expense_account = "Stock Adjustment - _TC"
pr1.save()
pr1.submit()
for key, value in get_supplied_items(pr1).items():
self.assertEqual(value.qty, 2)
- def test_partial_transfer_serial_no_components_based_on_material_transfer_for_purchase_invoice(self):
- '''
- - Set backflush based on Material Transferred for Subcontract
- - Create subcontracted PO for the item Subcontracted Item SA2.
- - Transfer the partial components from Stores to Supplier warehouse with serial nos.
- - Create partial purchase receipt against the PO and change the qty manually.
- - Transfer the remaining components from Stores to Supplier warehouse with serial nos.
- - Create purchase receipt for remaining qty against the PO and change the qty manually.
- '''
+ def test_partial_transfer_serial_no_components_based_on_material_transfer_for_purchase_invoice(
+ self,
+ ):
+ """
+ - Set backflush based on Material Transferred for Subcontract
+ - Create subcontracted PO for the item Subcontracted Item SA2.
+ - Transfer the partial components from Stores to Supplier warehouse with serial nos.
+ - Create partial purchase receipt against the PO and change the qty manually.
+ - Transfer the remaining components from Stores to Supplier warehouse with serial nos.
+ - Create purchase receipt for remaining qty against the PO and change the qty manually.
+ """
- set_backflush_based_on('Material Transferred for Subcontract')
- item_code = 'Subcontracted Item SA2'
- items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
+ set_backflush_based_on("Material Transferred for Subcontract")
+ item_code = "Subcontracted Item SA2"
+ items = [{"warehouse": "_Test Warehouse - _TC", "item_code": item_code, "qty": 10, "rate": 100}]
- rm_items = [{'item_code': 'Subcontracted SRM Item 2', 'qty': 5}]
+ rm_items = [{"item_code": "Subcontracted SRM Item 2", "qty": 5}]
itemwise_details = make_stock_in_entry(rm_items=rm_items)
- po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
- supplier_warehouse="_Test Warehouse 1 - _TC")
+ po = create_purchase_order(
+ rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC"
+ )
for d in rm_items:
- d['po_detail'] = po.items[0].name
+ d["po_detail"] = po.items[0].name
- make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
- rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+ make_stock_transfer_entry(
+ po_no=po.name,
+ main_item_code=item_code,
+ rm_items=rm_items,
+ itemwise_details=copy.deepcopy(itemwise_details),
+ )
pr1 = make_purchase_invoice(po.name)
pr1.update_stock = 1
pr1.items[0].qty = 5
- pr1.items[0].expense_account = 'Stock Adjustment - _TC'
+ pr1.items[0].expense_account = "Stock Adjustment - _TC"
pr1.save()
for key, value in get_supplied_items(pr1).items():
@@ -601,7 +701,9 @@ class TestSubcontracting(unittest.TestCase):
pr1.load_from_db()
pr1.supplied_items[0].consumed_qty = 5
- pr1.supplied_items[0].serial_no = '\n'.join(itemwise_details[pr1.supplied_items[0].rm_item_code]['serial_no'])
+ pr1.supplied_items[0].serial_no = "\n".join(
+ itemwise_details[pr1.supplied_items[0].rm_item_code]["serial_no"]
+ )
pr1.save()
pr1.submit()
@@ -612,14 +714,18 @@ class TestSubcontracting(unittest.TestCase):
itemwise_details = make_stock_in_entry(rm_items=rm_items)
for d in rm_items:
- d['po_detail'] = po.items[0].name
+ d["po_detail"] = po.items[0].name
- make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
- rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+ make_stock_transfer_entry(
+ po_no=po.name,
+ main_item_code=item_code,
+ rm_items=rm_items,
+ itemwise_details=copy.deepcopy(itemwise_details),
+ )
pr1 = make_purchase_invoice(po.name)
pr1.update_stock = 1
- pr1.items[0].expense_account = 'Stock Adjustment - _TC'
+ pr1.items[0].expense_account = "Stock Adjustment - _TC"
pr1.submit()
for key, value in get_supplied_items(pr1).items():
@@ -628,38 +734,43 @@ class TestSubcontracting(unittest.TestCase):
self.assertEqual(sorted(value.serial_no), sorted(details.serial_no))
def test_partial_transfer_batch_based_on_material_transfer_for_purchase_invoice(self):
- '''
- - Set backflush based on Material Transferred for Subcontract
- - Create subcontracted PO for the item Subcontracted Item SA6.
- - Transfer the partial components from Stores to Supplier warehouse with batch.
- - Create partial purchase receipt against the PO and change the qty manually.
- - Transfer the remaining components from Stores to Supplier warehouse with batch.
- - Create purchase receipt for remaining qty against the PO and change the qty manually.
- '''
+ """
+ - Set backflush based on Material Transferred for Subcontract
+ - Create subcontracted PO for the item Subcontracted Item SA6.
+ - Transfer the partial components from Stores to Supplier warehouse with batch.
+ - Create partial purchase receipt against the PO and change the qty manually.
+ - Transfer the remaining components from Stores to Supplier warehouse with batch.
+ - Create purchase receipt for remaining qty against the PO and change the qty manually.
+ """
- set_backflush_based_on('Material Transferred for Subcontract')
- item_code = 'Subcontracted Item SA6'
- items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
+ set_backflush_based_on("Material Transferred for Subcontract")
+ item_code = "Subcontracted Item SA6"
+ items = [{"warehouse": "_Test Warehouse - _TC", "item_code": item_code, "qty": 10, "rate": 100}]
- rm_items = [{'item_code': 'Subcontracted SRM Item 3', 'qty': 5}]
+ rm_items = [{"item_code": "Subcontracted SRM Item 3", "qty": 5}]
itemwise_details = make_stock_in_entry(rm_items=rm_items)
- po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
- supplier_warehouse="_Test Warehouse 1 - _TC")
+ po = create_purchase_order(
+ rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC"
+ )
for d in rm_items:
- d['po_detail'] = po.items[0].name
+ d["po_detail"] = po.items[0].name
- make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
- rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+ make_stock_transfer_entry(
+ po_no=po.name,
+ main_item_code=item_code,
+ rm_items=rm_items,
+ itemwise_details=copy.deepcopy(itemwise_details),
+ )
pr1 = make_purchase_invoice(po.name)
pr1.update_stock = 1
pr1.items[0].qty = 5
- pr1.items[0].expense_account = 'Stock Adjustment - _TC'
+ pr1.items[0].expense_account = "Stock Adjustment - _TC"
pr1.save()
- transferred_batch_no = ''
+ transferred_batch_no = ""
for key, value in get_supplied_items(pr1).items():
details = itemwise_details.get(key)
self.assertEqual(value.qty, 3)
@@ -679,14 +790,18 @@ class TestSubcontracting(unittest.TestCase):
itemwise_details = make_stock_in_entry(rm_items=rm_items)
for d in rm_items:
- d['po_detail'] = po.items[0].name
+ d["po_detail"] = po.items[0].name
- make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
- rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+ make_stock_transfer_entry(
+ po_no=po.name,
+ main_item_code=item_code,
+ rm_items=rm_items,
+ itemwise_details=copy.deepcopy(itemwise_details),
+ )
pr1 = make_purchase_invoice(po.name)
pr1.update_stock = 1
- pr1.items[0].expense_account = 'Stock Adjustment - _TC'
+ pr1.items[0].expense_account = "Stock Adjustment - _TC"
pr1.submit()
for key, value in get_supplied_items(pr1).items():
@@ -695,41 +810,47 @@ class TestSubcontracting(unittest.TestCase):
self.assertEqual(value.batch_no, details.batch_no)
def test_item_with_batch_based_on_bom_for_purchase_invoice(self):
- '''
- - Set backflush based on BOM
- - Create subcontracted PO for the item Subcontracted Item SA4 (has batch no).
- - Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
- - Transfer the components in multiple batches.
- - Create the 3 purchase receipt against the PO and split Subcontracted Items into two batches.
- - Keep the qty as 2 for Subcontracted Item in the purchase receipt.
- '''
+ """
+ - Set backflush based on BOM
+ - Create subcontracted PO for the item Subcontracted Item SA4 (has batch no).
+ - Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
+ - Transfer the components in multiple batches.
+ - Create the 3 purchase receipt against the PO and split Subcontracted Items into two batches.
+ - Keep the qty as 2 for Subcontracted Item in the purchase receipt.
+ """
- set_backflush_based_on('BOM')
- item_code = 'Subcontracted Item SA4'
- items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
+ set_backflush_based_on("BOM")
+ item_code = "Subcontracted Item SA4"
+ items = [{"warehouse": "_Test Warehouse - _TC", "item_code": item_code, "qty": 10, "rate": 100}]
- rm_items = [{'item_code': 'Subcontracted SRM Item 1', 'qty': 10},
- {'item_code': 'Subcontracted SRM Item 2', 'qty': 10},
- {'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
- {'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
- {'item_code': 'Subcontracted SRM Item 3', 'qty': 3},
- {'item_code': 'Subcontracted SRM Item 3', 'qty': 1}
+ rm_items = [
+ {"item_code": "Subcontracted SRM Item 1", "qty": 10},
+ {"item_code": "Subcontracted SRM Item 2", "qty": 10},
+ {"item_code": "Subcontracted SRM Item 3", "qty": 3},
+ {"item_code": "Subcontracted SRM Item 3", "qty": 3},
+ {"item_code": "Subcontracted SRM Item 3", "qty": 3},
+ {"item_code": "Subcontracted SRM Item 3", "qty": 1},
]
itemwise_details = make_stock_in_entry(rm_items=rm_items)
- po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
- supplier_warehouse="_Test Warehouse 1 - _TC")
+ po = create_purchase_order(
+ rm_items=items, is_subcontracted=1, supplier_warehouse="_Test Warehouse 1 - _TC"
+ )
for d in rm_items:
- d['po_detail'] = po.items[0].name
+ d["po_detail"] = po.items[0].name
- make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
- rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+ make_stock_transfer_entry(
+ po_no=po.name,
+ main_item_code=item_code,
+ rm_items=rm_items,
+ itemwise_details=copy.deepcopy(itemwise_details),
+ )
pr1 = make_purchase_invoice(po.name)
pr1.update_stock = 1
pr1.items[0].qty = 2
- pr1.items[0].expense_account = 'Stock Adjustment - _TC'
+ pr1.items[0].expense_account = "Stock Adjustment - _TC"
add_second_row_in_pr(pr1)
pr1.save()
pr1.submit()
@@ -740,7 +861,7 @@ class TestSubcontracting(unittest.TestCase):
pr1 = make_purchase_invoice(po.name)
pr1.update_stock = 1
pr1.items[0].qty = 2
- pr1.items[0].expense_account = 'Stock Adjustment - _TC'
+ pr1.items[0].expense_account = "Stock Adjustment - _TC"
add_second_row_in_pr(pr1)
pr1.save()
pr1.submit()
@@ -751,34 +872,50 @@ class TestSubcontracting(unittest.TestCase):
pr1 = make_purchase_invoice(po.name)
pr1.update_stock = 1
pr1.items[0].qty = 2
- pr1.items[0].expense_account = 'Stock Adjustment - _TC'
+ pr1.items[0].expense_account = "Stock Adjustment - _TC"
pr1.save()
pr1.submit()
for key, value in get_supplied_items(pr1).items():
self.assertEqual(value.qty, 2)
+
def add_second_row_in_pr(pr):
item_dict = {}
- for column in ['item_code', 'item_name', 'qty', 'uom', 'warehouse', 'stock_uom',
- 'purchase_order', 'purchase_order_item', 'conversion_factor', 'rate', 'expense_account', 'po_detail']:
+ for column in [
+ "item_code",
+ "item_name",
+ "qty",
+ "uom",
+ "warehouse",
+ "stock_uom",
+ "purchase_order",
+ "purchase_order_item",
+ "conversion_factor",
+ "rate",
+ "expense_account",
+ "po_detail",
+ ]:
item_dict[column] = pr.items[0].get(column)
- pr.append('items', item_dict)
+ pr.append("items", item_dict)
pr.set_missing_values()
+
def get_supplied_items(pr_doc):
supplied_items = {}
- for row in pr_doc.get('supplied_items'):
+ for row in pr_doc.get("supplied_items"):
if row.rm_item_code not in supplied_items:
- supplied_items.setdefault(row.rm_item_code,
- frappe._dict({'qty': 0, 'serial_no': [], 'batch_no': defaultdict(float)}))
+ supplied_items.setdefault(
+ row.rm_item_code, frappe._dict({"qty": 0, "serial_no": [], "batch_no": defaultdict(float)})
+ )
details = supplied_items[row.rm_item_code]
update_item_details(row, details)
return supplied_items
+
def make_stock_in_entry(**args):
args = frappe._dict(args)
@@ -786,11 +923,17 @@ def make_stock_in_entry(**args):
for row in args.rm_items:
row = frappe._dict(row)
- doc = make_stock_entry(target=row.warehouse or '_Test Warehouse - _TC',
- item_code=row.item_code, qty=row.qty or 1, basic_rate=row.rate or 100)
+ doc = make_stock_entry(
+ target=row.warehouse or "_Test Warehouse - _TC",
+ item_code=row.item_code,
+ qty=row.qty or 1,
+ basic_rate=row.rate or 100,
+ )
if row.item_code not in items:
- items.setdefault(row.item_code, frappe._dict({'qty': 0, 'serial_no': [], 'batch_no': defaultdict(float)}))
+ items.setdefault(
+ row.item_code, frappe._dict({"qty": 0, "serial_no": [], "batch_no": defaultdict(float)})
+ )
child_row = doc.items[0]
details = items[child_row.item_code]
@@ -798,15 +941,20 @@ def make_stock_in_entry(**args):
return items
+
def update_item_details(child_row, details):
- details.qty += (child_row.get('qty') if child_row.doctype == 'Stock Entry Detail'
- else child_row.get('consumed_qty'))
+ details.qty += (
+ child_row.get("qty")
+ if child_row.doctype == "Stock Entry Detail"
+ else child_row.get("consumed_qty")
+ )
if child_row.serial_no:
details.serial_no.extend(get_serial_nos(child_row.serial_no))
if child_row.batch_no:
- details.batch_no[child_row.batch_no] += (child_row.get('qty') or child_row.get('consumed_qty'))
+ details.batch_no[child_row.batch_no] += child_row.get("qty") or child_row.get("consumed_qty")
+
def make_stock_transfer_entry(**args):
args = frappe._dict(args)
@@ -815,21 +963,27 @@ def make_stock_transfer_entry(**args):
for row in args.rm_items:
row = frappe._dict(row)
- item = {'item_code': row.main_item_code or args.main_item_code, 'rm_item_code': row.item_code,
- 'qty': row.qty or 1, 'item_name': row.item_code, 'rate': row.rate or 100,
- 'stock_uom': row.stock_uom or 'Nos', 'warehouse': row.warehuose or '_Test Warehouse - _TC'}
+ item = {
+ "item_code": row.main_item_code or args.main_item_code,
+ "rm_item_code": row.item_code,
+ "qty": row.qty or 1,
+ "item_name": row.item_code,
+ "rate": row.rate or 100,
+ "stock_uom": row.stock_uom or "Nos",
+ "warehouse": row.warehuose or "_Test Warehouse - _TC",
+ }
item_details = args.itemwise_details.get(row.item_code)
if item_details and item_details.serial_no:
- serial_nos = item_details.serial_no[0:cint(row.qty)]
- item['serial_no'] = '\n'.join(serial_nos)
+ serial_nos = item_details.serial_no[0 : cint(row.qty)]
+ item["serial_no"] = "\n".join(serial_nos)
item_details.serial_no = list(set(item_details.serial_no) - set(serial_nos))
if item_details and item_details.batch_no:
for batch_no, batch_qty in item_details.batch_no.items():
if batch_qty >= row.qty:
- item['batch_no'] = batch_no
+ item["batch_no"] = batch_no
item_details.batch_no[batch_no] -= row.qty
break
@@ -842,42 +996,70 @@ def make_stock_transfer_entry(**args):
return doc
+
def make_subcontract_items():
- sub_contracted_items = {'Subcontracted Item SA1': {}, 'Subcontracted Item SA2': {}, 'Subcontracted Item SA3': {},
- 'Subcontracted Item SA4': {'has_batch_no': 1, 'create_new_batch': 1, 'batch_number_series': 'SBAT.####'},
- 'Subcontracted Item SA5': {}, 'Subcontracted Item SA6': {}}
+ sub_contracted_items = {
+ "Subcontracted Item SA1": {},
+ "Subcontracted Item SA2": {},
+ "Subcontracted Item SA3": {},
+ "Subcontracted Item SA4": {
+ "has_batch_no": 1,
+ "create_new_batch": 1,
+ "batch_number_series": "SBAT.####",
+ },
+ "Subcontracted Item SA5": {},
+ "Subcontracted Item SA6": {},
+ }
for item, properties in sub_contracted_items.items():
- if not frappe.db.exists('Item', item):
- properties.update({'is_stock_item': 1, 'is_sub_contracted_item': 1})
+ if not frappe.db.exists("Item", item):
+ properties.update({"is_stock_item": 1, "is_sub_contracted_item": 1})
make_item(item, properties)
+
def make_raw_materials():
- raw_materials = {'Subcontracted SRM Item 1': {},
- 'Subcontracted SRM Item 2': {'has_serial_no': 1, 'serial_no_series': 'SRI.####'},
- 'Subcontracted SRM Item 3': {'has_batch_no': 1, 'create_new_batch': 1, 'batch_number_series': 'BAT.####'},
- 'Subcontracted SRM Item 4': {'has_serial_no': 1, 'serial_no_series': 'SRII.####'},
- 'Subcontracted SRM Item 5': {'has_serial_no': 1, 'serial_no_series': 'SRII.####'}}
+ raw_materials = {
+ "Subcontracted SRM Item 1": {},
+ "Subcontracted SRM Item 2": {"has_serial_no": 1, "serial_no_series": "SRI.####"},
+ "Subcontracted SRM Item 3": {
+ "has_batch_no": 1,
+ "create_new_batch": 1,
+ "batch_number_series": "BAT.####",
+ },
+ "Subcontracted SRM Item 4": {"has_serial_no": 1, "serial_no_series": "SRII.####"},
+ "Subcontracted SRM Item 5": {"has_serial_no": 1, "serial_no_series": "SRII.####"},
+ }
for item, properties in raw_materials.items():
- if not frappe.db.exists('Item', item):
- properties.update({'is_stock_item': 1})
+ if not frappe.db.exists("Item", item):
+ properties.update({"is_stock_item": 1})
make_item(item, properties)
+
def make_bom_for_subcontracted_items():
boms = {
- 'Subcontracted Item SA1': ['Subcontracted SRM Item 1', 'Subcontracted SRM Item 2', 'Subcontracted SRM Item 3'],
- 'Subcontracted Item SA2': ['Subcontracted SRM Item 2'],
- 'Subcontracted Item SA3': ['Subcontracted SRM Item 2'],
- 'Subcontracted Item SA4': ['Subcontracted SRM Item 1', 'Subcontracted SRM Item 2', 'Subcontracted SRM Item 3'],
- 'Subcontracted Item SA5': ['Subcontracted SRM Item 5'],
- 'Subcontracted Item SA6': ['Subcontracted SRM Item 3']
+ "Subcontracted Item SA1": [
+ "Subcontracted SRM Item 1",
+ "Subcontracted SRM Item 2",
+ "Subcontracted SRM Item 3",
+ ],
+ "Subcontracted Item SA2": ["Subcontracted SRM Item 2"],
+ "Subcontracted Item SA3": ["Subcontracted SRM Item 2"],
+ "Subcontracted Item SA4": [
+ "Subcontracted SRM Item 1",
+ "Subcontracted SRM Item 2",
+ "Subcontracted SRM Item 3",
+ ],
+ "Subcontracted Item SA5": ["Subcontracted SRM Item 5"],
+ "Subcontracted Item SA6": ["Subcontracted SRM Item 3"],
}
for item_code, raw_materials in boms.items():
- if not frappe.db.exists('BOM', {'item': item_code}):
+ if not frappe.db.exists("BOM", {"item": item_code}):
make_bom(item=item_code, raw_materials=raw_materials, rate=100)
+
def set_backflush_based_on(based_on):
- frappe.db.set_value('Buying Settings', None,
- 'backflush_raw_materials_of_subcontract_based_on', based_on)
+ frappe.db.set_value(
+ "Buying Settings", None, "backflush_raw_materials_of_subcontract_based_on", based_on
+ )
diff --git a/erpnext/tests/test_webform.py b/erpnext/tests/test_webform.py
index 19255db33c5..202467b5450 100644
--- a/erpnext/tests/test_webform.py
+++ b/erpnext/tests/test_webform.py
@@ -7,132 +7,143 @@ from erpnext.buying.doctype.purchase_order.test_purchase_order import create_pur
class TestWebsite(unittest.TestCase):
def test_permission_for_custom_doctype(self):
- create_user('Supplier 1', 'supplier1@gmail.com')
- create_user('Supplier 2', 'supplier2@gmail.com')
- create_supplier_with_contact('Supplier1', 'All Supplier Groups', 'Supplier 1', 'supplier1@gmail.com')
- create_supplier_with_contact('Supplier2', 'All Supplier Groups', 'Supplier 2', 'supplier2@gmail.com')
- po1 = create_purchase_order(supplier='Supplier1')
- po2 = create_purchase_order(supplier='Supplier2')
+ create_user("Supplier 1", "supplier1@gmail.com")
+ create_user("Supplier 2", "supplier2@gmail.com")
+ create_supplier_with_contact(
+ "Supplier1", "All Supplier Groups", "Supplier 1", "supplier1@gmail.com"
+ )
+ create_supplier_with_contact(
+ "Supplier2", "All Supplier Groups", "Supplier 2", "supplier2@gmail.com"
+ )
+ po1 = create_purchase_order(supplier="Supplier1")
+ po2 = create_purchase_order(supplier="Supplier2")
create_custom_doctype()
create_webform()
- create_order_assignment(supplier='Supplier1', po = po1.name)
- create_order_assignment(supplier='Supplier2', po = po2.name)
+ create_order_assignment(supplier="Supplier1", po=po1.name)
+ create_order_assignment(supplier="Supplier2", po=po2.name)
frappe.set_user("Administrator")
# checking if data consist of all order assignment of Supplier1 and Supplier2
- self.assertTrue('Supplier1' and 'Supplier2' in [data.supplier for data in get_data()])
+ self.assertTrue("Supplier1" and "Supplier2" in [data.supplier for data in get_data()])
frappe.set_user("supplier1@gmail.com")
# checking if data only consist of order assignment of Supplier1
- self.assertTrue('Supplier1' in [data.supplier for data in get_data()])
- self.assertFalse([data.supplier for data in get_data() if data.supplier != 'Supplier1'])
+ self.assertTrue("Supplier1" in [data.supplier for data in get_data()])
+ self.assertFalse([data.supplier for data in get_data() if data.supplier != "Supplier1"])
frappe.set_user("supplier2@gmail.com")
# checking if data only consist of order assignment of Supplier2
- self.assertTrue('Supplier2' in [data.supplier for data in get_data()])
- self.assertFalse([data.supplier for data in get_data() if data.supplier != 'Supplier2'])
+ self.assertTrue("Supplier2" in [data.supplier for data in get_data()])
+ self.assertFalse([data.supplier for data in get_data() if data.supplier != "Supplier2"])
frappe.set_user("Administrator")
+
def get_data():
- webform_list_contexts = frappe.get_hooks('webform_list_context')
+ webform_list_contexts = frappe.get_hooks("webform_list_context")
if webform_list_contexts:
- context = frappe._dict(frappe.get_attr(webform_list_contexts[0])('Buying') or {})
- kwargs = dict(doctype='Order Assignment', order_by = 'modified desc')
+ context = frappe._dict(frappe.get_attr(webform_list_contexts[0])("Buying") or {})
+ kwargs = dict(doctype="Order Assignment", order_by="modified desc")
return context.get_list(**kwargs)
+
def create_user(name, email):
- frappe.get_doc({
- 'doctype': 'User',
- 'send_welcome_email': 0,
- 'user_type': 'Website User',
- 'first_name': name,
- 'email': email,
- 'roles': [{"doctype": "Has Role", "role": "Supplier"}]
- }).insert(ignore_if_duplicate = True)
+ frappe.get_doc(
+ {
+ "doctype": "User",
+ "send_welcome_email": 0,
+ "user_type": "Website User",
+ "first_name": name,
+ "email": email,
+ "roles": [{"doctype": "Has Role", "role": "Supplier"}],
+ }
+ ).insert(ignore_if_duplicate=True)
+
def create_supplier_with_contact(name, group, contact_name, contact_email):
- supplier = frappe.get_doc({
- 'doctype': 'Supplier',
- 'supplier_name': name,
- 'supplier_group': group
- }).insert(ignore_if_duplicate = True)
+ supplier = frappe.get_doc(
+ {"doctype": "Supplier", "supplier_name": name, "supplier_group": group}
+ ).insert(ignore_if_duplicate=True)
- if not frappe.db.exists('Contact', contact_name+'-1-'+name):
+ if not frappe.db.exists("Contact", contact_name + "-1-" + name):
new_contact = frappe.new_doc("Contact")
new_contact.first_name = contact_name
- new_contact.is_primary_contact = True,
- new_contact.append('links', {
- "link_doctype": "Supplier",
- "link_name": supplier.name
- })
- new_contact.append('email_ids', {
- "email_id": contact_email,
- "is_primary": 1
- })
+ new_contact.is_primary_contact = (True,)
+ new_contact.append("links", {"link_doctype": "Supplier", "link_name": supplier.name})
+ new_contact.append("email_ids", {"email_id": contact_email, "is_primary": 1})
new_contact.insert(ignore_mandatory=True)
+
def create_custom_doctype():
- frappe.get_doc({
- 'doctype': 'DocType',
- 'name': 'Order Assignment',
- 'module': 'Buying',
- 'custom': 1,
- 'autoname': 'field:po',
- 'fields': [
- {'label': 'PO', 'fieldname': 'po', 'fieldtype': 'Link', 'options': 'Purchase Order'},
- {'label': 'Supplier', 'fieldname': 'supplier', 'fieldtype': 'Data', "fetch_from": "po.supplier"}
- ],
- 'permissions': [
- {
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "share": 1,
- "write": 1
- },
- {
- "read": 1,
- "role": "Supplier"
- }
- ]
- }).insert(ignore_if_duplicate = True)
+ frappe.get_doc(
+ {
+ "doctype": "DocType",
+ "name": "Order Assignment",
+ "module": "Buying",
+ "custom": 1,
+ "autoname": "field:po",
+ "fields": [
+ {"label": "PO", "fieldname": "po", "fieldtype": "Link", "options": "Purchase Order"},
+ {
+ "label": "Supplier",
+ "fieldname": "supplier",
+ "fieldtype": "Data",
+ "fetch_from": "po.supplier",
+ },
+ ],
+ "permissions": [
+ {
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
+ "write": 1,
+ },
+ {"read": 1, "role": "Supplier"},
+ ],
+ }
+ ).insert(ignore_if_duplicate=True)
+
def create_webform():
- frappe.get_doc({
- 'doctype': 'Web Form',
- 'module': 'Buying',
- 'title': 'SO Schedule',
- 'route': 'so-schedule',
- 'doc_type': 'Order Assignment',
- 'web_form_fields': [
- {
- 'doctype': 'Web Form Field',
- 'fieldname': 'po',
- 'fieldtype': 'Link',
- 'options': 'Purchase Order',
- 'label': 'PO'
- },
- {
- 'doctype': 'Web Form Field',
- 'fieldname': 'supplier',
- 'fieldtype': 'Data',
- 'label': 'Supplier'
- }
- ]
+ frappe.get_doc(
+ {
+ "doctype": "Web Form",
+ "module": "Buying",
+ "title": "SO Schedule",
+ "route": "so-schedule",
+ "doc_type": "Order Assignment",
+ "web_form_fields": [
+ {
+ "doctype": "Web Form Field",
+ "fieldname": "po",
+ "fieldtype": "Link",
+ "options": "Purchase Order",
+ "label": "PO",
+ },
+ {
+ "doctype": "Web Form Field",
+ "fieldname": "supplier",
+ "fieldtype": "Data",
+ "label": "Supplier",
+ },
+ ],
+ }
+ ).insert(ignore_if_duplicate=True)
- }).insert(ignore_if_duplicate = True)
def create_order_assignment(supplier, po):
- frappe.get_doc({
- 'doctype': 'Order Assignment',
- 'po': po,
- 'supplier': supplier,
- }).insert(ignore_if_duplicate = True)
\ No newline at end of file
+ frappe.get_doc(
+ {
+ "doctype": "Order Assignment",
+ "po": po,
+ "supplier": supplier,
+ }
+ ).insert(ignore_if_duplicate=True)
diff --git a/erpnext/tests/test_woocommerce.py b/erpnext/tests/test_woocommerce.py
index 4a451aba4d0..663464b6b78 100644
--- a/erpnext/tests/test_woocommerce.py
+++ b/erpnext/tests/test_woocommerce.py
@@ -25,32 +25,126 @@ class TestWoocommerce(unittest.TestCase):
woo_settings.save(ignore_permissions=True)
def test_sales_order_for_woocommerce(self):
- frappe.flags.woocomm_test_order_data = {"id":75,"parent_id":0,"number":"74","order_key":"wc_order_5aa1281c2dacb","created_via":"checkout","version":"3.3.3","status":"processing","currency":"INR","date_created":"2018-03-08T12:10:04","date_created_gmt":"2018-03-08T12:10:04","date_modified":"2018-03-08T12:10:04","date_modified_gmt":"2018-03-08T12:10:04","discount_total":"0.00","discount_tax":"0.00","shipping_total":"150.00","shipping_tax":"0.00","cart_tax":"0.00","total":"649.00","total_tax":"0.00","prices_include_tax":False,"customer_id":12,"customer_ip_address":"103.54.99.5","customer_user_agent":"mozilla\\/5.0 (x11; linux x86_64) applewebkit\\/537.36 (khtml, like gecko) chrome\\/64.0.3282.186 safari\\/537.36","customer_note":"","billing":{"first_name":"Tony","last_name":"Stark","company":"_Test Company","address_1":"Mumbai","address_2":"","city":"Dadar","state":"MH","postcode":"123","country":"IN","email":"tony@gmail.com","phone":"123457890"},"shipping":{"first_name":"Tony","last_name":"Stark","company":"","address_1":"Mumbai","address_2":"","city":"Dadar","state":"MH","postcode":"123","country":"IN"},"payment_method":"cod","payment_method_title":"Cash on delivery","transaction_id":"","date_paid":"","date_paid_gmt":"","date_completed":"","date_completed_gmt":"","cart_hash":"8e76b020d5790066496f244860c4703f","meta_data":[],"line_items":[{"id":80,"name":"Marvel","product_id":56,"variation_id":0,"quantity":1,"tax_class":"","subtotal":"499.00","subtotal_tax":"0.00","total":"499.00","total_tax":"0.00","taxes":[],"meta_data":[],"sku":"","price":499}],"tax_lines":[],"shipping_lines":[{"id":81,"method_title":"Flat rate","method_id":"flat_rate:1","total":"150.00","total_tax":"0.00","taxes":[],"meta_data":[{"id":623,"key":"Items","value":"Marvel × 1"}]}],"fee_lines":[],"coupon_lines":[],"refunds":[]}
+ frappe.flags.woocomm_test_order_data = {
+ "id": 75,
+ "parent_id": 0,
+ "number": "74",
+ "order_key": "wc_order_5aa1281c2dacb",
+ "created_via": "checkout",
+ "version": "3.3.3",
+ "status": "processing",
+ "currency": "INR",
+ "date_created": "2018-03-08T12:10:04",
+ "date_created_gmt": "2018-03-08T12:10:04",
+ "date_modified": "2018-03-08T12:10:04",
+ "date_modified_gmt": "2018-03-08T12:10:04",
+ "discount_total": "0.00",
+ "discount_tax": "0.00",
+ "shipping_total": "150.00",
+ "shipping_tax": "0.00",
+ "cart_tax": "0.00",
+ "total": "649.00",
+ "total_tax": "0.00",
+ "prices_include_tax": False,
+ "customer_id": 12,
+ "customer_ip_address": "103.54.99.5",
+ "customer_user_agent": "mozilla\\/5.0 (x11; linux x86_64) applewebkit\\/537.36 (khtml, like gecko) chrome\\/64.0.3282.186 safari\\/537.36",
+ "customer_note": "",
+ "billing": {
+ "first_name": "Tony",
+ "last_name": "Stark",
+ "company": "_Test Company",
+ "address_1": "Mumbai",
+ "address_2": "",
+ "city": "Dadar",
+ "state": "MH",
+ "postcode": "123",
+ "country": "IN",
+ "email": "tony@gmail.com",
+ "phone": "123457890",
+ },
+ "shipping": {
+ "first_name": "Tony",
+ "last_name": "Stark",
+ "company": "",
+ "address_1": "Mumbai",
+ "address_2": "",
+ "city": "Dadar",
+ "state": "MH",
+ "postcode": "123",
+ "country": "IN",
+ },
+ "payment_method": "cod",
+ "payment_method_title": "Cash on delivery",
+ "transaction_id": "",
+ "date_paid": "",
+ "date_paid_gmt": "",
+ "date_completed": "",
+ "date_completed_gmt": "",
+ "cart_hash": "8e76b020d5790066496f244860c4703f",
+ "meta_data": [],
+ "line_items": [
+ {
+ "id": 80,
+ "name": "Marvel",
+ "product_id": 56,
+ "variation_id": 0,
+ "quantity": 1,
+ "tax_class": "",
+ "subtotal": "499.00",
+ "subtotal_tax": "0.00",
+ "total": "499.00",
+ "total_tax": "0.00",
+ "taxes": [],
+ "meta_data": [],
+ "sku": "",
+ "price": 499,
+ }
+ ],
+ "tax_lines": [],
+ "shipping_lines": [
+ {
+ "id": 81,
+ "method_title": "Flat rate",
+ "method_id": "flat_rate:1",
+ "total": "150.00",
+ "total_tax": "0.00",
+ "taxes": [],
+ "meta_data": [{"id": 623, "key": "Items", "value": "Marvel × 1"}],
+ }
+ ],
+ "fee_lines": [],
+ "coupon_lines": [],
+ "refunds": [],
+ }
order()
- self.assertTrue(frappe.get_value("Customer",{"woocommerce_email":"tony@gmail.com"}))
- self.assertTrue(frappe.get_value("Item",{"woocommerce_id": 56}))
- self.assertTrue(frappe.get_value("Sales Order",{"woocommerce_id":75}))
+ self.assertTrue(frappe.get_value("Customer", {"woocommerce_email": "tony@gmail.com"}))
+ self.assertTrue(frappe.get_value("Item", {"woocommerce_id": 56}))
+ self.assertTrue(frappe.get_value("Sales Order", {"woocommerce_id": 75}))
frappe.flags.woocomm_test_order_data = {}
+
def emulate_request():
# Emulate Woocommerce Request
headers = {
- "X-Wc-Webhook-Event":"created",
- "X-Wc-Webhook-Signature":"h1SjzQMPwd68MF5bficeFq20/RkQeRLsb9AVCUz/rLs="
+ "X-Wc-Webhook-Event": "created",
+ "X-Wc-Webhook-Signature": "h1SjzQMPwd68MF5bficeFq20/RkQeRLsb9AVCUz/rLs=",
}
# Emulate Request Data
data = """{"id":74,"parent_id":0,"number":"74","order_key":"wc_order_5aa1281c2dacb","created_via":"checkout","version":"3.3.3","status":"processing","currency":"INR","date_created":"2018-03-08T12:10:04","date_created_gmt":"2018-03-08T12:10:04","date_modified":"2018-03-08T12:10:04","date_modified_gmt":"2018-03-08T12:10:04","discount_total":"0.00","discount_tax":"0.00","shipping_total":"150.00","shipping_tax":"0.00","cart_tax":"0.00","total":"649.00","total_tax":"0.00","prices_include_tax":false,"customer_id":12,"customer_ip_address":"103.54.99.5","customer_user_agent":"mozilla\\/5.0 (x11; linux x86_64) applewebkit\\/537.36 (khtml, like gecko) chrome\\/64.0.3282.186 safari\\/537.36","customer_note":"","billing":{"first_name":"Tony","last_name":"Stark","company":"Woocommerce","address_1":"Mumbai","address_2":"","city":"Dadar","state":"MH","postcode":"123","country":"IN","email":"tony@gmail.com","phone":"123457890"},"shipping":{"first_name":"Tony","last_name":"Stark","company":"","address_1":"Mumbai","address_2":"","city":"Dadar","state":"MH","postcode":"123","country":"IN"},"payment_method":"cod","payment_method_title":"Cash on delivery","transaction_id":"","date_paid":null,"date_paid_gmt":null,"date_completed":null,"date_completed_gmt":null,"cart_hash":"8e76b020d5790066496f244860c4703f","meta_data":[],"line_items":[{"id":80,"name":"Marvel","product_id":56,"variation_id":0,"quantity":1,"tax_class":"","subtotal":"499.00","subtotal_tax":"0.00","total":"499.00","total_tax":"0.00","taxes":[],"meta_data":[],"sku":"","price":499}],"tax_lines":[],"shipping_lines":[{"id":81,"method_title":"Flat rate","method_id":"flat_rate:1","total":"150.00","total_tax":"0.00","taxes":[],"meta_data":[{"id":623,"key":"Items","value":"Marvel × 1"}]}],"fee_lines":[],"coupon_lines":[],"refunds":[]}"""
# Build URL
- port = frappe.get_site_config().webserver_port or '8000'
+ port = frappe.get_site_config().webserver_port or "8000"
- if os.environ.get('CI'):
- host = 'localhost'
+ if os.environ.get("CI"):
+ host = "localhost"
else:
host = frappe.local.site
- url = "http://{site}:{port}/api/method/erpnext.erpnext_integrations.connectors.woocommerce_connection.order".format(site=host, port=port)
+ url = "http://{site}:{port}/api/method/erpnext.erpnext_integrations.connectors.woocommerce_connection.order".format(
+ site=host, port=port
+ )
r = requests.post(url=url, headers=headers, data=data)
diff --git a/erpnext/tests/test_zform_loads.py b/erpnext/tests/test_zform_loads.py
index 5b82c7bbdd7..26e60c001a1 100644
--- a/erpnext/tests/test_zform_loads.py
+++ b/erpnext/tests/test_zform_loads.py
@@ -7,11 +7,14 @@ from frappe.www.printview import get_html_and_style
class TestFormLoads(FrappeTestCase):
-
@change_settings("Print Settings", {"allow_print_for_cancelled": 1})
def test_load(self):
erpnext_modules = frappe.get_all("Module Def", filters={"app_name": "erpnext"}, pluck="name")
- doctypes = frappe.get_all("DocType", {"istable": 0, "issingle": 0, "is_virtual": 0, "module": ("in", erpnext_modules)}, pluck="name")
+ doctypes = frappe.get_all(
+ "DocType",
+ {"istable": 0, "issingle": 0, "is_virtual": 0, "module": ("in", erpnext_modules)},
+ pluck="name",
+ )
for doctype in doctypes:
last_doc = frappe.db.get_value(doctype, {}, "name", order_by="modified desc")
@@ -23,7 +26,7 @@ class TestFormLoads(FrappeTestCase):
def assertFormLoad(self, doctype, docname):
# reset previous response
- frappe.response = frappe._dict({"docs":[]})
+ frappe.response = frappe._dict({"docs": []})
frappe.response.docinfo = None
try:
@@ -31,8 +34,12 @@ class TestFormLoads(FrappeTestCase):
except Exception as e:
self.fail(f"Failed to load {doctype}-{docname}: {e}")
- self.assertTrue(frappe.response.docs, msg=f"expected document in reponse, found: {frappe.response.docs}")
- self.assertTrue(frappe.response.docinfo, msg=f"expected docinfo in reponse, found: {frappe.response.docinfo}")
+ self.assertTrue(
+ frappe.response.docs, msg=f"expected document in reponse, found: {frappe.response.docs}"
+ )
+ self.assertTrue(
+ frappe.response.docinfo, msg=f"expected docinfo in reponse, found: {frappe.response.docinfo}"
+ )
def assertDocPrint(self, doctype, docname):
doc = frappe.get_doc(doctype, docname)
@@ -44,9 +51,8 @@ class TestFormLoads(FrappeTestCase):
messages_after = frappe.get_message_log()
if len(messages_after) > len(messages_before):
- new_messages = messages_after[len(messages_before):]
- self.fail("Print view showing error/warnings: \n"
- + "\n".join(str(msg) for msg in new_messages))
+ new_messages = messages_after[len(messages_before) :]
+ self.fail("Print view showing error/warnings: \n" + "\n".join(str(msg) for msg in new_messages))
# html should exist
self.assertTrue(bool(ret["html"]))
diff --git a/erpnext/tests/ui_test_bulk_transaction_processing.py b/erpnext/tests/ui_test_bulk_transaction_processing.py
index d78689eb5b3..a0dc76d54f5 100644
--- a/erpnext/tests/ui_test_bulk_transaction_processing.py
+++ b/erpnext/tests/ui_test_bulk_transaction_processing.py
@@ -18,4 +18,4 @@ def create_records():
gd.set("default_company", "Test Bulk")
gd.save()
frappe.clear_cache()
- create_so()
\ No newline at end of file
+ create_so()
diff --git a/erpnext/tests/ui_test_helpers.py b/erpnext/tests/ui_test_helpers.py
index 9c8c371e051..44834c8a77c 100644
--- a/erpnext/tests/ui_test_helpers.py
+++ b/erpnext/tests/ui_test_helpers.py
@@ -9,54 +9,67 @@ def create_employee_records():
frappe.db.sql("DELETE FROM tabEmployee WHERE company='Test Org Chart'")
- emp1 = create_employee('Test Employee 1', 'CEO')
- emp2 = create_employee('Test Employee 2', 'CTO')
- emp3 = create_employee('Test Employee 3', 'Head of Marketing and Sales', emp1)
- emp4 = create_employee('Test Employee 4', 'Project Manager', emp2)
- emp5 = create_employee('Test Employee 5', 'Engineer', emp2)
- emp6 = create_employee('Test Employee 6', 'Analyst', emp3)
- emp7 = create_employee('Test Employee 7', 'Software Developer', emp4)
+ emp1 = create_employee("Test Employee 1", "CEO")
+ emp2 = create_employee("Test Employee 2", "CTO")
+ emp3 = create_employee("Test Employee 3", "Head of Marketing and Sales", emp1)
+ emp4 = create_employee("Test Employee 4", "Project Manager", emp2)
+ emp5 = create_employee("Test Employee 5", "Engineer", emp2)
+ emp6 = create_employee("Test Employee 6", "Analyst", emp3)
+ emp7 = create_employee("Test Employee 7", "Software Developer", emp4)
employees = [emp1, emp2, emp3, emp4, emp5, emp6, emp7]
return employees
+
@frappe.whitelist()
def get_employee_records():
- return frappe.db.get_list('Employee', filters={
- 'company': 'Test Org Chart'
- }, pluck='name', order_by='name')
+ return frappe.db.get_list(
+ "Employee", filters={"company": "Test Org Chart"}, pluck="name", order_by="name"
+ )
+
def create_company():
- company = frappe.db.exists('Company', 'Test Org Chart')
+ company = frappe.db.exists("Company", "Test Org Chart")
if not company:
- company = frappe.get_doc({
- 'doctype': 'Company',
- 'company_name': 'Test Org Chart',
- 'country': 'India',
- 'default_currency': 'INR'
- }).insert().name
+ company = (
+ frappe.get_doc(
+ {
+ "doctype": "Company",
+ "company_name": "Test Org Chart",
+ "country": "India",
+ "default_currency": "INR",
+ }
+ )
+ .insert()
+ .name
+ )
return company
+
def create_employee(first_name, designation, reports_to=None):
- employee = frappe.db.exists('Employee', {'first_name': first_name, 'designation': designation})
+ employee = frappe.db.exists("Employee", {"first_name": first_name, "designation": designation})
if not employee:
- employee = frappe.get_doc({
- 'doctype': 'Employee',
- 'first_name': first_name,
- 'company': 'Test Org Chart',
- 'gender': 'Female',
- 'date_of_birth': getdate('08-12-1998'),
- 'date_of_joining': getdate('01-01-2021'),
- 'designation': designation,
- 'reports_to': reports_to
- }).insert().name
+ employee = (
+ frappe.get_doc(
+ {
+ "doctype": "Employee",
+ "first_name": first_name,
+ "company": "Test Org Chart",
+ "gender": "Female",
+ "date_of_birth": getdate("08-12-1998"),
+ "date_of_joining": getdate("01-01-2021"),
+ "designation": designation,
+ "reports_to": reports_to,
+ }
+ )
+ .insert()
+ .name
+ )
return employee
+
def create_missing_designation():
- if not frappe.db.exists('Designation', 'CTO'):
- frappe.get_doc({
- 'doctype': 'Designation',
- 'designation_name': 'CTO'
- }).insert()
+ if not frappe.db.exists("Designation", "CTO"):
+ frappe.get_doc({"doctype": "Designation", "designation_name": "CTO"}).insert()
diff --git a/erpnext/tests/utils.py b/erpnext/tests/utils.py
index d795253665d..159ce7078e3 100644
--- a/erpnext/tests/utils.py
+++ b/erpnext/tests/utils.py
@@ -9,83 +9,77 @@ from frappe.core.doctype.report.report import get_report_module_dotted_path
ReportFilters = Dict[str, Any]
ReportName = NewType("ReportName", str)
+
def create_test_contact_and_address():
- frappe.db.sql('delete from tabContact')
- frappe.db.sql('delete from `tabContact Email`')
- frappe.db.sql('delete from `tabContact Phone`')
- frappe.db.sql('delete from tabAddress')
- frappe.db.sql('delete from `tabDynamic Link`')
+ frappe.db.sql("delete from tabContact")
+ frappe.db.sql("delete from `tabContact Email`")
+ frappe.db.sql("delete from `tabContact Phone`")
+ frappe.db.sql("delete from tabAddress")
+ frappe.db.sql("delete from `tabDynamic Link`")
- frappe.get_doc({
- "doctype": "Address",
- "address_title": "_Test Address for Customer",
- "address_type": "Office",
- "address_line1": "Station Road",
- "city": "_Test City",
- "state": "Test State",
- "country": "India",
- "links": [
- {
- "link_doctype": "Customer",
- "link_name": "_Test Customer"
- }
- ]
- }).insert()
+ frappe.get_doc(
+ {
+ "doctype": "Address",
+ "address_title": "_Test Address for Customer",
+ "address_type": "Office",
+ "address_line1": "Station Road",
+ "city": "_Test City",
+ "state": "Test State",
+ "country": "India",
+ "links": [{"link_doctype": "Customer", "link_name": "_Test Customer"}],
+ }
+ ).insert()
- contact = frappe.get_doc({
- "doctype": 'Contact',
- "first_name": "_Test Contact for _Test Customer",
- "links": [
- {
- "link_doctype": "Customer",
- "link_name": "_Test Customer"
- }
- ]
- })
+ contact = frappe.get_doc(
+ {
+ "doctype": "Contact",
+ "first_name": "_Test Contact for _Test Customer",
+ "links": [{"link_doctype": "Customer", "link_name": "_Test Customer"}],
+ }
+ )
contact.add_email("test_contact_customer@example.com", is_primary=True)
contact.add_phone("+91 0000000000", is_primary_phone=True)
contact.insert()
- contact_two = frappe.get_doc({
- "doctype": 'Contact',
- "first_name": "_Test Contact 2 for _Test Customer",
- "links": [
- {
- "link_doctype": "Customer",
- "link_name": "_Test Customer"
- }
- ]
- })
+ contact_two = frappe.get_doc(
+ {
+ "doctype": "Contact",
+ "first_name": "_Test Contact 2 for _Test Customer",
+ "links": [{"link_doctype": "Customer", "link_name": "_Test Customer"}],
+ }
+ )
contact_two.add_email("test_contact_two_customer@example.com", is_primary=True)
contact_two.add_phone("+92 0000000000", is_primary_phone=True)
contact_two.insert()
def execute_script_report(
- report_name: ReportName,
- module: str,
- filters: ReportFilters,
- default_filters: Optional[ReportFilters] = None,
- optional_filters: Optional[ReportFilters] = None
- ):
+ report_name: ReportName,
+ module: str,
+ filters: ReportFilters,
+ default_filters: Optional[ReportFilters] = None,
+ optional_filters: Optional[ReportFilters] = None,
+):
"""Util for testing execution of a report with specified filters.
Tests the execution of report with default_filters + filters.
Tests the execution using optional_filters one at a time.
Args:
- report_name: Human readable name of report (unscrubbed)
- module: module to which report belongs to
- filters: specific values for filters
- default_filters: default values for filters such as company name.
- optional_filters: filters which should be tested one at a time in addition to default filters.
+ report_name: Human readable name of report (unscrubbed)
+ module: module to which report belongs to
+ filters: specific values for filters
+ default_filters: default values for filters such as company name.
+ optional_filters: filters which should be tested one at a time in addition to default filters.
"""
if default_filters is None:
default_filters = {}
test_filters = []
- report_execute_fn = frappe.get_attr(get_report_module_dotted_path(module, report_name) + ".execute")
+ report_execute_fn = frappe.get_attr(
+ get_report_module_dotted_path(module, report_name) + ".execute"
+ )
report_filters = frappe._dict(default_filters).copy().update(filters)
test_filters.append(report_filters)
diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv
index 3cdae454ab5..db454def738 100644
--- a/erpnext/translations/fr.csv
+++ b/erpnext/translations/fr.csv
@@ -285,7 +285,7 @@ Asset scrapped via Journal Entry {0},Actif mis au rebut via Écriture de Journal
"Asset {0} cannot be scrapped, as it is already {1}","L'actif {0} ne peut pas être mis au rebut, car il est déjà {1}",
Asset {0} does not belong to company {1},L'actif {0} ne fait pas partie à la société {1},
Asset {0} must be submitted,L'actif {0} doit être soumis,
-Assets,Les atouts,
+Assets,Actifs - Immo.,
Assign,Assigner,
Assign Salary Structure,Affecter la structure salariale,
Assign To,Attribuer À,
@@ -1211,7 +1211,7 @@ Hello,Bonjour,
Help Results for,Aide Résultats pour,
High,Haut,
High Sensitivity,Haute sensibilité,
-Hold,Tenir,
+Hold,Mettre en attente,
Hold Invoice,Facture en attente,
Holiday,Vacances,
Holiday List,Liste de vacances,
@@ -4240,7 +4240,7 @@ For Default Supplier (Optional),Pour le fournisseur par défaut (facultatif),
From date cannot be greater than To date,La Date Initiale ne peut pas être postérieure à la Date Finale,
Group by,Grouper Par,
In stock,En stock,
-Item name,Nom de l'article,
+Item name,Libellé de l'article,
Loan amount is mandatory,Le montant du prêt est obligatoire,
Minimum Qty,Quantité minimum,
More details,Plus de détails,
@@ -5473,7 +5473,7 @@ Percentage you are allowed to transfer more against the quantity ordered. For ex
PUR-ORD-.YYYY.-,PUR-ORD-.YYYY.-,
Get Items from Open Material Requests,Obtenir des Articles de Demandes Matérielles Ouvertes,
Fetch items based on Default Supplier.,Récupérez les articles en fonction du fournisseur par défaut.,
-Required By,Requis Par,
+Required By,Requis pour le,
Order Confirmation No,No de confirmation de commande,
Order Confirmation Date,Date de confirmation de la commande,
Customer Mobile No,N° de Portable du Client,
@@ -7223,8 +7223,8 @@ Basic Rate (Company Currency),Taux de Base (Devise de la Société ),
Scrap %,% de Rebut,
Original Item,Article original,
BOM Operation,Opération LDM,
-Operation Time ,Moment de l'opération,
-In minutes,En quelques minutes,
+Operation Time ,Durée de l'opération,
+In minutes,En minutes,
Batch Size,Taille du lot,
Base Hour Rate(Company Currency),Taux Horaire de Base (Devise de la Société),
Operating Cost(Company Currency),Coût d'Exploitation (Devise Société),
@@ -9267,7 +9267,7 @@ Sales Order Analysis,Analyse des commandes clients,
Amount Delivered,Montant livré,
Delay (in Days),Retard (en jours),
Group by Sales Order,Regrouper par commande client,
- Sales Value,La valeur des ventes,
+Sales Value,La valeur des ventes,
Stock Qty vs Serial No Count,Quantité de stock vs numéro de série,
Serial No Count,Numéro de série,
Work Order Summary,Résumé de l'ordre de travail,
@@ -9647,7 +9647,7 @@ Allow Multiple Sales Orders Against a Customer's Purchase Order,Autoriser plusie
Validate Selling Price for Item Against Purchase Rate or Valuation Rate,Valider le prix de vente de l'article par rapport au taux d'achat ou au taux de valorisation,
Hide Customer's Tax ID from Sales Transactions,Masquer le numéro d'identification fiscale du client dans les transactions de vente,
"The percentage you are allowed to receive or deliver more against the quantity ordered. For example, if you have ordered 100 units, and your Allowance is 10%, then you are allowed to receive 110 units.","Le pourcentage que vous êtes autorisé à recevoir ou à livrer plus par rapport à la quantité commandée. Par exemple, si vous avez commandé 100 unités et que votre allocation est de 10%, vous êtes autorisé à recevoir 110 unités.",
-Action If Quality Inspection Is Not Submitted,Action si l'inspection de la qualité n'est pas soumise,
+Action If Quality Inspection Is Not Submitted,Action si l'inspection qualité n'est pas soumise,
Auto Insert Price List Rate If Missing,Taux de liste de prix d'insertion automatique s'il est manquant,
Automatically Set Serial Nos Based on FIFO,Définir automatiquement les numéros de série en fonction de FIFO,
Set Qty in Transactions Based on Serial No Input,Définir la quantité dans les transactions en fonction du numéro de série,
@@ -9838,3 +9838,35 @@ Enable European Access,Activer l'accès européen,
Creating Purchase Order ...,Création d'une commande d'achat ...,
"Select a Supplier from the Default Suppliers of the items below. On selection, a Purchase Order will be made against items belonging to the selected Supplier only.","Sélectionnez un fournisseur parmi les fournisseurs par défaut des articles ci-dessous. Lors de la sélection, un bon de commande sera effectué contre des articles appartenant uniquement au fournisseur sélectionné.",
Row #{}: You must select {} serial numbers for item {}.,Ligne n ° {}: vous devez sélectionner {} numéros de série pour l'article {}.,
+Update Rate as per Last Purchase,Mettre à jour avec les derniers prix d'achats
+Company Shipping Address,Adresse d'expédition
+Shipping Address Details,Détail d'adresse d'expédition
+Company Billing Address,Adresse de la société de facturation
+Supplier Address Details,
+Bank Reconciliation Tool,Outil de réconcialiation d'écritures bancaires
+Supplier Contact,Contact fournisseur
+Subcontracting,Sous traitance
+Order Status,Statut de la commande
+Build,Personnalisations avancées
+Dispatch Address Name,Adresse de livraison intermédiaire
+Amount Eligible for Commission,Montant éligible à comission
+Grant Commission,Eligible aux commissions
+Stock Transactions Settings, Paramétre des transactions
+Role Allowed to Over Deliver/Receive, Rôle autorisé à dépasser cette limite
+Users with this role are allowed to over deliver/receive against orders above the allowance percentage,Rôle Utilisateur qui sont autorisé à livrée/commandé au-delà de la limite
+Over Transfer Allowance,Autorisation de limite de transfert
+Quality Inspection Settings,Paramétre de l'inspection qualité
+Action If Quality Inspection Is Rejected,Action si l'inspection qualité est rejetée
+Disable Serial No And Batch Selector,Désactiver le sélecteur de numéro de lot/série
+Is Rate Adjustment Entry (Debit Note),Est un justement du prix de la note de débit
+Issue a debit note with 0 qty against an existing Sales Invoice,Creer une note de débit avec une quatité à O pour la facture
+Control Historical Stock Transactions,Controle de l'historique des stransaction de stock
+No stock transactions can be created or modified before this date.,Aucune transaction ne peux être créée ou modifié avant cette date.
+Stock transactions that are older than the mentioned days cannot be modified.,Les transactions de stock plus ancienne que le nombre de jours ci-dessus ne peuvent être modifiées
+Role Allowed to Create/Edit Back-dated Transactions,Rôle autorisé à créer et modifier des transactions anti-datée
+"If mentioned, the system will allow only the users with this Role to create or modify any stock transaction earlier than the latest stock transaction for a specific item and warehouse. If set as blank, it allows all users to create/edit back-dated transactions.","LEs utilisateur de ce role pourront creer et modifier des transactions dans le passé. Si vide tout les utilisateurs pourrons le faire"
+Auto Insert Item Price If Missing,Création du prix de l'article dans les listes de prix si abscent
+Update Existing Price List Rate,Mise a jour automatique du prix dans les listes de prix
+Show Barcode Field in Stock Transactions,Afficher le champ Code Barre dans les transactions de stock
+Convert Item Description to Clean HTML in Transactions,Convertir les descriptions d'articles en HTML valide lors des transactions
+Have Default Naming Series for Batch ID?,Nom de série par défaut pour les Lots ou Séries
diff --git a/erpnext/utilities/__init__.py b/erpnext/utilities/__init__.py
index 3749cdeaa88..c2b4229f171 100644
--- a/erpnext/utilities/__init__.py
+++ b/erpnext/utilities/__init__.py
@@ -7,9 +7,12 @@ from erpnext.utilities.activation import get_level
def update_doctypes():
- for d in frappe.db.sql("""select df.parent, df.fieldname
+ for d in frappe.db.sql(
+ """select df.parent, df.fieldname
from tabDocField df, tabDocType dt where df.fieldname
- like "%description%" and df.parent = dt.name and dt.istable = 1""", as_dict=1):
+ like "%description%" and df.parent = dt.name and dt.istable = 1""",
+ as_dict=1,
+ ):
dt = frappe.get_doc("DocType", d.parent)
for f in dt.fields:
@@ -18,20 +21,17 @@ def update_doctypes():
dt.save()
break
+
def get_site_info(site_info):
# called via hook
- company = frappe.db.get_single_value('Global Defaults', 'default_company')
+ company = frappe.db.get_single_value("Global Defaults", "default_company")
domain = None
if not company:
- company = frappe.db.sql('select name from `tabCompany` order by creation asc')
+ company = frappe.db.sql("select name from `tabCompany` order by creation asc")
company = company[0][0] if company else None
if company:
- domain = frappe.get_cached_value('Company', cstr(company), 'domain')
+ domain = frappe.get_cached_value("Company", cstr(company), "domain")
- return {
- 'company': company,
- 'domain': domain,
- 'activation': get_level()
- }
+ return {"company": company, "domain": domain, "activation": get_level()}
diff --git a/erpnext/utilities/activation.py b/erpnext/utilities/activation.py
index faf3fd4a20c..43af0dc3c1b 100644
--- a/erpnext/utilities/activation.py
+++ b/erpnext/utilities/activation.py
@@ -41,7 +41,7 @@ def get_level():
"Supplier": 5,
"Task": 5,
"User": 5,
- "Work Order": 5
+ "Work Order": 5,
}
for doctype, min_count in doctypes.items():
@@ -50,111 +50,118 @@ def get_level():
activation_level += 1
sales_data.append({doctype: count})
- if frappe.db.get_single_value('System Settings', 'setup_complete'):
+ if frappe.db.get_single_value("System Settings", "setup_complete"):
activation_level += 1
- communication_number = frappe.db.count('Communication', dict(communication_medium='Email'))
+ communication_number = frappe.db.count("Communication", dict(communication_medium="Email"))
if communication_number > 10:
activation_level += 1
sales_data.append({"Communication": communication_number})
# recent login
- if frappe.db.sql('select name from tabUser where last_login > date_sub(now(), interval 2 day) limit 1'):
+ if frappe.db.sql(
+ "select name from tabUser where last_login > date_sub(now(), interval 2 day) limit 1"
+ ):
activation_level += 1
level = {"activation_level": activation_level, "sales_data": sales_data}
return level
+
def get_help_messages():
- '''Returns help messages to be shown on Desktop'''
+ """Returns help messages to be shown on Desktop"""
if get_level() > 6:
return []
- domain = frappe.get_cached_value('Company', erpnext.get_default_company(), 'domain')
+ domain = frappe.get_cached_value("Company", erpnext.get_default_company(), "domain")
messages = []
message_settings = [
frappe._dict(
- doctype='Lead',
- title=_('Create Leads'),
- description=_('Leads help you get business, add all your contacts and more as your leads'),
- action=_('Create Lead'),
- route='List/Lead',
- domain=('Manufacturing', 'Retail', 'Services', 'Distribution'),
- target=3
+ doctype="Lead",
+ title=_("Create Leads"),
+ description=_("Leads help you get business, add all your contacts and more as your leads"),
+ action=_("Create Lead"),
+ route="List/Lead",
+ domain=("Manufacturing", "Retail", "Services", "Distribution"),
+ target=3,
),
frappe._dict(
- doctype='Quotation',
- title=_('Create customer quotes'),
- description=_('Quotations are proposals, bids you have sent to your customers'),
- action=_('Create Quotation'),
- route='List/Quotation',
- domain=('Manufacturing', 'Retail', 'Services', 'Distribution'),
- target=3
+ doctype="Quotation",
+ title=_("Create customer quotes"),
+ description=_("Quotations are proposals, bids you have sent to your customers"),
+ action=_("Create Quotation"),
+ route="List/Quotation",
+ domain=("Manufacturing", "Retail", "Services", "Distribution"),
+ target=3,
),
frappe._dict(
- doctype='Sales Order',
- title=_('Manage your orders'),
- description=_('Create Sales Orders to help you plan your work and deliver on-time'),
- action=_('Create Sales Order'),
- route='List/Sales Order',
- domain=('Manufacturing', 'Retail', 'Services', 'Distribution'),
- target=3
+ doctype="Sales Order",
+ title=_("Manage your orders"),
+ description=_("Create Sales Orders to help you plan your work and deliver on-time"),
+ action=_("Create Sales Order"),
+ route="List/Sales Order",
+ domain=("Manufacturing", "Retail", "Services", "Distribution"),
+ target=3,
),
frappe._dict(
- doctype='Purchase Order',
- title=_('Create Purchase Orders'),
- description=_('Purchase orders help you plan and follow up on your purchases'),
- action=_('Create Purchase Order'),
- route='List/Purchase Order',
- domain=('Manufacturing', 'Retail', 'Services', 'Distribution'),
- target=3
+ doctype="Purchase Order",
+ title=_("Create Purchase Orders"),
+ description=_("Purchase orders help you plan and follow up on your purchases"),
+ action=_("Create Purchase Order"),
+ route="List/Purchase Order",
+ domain=("Manufacturing", "Retail", "Services", "Distribution"),
+ target=3,
),
frappe._dict(
- doctype='User',
- title=_('Create Users'),
- description=_('Add the rest of your organization as your users. You can also add invite Customers to your portal by adding them from Contacts'),
- action=_('Create User'),
- route='List/User',
- domain=('Manufacturing', 'Retail', 'Services', 'Distribution'),
- target=3
+ doctype="User",
+ title=_("Create Users"),
+ description=_(
+ "Add the rest of your organization as your users. You can also add invite Customers to your portal by adding them from Contacts"
+ ),
+ action=_("Create User"),
+ route="List/User",
+ domain=("Manufacturing", "Retail", "Services", "Distribution"),
+ target=3,
),
frappe._dict(
- doctype='Timesheet',
- title=_('Add Timesheets'),
- description=_('Timesheets help keep track of time, cost and billing for activites done by your team'),
- action=_('Create Timesheet'),
- route='List/Timesheet',
- domain=('Services',),
- target=5
+ doctype="Timesheet",
+ title=_("Add Timesheets"),
+ description=_(
+ "Timesheets help keep track of time, cost and billing for activites done by your team"
+ ),
+ action=_("Create Timesheet"),
+ route="List/Timesheet",
+ domain=("Services",),
+ target=5,
),
frappe._dict(
- doctype='Student',
- title=_('Add Students'),
- description=_('Students are at the heart of the system, add all your students'),
- action=_('Create Student'),
- route='List/Student',
- domain=('Education',),
- target=5
+ doctype="Student",
+ title=_("Add Students"),
+ description=_("Students are at the heart of the system, add all your students"),
+ action=_("Create Student"),
+ route="List/Student",
+ domain=("Education",),
+ target=5,
),
frappe._dict(
- doctype='Student Batch',
- title=_('Group your students in batches'),
- description=_('Student Batches help you track attendance, assessments and fees for students'),
- action=_('Create Student Batch'),
- route='List/Student Batch',
- domain=('Education',),
- target=3
+ doctype="Student Batch",
+ title=_("Group your students in batches"),
+ description=_("Student Batches help you track attendance, assessments and fees for students"),
+ action=_("Create Student Batch"),
+ route="List/Student Batch",
+ domain=("Education",),
+ target=3,
),
frappe._dict(
- doctype='Employee',
- title=_('Create Employee Records'),
- description=_('Create Employee records to manage leaves, expense claims and payroll'),
- action=_('Create Employee'),
- route='List/Employee',
- target=3
- )
+ doctype="Employee",
+ title=_("Create Employee Records"),
+ description=_("Create Employee records to manage leaves, expense claims and payroll"),
+ action=_("Create Employee"),
+ route="List/Employee",
+ target=3,
+ ),
]
for m in message_settings:
diff --git a/erpnext/utilities/bot.py b/erpnext/utilities/bot.py
index 87a350864f6..5c2e576dd20 100644
--- a/erpnext/utilities/bot.py
+++ b/erpnext/utilities/bot.py
@@ -9,13 +9,16 @@ from frappe.utils.bot import BotParser
class FindItemBot(BotParser):
def get_reply(self):
- if self.startswith('where is', 'find item', 'locate'):
- if not frappe.has_permission('Warehouse'):
+ if self.startswith("where is", "find item", "locate"):
+ if not frappe.has_permission("Warehouse"):
raise frappe.PermissionError
- item = '%{0}%'.format(self.strip_words(self.query, 'where is', 'find item', 'locate'))
- items = frappe.db.sql('''select name from `tabItem` where item_code like %(txt)s
- or item_name like %(txt)s or description like %(txt)s''', dict(txt=item))
+ item = "%{0}%".format(self.strip_words(self.query, "where is", "find item", "locate"))
+ items = frappe.db.sql(
+ """select name from `tabItem` where item_code like %(txt)s
+ or item_name like %(txt)s or description like %(txt)s""",
+ dict(txt=item),
+ )
if items:
out = []
@@ -23,14 +26,19 @@ class FindItemBot(BotParser):
for item in items:
found = False
for warehouse in warehouses:
- qty = frappe.db.get_value("Bin", {'item_code': item[0], 'warehouse': warehouse.name}, 'actual_qty')
+ qty = frappe.db.get_value(
+ "Bin", {"item_code": item[0], "warehouse": warehouse.name}, "actual_qty"
+ )
if qty:
- out.append(_('{0} units of [{1}](/app/Form/Item/{1}) found in [{2}](/app/Form/Warehouse/{2})').format(qty,
- item[0], warehouse.name))
+ out.append(
+ _("{0} units of [{1}](/app/Form/Item/{1}) found in [{2}](/app/Form/Warehouse/{2})").format(
+ qty, item[0], warehouse.name
+ )
+ )
found = True
if not found:
- out.append(_('[{0}](/app/Form/Item/{0}) is out of stock').format(item[0]))
+ out.append(_("[{0}](/app/Form/Item/{0}) is out of stock").format(item[0]))
return "\n\n".join(out)
diff --git a/erpnext/utilities/bulk_transaction.py b/erpnext/utilities/bulk_transaction.py
index 64e2ff42184..bfcba0766eb 100644
--- a/erpnext/utilities/bulk_transaction.py
+++ b/erpnext/utilities/bulk_transaction.py
@@ -45,9 +45,13 @@ def job(deserialized_data, from_doctype, to_doctype):
frappe.db.rollback(save_point="before_creation_state")
failed_history.append(e)
failed.append(e)
- update_logger(doc_name, e, from_doctype, to_doctype, status="Failed", log_date=str(date.today()))
+ update_logger(
+ doc_name, e, from_doctype, to_doctype, status="Failed", log_date=str(date.today())
+ )
if not failed:
- update_logger(doc_name, None, from_doctype, to_doctype, status="Success", log_date=str(date.today()))
+ update_logger(
+ doc_name, None, from_doctype, to_doctype, status="Success", log_date=str(date.today())
+ )
show_job_status(failed_history, deserialized_data, to_doctype)
@@ -96,7 +100,7 @@ def task(doc_name, from_doctype, to_doctype):
},
"Purchase Receipt": {"Purchase Invoice": purchase_receipt.make_purchase_invoice},
}
- if to_doctype in ['Advance Payment', 'Payment']:
+ if to_doctype in ["Advance Payment", "Payment"]:
obj = mapper[from_doctype][to_doctype](from_doctype, doc_name)
else:
obj = mapper[from_doctype][to_doctype](doc_name)
@@ -142,9 +146,7 @@ def update_logger(doc_name, e, from_doctype, to_doctype, status, log_date=None,
else:
log_doc = get_logger_doc(log_date)
if record_exists(log_doc, doc_name, status):
- append_data_to_logger(
- log_doc, doc_name, e, from_doctype, to_doctype, status, restarted
- )
+ append_data_to_logger(log_doc, doc_name, e, from_doctype, to_doctype, status, restarted)
log_doc.save()
@@ -158,20 +160,20 @@ def show_job_status(failed_history, deserialized_data, to_doctype):
if len(failed_history) != 0 and len(failed_history) < len(deserialized_data):
frappe.msgprint(
- _("""Creation of {0} partially successful.
- Check
Bulk Transaction Log""").format(
- to_doctype
- ),
+ _(
+ """Creation of {0} partially successful.
+ Check
Bulk Transaction Log"""
+ ).format(to_doctype),
title="Partially successful",
indicator="orange",
)
if len(failed_history) == len(deserialized_data):
frappe.msgprint(
- _("""Creation of {0} failed.
- Check
Bulk Transaction Log""").format(
- to_doctype
- ),
+ _(
+ """Creation of {0} failed.
+ Check
Bulk Transaction Log"""
+ ).format(to_doctype),
title="Failed",
indicator="red",
)
@@ -198,4 +200,4 @@ def mark_retrired_transaction(log_doc, doc_name):
log_doc.save()
- return record
\ No newline at end of file
+ return record
diff --git a/erpnext/utilities/doctype/rename_tool/rename_tool.py b/erpnext/utilities/doctype/rename_tool/rename_tool.py
index 74de54ac310..b31574cdc85 100644
--- a/erpnext/utilities/doctype/rename_tool/rename_tool.py
+++ b/erpnext/utilities/doctype/rename_tool/rename_tool.py
@@ -12,14 +12,19 @@ from frappe.model.rename_doc import bulk_rename
class RenameTool(Document):
pass
+
@frappe.whitelist()
def get_doctypes():
- return frappe.db.sql_list("""select name from tabDocType
- where allow_rename=1 and module!='Core' order by name""")
+ return frappe.db.sql_list(
+ """select name from tabDocType
+ where allow_rename=1 and module!='Core' order by name"""
+ )
+
@frappe.whitelist()
def upload(select_doctype=None, rows=None):
from frappe.utils.csvutils import read_csv_content_from_attached_file
+
if not select_doctype:
select_doctype = frappe.form_dict.select_doctype
diff --git a/erpnext/utilities/doctype/sms_log/test_sms_log.py b/erpnext/utilities/doctype/sms_log/test_sms_log.py
index 5f7abdc1a86..3ff02023882 100644
--- a/erpnext/utilities/doctype/sms_log/test_sms_log.py
+++ b/erpnext/utilities/doctype/sms_log/test_sms_log.py
@@ -5,5 +5,6 @@ import unittest
# test_records = frappe.get_test_records('SMS Log')
+
class TestSMSLog(unittest.TestCase):
pass
diff --git a/erpnext/utilities/doctype/video/video.js b/erpnext/utilities/doctype/video/video.js
index 9cb5a155ade..e6c6efbbb7c 100644
--- a/erpnext/utilities/doctype/video/video.js
+++ b/erpnext/utilities/doctype/video/video.js
@@ -4,7 +4,7 @@
frappe.ui.form.on('Video', {
refresh: function (frm) {
frm.events.toggle_youtube_statistics_section(frm);
- frm.add_custom_button("Watch Video", () => frappe.help.show_video(frm.doc.url, frm.doc.title));
+ frm.add_custom_button(__("Watch Video"), () => frappe.help.show_video(frm.doc.url, frm.doc.title));
},
toggle_youtube_statistics_section: (frm) => {
diff --git a/erpnext/utilities/doctype/video/video.py b/erpnext/utilities/doctype/video/video.py
index ae13952bc7d..330812dc275 100644
--- a/erpnext/utilities/doctype/video/video.py
+++ b/erpnext/utilities/doctype/video/video.py
@@ -28,16 +28,17 @@ class Video(Document):
try:
video = api.get_video_by_id(video_id=self.youtube_video_id)
- video_stats = video.items[0].to_dict().get('statistics')
+ video_stats = video.items[0].to_dict().get("statistics")
- self.like_count = video_stats.get('likeCount')
- self.view_count = video_stats.get('viewCount')
- self.dislike_count = video_stats.get('dislikeCount')
- self.comment_count = video_stats.get('commentCount')
+ self.like_count = video_stats.get("likeCount")
+ self.view_count = video_stats.get("viewCount")
+ self.dislike_count = video_stats.get("dislikeCount")
+ self.comment_count = video_stats.get("commentCount")
except Exception:
title = "Failed to Update YouTube Statistics for Video: {0}".format(self.name)
- frappe.log_error(title + "\n\n" + frappe.get_traceback(), title=title)
+ frappe.log_error(title + "\n\n" + frappe.get_traceback(), title=title)
+
def is_tracking_enabled():
return frappe.db.get_single_value("Video Settings", "enable_youtube_tracking")
@@ -54,7 +55,9 @@ def get_frequency(value):
def update_youtube_data():
# Called every 30 minutes via hooks
- enable_youtube_tracking, frequency = frappe.db.get_value("Video Settings", "Video Settings", ["enable_youtube_tracking", "frequency"])
+ enable_youtube_tracking, frequency = frappe.db.get_value(
+ "Video Settings", "Video Settings", ["enable_youtube_tracking", "frequency"]
+ )
if not enable_youtube_tracking:
return
@@ -77,19 +80,21 @@ def get_formatted_ids(video_list):
for video in video_list:
ids.append(video.youtube_video_id)
- return ','.join(ids)
+ return ",".join(ids)
@frappe.whitelist()
def get_id_from_url(url):
"""
- Returns video id from url
- :param youtube url: String URL
+ Returns video id from url
+ :param youtube url: String URL
"""
if not isinstance(url, str):
frappe.throw(_("URL can only be a string"), title=_("Invalid URL"))
- pattern = re.compile(r'[a-z\:\//\.]+(youtube|youtu)\.(com|be)/(watch\?v=|embed/|.+\?v=)?([^"&?\s]{11})?')
+ pattern = re.compile(
+ r'[a-z\:\//\.]+(youtube|youtu)\.(com|be)/(watch\?v=|embed/|.+\?v=)?([^"&?\s]{11})?'
+ )
id = pattern.match(url)
return id.groups()[-1]
@@ -105,7 +110,7 @@ def batch_update_youtube_data():
return video_stats
except Exception:
title = "Failed to Update YouTube Statistics"
- frappe.log_error(title + "\n\n" + frappe.get_traceback(), title=title)
+ frappe.log_error(title + "\n\n" + frappe.get_traceback(), title=title)
def prepare_and_set_data(video_list):
video_ids = get_formatted_ids(video_list)
@@ -114,24 +119,27 @@ def batch_update_youtube_data():
def set_youtube_data(entries):
for entry in entries:
- video_stats = entry.to_dict().get('statistics')
- video_id = entry.to_dict().get('id')
+ video_stats = entry.to_dict().get("statistics")
+ video_id = entry.to_dict().get("id")
stats = {
- 'like_count' : video_stats.get('likeCount'),
- 'view_count' : video_stats.get('viewCount'),
- 'dislike_count' : video_stats.get('dislikeCount'),
- 'comment_count' : video_stats.get('commentCount'),
- 'video_id': video_id
+ "like_count": video_stats.get("likeCount"),
+ "view_count": video_stats.get("viewCount"),
+ "dislike_count": video_stats.get("dislikeCount"),
+ "comment_count": video_stats.get("commentCount"),
+ "video_id": video_id,
}
- frappe.db.sql("""
+ frappe.db.sql(
+ """
UPDATE `tabVideo`
SET
like_count = %(like_count)s,
view_count = %(view_count)s,
dislike_count = %(dislike_count)s,
comment_count = %(comment_count)s
- WHERE youtube_video_id = %(video_id)s""", stats)
+ WHERE youtube_video_id = %(video_id)s""",
+ stats,
+ )
video_list = frappe.get_all("Video", fields=["youtube_video_id"])
if len(video_list) > 50:
diff --git a/erpnext/utilities/doctype/video_settings/video_settings.py b/erpnext/utilities/doctype/video_settings/video_settings.py
index 6f1e2bba167..97fbc41934b 100644
--- a/erpnext/utilities/doctype/video_settings/video_settings.py
+++ b/erpnext/utilities/doctype/video_settings/video_settings.py
@@ -18,5 +18,5 @@ class VideoSettings(Document):
build("youtube", "v3", developerKey=self.api_key)
except Exception:
title = _("Failed to Authenticate the API key.")
- frappe.log_error(title + "\n\n" + frappe.get_traceback(), title=title)
+ frappe.log_error(title + "\n\n" + frappe.get_traceback(), title=title)
frappe.throw(title + " Please check the error logs.", title=_("Invalid Credentials"))
diff --git a/erpnext/utilities/hierarchy_chart.py b/erpnext/utilities/hierarchy_chart.py
index c18ce107add..4bf4353cdfc 100644
--- a/erpnext/utilities/hierarchy_chart.py
+++ b/erpnext/utilities/hierarchy_chart.py
@@ -8,11 +8,11 @@ from frappe import _
@frappe.whitelist()
def get_all_nodes(method, company):
- '''Recursively gets all data from nodes'''
+ """Recursively gets all data from nodes"""
method = frappe.get_attr(method)
if method not in frappe.whitelisted:
- frappe.throw(_('Not Permitted'), frappe.PermissionError)
+ frappe.throw(_("Not Permitted"), frappe.PermissionError)
root_nodes = method(company=company)
result = []
@@ -21,14 +21,16 @@ def get_all_nodes(method, company):
for root in root_nodes:
data = method(root.id, company)
result.append(dict(parent=root.id, parent_name=root.name, data=data))
- nodes_to_expand.extend([{'id': d.get('id'), 'name': d.get('name')} for d in data if d.get('expandable')])
+ nodes_to_expand.extend(
+ [{"id": d.get("id"), "name": d.get("name")} for d in data if d.get("expandable")]
+ )
while nodes_to_expand:
parent = nodes_to_expand.pop(0)
- data = method(parent.get('id'), company)
- result.append(dict(parent=parent.get('id'), parent_name=parent.get('name'), data=data))
+ data = method(parent.get("id"), company)
+ result.append(dict(parent=parent.get("id"), parent_name=parent.get("name"), data=data))
for d in data:
- if d.get('expandable'):
- nodes_to_expand.append({'id': d.get('id'), 'name': d.get('name')})
+ if d.get("expandable"):
+ nodes_to_expand.append({"id": d.get("id"), "name": d.get("name")})
return result
diff --git a/erpnext/utilities/product.py b/erpnext/utilities/product.py
index 0a450024224..04ee0b3b1eb 100644
--- a/erpnext/utilities/product.py
+++ b/erpnext/utilities/product.py
@@ -9,32 +9,41 @@ from erpnext.stock.doctype.batch.batch import get_batch_qty
def get_web_item_qty_in_stock(item_code, item_warehouse_field, warehouse=None):
- in_stock, stock_qty = 0, ''
- template_item_code, is_stock_item = frappe.db.get_value("Item", item_code, ["variant_of", "is_stock_item"])
+ in_stock, stock_qty = 0, ""
+ template_item_code, is_stock_item = frappe.db.get_value(
+ "Item", item_code, ["variant_of", "is_stock_item"]
+ )
if not warehouse:
warehouse = frappe.db.get_value("Website Item", {"item_code": item_code}, item_warehouse_field)
if not warehouse and template_item_code and template_item_code != item_code:
- warehouse = frappe.db.get_value("Website Item", {"item_code": template_item_code}, item_warehouse_field)
+ warehouse = frappe.db.get_value(
+ "Website Item", {"item_code": template_item_code}, item_warehouse_field
+ )
if warehouse:
- stock_qty = frappe.db.sql("""
+ stock_qty = frappe.db.sql(
+ """
select GREATEST(S.actual_qty - S.reserved_qty - S.reserved_qty_for_production - S.reserved_qty_for_sub_contract, 0) / IFNULL(C.conversion_factor, 1)
from tabBin S
inner join `tabItem` I on S.item_code = I.Item_code
left join `tabUOM Conversion Detail` C on I.sales_uom = C.uom and C.parent = I.Item_code
- where S.item_code=%s and S.warehouse=%s""", (item_code, warehouse))
+ where S.item_code=%s and S.warehouse=%s""",
+ (item_code, warehouse),
+ )
if stock_qty:
stock_qty = adjust_qty_for_expired_items(item_code, stock_qty, warehouse)
in_stock = stock_qty[0][0] > 0 and 1 or 0
- return frappe._dict({"in_stock": in_stock, "stock_qty": stock_qty, "is_stock_item": is_stock_item})
+ return frappe._dict(
+ {"in_stock": in_stock, "stock_qty": stock_qty, "is_stock_item": is_stock_item}
+ )
def adjust_qty_for_expired_items(item_code, stock_qty, warehouse):
- batches = frappe.get_all('Batch', filters=[{'item': item_code}], fields=['expiry_date', 'name'])
+ batches = frappe.get_all("Batch", filters=[{"item": item_code}], fields=["expiry_date", "name"])
expired_batches = get_expired_batches(batches)
stock_qty = [list(item) for item in stock_qty]
@@ -67,33 +76,42 @@ def qty_from_all_warehouses(batch_info):
return qty
+
def get_price(item_code, price_list, customer_group, company, qty=1):
from erpnext.e_commerce.shopping_cart.cart import get_party
template_item_code = frappe.db.get_value("Item", item_code, "variant_of")
if price_list:
- price = frappe.get_all("Item Price", fields=["price_list_rate", "currency"],
- filters={"price_list": price_list, "item_code": item_code})
+ price = frappe.get_all(
+ "Item Price",
+ fields=["price_list_rate", "currency"],
+ filters={"price_list": price_list, "item_code": item_code},
+ )
if template_item_code and not price:
- price = frappe.get_all("Item Price", fields=["price_list_rate", "currency"],
- filters={"price_list": price_list, "item_code": template_item_code})
+ price = frappe.get_all(
+ "Item Price",
+ fields=["price_list_rate", "currency"],
+ filters={"price_list": price_list, "item_code": template_item_code},
+ )
if price:
party = get_party()
- pricing_rule_dict = frappe._dict({
- "item_code": item_code,
- "qty": qty,
- "stock_qty": qty,
- "transaction_type": "selling",
- "price_list": price_list,
- "customer_group": customer_group,
- "company": company,
- "conversion_rate": 1,
- "for_shopping_cart": True,
- "currency": frappe.db.get_value("Price List", price_list, "currency")
- })
+ pricing_rule_dict = frappe._dict(
+ {
+ "item_code": item_code,
+ "qty": qty,
+ "stock_qty": qty,
+ "transaction_type": "selling",
+ "price_list": price_list,
+ "customer_group": customer_group,
+ "company": company,
+ "conversion_rate": 1,
+ "for_shopping_cart": True,
+ "currency": frappe.db.get_value("Price List", price_list, "currency"),
+ }
+ )
if party and party.doctype == "Customer":
pricing_rule_dict.update({"customer": party.name})
@@ -108,7 +126,9 @@ def get_price(item_code, price_list, customer_group, company, qty=1):
if pricing_rule.pricing_rule_for == "Discount Percentage":
price_obj.discount_percent = pricing_rule.discount_percentage
price_obj.formatted_discount_percent = str(flt(pricing_rule.discount_percentage, 0)) + "%"
- price_obj.price_list_rate = flt(price_obj.price_list_rate * (1.0 - (flt(pricing_rule.discount_percentage) / 100.0)))
+ price_obj.price_list_rate = flt(
+ price_obj.price_list_rate * (1.0 - (flt(pricing_rule.discount_percentage) / 100.0))
+ )
if pricing_rule.pricing_rule_for == "Rate":
rate_discount = flt(mrp) - flt(pricing_rule.price_list_rate)
@@ -117,21 +137,33 @@ def get_price(item_code, price_list, customer_group, company, qty=1):
price_obj.price_list_rate = pricing_rule.price_list_rate or 0
if price_obj:
- price_obj["formatted_price"] = fmt_money(price_obj["price_list_rate"], currency=price_obj["currency"])
+ price_obj["formatted_price"] = fmt_money(
+ price_obj["price_list_rate"], currency=price_obj["currency"]
+ )
if mrp != price_obj["price_list_rate"]:
price_obj["formatted_mrp"] = fmt_money(mrp, currency=price_obj["currency"])
- price_obj["currency_symbol"] = not cint(frappe.db.get_default("hide_currency_symbol")) \
- and (frappe.db.get_value("Currency", price_obj.currency, "symbol", cache=True) or price_obj.currency) \
+ price_obj["currency_symbol"] = (
+ not cint(frappe.db.get_default("hide_currency_symbol"))
+ and (
+ frappe.db.get_value("Currency", price_obj.currency, "symbol", cache=True)
+ or price_obj.currency
+ )
or ""
+ )
- uom_conversion_factor = frappe.db.sql("""select C.conversion_factor
+ uom_conversion_factor = frappe.db.sql(
+ """select C.conversion_factor
from `tabUOM Conversion Detail` C
inner join `tabItem` I on C.parent = I.name and C.uom = I.sales_uom
- where I.name = %s""", item_code)
+ where I.name = %s""",
+ item_code,
+ )
uom_conversion_factor = uom_conversion_factor[0][0] if uom_conversion_factor else 1
- price_obj["formatted_price_sales_uom"] = fmt_money(price_obj["price_list_rate"] * uom_conversion_factor, currency=price_obj["currency"])
+ price_obj["formatted_price_sales_uom"] = fmt_money(
+ price_obj["price_list_rate"] * uom_conversion_factor, currency=price_obj["currency"]
+ )
if not price_obj["price_list_rate"]:
price_obj["price_list_rate"] = 0
@@ -144,11 +176,17 @@ def get_price(item_code, price_list, customer_group, company, qty=1):
return price_obj
+
def get_non_stock_item_status(item_code, item_warehouse_field):
# if item is a product bundle, check if its bundle items are in stock
if frappe.db.exists("Product Bundle", item_code):
items = frappe.get_doc("Product Bundle", item_code).get_all_children()
- bundle_warehouse = frappe.db.get_value("Website Item", {"item_code": item_code}, item_warehouse_field)
- return all(get_web_item_qty_in_stock(d.item_code, item_warehouse_field, bundle_warehouse).in_stock for d in items)
+ bundle_warehouse = frappe.db.get_value(
+ "Website Item", {"item_code": item_code}, item_warehouse_field
+ )
+ return all(
+ get_web_item_qty_in_stock(d.item_code, item_warehouse_field, bundle_warehouse).in_stock
+ for d in items
+ )
else:
return 1
diff --git a/erpnext/utilities/report/youtube_interactions/youtube_interactions.py b/erpnext/utilities/report/youtube_interactions/youtube_interactions.py
index a185a7012c6..a2cb4e80cd8 100644
--- a/erpnext/utilities/report/youtube_interactions/youtube_interactions.py
+++ b/erpnext/utilities/report/youtube_interactions/youtube_interactions.py
@@ -16,98 +16,58 @@ def execute(filters=None):
chart_data, summary = get_chart_summary_data(data)
return columns, data, None, chart_data, summary
+
def get_columns():
return [
- {
- "label": _("Published Date"),
- "fieldname": "publish_date",
- "fieldtype": "Date",
- "width": 100
- },
- {
- "label": _("Title"),
- "fieldname": "title",
- "fieldtype": "Data",
- "width": 200
- },
- {
- "label": _("Duration"),
- "fieldname": "duration",
- "fieldtype": "Duration",
- "width": 100
- },
- {
- "label": _("Views"),
- "fieldname": "view_count",
- "fieldtype": "Float",
- "width": 200
- },
- {
- "label": _("Likes"),
- "fieldname": "like_count",
- "fieldtype": "Float",
- "width": 200
- },
- {
- "label": _("Dislikes"),
- "fieldname": "dislike_count",
- "fieldtype": "Float",
- "width": 100
- },
- {
- "label": _("Comments"),
- "fieldname": "comment_count",
- "fieldtype": "Float",
- "width": 100
- }
+ {"label": _("Published Date"), "fieldname": "publish_date", "fieldtype": "Date", "width": 100},
+ {"label": _("Title"), "fieldname": "title", "fieldtype": "Data", "width": 200},
+ {"label": _("Duration"), "fieldname": "duration", "fieldtype": "Duration", "width": 100},
+ {"label": _("Views"), "fieldname": "view_count", "fieldtype": "Float", "width": 200},
+ {"label": _("Likes"), "fieldname": "like_count", "fieldtype": "Float", "width": 200},
+ {"label": _("Dislikes"), "fieldname": "dislike_count", "fieldtype": "Float", "width": 100},
+ {"label": _("Comments"), "fieldname": "comment_count", "fieldtype": "Float", "width": 100},
]
+
def get_data(filters):
- return frappe.db.sql("""
+ return frappe.db.sql(
+ """
SELECT
publish_date, title, provider, duration,
view_count, like_count, dislike_count, comment_count
FROM `tabVideo`
WHERE view_count is not null
and publish_date between %(from_date)s and %(to_date)s
- ORDER BY view_count desc""", filters, as_dict=1)
+ ORDER BY view_count desc""",
+ filters,
+ as_dict=1,
+ )
+
def get_chart_summary_data(data):
labels, likes, views = [], [], []
total_views = 0
for row in data:
- labels.append(row.get('title'))
- likes.append(row.get('like_count'))
- views.append(row.get('view_count'))
- total_views += flt(row.get('view_count'))
-
+ labels.append(row.get("title"))
+ likes.append(row.get("like_count"))
+ views.append(row.get("view_count"))
+ total_views += flt(row.get("view_count"))
chart_data = {
- "data" : {
- "labels" : labels,
- "datasets" : [
- {
- "name" : "Likes",
- "values" : likes
- },
- {
- "name" : "Views",
- "values" : views
- }
- ]
+ "data": {
+ "labels": labels,
+ "datasets": [{"name": "Likes", "values": likes}, {"name": "Views", "values": views}],
},
"type": "bar",
- "barOptions": {
- "stacked": 1
- },
+ "barOptions": {"stacked": 1},
}
summary = [
{
"value": total_views,
"indicator": "Blue",
- "label": "Total Views",
+ "label": _("Total Views"),
"datatype": "Float",
}
]
diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py
index feea2284b78..73cbcd4094c 100644
--- a/erpnext/utilities/transaction_base.py
+++ b/erpnext/utilities/transaction_base.py
@@ -10,7 +10,9 @@ from frappe.utils import cint, cstr, flt, get_time, now_datetime
from erpnext.controllers.status_updater import StatusUpdater
-class UOMMustBeIntegerError(frappe.ValidationError): pass
+class UOMMustBeIntegerError(frappe.ValidationError):
+ pass
+
class TransactionBase(StatusUpdater):
def validate_posting_time(self):
@@ -18,69 +20,79 @@ class TransactionBase(StatusUpdater):
if frappe.flags.in_import and self.posting_date:
self.set_posting_time = 1
- if not getattr(self, 'set_posting_time', None):
+ if not getattr(self, "set_posting_time", None):
now = now_datetime()
- self.posting_date = now.strftime('%Y-%m-%d')
- self.posting_time = now.strftime('%H:%M:%S.%f')
+ self.posting_date = now.strftime("%Y-%m-%d")
+ self.posting_time = now.strftime("%H:%M:%S.%f")
elif self.posting_time:
try:
get_time(self.posting_time)
except ValueError:
- frappe.throw(_('Invalid Posting Time'))
+ frappe.throw(_("Invalid Posting Time"))
def add_calendar_event(self, opts, force=False):
- if cstr(self.contact_by) != cstr(self._prev.contact_by) or \
- cstr(self.contact_date) != cstr(self._prev.contact_date) or force or \
- (hasattr(self, "ends_on") and cstr(self.ends_on) != cstr(self._prev.ends_on)):
+ if (
+ cstr(self.contact_by) != cstr(self._prev.contact_by)
+ or cstr(self.contact_date) != cstr(self._prev.contact_date)
+ or force
+ or (hasattr(self, "ends_on") and cstr(self.ends_on) != cstr(self._prev.ends_on))
+ ):
self.delete_events()
self._add_calendar_event(opts)
def delete_events(self):
- participations = frappe.get_all("Event Participants", filters={"reference_doctype": self.doctype, "reference_docname": self.name,
- "parenttype": "Event"}, fields=["name", "parent"])
+ participations = frappe.get_all(
+ "Event Participants",
+ filters={
+ "reference_doctype": self.doctype,
+ "reference_docname": self.name,
+ "parenttype": "Event",
+ },
+ fields=["name", "parent"],
+ )
if participations:
for participation in participations:
- total_participants = frappe.get_all("Event Participants", filters={"parenttype": "Event", "parent": participation.parent})
+ total_participants = frappe.get_all(
+ "Event Participants", filters={"parenttype": "Event", "parent": participation.parent}
+ )
if len(total_participants) <= 1:
frappe.db.sql("delete from `tabEvent` where name='%s'" % participation.parent)
frappe.db.sql("delete from `tabEvent Participants` where name='%s'" % participation.name)
-
def _add_calendar_event(self, opts):
opts = frappe._dict(opts)
if self.contact_date:
- event = frappe.get_doc({
- "doctype": "Event",
- "owner": opts.owner or self.owner,
- "subject": opts.subject,
- "description": opts.description,
- "starts_on": self.contact_date,
- "ends_on": opts.ends_on,
- "event_type": "Private"
- })
-
- event.append('event_participants', {
- "reference_doctype": self.doctype,
- "reference_docname": self.name
+ event = frappe.get_doc(
+ {
+ "doctype": "Event",
+ "owner": opts.owner or self.owner,
+ "subject": opts.subject,
+ "description": opts.description,
+ "starts_on": self.contact_date,
+ "ends_on": opts.ends_on,
+ "event_type": "Private",
}
)
+ event.append(
+ "event_participants", {"reference_doctype": self.doctype, "reference_docname": self.name}
+ )
+
event.insert(ignore_permissions=True)
if frappe.db.exists("User", self.contact_by):
- frappe.share.add("Event", event.name, self.contact_by,
- flags={"ignore_share_permission": True})
+ frappe.share.add("Event", event.name, self.contact_by, flags={"ignore_share_permission": True})
def validate_uom_is_integer(self, uom_field, qty_fields):
validate_uom_is_integer(self, uom_field, qty_fields)
def validate_with_previous_doc(self, ref):
- self.exclude_fields = ["conversion_factor", "uom"] if self.get('is_return') else []
+ self.exclude_fields = ["conversion_factor", "uom"] if self.get("is_return") else []
for key, val in ref.items():
is_child = val.get("is_child_table")
@@ -105,8 +117,9 @@ class TransactionBase(StatusUpdater):
def compare_values(self, ref_doc, fields, doc=None):
for reference_doctype, ref_dn_list in ref_doc.items():
for reference_name in ref_dn_list:
- prevdoc_values = frappe.db.get_value(reference_doctype, reference_name,
- [d[0] for d in fields], as_dict=1)
+ prevdoc_values = frappe.db.get_value(
+ reference_doctype, reference_name, [d[0] for d in fields], as_dict=1
+ )
if not prevdoc_values:
frappe.throw(_("Invalid reference {0} {1}").format(reference_doctype, reference_name))
@@ -115,7 +128,6 @@ class TransactionBase(StatusUpdater):
if prevdoc_values[field] is not None and field not in self.exclude_fields:
self.validate_value(field, condition, prevdoc_values[field], doc)
-
def validate_rate_with_reference_doc(self, ref_details):
buying_doctypes = ["Purchase Order", "Purchase Invoice", "Purchase Receipt"]
@@ -131,17 +143,26 @@ class TransactionBase(StatusUpdater):
if d.get(ref_link_field):
ref_rate = frappe.db.get_value(ref_dt + " Item", d.get(ref_link_field), "rate")
- if abs(flt(d.rate - ref_rate, d.precision("rate"))) >= .01:
+ if abs(flt(d.rate - ref_rate, d.precision("rate"))) >= 0.01:
if action == "Stop":
- role_allowed_to_override = frappe.db.get_single_value(settings_doc, 'role_to_override_stop_action')
+ role_allowed_to_override = frappe.db.get_single_value(
+ settings_doc, "role_to_override_stop_action"
+ )
if role_allowed_to_override not in frappe.get_roles():
- frappe.throw(_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4})").format(
- d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate))
+ frappe.throw(
+ _("Row #{0}: Rate must be same as {1}: {2} ({3} / {4})").format(
+ d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate
+ )
+ )
else:
- frappe.msgprint(_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4})").format(
- d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate), title=_("Warning"), indicator="orange")
-
+ frappe.msgprint(
+ _("Row #{0}: Rate must be same as {1}: {2} ({3} / {4})").format(
+ d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate
+ ),
+ title=_("Warning"),
+ indicator="orange",
+ )
def get_link_filters(self, for_doctype):
if hasattr(self, "prev_link_mapper") and self.prev_link_mapper.get(for_doctype):
@@ -150,11 +171,7 @@ class TransactionBase(StatusUpdater):
values = filter(None, tuple(item.as_dict()[fieldname] for item in self.items))
if values:
- ret = {
- for_doctype : {
- "filters": [[for_doctype, "name", "in", values]]
- }
- }
+ ret = {for_doctype: {"filters": [[for_doctype, "name", "in", values]]}}
else:
ret = None
else:
@@ -163,17 +180,17 @@ class TransactionBase(StatusUpdater):
return ret
def reset_default_field_value(self, default_field: str, child_table: str, child_table_field: str):
- """ Reset "Set default X" fields on forms to avoid confusion.
+ """Reset "Set default X" fields on forms to avoid confusion.
- example:
- doc = {
- "set_from_warehouse": "Warehouse A",
- "items": [{"from_warehouse": "warehouse B"}, {"from_warehouse": "warehouse A"}],
- }
- Since this has dissimilar values in child table, the default field will be erased.
+ example:
+ doc = {
+ "set_from_warehouse": "Warehouse A",
+ "items": [{"from_warehouse": "warehouse B"}, {"from_warehouse": "warehouse A"}],
+ }
+ Since this has dissimilar values in child table, the default field will be erased.
- doc.reset_default_field_value("set_from_warehouse", "items", "from_warehouse")
- """
+ doc.reset_default_field_value("set_from_warehouse", "items", "from_warehouse")
+ """
child_table_values = set()
for row in self.get(child_table):
@@ -182,8 +199,11 @@ class TransactionBase(StatusUpdater):
if len(child_table_values) > 1:
self.set(default_field, None)
+
def delete_events(ref_type, ref_name):
- events = frappe.db.sql_list(""" SELECT
+ events = (
+ frappe.db.sql_list(
+ """ SELECT
distinct `tabEvent`.name
from
`tabEvent`, `tabEvent Participants`
@@ -191,18 +211,27 @@ def delete_events(ref_type, ref_name):
`tabEvent`.name = `tabEvent Participants`.parent
and `tabEvent Participants`.reference_doctype = %s
and `tabEvent Participants`.reference_docname = %s
- """, (ref_type, ref_name)) or []
+ """,
+ (ref_type, ref_name),
+ )
+ or []
+ )
if events:
frappe.delete_doc("Event", events, for_reload=True)
+
def validate_uom_is_integer(doc, uom_field, qty_fields, child_dt=None):
if isinstance(qty_fields, str):
qty_fields = [qty_fields]
distinct_uoms = list(set(d.get(uom_field) for d in doc.get_all_children()))
- integer_uoms = list(filter(lambda uom: frappe.db.get_value("UOM", uom,
- "must_be_whole_number", cache=True) or None, distinct_uoms))
+ integer_uoms = list(
+ filter(
+ lambda uom: frappe.db.get_value("UOM", uom, "must_be_whole_number", cache=True) or None,
+ distinct_uoms,
+ )
+ )
if not integer_uoms:
return
@@ -213,6 +242,11 @@ def validate_uom_is_integer(doc, uom_field, qty_fields, child_dt=None):
qty = d.get(f)
if qty:
if abs(cint(qty) - flt(qty)) > 0.0000001:
- frappe.throw(_("Row {1}: Quantity ({0}) cannot be a fraction. To allow this, disable '{2}' in UOM {3}.") \
- .format(qty, d.idx, frappe.bold(_("Must be Whole Number")), frappe.bold(d.get(uom_field))),
- UOMMustBeIntegerError)
+ frappe.throw(
+ _(
+ "Row {1}: Quantity ({0}) cannot be a fraction. To allow this, disable '{2}' in UOM {3}."
+ ).format(
+ qty, d.idx, frappe.bold(_("Must be Whole Number")), frappe.bold(d.get(uom_field))
+ ),
+ UOMMustBeIntegerError,
+ )
diff --git a/erpnext/www/all-products/index.py b/erpnext/www/all-products/index.py
index ffaead64f6a..fbf0dce0590 100644
--- a/erpnext/www/all-products/index.py
+++ b/erpnext/www/all-products/index.py
@@ -5,15 +5,18 @@ from erpnext.e_commerce.product_data_engine.filters import ProductFiltersBuilder
sitemap = 1
+
def get_context(context):
# Add homepage as parent
context.body_class = "product-page"
- context.parents = [{"name": frappe._("Home"), "route":"/"}]
+ context.parents = [{"name": frappe._("Home"), "route": "/"}]
filter_engine = ProductFiltersBuilder()
context.field_filters = filter_engine.get_field_filters()
context.attribute_filters = filter_engine.get_attribute_filters()
- context.page_length = cint(frappe.db.get_single_value('E Commerce Settings', 'products_per_page'))or 20
+ context.page_length = (
+ cint(frappe.db.get_single_value("E Commerce Settings", "products_per_page")) or 20
+ )
- context.no_cache = 1
\ No newline at end of file
+ context.no_cache = 1
diff --git a/erpnext/www/book_appointment/index.py b/erpnext/www/book_appointment/index.py
index 8cda3c1b90d..06e99da3f94 100644
--- a/erpnext/www/book_appointment/index.py
+++ b/erpnext/www/book_appointment/index.py
@@ -11,38 +11,46 @@ no_cache = 1
def get_context(context):
- is_enabled = frappe.db.get_single_value('Appointment Booking Settings', 'enable_scheduling')
+ is_enabled = frappe.db.get_single_value("Appointment Booking Settings", "enable_scheduling")
if is_enabled:
return context
else:
- frappe.redirect_to_message(_("Appointment Scheduling Disabled"), _("Appointment Scheduling has been disabled for this site"),
- http_status_code=302, indicator_color="red")
+ frappe.redirect_to_message(
+ _("Appointment Scheduling Disabled"),
+ _("Appointment Scheduling has been disabled for this site"),
+ http_status_code=302,
+ indicator_color="red",
+ )
raise frappe.Redirect
+
@frappe.whitelist(allow_guest=True)
def get_appointment_settings():
- settings = frappe.get_doc('Appointment Booking Settings')
- settings.holiday_list = frappe.get_doc('Holiday List', settings.holiday_list)
+ settings = frappe.get_doc("Appointment Booking Settings")
+ settings.holiday_list = frappe.get_doc("Holiday List", settings.holiday_list)
return settings
+
@frappe.whitelist(allow_guest=True)
def get_timezones():
import pytz
+
return pytz.all_timezones
+
@frappe.whitelist(allow_guest=True)
def get_appointment_slots(date, timezone):
# Convert query to local timezones
- format_string = '%Y-%m-%d %H:%M:%S'
- query_start_time = datetime.datetime.strptime(date + ' 00:00:00', format_string)
- query_end_time = datetime.datetime.strptime(date + ' 23:59:59', format_string)
+ format_string = "%Y-%m-%d %H:%M:%S"
+ query_start_time = datetime.datetime.strptime(date + " 00:00:00", format_string)
+ query_end_time = datetime.datetime.strptime(date + " 23:59:59", format_string)
query_start_time = convert_to_system_timezone(timezone, query_start_time)
query_end_time = convert_to_system_timezone(timezone, query_end_time)
now = convert_to_guest_timezone(timezone, datetime.datetime.now())
# Database queries
- settings = frappe.get_doc('Appointment Booking Settings')
- holiday_list = frappe.get_doc('Holiday List', settings.holiday_list)
+ settings = frappe.get_doc("Appointment Booking Settings")
+ holiday_list = frappe.get_doc("Holiday List", settings.holiday_list)
timeslots = get_available_slots_between(query_start_time, query_end_time, settings)
# Filter and convert timeslots
@@ -58,15 +66,15 @@ def get_appointment_slots(date, timezone):
converted_timeslots.append(dict(time=converted_timeslot, availability=True))
else:
converted_timeslots.append(dict(time=converted_timeslot, availability=False))
- date_required = datetime.datetime.strptime(date + ' 00:00:00', format_string).date()
+ date_required = datetime.datetime.strptime(date + " 00:00:00", format_string).date()
converted_timeslots = filter_timeslots(date_required, converted_timeslots)
return converted_timeslots
+
def get_available_slots_between(query_start_time, query_end_time, settings):
records = _get_records(query_start_time, query_end_time, settings)
timeslots = []
- appointment_duration = datetime.timedelta(
- minutes=settings.appointment_duration)
+ appointment_duration = datetime.timedelta(minutes=settings.appointment_duration)
for record in records:
if record.day_of_week == WEEKDAYS[query_start_time.weekday()]:
current_time = _deltatime_to_datetime(query_start_time, record.from_time)
@@ -82,33 +90,35 @@ def get_available_slots_between(query_start_time, query_end_time, settings):
@frappe.whitelist(allow_guest=True)
def create_appointment(date, time, tz, contact):
- format_string = '%Y-%m-%d %H:%M:%S'
+ format_string = "%Y-%m-%d %H:%M:%S"
scheduled_time = datetime.datetime.strptime(date + " " + time, format_string)
# Strip tzinfo from datetime objects since it's handled by the doctype
- scheduled_time = scheduled_time.replace(tzinfo = None)
+ scheduled_time = scheduled_time.replace(tzinfo=None)
scheduled_time = convert_to_system_timezone(tz, scheduled_time)
- scheduled_time = scheduled_time.replace(tzinfo = None)
+ scheduled_time = scheduled_time.replace(tzinfo=None)
# Create a appointment document from form
- appointment = frappe.new_doc('Appointment')
+ appointment = frappe.new_doc("Appointment")
appointment.scheduled_time = scheduled_time
contact = json.loads(contact)
- appointment.customer_name = contact.get('name', None)
- appointment.customer_phone_number = contact.get('number', None)
- appointment.customer_skype = contact.get('skype', None)
- appointment.customer_details = contact.get('notes', None)
- appointment.customer_email = contact.get('email', None)
- appointment.status = 'Open'
+ appointment.customer_name = contact.get("name", None)
+ appointment.customer_phone_number = contact.get("number", None)
+ appointment.customer_skype = contact.get("skype", None)
+ appointment.customer_details = contact.get("notes", None)
+ appointment.customer_email = contact.get("email", None)
+ appointment.status = "Open"
appointment.insert()
return appointment
+
# Helper Functions
def filter_timeslots(date, timeslots):
filtered_timeslots = []
for timeslot in timeslots:
- if(timeslot['time'].date() == date):
+ if timeslot["time"].date() == date:
filtered_timeslots.append(timeslot)
return filtered_timeslots
+
def convert_to_guest_timezone(guest_tz, datetimeobject):
guest_tz = pytz.timezone(guest_tz)
local_timezone = pytz.timezone(frappe.utils.get_time_zone())
@@ -116,15 +126,18 @@ def convert_to_guest_timezone(guest_tz, datetimeobject):
datetimeobject = datetimeobject.astimezone(guest_tz)
return datetimeobject
-def convert_to_system_timezone(guest_tz,datetimeobject):
+
+def convert_to_system_timezone(guest_tz, datetimeobject):
guest_tz = pytz.timezone(guest_tz)
datetimeobject = guest_tz.localize(datetimeobject)
system_tz = pytz.timezone(frappe.utils.get_time_zone())
datetimeobject = datetimeobject.astimezone(system_tz)
return datetimeobject
+
def check_availabilty(timeslot, settings):
- return frappe.db.count('Appointment', {'scheduled_time': timeslot}) < settings.number_of_agents
+ return frappe.db.count("Appointment", {"scheduled_time": timeslot}) < settings.number_of_agents
+
def _is_holiday(date, holiday_list):
for holiday in holiday_list.holidays:
@@ -136,7 +149,10 @@ def _is_holiday(date, holiday_list):
def _get_records(start_time, end_time, settings):
records = []
for record in settings.availability_of_slots:
- if record.day_of_week == WEEKDAYS[start_time.weekday()] or record.day_of_week == WEEKDAYS[end_time.weekday()]:
+ if (
+ record.day_of_week == WEEKDAYS[start_time.weekday()]
+ or record.day_of_week == WEEKDAYS[end_time.weekday()]
+ ):
records.append(record)
return records
@@ -148,4 +164,4 @@ def _deltatime_to_datetime(date, deltatime):
def _datetime_to_deltatime(date_time):
midnight = datetime.datetime.combine(date_time.date(), datetime.time.min)
- return (date_time-midnight)
+ return date_time - midnight
diff --git a/erpnext/www/book_appointment/verify/index.py b/erpnext/www/book_appointment/verify/index.py
index dc36f4fc975..1a5ba9de7e5 100644
--- a/erpnext/www/book_appointment/verify/index.py
+++ b/erpnext/www/book_appointment/verify/index.py
@@ -8,11 +8,11 @@ def get_context(context):
context.success = False
return context
- email = frappe.form_dict['email']
- appointment_name = frappe.form_dict['appointment']
+ email = frappe.form_dict["email"]
+ appointment_name = frappe.form_dict["appointment"]
if email and appointment_name:
- appointment = frappe.get_doc('Appointment',appointment_name)
+ appointment = frappe.get_doc("Appointment", appointment_name)
appointment.set_verified(email)
context.success = True
return context
diff --git a/erpnext/www/lms/content.py b/erpnext/www/lms/content.py
index b187a788657..99462ceeee5 100644
--- a/erpnext/www/lms/content.py
+++ b/erpnext/www/lms/content.py
@@ -4,28 +4,27 @@ import erpnext.education.utils as utils
no_cache = 1
+
def get_context(context):
# Load Query Parameters
try:
- program = frappe.form_dict['program']
- content = frappe.form_dict['content']
- content_type = frappe.form_dict['type']
- course = frappe.form_dict['course']
- topic = frappe.form_dict['topic']
+ program = frappe.form_dict["program"]
+ content = frappe.form_dict["content"]
+ content_type = frappe.form_dict["type"]
+ course = frappe.form_dict["course"]
+ topic = frappe.form_dict["topic"]
except KeyError:
- frappe.local.flags.redirect_location = '/lms'
+ frappe.local.flags.redirect_location = "/lms"
raise frappe.Redirect
-
# Check if user has access to the content
has_program_access = utils.allowed_program_access(program)
has_content_access = allowed_content_access(program, content, content_type)
if frappe.session.user == "Guest" or not has_program_access or not has_content_access:
- frappe.local.flags.redirect_location = '/lms'
+ frappe.local.flags.redirect_location = "/lms"
raise frappe.Redirect
-
# Set context for content to be displayer
context.content = frappe.get_doc(content_type, content).as_dict()
context.content_type = content_type
@@ -34,35 +33,43 @@ def get_context(context):
context.topic = topic
topic = frappe.get_doc("Topic", topic)
- content_list = [{'content_type':item.content_type, 'content':item.content} for item in topic.topic_content]
+ content_list = [
+ {"content_type": item.content_type, "content": item.content} for item in topic.topic_content
+ ]
# Set context for progress numbers
- context.position = content_list.index({'content': content, 'content_type': content_type})
+ context.position = content_list.index({"content": content, "content_type": content_type})
context.length = len(content_list)
# Set context for navigation
context.previous = get_previous_content(content_list, context.position)
context.next = get_next_content(content_list, context.position)
+
def get_next_content(content_list, current_index):
try:
return content_list[current_index + 1]
except IndexError:
return None
+
def get_previous_content(content_list, current_index):
if current_index == 0:
return None
else:
return content_list[current_index - 1]
+
def allowed_content_access(program, content, content_type):
- contents_of_program = frappe.db.sql("""select `tabTopic Content`.content, `tabTopic Content`.content_type
+ contents_of_program = frappe.db.sql(
+ """select `tabTopic Content`.content, `tabTopic Content`.content_type
from `tabCourse Topic`,
`tabProgram Course`,
`tabTopic Content`
where `tabCourse Topic`.parent = `tabProgram Course`.course
and `tabTopic Content`.parent = `tabCourse Topic`.topic
- and `tabProgram Course`.parent = %(program)s""", {'program': program})
+ and `tabProgram Course`.parent = %(program)s""",
+ {"program": program},
+ )
return (content, content_type) in contents_of_program
diff --git a/erpnext/www/lms/course.py b/erpnext/www/lms/course.py
index 012e25ce52f..840beee3ad2 100644
--- a/erpnext/www/lms/course.py
+++ b/erpnext/www/lms/course.py
@@ -4,23 +4,25 @@ import erpnext.education.utils as utils
no_cache = 1
+
def get_context(context):
try:
- program = frappe.form_dict['program']
- course_name = frappe.form_dict['name']
+ program = frappe.form_dict["program"]
+ course_name = frappe.form_dict["name"]
except KeyError:
- frappe.local.flags.redirect_location = '/lms'
+ frappe.local.flags.redirect_location = "/lms"
raise frappe.Redirect
context.education_settings = frappe.get_single("Education Settings")
- course = frappe.get_doc('Course', course_name)
+ course = frappe.get_doc("Course", course_name)
context.program = program
context.course = course
context.topics = course.get_topics()
- context.has_access = utils.allowed_program_access(context.program)
+ context.has_access = utils.allowed_program_access(context.program)
context.progress = get_topic_progress(context.topics, course, context.program)
+
def get_topic_progress(topics, course, program):
progress = {topic.name: utils.get_topic_progress(topic, course.name, program) for topic in topics}
return progress
diff --git a/erpnext/www/lms/index.py b/erpnext/www/lms/index.py
index 035f7d9f72c..782ac481a06 100644
--- a/erpnext/www/lms/index.py
+++ b/erpnext/www/lms/index.py
@@ -4,10 +4,11 @@ import erpnext.education.utils as utils
no_cache = 1
+
def get_context(context):
context.education_settings = frappe.get_single("Education Settings")
if not context.education_settings.enable_lms:
- frappe.local.flags.redirect_location = '/'
+ frappe.local.flags.redirect_location = "/"
raise frappe.Redirect
context.featured_programs = get_featured_programs()
diff --git a/erpnext/www/lms/profile.py b/erpnext/www/lms/profile.py
index 8cd2f24fdcb..c4c1cd78eb7 100644
--- a/erpnext/www/lms/profile.py
+++ b/erpnext/www/lms/profile.py
@@ -4,23 +4,34 @@ import erpnext.education.utils as utils
no_cache = 1
+
def get_context(context):
if frappe.session.user == "Guest":
- frappe.local.flags.redirect_location = '/lms'
+ frappe.local.flags.redirect_location = "/lms"
raise frappe.Redirect
context.student = utils.get_current_student()
if not context.student:
- context.student = frappe.get_doc('User', frappe.session.user)
+ context.student = frappe.get_doc("User", frappe.session.user)
context.progress = get_program_progress(context.student.name)
+
def get_program_progress(student):
- enrolled_programs = frappe.get_all("Program Enrollment", filters={'student':student}, fields=['program'])
+ enrolled_programs = frappe.get_all(
+ "Program Enrollment", filters={"student": student}, fields=["program"]
+ )
student_progress = []
for list_item in enrolled_programs:
program = frappe.get_doc("Program", list_item.program)
progress = utils.get_program_progress(program)
completion = utils.get_program_completion(program)
- student_progress.append({'program': program.program_name, 'name': program.name, 'progress':progress, 'completion': completion})
+ student_progress.append(
+ {
+ "program": program.program_name,
+ "name": program.name,
+ "progress": progress,
+ "completion": completion,
+ }
+ )
return student_progress
diff --git a/erpnext/www/lms/program.py b/erpnext/www/lms/program.py
index db2653a6d54..1df2aa5bacd 100644
--- a/erpnext/www/lms/program.py
+++ b/erpnext/www/lms/program.py
@@ -5,11 +5,12 @@ import erpnext.education.utils as utils
no_cache = 1
+
def get_context(context):
try:
- program = frappe.form_dict['program']
+ program = frappe.form_dict["program"]
except KeyError:
- frappe.local.flags.redirect_location = '/lms'
+ frappe.local.flags.redirect_location = "/lms"
raise frappe.Redirect
context.education_settings = frappe.get_single("Education Settings")
@@ -18,12 +19,14 @@ def get_context(context):
context.has_access = utils.allowed_program_access(program)
context.progress = get_course_progress(context.courses, context.program)
+
def get_program(program_name):
try:
- return frappe.get_doc('Program', program_name)
+ return frappe.get_doc("Program", program_name)
except frappe.DoesNotExistError:
frappe.throw(_("Program {0} does not exist.").format(program_name))
+
def get_course_progress(courses, program):
progress = {course.name: utils.get_course_progress(course, program) for course in courses}
return progress or {}
diff --git a/erpnext/www/lms/topic.py b/erpnext/www/lms/topic.py
index 17fc8f79925..7783211a41b 100644
--- a/erpnext/www/lms/topic.py
+++ b/erpnext/www/lms/topic.py
@@ -4,20 +4,22 @@ import erpnext.education.utils as utils
no_cache = 1
+
def get_context(context):
try:
- course = frappe.form_dict['course']
- program = frappe.form_dict['program']
- topic = frappe.form_dict['topic']
+ course = frappe.form_dict["course"]
+ program = frappe.form_dict["program"]
+ topic = frappe.form_dict["topic"]
except KeyError:
- frappe.local.flags.redirect_location = '/lms'
+ frappe.local.flags.redirect_location = "/lms"
raise frappe.Redirect
context.program = program
context.course = course
context.topic = frappe.get_doc("Topic", topic)
context.contents = get_contents(context.topic, course, program)
- context.has_access = utils.allowed_program_access(program)
+ context.has_access = utils.allowed_program_access(program)
+
def get_contents(topic, course, program):
student = utils.get_current_student()
@@ -27,19 +29,29 @@ def get_contents(topic, course, program):
progress = []
if contents:
for content in contents:
- if content.doctype in ('Article', 'Video'):
+ if content.doctype in ("Article", "Video"):
if student:
status = utils.check_content_completion(content.name, content.doctype, course_enrollment.name)
else:
status = True
- progress.append({'content': content, 'content_type': content.doctype, 'completed': status})
- elif content.doctype == 'Quiz':
+ progress.append({"content": content, "content_type": content.doctype, "completed": status})
+ elif content.doctype == "Quiz":
if student:
- status, score, result, time_taken = utils.check_quiz_completion(content, course_enrollment.name)
+ status, score, result, time_taken = utils.check_quiz_completion(
+ content, course_enrollment.name
+ )
else:
status = False
score = None
result = None
- progress.append({'content': content, 'content_type': content.doctype, 'completed': status, 'score': score, 'result': result})
+ progress.append(
+ {
+ "content": content,
+ "content_type": content.doctype,
+ "completed": status,
+ "score": score,
+ "result": result,
+ }
+ )
return progress
diff --git a/erpnext/www/payment_setup_certification.py b/erpnext/www/payment_setup_certification.py
index c65cddb5cac..5d62d60f5eb 100644
--- a/erpnext/www/payment_setup_certification.py
+++ b/erpnext/www/payment_setup_certification.py
@@ -2,18 +2,23 @@ import frappe
no_cache = 1
+
def get_context(context):
- if frappe.session.user != 'Guest':
+ if frappe.session.user != "Guest":
context.all_certifications = get_all_certifications_of_a_member()
context.show_sidebar = True
def get_all_certifications_of_a_member():
- '''Returns all certifications'''
+ """Returns all certifications"""
all_certifications = []
- all_certifications = frappe.db.sql(""" select cc.name,cc.from_date,cc.to_date,ca.amount,ca.currency
+ all_certifications = frappe.db.sql(
+ """ select cc.name,cc.from_date,cc.to_date,ca.amount,ca.currency
from `tabCertified Consultant` cc
inner join `tabCertification Application` ca
on cc.certification_application = ca.name
- where paid = 1 and email = %(user)s order by cc.to_date desc""" ,{'user': frappe.session.user},as_dict=True)
+ where paid = 1 and email = %(user)s order by cc.to_date desc""",
+ {"user": frappe.session.user},
+ as_dict=True,
+ )
return all_certifications
diff --git a/erpnext/www/shop-by-category/index.py b/erpnext/www/shop-by-category/index.py
index 09f97ba5ef7..8a92418d25e 100644
--- a/erpnext/www/shop-by-category/index.py
+++ b/erpnext/www/shop-by-category/index.py
@@ -3,6 +3,7 @@ from frappe import _
sitemap = 1
+
def get_context(context):
context.body_class = "product-page"
@@ -18,13 +19,9 @@ def get_context(context):
context.no_cache = 1
+
def get_slideshow(slideshow):
- values = {
- 'show_indicators': 1,
- 'show_controls': 1,
- 'rounded': 1,
- 'slider_name': "Categories"
- }
+ values = {"show_indicators": 1, "show_controls": 1, "rounded": 1, "slider_name": "Categories"}
slideshow = frappe.get_cached_doc("Website Slideshow", slideshow)
slides = slideshow.get({"doctype": "Website Slideshow Item"})
for index, slide in enumerate(slides, start=1):
@@ -37,9 +34,10 @@ def get_slideshow(slideshow):
return values
+
def get_tabs(categories):
tab_values = {
- 'title': _("Shop by Category"),
+ "title": _("Shop by Category"),
}
categorical_data = get_category_records(categories)
@@ -48,21 +46,19 @@ def get_tabs(categories):
# pre-render cards for each tab
tab_values[f"tab_{index + 1}_content"] = frappe.render_template(
"erpnext/www/shop-by-category/category_card_section.html",
- {"data": categorical_data[tab], "type": tab}
+ {"data": categorical_data[tab], "type": tab},
)
return tab_values
+
def get_category_records(categories):
categorical_data = {}
for category in categories:
if category == "item_group":
categorical_data["item_group"] = frappe.db.get_all(
"Item Group",
- filters={
- "parent_item_group": "All Item Groups",
- "show_in_website": 1
- },
- fields=["name", "parent_item_group", "is_group", "image", "route"]
+ filters={"parent_item_group": "All Item Groups", "show_in_website": 1},
+ fields=["name", "parent_item_group", "is_group", "image", "route"],
)
else:
doctype = frappe.unscrub(category)
@@ -73,4 +69,3 @@ def get_category_records(categories):
categorical_data[category] = frappe.db.get_all(doctype, fields=fields)
return categorical_data
-
diff --git a/erpnext/www/support/index.py b/erpnext/www/support/index.py
index 408ddf43a52..aa00e928804 100644
--- a/erpnext/www/support/index.py
+++ b/erpnext/www/support/index.py
@@ -3,7 +3,7 @@ import frappe
def get_context(context):
context.no_cache = 1
- context.align_greeting = ''
+ context.align_greeting = ""
setting = frappe.get_doc("Support Settings")
context.greeting_title = setting.greeting_title
@@ -16,18 +16,22 @@ def get_context(context):
if favorite_articles:
for article in favorite_articles:
name_list.append(article.name)
- for record in (frappe.get_all("Help Article",
+ for record in frappe.get_all(
+ "Help Article",
fields=["title", "content", "route", "category"],
- filters={"name": ['not in', tuple(name_list)], "published": 1},
- order_by="creation desc", limit=(6-len(favorite_articles)))):
+ filters={"name": ["not in", tuple(name_list)], "published": 1},
+ order_by="creation desc",
+ limit=(6 - len(favorite_articles)),
+ ):
favorite_articles.append(record)
context.favorite_article_list = get_favorite_articles(favorite_articles)
context.help_article_list = get_help_article_list()
+
def get_favorite_articles_by_page_view():
return frappe.db.sql(
- """
+ """
SELECT
t1.name as name,
t1.title as title,
@@ -43,32 +47,42 @@ def get_favorite_articles_by_page_view():
GROUP BY route
ORDER BY count DESC
LIMIT 6;
- """, as_dict=True)
+ """,
+ as_dict=True,
+ )
+
def get_favorite_articles(favorite_articles):
- favorite_article_list=[]
+ favorite_article_list = []
for article in favorite_articles:
description = frappe.utils.strip_html(article.content)
if len(description) > 120:
- description = description[:120] + '...'
+ description = description[:120] + "..."
favorite_article_dict = {
- 'title': article.title,
- 'description': description,
- 'route': article.route,
- 'category': article.category,
+ "title": article.title,
+ "description": description,
+ "route": article.route,
+ "category": article.category,
}
favorite_article_list.append(favorite_article_dict)
return favorite_article_list
+
def get_help_article_list():
- help_article_list=[]
+ help_article_list = []
category_list = frappe.get_all("Help Category", fields="name")
for category in category_list:
- help_articles = frappe.get_all("Help Article", fields="*", filters={"category": category.name, "published": 1}, order_by="modified desc", limit=5)
+ help_articles = frappe.get_all(
+ "Help Article",
+ fields="*",
+ filters={"category": category.name, "published": 1},
+ order_by="modified desc",
+ limit=5,
+ )
if help_articles:
help_aricles_per_caetgory = {
- 'category': category,
- 'articles': help_articles,
+ "category": category,
+ "articles": help_articles,
}
help_article_list.append(help_aricles_per_caetgory)
return help_article_list
diff --git a/requirements.txt b/requirements.txt
index 39591caf922..657054fb240 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,7 +1,7 @@
# frappe # https://github.com/frappe/frappe is installed during bench-init
gocardless-pro~=1.22.0
googlemaps
-pandas~=1.1.5
+pandas>=1.1.5,<2.0.0
plaid-python~=7.2.1
pycountry~=20.7.3
PyGithub~=1.55
@@ -10,4 +10,4 @@ python-youtube~=0.8.0
taxjar~=1.9.2
tweepy~=3.10.0
Unidecode~=1.2.0
-redisearch==2.0.0
\ No newline at end of file
+redisearch~=2.1.0
diff --git a/setup.py b/setup.py
index 81407004226..1faff0412f8 100644
--- a/setup.py
+++ b/setup.py
@@ -2,23 +2,22 @@ from setuptools import setup, find_packages
import re, ast
# get version from __version__ variable in erpnext/__init__.py
-_version_re = re.compile(r'__version__\s+=\s+(.*)')
+_version_re = re.compile(r"__version__\s+=\s+(.*)")
-with open('requirements.txt') as f:
- install_requires = f.read().strip().split('\n')
+with open("requirements.txt") as f:
+ install_requires = f.read().strip().split("\n")
-with open('erpnext/__init__.py', 'rb') as f:
- version = str(ast.literal_eval(_version_re.search(
- f.read().decode('utf-8')).group(1)))
+with open("erpnext/__init__.py", "rb") as f:
+ version = str(ast.literal_eval(_version_re.search(f.read().decode("utf-8")).group(1)))
setup(
- name='erpnext',
+ name="erpnext",
version=version,
- description='Open Source ERP',
- author='Frappe Technologies',
- author_email='info@erpnext.com',
+ description="Open Source ERP",
+ author="Frappe Technologies",
+ author_email="info@erpnext.com",
packages=find_packages(),
zip_safe=False,
include_package_data=True,
- install_requires=install_requires
+ install_requires=install_requires,
)