brotherton-erpnext/erpnext/stock/utils.py

600 lines
17 KiB
Python
Raw Normal View History

# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import json
from typing import Dict, Optional
import frappe
from frappe import _
from frappe.query_builder.functions import CombineDatetime
from frappe.utils import cstr, flt, get_link_to_form, nowdate, nowtime
import erpnext
from erpnext.stock.valuation import FIFOValuation, LIFOValuation
2022-03-28 13:22:46 +00:00
class InvalidWarehouseCompany(frappe.ValidationError):
pass
class PendingRepostingError(frappe.ValidationError):
pass
def get_stock_value_from_bin(warehouse=None, item_code=None):
values = {}
conditions = ""
if warehouse:
conditions += """ and `tabBin`.warehouse in (
select w2.name from `tabWarehouse` w1
join `tabWarehouse` w2 on
w1.name = %(warehouse)s
and w2.lft between w1.lft and w1.rgt
) """
2022-03-28 13:22:46 +00:00
values["warehouse"] = warehouse
if item_code:
conditions += " and `tabBin`.item_code = %(item_code)s"
2022-03-28 13:22:46 +00:00
values["item_code"] = item_code
2022-03-28 13:22:46 +00:00
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
2022-03-28 13:22:46 +00:00
def get_stock_value_on(warehouse=None, posting_date=None, item_code=None):
2022-03-28 13:22:46 +00:00
if not posting_date:
posting_date = nowdate()
values, condition = [posting_date], ""
if warehouse:
lft, rgt, is_group = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt", "is_group"])
2016-06-26 17:20:11 +00:00
if is_group:
values.extend([lft, rgt])
condition += "and exists (\
select name from `tabWarehouse` wh where wh.name = sle.warehouse\
and wh.lft >= %s and wh.rgt <= %s)"
else:
values.append(warehouse)
condition += " AND warehouse = %s"
if item_code:
values.append(item_code)
condition += " AND item_code = %s"
2022-03-28 13:22:46 +00:00
stock_ledger_entries = frappe.db.sql(
"""
SELECT item_code, stock_value, name, warehouse
FROM `tabStock Ledger Entry` sle
WHERE posting_date <= %s {0}
Repost item valuation (#24031) * feat: Reposting logic for future finished/transferred item * feat: added fields to identify needs to recalculate rate while reposting * refactor: Set rate for outgoing and finished items * refactor: Arranged fields in Stock Entry item table and added fields to identify finished and scrap item * refactor: Arranged fields in Stock Entry item table and added fields to identify finished and scrap item * refactor: Get outgoing rate for purchase return * refactor: Get incoming rate for sales return * test: Added tests for reposting valuation of transferred/finished/returned items * feat: added incoming rate field in DN, SI and Packed Item table * feat: get incoming rate for returned item * fix: no error while getting valuation rate in stock entry * fix: update stock ledger for DN and SI * feat: update item valuation rate in PR and PI based on supplied items cost * feat: SLE reposting logic for sales return and subcontracted item with test cases * feat: update qty in future sle * feat: repost future sle and gle via Repost Item Valuation * fix: Skip unwanted function calling while reposting * fix: repost sle for specific item and warehouse * test: Modified tests for backdated stock reco * fix: ignore cancelled sle in few methods * feat: role allowed to do backdated entry * feat: Show reposting status on stock valuation related reports * fix: minor fixes * fix: fixed sider issues * fix: serial no fix related to immutable ledger * fix: Test cases fixes related to perpetual inventory * fix: Test cases fixed * fix: Fixed reposting on cancel and test cases * feat: Restart reposting item valuation * refactor: Code cleanup using small functions and test case fixes * fix: minor fixes * fix: Raise on error while reposting item valuation * fix: minor fix * fix: Tests fixed * fix: skip some validation ig gle made from reposting * fix: test fixes * fix: debugging stock and account validation * fix: debugging stock and account validation * fix: debugging travis for stock and account sync validation * fix: debugging travis * fix: debugging travis * fix: debugging travis
2020-12-21 09:15:50 +00:00
and is_cancelled = 0
ORDER BY timestamp(posting_date, posting_time) DESC, creation DESC
2022-03-28 13:22:46 +00:00
""".format(
condition
),
values,
as_dict=1,
)
sle_map = {}
for sle in stock_ledger_entries:
if not (sle.item_code, sle.warehouse) in sle_map:
sle_map[(sle.item_code, sle.warehouse)] = flt(sle.stock_value)
return sum(sle_map.values())
2022-03-28 13:22:46 +00:00
@frappe.whitelist()
2022-03-28 13:22:46 +00:00
def get_stock_balance(
item_code,
warehouse,
posting_date=None,
posting_time=None,
with_valuation_rate=False,
with_serial_no=False,
):
2015-02-17 07:20:20 +00:00
"""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)"""
2015-02-20 09:41:56 +00:00
from erpnext.stock.stock_ledger import get_previous_sle
2022-03-28 13:22:46 +00:00
if posting_date is None:
posting_date = nowdate()
if posting_time is None:
posting_time = nowtime()
2015-02-20 09:41:56 +00:00
args = {
2015-02-20 09:41:56 +00:00
"item_code": item_code,
2022-03-28 13:22:46 +00:00
"warehouse": warehouse,
2015-02-20 09:41:56 +00:00
"posting_date": posting_date,
2022-03-28 13:22:46 +00:00
"posting_time": posting_time,
}
last_entry = get_previous_sle(args)
2015-02-17 07:20:20 +00:00
if with_valuation_rate:
if with_serial_no:
serial_nos = get_serial_nos_data_after_transactions(args)
2022-03-28 13:22:46 +00:00
return (
(last_entry.qty_after_transaction, last_entry.valuation_rate, serial_nos)
if last_entry
else (0.0, 0.0, None)
)
else:
2022-03-28 13:22:46 +00:00
return (
(last_entry.qty_after_transaction, last_entry.valuation_rate) if last_entry else (0.0, 0.0)
)
else:
2017-06-20 11:43:29 +00:00
return last_entry.qty_after_transaction if last_entry else 0.0
2022-03-28 13:22:46 +00:00
def get_serial_nos_data_after_transactions(args):
2021-10-21 11:47:11 +00:00
2021-10-29 09:26:54 +00:00
serial_nos = set()
2021-10-21 11:47:11 +00:00
args = frappe._dict(args)
2022-03-28 13:22:46 +00:00
sle = frappe.qb.DocType("Stock Ledger Entry")
stock_ledger_entries = (
frappe.qb.from_(sle)
.select("serial_no", "actual_qty")
.where(
(sle.item_code == args.item_code)
& (sle.warehouse == args.warehouse)
& (
CombineDatetime(sle.posting_date, sle.posting_time)
< CombineDatetime(args.posting_date, args.posting_time)
2022-03-28 13:22:46 +00:00
)
& (sle.is_cancelled == 0)
)
.orderby(sle.posting_date, sle.posting_time, sle.creation)
.run(as_dict=1)
)
2021-10-29 09:26:54 +00:00
for stock_ledger_entry in stock_ledger_entries:
changed_serial_no = get_serial_nos_data(stock_ledger_entry.serial_no)
if stock_ledger_entry.actual_qty > 0:
serial_nos.update(changed_serial_no)
else:
2021-10-29 09:26:54 +00:00
serial_nos.difference_update(changed_serial_no)
2022-03-28 13:22:46 +00:00
return "\n".join(serial_nos)
def get_serial_nos_data(serial_nos):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
2022-03-28 13:22:46 +00:00
return get_serial_nos(serial_nos)
2022-03-28 13:22:46 +00:00
@frappe.whitelist()
def get_latest_stock_qty(item_code, warehouse=None):
values, condition = [item_code], ""
if warehouse:
lft, rgt, is_group = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt", "is_group"])
if is_group:
values.extend([lft, rgt])
condition += "and exists (\
select name from `tabWarehouse` wh where wh.name = tabBin.warehouse\
and wh.lft >= %s and wh.rgt <= %s)"
else:
values.append(warehouse)
condition += " AND warehouse = %s"
2022-03-28 13:22:46 +00:00
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 = {}
2022-03-28 13:22:46 +00:00
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
2022-03-28 13:22:46 +00:00
def get_bin(item_code, warehouse):
2014-02-26 07:05:33 +00:00
bin = frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse})
if not bin:
bin_obj = _create_bin(item_code, warehouse)
else:
2022-03-28 13:22:46 +00:00
bin_obj = frappe.get_doc("Bin", bin, for_update=True)
bin_obj.flags.ignore_permissions = True
return bin_obj
2022-03-28 13:22:46 +00:00
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
2022-03-28 13:22:46 +00:00
def _create_bin(item_code, warehouse):
"""Create a bin and take care of concurrent inserts."""
bin_creation_savepoint = "create_bin"
try:
frappe.db.savepoint(bin_creation_savepoint)
bin_obj = frappe.get_doc(doctype="Bin", item_code=item_code, warehouse=warehouse)
bin_obj.flags.ignore_permissions = 1
bin_obj.insert()
except frappe.UniqueValidationError:
frappe.db.rollback(save_point=bin_creation_savepoint) # preserve transaction in postgres
bin_obj = frappe.get_last_doc("Bin", {"item_code": item_code, "warehouse": warehouse})
return bin_obj
2022-03-28 13:22:46 +00:00
@frappe.whitelist()
def get_incoming_rate(args, raise_error_if_no_rate=True):
"""Get Incoming Rate based on valuation method"""
from erpnext.stock.stock_ledger import (
get_batch_incoming_rate,
get_previous_sle,
get_valuation_rate,
)
2022-03-28 13:22:46 +00:00
2021-11-04 14:18:32 +00:00
if isinstance(args, str):
args = json.loads(args)
2022-03-28 13:22:46 +00:00
voucher_no = args.get("voucher_no") or args.get("name")
in_rate = None
2014-08-29 10:58:31 +00:00
if (args.get("serial_no") or "").strip():
in_rate = get_avg_purchase_rate(args.get("serial_no"))
2022-03-28 13:22:46 +00:00
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(
2022-03-28 13:22:46 +00:00
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"),
)
else:
valuation_method = get_valuation_method(args.get("item_code"))
previous_sle = get_previous_sle(args)
2022-03-28 13:22:46 +00:00
if valuation_method in ("FIFO", "LIFO"):
2017-12-15 06:43:50 +00:00
if previous_sle:
2022-03-28 13:22:46 +00:00
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:
2022-03-28 13:22:46 +00:00
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"),
)
2017-12-15 06:43:50 +00:00
Repost item valuation (#24031) * feat: Reposting logic for future finished/transferred item * feat: added fields to identify needs to recalculate rate while reposting * refactor: Set rate for outgoing and finished items * refactor: Arranged fields in Stock Entry item table and added fields to identify finished and scrap item * refactor: Arranged fields in Stock Entry item table and added fields to identify finished and scrap item * refactor: Get outgoing rate for purchase return * refactor: Get incoming rate for sales return * test: Added tests for reposting valuation of transferred/finished/returned items * feat: added incoming rate field in DN, SI and Packed Item table * feat: get incoming rate for returned item * fix: no error while getting valuation rate in stock entry * fix: update stock ledger for DN and SI * feat: update item valuation rate in PR and PI based on supplied items cost * feat: SLE reposting logic for sales return and subcontracted item with test cases * feat: update qty in future sle * feat: repost future sle and gle via Repost Item Valuation * fix: Skip unwanted function calling while reposting * fix: repost sle for specific item and warehouse * test: Modified tests for backdated stock reco * fix: ignore cancelled sle in few methods * feat: role allowed to do backdated entry * feat: Show reposting status on stock valuation related reports * fix: minor fixes * fix: fixed sider issues * fix: serial no fix related to immutable ledger * fix: Test cases fixes related to perpetual inventory * fix: Test cases fixed * fix: Fixed reposting on cancel and test cases * feat: Restart reposting item valuation * refactor: Code cleanup using small functions and test case fixes * fix: minor fixes * fix: Raise on error while reposting item valuation * fix: minor fix * fix: Tests fixed * fix: skip some validation ig gle made from reposting * fix: test fixes * fix: debugging stock and account validation * fix: debugging stock and account validation * fix: debugging travis for stock and account sync validation * fix: debugging travis * fix: debugging travis * fix: debugging travis
2020-12-21 09:15:50 +00:00
return flt(in_rate)
2022-03-28 13:22:46 +00:00
def get_avg_purchase_rate(serial_nos):
"""get average value of serial numbers"""
serial_nos = get_valid_serial_nos(serial_nos)
2022-03-28 13:22:46 +00:00
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"""
2022-03-28 13:22:46 +00:00
val_method = frappe.db.get_value("Item", item_code, "valuation_method", cache=True)
if not val_method:
2022-03-28 13:22:46 +00:00
val_method = (
frappe.db.get_value("Stock Settings", None, "valuation_method", cache=True) or "FIFO"
)
return val_method
2022-03-28 13:22:46 +00:00
def get_fifo_rate(previous_stock_queue, qty):
"""get FIFO (average) Rate from Queue"""
return _get_fifo_lifo_rate(previous_stock_queue, qty, "FIFO")
2022-03-28 13:22:46 +00:00
def get_lifo_rate(previous_stock_queue, qty):
"""get LIFO (average) Rate from Queue"""
return _get_fifo_lifo_rate(previous_stock_queue, qty, "LIFO")
def _get_fifo_lifo_rate(previous_stock_queue, qty, method):
ValuationKlass = LIFOValuation if method == "LIFO" else FIFOValuation
stock_queue = ValuationKlass(previous_stock_queue)
if flt(qty) >= 0:
total_qty, total_value = stock_queue.get_total_stock_and_value()
return total_value / total_qty if total_qty else 0.0
else:
popped_bins = stock_queue.remove_stock(abs(flt(qty)))
total_qty, total_value = ValuationKlass(popped_bins).get_total_stock_and_value()
return total_value / total_qty if total_qty else 0.0
2022-03-28 13:22:46 +00:00
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
2022-03-28 13:22:46 +00:00
serial_nos = cstr(sr_nos).strip().replace(",", "\n").split("\n")
valid_serial_nos = []
for val in serial_nos:
if val:
val = val.strip()
if val in valid_serial_nos:
2014-04-14 13:50:45 +00:00
frappe.throw(_("Serial number {0} entered more than once").format(val))
else:
valid_serial_nos.append(val)
if qty and len(valid_serial_nos) != abs(qty):
2014-04-14 13:50:45 +00:00
frappe.throw(_("{0} valid serial nos for Item {1}").format(abs(qty), item_code))
return valid_serial_nos
2013-03-06 13:20:53 +00:00
2022-03-28 13:22:46 +00:00
2013-10-10 10:34:40 +00:00
def validate_warehouse_company(warehouse, company):
warehouse_company = frappe.db.get_value("Warehouse", warehouse, "company", cache=True)
2013-10-10 10:34:40 +00:00
if warehouse_company and warehouse_company != company:
2022-03-28 13:22:46 +00:00
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"))
2022-03-28 13:22:46 +00:00
def validate_disabled_warehouse(warehouse):
if frappe.db.get_value("Warehouse", warehouse, "disabled", cache=True):
2022-03-28 13:22:46 +00:00
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:
return
convertible_cols = {}
is_dict_obj = False
if isinstance(result[0], dict):
is_dict_obj = True
convertible_columns = {}
for idx, d in enumerate(columns):
key = d.get("fieldname") if is_dict_obj else idx
if d.get("convertible"):
convertible_columns.setdefault(key, d.get("convertible"))
# Add new column to show qty/rate as per the selected UOM
2022-03-28 13:22:46 +00:00
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):
data = row.items() if is_dict_obj else enumerate(row)
for key, value in data:
if key not in convertible_columns:
continue
# If no conversion factor for the UOM, defaults to 1
if not conversion_factors[row_idx]:
conversion_factors[row_idx] = 1
2022-03-28 13:22:46 +00:00
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:
2022-03-28 13:22:46 +00:00
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])
for data in update_dict_values:
row, key, value = data
row[key] = value
2019-04-28 13:09:18 +00:00
2022-03-28 13:22:46 +00:00
def get_available_serial_nos(args):
2022-03-28 13:22:46 +00:00
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)
2022-03-28 13:22:46 +00:00
""",
args,
as_dict=1,
)
def add_additional_uom_columns(columns, result, include_uom, conversion_factors):
if not include_uom or not conversion_factors:
return
convertible_column_map = {}
for col_idx in list(reversed(range(0, len(columns)))):
col = columns[col_idx]
2022-03-28 13:22:46 +00:00
if isinstance(col, dict) and col.get("convertible") in ["rate", "qty"]:
next_col = col_idx + 1
columns.insert(next_col, col.copy())
2022-03-28 13:22:46 +00:00
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:
2022-03-28 13:22:46 +00:00
columns[next_col]["label"] += " ({})".format(include_uom)
for row_idx, row in enumerate(result):
for convertible_col, data in convertible_column_map.items():
2022-03-28 13:22:46 +00:00
conversion_factor = conversion_factors[row.get("item_code")] or 1
for_type = data.for_type
value_before_conversion = row.get(convertible_col)
2022-03-28 13:22:46 +00:00
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
feat: Immutable ledger (#18740) * fix: Reverse GL entry on cancellation of document * fix: Removed set posting time field for multiple docs * fix: Stop future reposting and reverse entry for purchase receipt and delivery note * fix: Change is_cancelled field from select to check * Revert "fix: Removed set posting time field for multiple docs" This reverts commit 81fb808db7da69ab1e58ba226c0cd0b77e5b90c8. * fix: Multiple fixes in GL Entry * fix: Remove future reporting from doctypes * fix: Canceled entry filters in Stock Ledger and General Ledger Report * fix: Remove print statement * fix: Validation for back dated entries * fix: Codacy fixes * fix: Add ignore links to multiple doctypes * fix: Codacy Fixes * fix: Ignore GL Entry and Stock Ledger entry while cancel * fix: Test case fixes * fix: Patch * fix: Codacy * fix: Budget Test Cases * fix: Patch * fix: Patch * fix: Multiple test cases * fix: changes in make_reverse_entry function * fix: Update patch * fix: Test Cases * fix: Test Case fixes * fix: Move patch upward in patches.txt * fix: Budget Test Cases * fix: Test Case and codacy * fix: Patch * fix: Minor label and UX fixes * fix: Move freezing date check * fix: Test Cases * fix: Test cases * fix: Test Cases * fix: Test Case * fix: Remove update_gl_entries_after function * fix: Remove update_gl_entries_after function * fix: Test Cases * fix: Fiscal Year wise backdated entry * fix: Update entries only for current SLE * fix: Remove is_cancelled * fix: Test Cases * fix: Test cases * fix: Test Cases * fix: Uncomment account and stock balance sync logic * fix: Stock balance and Account balance out of sync fixes * fix: Test Cases * fix: Test cases for POS, Stock Reco and Purchase Receipt * fix: Stock Reco tests * fix: Test stock reco precision * fix: Test stock reco for fifo precision * fix: Test stock reco for fifo precision * fix: Stock Entry test case Co-authored-by: Nabin Hait <nabinhait@gmail.com>
2020-04-30 05:08:58 +00:00
result[row_idx] = row
2022-03-28 13:22:46 +00:00
feat: Immutable ledger (#18740) * fix: Reverse GL entry on cancellation of document * fix: Removed set posting time field for multiple docs * fix: Stop future reposting and reverse entry for purchase receipt and delivery note * fix: Change is_cancelled field from select to check * Revert "fix: Removed set posting time field for multiple docs" This reverts commit 81fb808db7da69ab1e58ba226c0cd0b77e5b90c8. * fix: Multiple fixes in GL Entry * fix: Remove future reporting from doctypes * fix: Canceled entry filters in Stock Ledger and General Ledger Report * fix: Remove print statement * fix: Validation for back dated entries * fix: Codacy fixes * fix: Add ignore links to multiple doctypes * fix: Codacy Fixes * fix: Ignore GL Entry and Stock Ledger entry while cancel * fix: Test case fixes * fix: Patch * fix: Codacy * fix: Budget Test Cases * fix: Patch * fix: Patch * fix: Multiple test cases * fix: changes in make_reverse_entry function * fix: Update patch * fix: Test Cases * fix: Test Case fixes * fix: Move patch upward in patches.txt * fix: Budget Test Cases * fix: Test Case and codacy * fix: Patch * fix: Minor label and UX fixes * fix: Move freezing date check * fix: Test Cases * fix: Test cases * fix: Test Cases * fix: Test Case * fix: Remove update_gl_entries_after function * fix: Remove update_gl_entries_after function * fix: Test Cases * fix: Fiscal Year wise backdated entry * fix: Update entries only for current SLE * fix: Remove is_cancelled * fix: Test Cases * fix: Test cases * fix: Test Cases * fix: Uncomment account and stock balance sync logic * fix: Stock balance and Account balance out of sync fixes * fix: Test Cases * fix: Test cases for POS, Stock Reco and Purchase Receipt * fix: Stock Reco tests * fix: Test stock reco precision * fix: Test stock reco for fifo precision * fix: Test stock reco for fifo precision * fix: Stock Entry test case Co-authored-by: Nabin Hait <nabinhait@gmail.com>
2020-04-30 05:08:58 +00:00
def get_incoming_outgoing_rate_for_cancel(item_code, voucher_type, voucher_no, voucher_detail_no):
2022-03-28 13:22:46 +00:00
outgoing_rate = frappe.db.sql(
"""SELECT CASE WHEN actual_qty = 0 THEN 0 ELSE abs(stock_value_difference / actual_qty) END
feat: Immutable ledger (#18740) * fix: Reverse GL entry on cancellation of document * fix: Removed set posting time field for multiple docs * fix: Stop future reposting and reverse entry for purchase receipt and delivery note * fix: Change is_cancelled field from select to check * Revert "fix: Removed set posting time field for multiple docs" This reverts commit 81fb808db7da69ab1e58ba226c0cd0b77e5b90c8. * fix: Multiple fixes in GL Entry * fix: Remove future reporting from doctypes * fix: Canceled entry filters in Stock Ledger and General Ledger Report * fix: Remove print statement * fix: Validation for back dated entries * fix: Codacy fixes * fix: Add ignore links to multiple doctypes * fix: Codacy Fixes * fix: Ignore GL Entry and Stock Ledger entry while cancel * fix: Test case fixes * fix: Patch * fix: Codacy * fix: Budget Test Cases * fix: Patch * fix: Patch * fix: Multiple test cases * fix: changes in make_reverse_entry function * fix: Update patch * fix: Test Cases * fix: Test Case fixes * fix: Move patch upward in patches.txt * fix: Budget Test Cases * fix: Test Case and codacy * fix: Patch * fix: Minor label and UX fixes * fix: Move freezing date check * fix: Test Cases * fix: Test cases * fix: Test Cases * fix: Test Case * fix: Remove update_gl_entries_after function * fix: Remove update_gl_entries_after function * fix: Test Cases * fix: Fiscal Year wise backdated entry * fix: Update entries only for current SLE * fix: Remove is_cancelled * fix: Test Cases * fix: Test cases * fix: Test Cases * fix: Uncomment account and stock balance sync logic * fix: Stock balance and Account balance out of sync fixes * fix: Test Cases * fix: Test cases for POS, Stock Reco and Purchase Receipt * fix: Stock Reco tests * fix: Test stock reco precision * fix: Test stock reco for fifo precision * fix: Test stock reco for fifo precision * fix: Stock Entry test case Co-authored-by: Nabin Hait <nabinhait@gmail.com>
2020-04-30 05:08:58 +00:00
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""",
2022-03-28 13:22:46 +00:00
(voucher_type, voucher_no, item_code, voucher_detail_no),
)
feat: Immutable ledger (#18740) * fix: Reverse GL entry on cancellation of document * fix: Removed set posting time field for multiple docs * fix: Stop future reposting and reverse entry for purchase receipt and delivery note * fix: Change is_cancelled field from select to check * Revert "fix: Removed set posting time field for multiple docs" This reverts commit 81fb808db7da69ab1e58ba226c0cd0b77e5b90c8. * fix: Multiple fixes in GL Entry * fix: Remove future reporting from doctypes * fix: Canceled entry filters in Stock Ledger and General Ledger Report * fix: Remove print statement * fix: Validation for back dated entries * fix: Codacy fixes * fix: Add ignore links to multiple doctypes * fix: Codacy Fixes * fix: Ignore GL Entry and Stock Ledger entry while cancel * fix: Test case fixes * fix: Patch * fix: Codacy * fix: Budget Test Cases * fix: Patch * fix: Patch * fix: Multiple test cases * fix: changes in make_reverse_entry function * fix: Update patch * fix: Test Cases * fix: Test Case fixes * fix: Move patch upward in patches.txt * fix: Budget Test Cases * fix: Test Case and codacy * fix: Patch * fix: Minor label and UX fixes * fix: Move freezing date check * fix: Test Cases * fix: Test cases * fix: Test Cases * fix: Test Case * fix: Remove update_gl_entries_after function * fix: Remove update_gl_entries_after function * fix: Test Cases * fix: Fiscal Year wise backdated entry * fix: Update entries only for current SLE * fix: Remove is_cancelled * fix: Test Cases * fix: Test cases * fix: Test Cases * fix: Uncomment account and stock balance sync logic * fix: Stock balance and Account balance out of sync fixes * fix: Test Cases * fix: Test cases for POS, Stock Reco and Purchase Receipt * fix: Stock Reco tests * fix: Test stock reco precision * fix: Test stock reco for fifo precision * fix: Test stock reco for fifo precision * fix: Stock Entry test case Co-authored-by: Nabin Hait <nabinhait@gmail.com>
2020-04-30 05:08:58 +00:00
outgoing_rate = outgoing_rate[0][0] if outgoing_rate else 0.0
Repost item valuation (#24031) * feat: Reposting logic for future finished/transferred item * feat: added fields to identify needs to recalculate rate while reposting * refactor: Set rate for outgoing and finished items * refactor: Arranged fields in Stock Entry item table and added fields to identify finished and scrap item * refactor: Arranged fields in Stock Entry item table and added fields to identify finished and scrap item * refactor: Get outgoing rate for purchase return * refactor: Get incoming rate for sales return * test: Added tests for reposting valuation of transferred/finished/returned items * feat: added incoming rate field in DN, SI and Packed Item table * feat: get incoming rate for returned item * fix: no error while getting valuation rate in stock entry * fix: update stock ledger for DN and SI * feat: update item valuation rate in PR and PI based on supplied items cost * feat: SLE reposting logic for sales return and subcontracted item with test cases * feat: update qty in future sle * feat: repost future sle and gle via Repost Item Valuation * fix: Skip unwanted function calling while reposting * fix: repost sle for specific item and warehouse * test: Modified tests for backdated stock reco * fix: ignore cancelled sle in few methods * feat: role allowed to do backdated entry * feat: Show reposting status on stock valuation related reports * fix: minor fixes * fix: fixed sider issues * fix: serial no fix related to immutable ledger * fix: Test cases fixes related to perpetual inventory * fix: Test cases fixed * fix: Fixed reposting on cancel and test cases * feat: Restart reposting item valuation * refactor: Code cleanup using small functions and test case fixes * fix: minor fixes * fix: Raise on error while reposting item valuation * fix: minor fix * fix: Tests fixed * fix: skip some validation ig gle made from reposting * fix: test fixes * fix: debugging stock and account validation * fix: debugging stock and account validation * fix: debugging travis for stock and account sync validation * fix: debugging travis * fix: debugging travis * fix: debugging travis
2020-12-21 09:15:50 +00:00
return outgoing_rate
2022-03-28 13:22:46 +00:00
Repost item valuation (#24031) * feat: Reposting logic for future finished/transferred item * feat: added fields to identify needs to recalculate rate while reposting * refactor: Set rate for outgoing and finished items * refactor: Arranged fields in Stock Entry item table and added fields to identify finished and scrap item * refactor: Arranged fields in Stock Entry item table and added fields to identify finished and scrap item * refactor: Get outgoing rate for purchase return * refactor: Get incoming rate for sales return * test: Added tests for reposting valuation of transferred/finished/returned items * feat: added incoming rate field in DN, SI and Packed Item table * feat: get incoming rate for returned item * fix: no error while getting valuation rate in stock entry * fix: update stock ledger for DN and SI * feat: update item valuation rate in PR and PI based on supplied items cost * feat: SLE reposting logic for sales return and subcontracted item with test cases * feat: update qty in future sle * feat: repost future sle and gle via Repost Item Valuation * fix: Skip unwanted function calling while reposting * fix: repost sle for specific item and warehouse * test: Modified tests for backdated stock reco * fix: ignore cancelled sle in few methods * feat: role allowed to do backdated entry * feat: Show reposting status on stock valuation related reports * fix: minor fixes * fix: fixed sider issues * fix: serial no fix related to immutable ledger * fix: Test cases fixes related to perpetual inventory * fix: Test cases fixed * fix: Fixed reposting on cancel and test cases * feat: Restart reposting item valuation * refactor: Code cleanup using small functions and test case fixes * fix: minor fixes * fix: Raise on error while reposting item valuation * fix: minor fix * fix: Tests fixed * fix: skip some validation ig gle made from reposting * fix: test fixes * fix: debugging stock and account validation * fix: debugging stock and account validation * fix: debugging travis for stock and account sync validation * fix: debugging travis * fix: debugging travis * fix: debugging travis
2020-12-21 09:15:50 +00:00
def is_reposting_item_valuation_in_progress():
2022-03-28 13:22:46 +00:00
reposting_in_progress = frappe.db.exists(
"Repost Item Valuation", {"docstatus": 1, "status": ["in", ["Queued", "In Progress"]]}
)
Repost item valuation (#24031) * feat: Reposting logic for future finished/transferred item * feat: added fields to identify needs to recalculate rate while reposting * refactor: Set rate for outgoing and finished items * refactor: Arranged fields in Stock Entry item table and added fields to identify finished and scrap item * refactor: Arranged fields in Stock Entry item table and added fields to identify finished and scrap item * refactor: Get outgoing rate for purchase return * refactor: Get incoming rate for sales return * test: Added tests for reposting valuation of transferred/finished/returned items * feat: added incoming rate field in DN, SI and Packed Item table * feat: get incoming rate for returned item * fix: no error while getting valuation rate in stock entry * fix: update stock ledger for DN and SI * feat: update item valuation rate in PR and PI based on supplied items cost * feat: SLE reposting logic for sales return and subcontracted item with test cases * feat: update qty in future sle * feat: repost future sle and gle via Repost Item Valuation * fix: Skip unwanted function calling while reposting * fix: repost sle for specific item and warehouse * test: Modified tests for backdated stock reco * fix: ignore cancelled sle in few methods * feat: role allowed to do backdated entry * feat: Show reposting status on stock valuation related reports * fix: minor fixes * fix: fixed sider issues * fix: serial no fix related to immutable ledger * fix: Test cases fixes related to perpetual inventory * fix: Test cases fixed * fix: Fixed reposting on cancel and test cases * feat: Restart reposting item valuation * refactor: Code cleanup using small functions and test case fixes * fix: minor fixes * fix: Raise on error while reposting item valuation * fix: minor fix * fix: Tests fixed * fix: skip some validation ig gle made from reposting * fix: test fixes * fix: debugging stock and account validation * fix: debugging stock and account validation * fix: debugging travis for stock and account sync validation * fix: debugging travis * fix: debugging travis * fix: debugging travis
2020-12-21 09:15:50 +00:00
if reposting_in_progress:
2022-03-28 13:22:46 +00:00
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"]],
"posting_date": ["<=", posting_date],
}
2022-03-28 13:22:46 +00:00
reposting_pending = frappe.db.exists("Repost Item Valuation", filters)
if reposting_pending and throw_error:
2022-03-28 13:22:46 +00:00
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},
2022-06-01 11:13:56 +00:00
["barcode", "parent as item_code", "uom"],
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