brotherton-erpnext/erpnext/stock/utils.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

604 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
2014-04-14 13:50:45 +00:00
import frappe
from frappe import _
2023-04-21 07:58:14 +00:00
from frappe.query_builder.functions import CombineDatetime, IfNull, Sum
from frappe.utils import cstr, flt, get_link_to_form, nowdate, nowtime
2017-12-15 06:43:50 +00:00
import erpnext
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
from erpnext.stock.serial_batch_bundle import BatchNoValuation, SerialNoValuation
from erpnext.stock.valuation import FIFOValuation, LIFOValuation
BarcodeScanResult = Dict[str, Optional[str]]
2014-02-14 10:17:51 +00:00
class InvalidWarehouseCompany(frappe.ValidationError):
pass
2022-03-28 13:22:46 +00:00
2021-12-10 07:09:38 +00:00
class PendingRepostingError(frappe.ValidationError):
pass
2022-03-28 13:22:46 +00:00
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
) """
values["warehouse"] = warehouse
if item_code:
conditions += " and `tabBin`.item_code = %(item_code)s"
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
2022-03-28 13:22:46 +00:00
)
stock_value = frappe.db.sql(query, values)
return stock_value
2022-03-28 13:22:46 +00:00
def get_stock_value_on(
warehouses: list | str = None, posting_date: str = None, item_code: str = None
) -> float:
if not posting_date:
posting_date = nowdate()
sle = frappe.qb.DocType("Stock Ledger Entry")
query = (
frappe.qb.from_(sle)
2023-04-21 07:58:14 +00:00
.select(IfNull(Sum(sle.stock_value_difference), 0))
.where((sle.posting_date <= posting_date) & (sle.is_cancelled == 0))
.orderby(CombineDatetime(sle.posting_date, sle.posting_time), order=frappe.qb.desc)
.orderby(sle.creation, order=frappe.qb.desc)
)
if warehouses:
if isinstance(warehouses, str):
warehouses = [warehouses]
warehouses = set(warehouses)
for wh in list(warehouses):
if frappe.db.get_value("Warehouse", wh, "is_group"):
warehouses.update(get_child_warehouses(wh))
query = query.where(sle.warehouse.isin(warehouses))
if item_code:
query = query.where(sle.item_code == item_code)
2023-04-21 07:58:14 +00:00
return query.run(as_list=True)[0][0]
2022-03-28 13:22:46 +00:00
@frappe.whitelist()
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
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,
"warehouse": warehouse,
"posting_date": posting_date,
"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)
return (
(last_entry.qty_after_transaction, last_entry.valuation_rate, serial_nos)
if last_entry
else (0.0, 0.0, None)
2022-03-28 13:22:46 +00:00
)
else:
return (
(last_entry.qty_after_transaction, last_entry.valuation_rate) if last_entry else (0.0, 0.0)
2022-03-28 13:22:46 +00:00
)
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)
sle = frappe.qb.DocType("Stock Ledger Entry")
2022-03-28 13:22:46 +00:00
2021-10-29 09:26:54 +00:00
stock_ledger_entries = (
frappe.qb.from_(sle)
2021-10-21 11:47:11 +00:00
.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
)
2021-10-21 11:47:11 +00:00
& (sle.is_cancelled == 0)
)
2021-10-29 11:00:12 +00:00
.orderby(sle.posting_date, sle.posting_time, sle.creation)
2021-10-21 11:47:11 +00:00
.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)
return "\n".join(serial_nos)
2022-03-28 13:22:46 +00:00
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"
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))
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:
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.get_cached_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_previous_sle, get_valuation_rate
2022-03-28 13:22:46 +00:00
if isinstance(args, str):
args = json.loads(args)
in_rate = None
item_details = frappe.get_cached_value(
"Item", args.get("item_code"), ["has_serial_no", "has_batch_no"], as_dict=1
)
if isinstance(args, dict):
args = frappe._dict(args)
2023-03-31 03:33:54 +00:00
if item_details and item_details.has_serial_no and args.get("serial_and_batch_bundle"):
args.actual_qty = args.qty
sn_obj = SerialNoValuation(
sle=args,
warehouse=args.get("warehouse"),
item_code=args.get("item_code"),
)
in_rate = sn_obj.get_incoming_rate()
2023-03-31 03:33:54 +00:00
elif item_details and item_details.has_batch_no and args.get("serial_and_batch_bundle"):
args.actual_qty = args.qty
batch_obj = BatchNoValuation(
sle=args,
warehouse=args.get("warehouse"),
item_code=args.get("item_code"),
)
in_rate = batch_obj.get_incoming_rate()
else:
valuation_method = get_valuation_method(args.get("item_code"))
previous_sle = get_previous_sle(args)
if valuation_method in ("FIFO", "LIFO"):
2017-12-15 06:43:50 +00:00
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
2023-04-16 07:33:16 +00:00
else None
2022-03-28 13:22:46 +00:00
)
elif valuation_method == "Moving Average":
2023-04-16 07:33:16 +00:00
in_rate = previous_sle.get("valuation_rate")
if in_rate is None:
2023-04-18 11:20:09 +00:00
voucher_no = args.get("voucher_no") or args.get("name")
2017-12-15 06:43:50 +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,
)
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)
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]
)
2022-03-28 13:22:46 +00:00
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)
if not val_method:
val_method = (
frappe.db.get_value("Stock Settings", None, "valuation_method", cache=True) or "FIFO"
2022-03-28 13:22:46 +00:00
)
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
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:
2014-04-14 13:50:45 +00:00
frappe.throw(
_("Warehouse {0} does not belong to company {1}").format(warehouse, company),
InvalidWarehouseCompany,
)
2022-03-28 13:22:46 +00:00
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):
frappe.throw(
_("Disabled Warehouse {0} cannot be used for this transaction.").format(
get_link_to_form("Warehouse", warehouse)
2022-03-28 13:22:46 +00:00
)
)
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
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
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)
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 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]
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)
else:
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.get(row.get("item_code")) or 1.0
for_type = data.for_type
value_before_conversion = row.get(convertible_col)
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):
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""",
(voucher_type, voucher_no, item_code, voucher_detail_no),
)
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():
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
)
2022-03-28 13:22:46 +00:00
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],
}
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,
2021-12-10 07:09:38 +00:00
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) -> BarcodeScanResult:
def set_cache(data: BarcodeScanResult):
frappe.cache().set_value(f"erpnext:barcode_scan:{search_value}", data, expires_in_sec=120)
def get_cache() -> Optional[BarcodeScanResult]:
if data := frappe.cache().get_value(f"erpnext:barcode_scan:{search_value}"):
return data
if scan_data := get_cache():
return scan_data
# 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:
_update_item_info(barcode_data)
set_cache(barcode_data)
return 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:
_update_item_info(serial_no_data)
set_cache(serial_no_data)
return 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:
_update_item_info(batch_no_data)
set_cache(batch_no_data)
return 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