refactor(treewide): formatting and ruff fixes, + manually enabled F401

Signed-off-by: Akhil Narang <me@akhilnarang.dev>
This commit is contained in:
Akhil Narang
2024-03-27 11:37:26 +05:30
parent c28d19cf7f
commit 4d34b1ead7
618 changed files with 4188 additions and 6384 deletions

View File

@@ -17,9 +17,9 @@ install_docs = [
def get_warehouse_account_map(company=None):
company_warehouse_account_map = company and frappe.flags.setdefault(
"warehouse_account_map", {}
).get(company)
company_warehouse_account_map = company and frappe.flags.setdefault("warehouse_account_map", {}).get(
company
)
warehouse_account_map = frappe.flags.warehouse_account_map
if not warehouse_account_map or not company_warehouse_account_map or frappe.flags.in_test:

View File

@@ -27,9 +27,7 @@ def get(
if filters and filters.get("company"):
warehouse_filters.append(["company", "=", filters.get("company")])
warehouses = frappe.get_list(
"Warehouse", pluck="name", filters=warehouse_filters, order_by="name"
)
warehouses = frappe.get_list("Warehouse", pluck="name", filters=warehouse_filters, order_by="name")
warehouses = frappe.get_list(
"Bin",

View File

@@ -165,9 +165,7 @@ 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
@@ -221,9 +219,7 @@ def get_batch_qty(
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

View File

@@ -81,9 +81,7 @@ class TestBatch(FrappeTestCase):
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"""
@@ -159,9 +157,7 @@ class TestBatch(FrappeTestCase):
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)
@@ -359,9 +355,7 @@ 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)

View File

@@ -58,9 +58,7 @@ class Bin(Document):
get_reserved_qty_for_sub_assembly,
)
reserved_qty_for_production_plan = get_reserved_qty_for_sub_assembly(
self.item_code, self.warehouse
)
reserved_qty_for_production_plan = get_reserved_qty_for_sub_assembly(self.item_code, self.warehouse)
if reserved_qty_for_production_plan is None and not self.reserved_qty_for_production_plan:
return
@@ -81,9 +79,7 @@ class Bin(Document):
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.db_set(
"reserved_qty_for_production", flt(self.reserved_qty_for_production), update_modified=True
@@ -131,9 +127,7 @@ class Bin(Document):
se_item = frappe.qb.DocType("Stock Entry Detail")
if frappe.db.field_exists("Stock Entry", "is_return"):
qty_field = (
Case().when(se.is_return == 1, se_item.transfer_qty * -1).else_(se_item.transfer_qty)
)
qty_field = Case().when(se.is_return == 1, se_item.transfer_qty * -1).else_(se_item.transfer_qty)
else:
qty_field = se_item.transfer_qty

View File

@@ -31,4 +31,4 @@ class TestBin(FrappeTestCase):
def test_index_exists(self):
indexes = frappe.db.sql("show index from tabBin where Non_unique = 0", as_dict=1)
if not any(index.get("Key_name") == "unique_item_warehouse" for index in indexes):
self.fail(f"Expected unique index on item-warehouse")
self.fail("Expected unique index on item-warehouse")

View File

@@ -126,6 +126,6 @@ def prepare_closing_stock_balance(name):
try:
doc.create_closing_stock_balance_entries()
doc.db_set("status", "Completed")
except Exception as e:
except Exception:
doc.db_set("status", "Failed")
doc.log_error(title="Closing Stock Balance Failed")

View File

@@ -20,7 +20,7 @@ form_grid_templates = {"items": "templates/form_grid/item_grid.html"}
class DeliveryNote(SellingController):
def __init__(self, *args, **kwargs):
super(DeliveryNote, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self.status_updater = [
{
"source_dt": "Delivery Note Item",
@@ -108,7 +108,7 @@ class DeliveryNote(SellingController):
for f in fieldname:
toggle_print_hide(self.meta if key == "parent" else item_meta, f)
super(DeliveryNote, self).before_print(settings)
super().before_print(settings)
def set_actual_qty(self):
for d in self.get("items"):
@@ -129,7 +129,7 @@ class DeliveryNote(SellingController):
def validate(self):
self.validate_posting_time()
super(DeliveryNote, self).validate()
super().validate()
self.validate_references()
self.set_status()
self.so_required()
@@ -159,11 +159,16 @@ class DeliveryNote(SellingController):
self.reset_default_field_value("set_warehouse", "items", "warehouse")
def validate_with_previous_doc(self):
super(DeliveryNote, self).validate_with_previous_doc(
super().validate_with_previous_doc(
{
"Sales Order": {
"ref_dn_field": "against_sales_order",
"compare_fields": [["customer", "="], ["company", "="], ["project", "="], ["currency", "="]],
"compare_fields": [
["customer", "="],
["company", "="],
["project", "="],
["currency", "="],
],
},
"Sales Order Item": {
"ref_dn_field": "so_detail",
@@ -173,7 +178,12 @@ class DeliveryNote(SellingController):
},
"Sales Invoice": {
"ref_dn_field": "against_sales_invoice",
"compare_fields": [["customer", "="], ["company", "="], ["project", "="], ["currency", "="]],
"compare_fields": [
["customer", "="],
["company", "="],
["project", "="],
["currency", "="],
],
},
"Sales Invoice Item": {
"ref_dn_field": "si_detail",
@@ -263,7 +273,7 @@ class DeliveryNote(SellingController):
)
def validate_warehouse(self):
super(DeliveryNote, self).validate_warehouse()
super().validate_warehouse()
for d in self.get_item_list():
if not d["warehouse"] and frappe.get_cached_value("Item", d["item_code"], "is_stock_item") == 1:
@@ -311,7 +321,7 @@ class DeliveryNote(SellingController):
self.repost_future_sle_and_gle()
def on_cancel(self):
super(DeliveryNote, self).on_cancel()
super().on_cancel()
self.check_sales_order_on_hold_or_close("against_sales_order")
self.check_next_docstatus()
@@ -851,7 +861,7 @@ def make_shipment(source_name, target_doc=None):
"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 = f"{user.full_name}"
if user:
if user.email:
pickup_contact_display += "<br>" + user.email
@@ -867,7 +877,7 @@ def make_shipment(source_name, target_doc=None):
contact = frappe.db.get_value(
"Contact", source.contact_person, ["email_id", "phone", "mobile_no"], as_dict=1
)
delivery_contact_display = "{}".format(source.contact_display)
delivery_contact_display = f"{source.contact_display}"
if contact:
if contact.email_id:
delivery_contact_display += "<br>" + contact.email_id
@@ -1043,8 +1053,7 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
"postprocess": update_details,
"field_no_map": ["taxes_and_charges", "set_warehouse"],
},
doctype
+ " Item": {
doctype + " Item": {
"doctype": target_doctype + " Item",
"field_map": {
source_document_warehouse_field: target_document_warehouse_field,

View File

@@ -54,7 +54,7 @@ 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")
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(
@@ -71,16 +71,14 @@ class TestDeliveryNote(FrappeTestCase):
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.assertFalse(get_gl_entries("Delivery Note", dn.name))
def test_delivery_note_gl_entry_packing_item(self):
company = frappe.db.get_value("Warehouse", "Stores - TCP1", "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(
@@ -127,7 +125,7 @@ class TestDeliveryNote(FrappeTestCase):
stock_in_hand_account: [0.0, stock_value_diff],
"Cost of Goods Sold - TCP1": [stock_value_diff, 0.0],
}
for i, gle in enumerate(gl_entries):
for _i, gle in enumerate(gl_entries):
self.assertEqual([gle.debit, gle.credit], expected_values.get(gle.account))
# check stock in hand balance
@@ -159,9 +157,7 @@ class TestDeliveryNote(FrappeTestCase):
serial_no = get_serial_nos(se.get("items")[0].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
@@ -694,7 +690,7 @@ class TestDeliveryNote(FrappeTestCase):
"Stock In Hand - TCP1": [0.0, stock_value_difference],
target_warehouse: [stock_value_difference, 0.0],
}
for i, gle in enumerate(gl_entries):
for _i, gle in enumerate(gl_entries):
self.assertEqual([gle.debit, gle.credit], expected_values.get(gle.account))
# tear down
@@ -902,7 +898,7 @@ class TestDeliveryNote(FrappeTestCase):
"Cost of Goods Sold - TCP1": {"cost_center": cost_center},
stock_in_hand_account: {"cost_center": cost_center},
}
for i, gle in enumerate(gl_entries):
for _i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
def test_delivery_note_cost_center_with_balance_sheet_account(self):
@@ -931,7 +927,7 @@ class TestDeliveryNote(FrappeTestCase):
"Cost of Goods Sold - TCP1": {"cost_center": cost_center},
stock_in_hand_account: {"cost_center": cost_center},
}
for i, gle in enumerate(gl_entries):
for _i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
def test_make_sales_invoice_from_dn_for_returned_qty(self):
@@ -997,9 +993,7 @@ class TestDeliveryNote(FrappeTestCase):
},
)
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)
@@ -1008,9 +1002,7 @@ 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 (
@@ -1185,9 +1177,7 @@ class TestDeliveryNote(FrappeTestCase):
warehouse=warehouse,
target_warehouse=target,
)
self.assertFalse(
frappe.db.exists("GL Entry", {"voucher_no": dn.name, "voucher_type": dn.doctype})
)
self.assertFalse(frappe.db.exists("GL Entry", {"voucher_no": dn.name, "voucher_type": dn.doctype}))
def test_batch_expiry_for_delivery_note(self):
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
@@ -1262,11 +1252,9 @@ class TestDeliveryNote(FrappeTestCase):
).name
# Step - 2: Inward Stock
se1 = make_stock_entry(item_code=batch_item, target="_Test Warehouse - _TC", qty=3)
make_stock_entry(item_code=batch_item, target="_Test Warehouse - _TC", qty=3)
serial_nos = (
make_stock_entry(item_code=serial_item, target="_Test Warehouse - _TC", qty=3)
.items[0]
.serial_no
make_stock_entry(item_code=serial_item, target="_Test Warehouse - _TC", qty=3).items[0].serial_no
)
# Step - 3: Create a Product Bundle
@@ -1353,9 +1341,7 @@ class TestDeliveryNote(FrappeTestCase):
basic_rate=100.0,
posting_date=add_days(nowdate(), -5),
)
dn = create_delivery_note(
item_code=item_code, qty=5, rate=500, posting_date=add_days(nowdate(), -4)
)
dn = create_delivery_note(item_code=item_code, qty=5, rate=500, posting_date=add_days(nowdate(), -4))
self.assertEqual(dn.items[0].incoming_rate, 100.0)
make_stock_entry(

View File

@@ -13,7 +13,7 @@ from frappe.utils import cint, get_datetime, get_link_to_form
class DeliveryTrip(Document):
def __init__(self, *args, **kwargs):
super(DeliveryTrip, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
# Google Maps returns distances in meters by default
self.default_distance_uom = (
@@ -67,9 +67,7 @@ class DeliveryTrip(Document):
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,
@@ -315,14 +313,11 @@ def get_contact_display(contact):
"Contact", contact, ["first_name", "last_name", "phone", "mobile_no"], as_dict=1
)
contact_info.html = (
""" <b>%(first_name)s %(last_name)s</b> <br> %(phone)s <br> %(mobile_no)s"""
% {
"first_name": contact_info.first_name,
"last_name": contact_info.last_name or "",
"phone": contact_info.phone or "",
"mobile_no": contact_info.mobile_no or "",
}
contact_info.html = """ <b>{first_name} {last_name}</b> <br> {phone} <br> {mobile_no}""".format(
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

View File

@@ -1,7 +1,6 @@
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase

View File

@@ -360,9 +360,7 @@ def delete_dimension(dimension):
@frappe.whitelist()
def get_parent_fields(child_doctype, dimension_name):
parent_doctypes = frappe.get_all(
"DocField", fields=["parent"], filters={"options": child_doctype}
)
parent_doctypes = frappe.get_all("DocField", fields=["parent"], filters={"options": child_doctype})
fields = []

View File

@@ -210,9 +210,7 @@ class TestInventoryDimension(FrappeTestCase):
)
self.assertFalse(
frappe.db.get_value(
"Custom Field", {"fieldname": "project", "dt": "Stock Ledger Entry"}, "name"
)
frappe.db.get_value("Custom Field", {"fieldname": "project", "dt": "Stock Ledger Entry"}, "name")
)
def test_check_mandatory_dimensions(self):
@@ -296,9 +294,7 @@ class TestInventoryDimension(FrappeTestCase):
se_doc.save()
se_doc.submit()
entries = get_voucher_sl_entries(
se_doc.name, ["warehouse", "store", "incoming_rate", "actual_qty"]
)
entries = get_voucher_sl_entries(se_doc.name, ["warehouse", "store", "incoming_rate", "actual_qty"])
for entry in entries:
self.assertEqual(entry.warehouse, warehouse)
@@ -488,7 +484,14 @@ def create_store_dimension():
"autoname": "field:store_name",
"fields": [{"label": "Store Name", "fieldname": "store_name", "fieldtype": "Data"}],
"permissions": [
{"role": "System Manager", "permlevel": 0, "read": 1, "write": 1, "create": 1, "delete": 1}
{
"role": "System Manager",
"permlevel": 0,
"read": 1,
"write": 1,
"create": 1,
"delete": 1,
}
],
}
).insert(ignore_permissions=True)
@@ -510,7 +513,14 @@ def prepare_test_data():
"autoname": "field:shelf_name",
"fields": [{"label": "Shelf Name", "fieldname": "shelf_name", "fieldtype": "Data"}],
"permissions": [
{"role": "System Manager", "permlevel": 0, "read": 1, "write": 1, "create": 1, "delete": 1}
{
"role": "System Manager",
"permlevel": 0,
"read": 1,
"write": 1,
"create": 1,
"delete": 1,
}
],
}
).insert(ignore_permissions=True)
@@ -532,7 +542,14 @@ def prepare_test_data():
"autoname": "field:rack_name",
"fields": [{"label": "Rack Name", "fieldname": "rack_name", "fieldtype": "Data"}],
"permissions": [
{"role": "System Manager", "permlevel": 0, "read": 1, "write": 1, "create": 1, "delete": 1}
{
"role": "System Manager",
"permlevel": 0,
"read": 1,
"write": 1,
"create": 1,
"delete": 1,
}
],
}
).insert(ignore_permissions=True)
@@ -554,7 +571,14 @@ def prepare_test_data():
"autoname": "field:pallet_name",
"fields": [{"label": "Pallet Name", "fieldname": "pallet_name", "fieldtype": "Data"}],
"permissions": [
{"role": "System Manager", "permlevel": 0, "read": 1, "write": 1, "create": 1, "delete": 1}
{
"role": "System Manager",
"permlevel": 0,
"read": 1,
"write": 1,
"create": 1,
"delete": 1,
}
],
}
).insert(ignore_permissions=True)
@@ -570,7 +594,14 @@ def prepare_test_data():
"autoname": "field:site_name",
"fields": [{"label": "Site Name", "fieldname": "site_name", "fieldtype": "Data"}],
"permissions": [
{"role": "System Manager", "permlevel": 0, "read": 1, "write": 1, "create": 1, "delete": 1}
{
"role": "System Manager",
"permlevel": 0,
"read": 1,
"write": 1,
"create": 1,
"delete": 1,
}
],
}
).insert(ignore_permissions=True)
@@ -623,9 +654,7 @@ def prepare_data_for_internal_transfer():
to_warehouse = create_warehouse("_Test Internal Warehouse GIT A", company=company)
pr_doc = make_purchase_receipt(
company=company, warehouse=warehouse, qty=10, rate=100, do_not_submit=True
)
pr_doc = make_purchase_receipt(company=company, warehouse=warehouse, qty=10, rate=100, do_not_submit=True)
pr_doc.items[0].store = "Inter Transfer Store 1"
pr_doc.submit()
@@ -651,9 +680,7 @@ def prepare_data_for_internal_transfer():
expense_account = frappe.db.get_value(
"Company", company, "stock_adjustment_account"
) or frappe.db.get_value(
"Account", {"company": company, "account_type": "Expense Account"}, "name"
)
) or frappe.db.get_value("Account", {"company": company, "account_type": "Expense Account"}, "name")
return frappe._dict(
{

View File

@@ -3,7 +3,6 @@
import copy
import json
from typing import Dict, List, Optional
import frappe
from frappe import _
@@ -300,9 +299,9 @@ class Item(Document):
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))
@@ -354,7 +353,7 @@ class Item(Document):
frappe.throw(
_("{0} entered twice {1} in Item Taxes").format(
frappe.bold(d.item_tax_template),
"for tax category {0}".format(frappe.bold(d.tax_category)) if d.tax_category else "",
f"for tax category {frappe.bold(d.tax_category)}" if d.tax_category else "",
)
)
else:
@@ -373,7 +372,9 @@ class Item(Document):
)
if duplicate:
frappe.throw(
_("Barcode {0} already used in Item {1}").format(item_barcode.barcode, duplicate[0][0])
_("Barcode {0} already used in Item {1}").format(
item_barcode.barcode, duplicate[0][0]
)
)
item_barcode.barcode_type = (
@@ -403,9 +404,9 @@ class Item(Document):
warehouse_material_request_type += [(d.get("warehouse"), d.get("material_request_type"))]
else:
frappe.throw(
_("Row #{0}: A reorder entry already exists for warehouse {1} with reorder type {2}.").format(
d.idx, d.warehouse, d.material_request_type
),
_(
"Row #{0}: A reorder entry already exists for warehouse {1} with reorder type {2}."
).format(d.idx, d.warehouse, d.material_request_type),
DuplicateReorderRows,
)
@@ -477,20 +478,21 @@ class Item(Document):
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
),
f"""select name, item_wise_tax_detail from `tab{dt}`
where ifnull(item_wise_tax_detail, '') != ''""",
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
dt,
d.name,
"item_wise_tax_detail",
json.dumps(item_wise_tax_detail),
update_modified=False,
)
def delete_old_bins(self, old_name):
@@ -517,9 +519,7 @@ class Item(Document):
)
msg += " <br>"
msg += (
", ".join([get_link_to_form("Stock Reconciliation", d.parent) for d in records]) + "<br><br>"
)
msg += ", ".join([get_link_to_form("Stock Reconciliation", d.parent) for d in records]) + "<br><br>"
msg += _(
"Note: To merge the items, create a separate Stock Reconciliation for the old item {0}"
@@ -542,12 +542,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, "disabled": 0}
)
new_bundle = frappe.get_value(
"Product Bundle", filters={"new_item_code": new_name, "disabled": 0}
)
old_bundle = frappe.get_value("Product Bundle", filters={"new_item_code": old_name, "disabled": 0})
new_bundle = frappe.get_value("Product Bundle", filters={"new_item_code": new_name, "disabled": 0})
if old_bundle and new_bundle:
bundle_link = get_link_to_form("Product Bundle", old_bundle)
@@ -572,7 +568,7 @@ class Item(Document):
if len(web_items) <= 1:
return
old_web_item = [d.get("name") for d in web_items if d.get("item_code") == old_name][0]
old_web_item = next(d.get("name") for d in web_items if d.get("item_code") == old_name)
web_item_link = get_link_to_form("Website Item", old_web_item)
old_name, new_name = frappe.bold(old_name), frappe.bold(new_name)
@@ -586,9 +582,7 @@ 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(
@@ -605,9 +599,7 @@ 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():
@@ -768,16 +760,14 @@ class Item(Document):
return "<br>".join(docnames)
def table_row(title, body):
return """<tr>
<td>{0}</td>
<td>{1}</td>
</tr>""".format(
title, body
)
return f"""<tr>
<td>{title}</td>
<td>{body}</td>
</tr>"""
rows = ""
for docname, attr_list in not_included.items():
link = "<a href='/app/Form/Item/{0}'>{0}</a>".format(frappe.bold(_(docname)))
link = f"<a href='/app/Form/Item/{frappe.bold(_(docname))}'>{frappe.bold(_(docname))}</a>"
rows += table_row(link, body(attr_list))
error_description = _(
@@ -785,17 +775,15 @@ class Item(Document):
)
message = """
<div>{0}</div><br>
<div>{}</div><br>
<table class="table">
<thead>
<td>{1}</td>
<td>{2}</td>
<td>{}</td>
<td>{}</td>
</thead>
{3}
{}
</table>
""".format(
error_description, _("Variant Items"), _("Attributes"), rows
)
""".format(error_description, _("Variant Items"), _("Attributes"), rows)
frappe.throw(message, title=_("Variant Attribute Error"), is_minimizable=True, wide=True)
@@ -925,7 +913,7 @@ class Item(Document):
frappe.throw(msg, title=_("Linked with submitted documents"))
def _get_linked_submitted_documents(self, changed_fields: List[str]) -> Optional[Dict[str, str]]:
def _get_linked_submitted_documents(self, changed_fields: list[str]) -> dict[str, str] | None:
linked_doctypes = [
"Delivery Note Item",
"Sales Invoice Item",
@@ -1088,17 +1076,13 @@ def get_last_purchase_details(item_code, doc_name=None, conversion_rate=1.0):
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
@@ -1305,7 +1289,7 @@ def set_item_tax_from_hsn_code(item):
pass
def validate_item_default_company_links(item_defaults: List[ItemDefault]) -> None:
def validate_item_default_company_links(item_defaults: list[ItemDefault]) -> None:
for item_default in item_defaults:
for doctype, field in [
["Warehouse", "default_warehouse"],

View File

@@ -315,7 +315,6 @@ class TestItem(FrappeTestCase):
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",
@@ -469,9 +468,7 @@ class TestItem(FrappeTestCase):
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 - _TC"}))
self.assertTrue(
frappe.db.get_value("Bin", {"item_code": new, "warehouse": "_Test Warehouse 1 - _TC"})
)
@@ -715,9 +712,7 @@ class TestItem(FrappeTestCase):
@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)
@@ -790,7 +785,7 @@ class TestItem(FrappeTestCase):
def test_customer_codes_length(self):
"""Check if item code with special characters are allowed."""
item = make_item(properties={"item_code": "Test Item Code With Special Characters"})
for row in range(3):
for _row in range(3):
item.append("customer_items", {"ref_code": frappe.generate_hash("", 120)})
item.save()
self.assertTrue(len(item.customer_code) > 140)
@@ -831,9 +826,7 @@ class TestItem(FrappeTestCase):
make_property_setter("Item", None, "search_fields", "item_name", "Data", for_doctype="Doctype")
item = make_item(properties={"item_name": "Test Item", "description": "Test Description"})
data = item_query(
"Item", "Test Item", "", 0, 20, filters={"item_name": "Test Item"}, as_dict=True
)
data = item_query("Item", "Test Item", "", 0, 20, filters={"item_name": "Test Item"}, as_dict=True)
self.assertEqual(data[0].name, item.name)
self.assertEqual(data[0].item_name, item.item_name)
self.assertTrue("description" not in data[0])
@@ -841,9 +834,7 @@ class TestItem(FrappeTestCase):
make_property_setter(
"Item", None, "search_fields", "item_name, description", "Data", for_doctype="Doctype"
)
data = item_query(
"Item", "Test Item", "", 0, 20, filters={"item_name": "Test Item"}, as_dict=True
)
data = item_query("Item", "Test Item", "", 0, 20, filters={"item_name": "Test Item"}, as_dict=True)
self.assertEqual(data[0].name, item.name)
self.assertEqual(data[0].item_name, item.item_name)
self.assertEqual(data[0].description, item.description)

View File

@@ -30,9 +30,7 @@ class ItemAlternative(Document):
"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):
@@ -72,14 +70,12 @@ class ItemAlternative(Document):
@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`
f""" (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 {1} offset {0}
""".format(
start, page_len
),
and two_way = 1) limit {page_len} offset {start}
""",
{"item_code": filters.get("item_code"), "txt": "%" + txt + "%"},
)

View File

@@ -54,9 +54,7 @@ class TestItemAlternative(FrappeTestCase):
"fg_item_qty": 5,
},
]
sco = get_subcontracting_order(
service_items=service_items, supplier_warehouse=supplier_warehouse
)
sco = get_subcontracting_order(service_items=service_items, supplier_warehouse=supplier_warehouse)
rm_items = [
{
"item_code": "Test Finished Goods - A",
@@ -106,9 +104,7 @@ class TestItemAlternative(FrappeTestCase):
"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))
scr = make_subcontracting_receipt(sco.name)
scr.save()
@@ -159,9 +155,7 @@ class TestItemAlternative(FrappeTestCase):
"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

View File

@@ -75,7 +75,9 @@ class ItemAttribute(Document):
values, abbrs = [], []
for d in self.item_attribute_values:
if d.attribute_value.lower() in map(str.lower, values):
frappe.throw(_("Attribute value: {0} must appear only once").format(d.attribute_value.title()))
frappe.throw(
_("Attribute value: {0} must appear only once").format(d.attribute_value.title())
)
values.append(d.attribute_value)
if d.abbr.lower() in map(str.lower, abbrs):

View File

@@ -41,7 +41,9 @@ class ItemManufacturer(Document):
# 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}
"Item",
item.name,
{"default_item_manufacturer": None, "default_manufacturer_part_no": None},
)
elif self.is_default:

View File

@@ -40,7 +40,7 @@ class ItemPrice(Document):
if not price_list_details:
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))
frappe.throw(f"The price list {link} does not exist or is disabled")
self.buying, self.selling, self.currency = price_list_details
@@ -57,7 +57,6 @@ class ItemPrice(Document):
frappe.throw(_(msg))
def check_duplicates(self):
item_price = frappe.qb.DocType("Item Price")
query = (

View File

@@ -24,7 +24,6 @@ class ItemVariantSettings(Document):
"description",
"variant_of",
"valuation_rate",
"description",
"barcodes",
"has_variants",
"attributes",
@@ -50,4 +49,6 @@ class ItemVariantSettings(Document):
def validate(self):
for d in self.fields:
if d.field_name in self.invalid_fields_for_copy_fields_in_variants:
frappe.throw(_("Cannot set the field <b>{0}</b> for copying in variants").format(d.field_name))
frappe.throw(
_("Cannot set the field <b>{0}</b> for copying in variants").format(d.field_name)
)

View File

@@ -76,13 +76,13 @@ class LandedCostVoucher(Document):
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")
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)
@@ -132,7 +132,8 @@ class LandedCostVoucher(Document):
)
item.applicable_charges = flt(
flt(item.get(based_on_field)) * (flt(self.total_taxes_and_charges) / flt(total_item_cost)),
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
@@ -230,7 +231,9 @@ class LandedCostVoucher(Document):
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"
"purchase_invoice"
if item.receipt_document_type == "Purchase Invoice"
else "purchase_receipt"
)
docs = frappe.db.get_all(
"Asset",
@@ -249,9 +252,7 @@ class LandedCostVoucher(Document):
frappe.throw(
_(
"{0} <b>{1}</b> has submitted Assets. Remove Item <b>{2}</b> from table to continue."
).format(
item.receipt_document_type, item.receipt_document, item.item_code
)
).format(item.receipt_document_type, item.receipt_document, item.item_code)
)
def update_rate_in_serial_no_for_non_asset_items(self, receipt_document):
@@ -260,10 +261,10 @@ class LandedCostVoucher(Document):
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(
"update `tabSerial No` set purchase_rate=%s where name in ({})".format(
", ".join(["%s"] * len(serial_nos))
),
tuple([item.valuation_rate] + serial_nos),
tuple([item.valuation_rate, *serial_nos]),
)

View File

@@ -62,9 +62,7 @@ class TestLandedCostVoucher(FrappeTestCase):
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
@@ -87,7 +85,6 @@ class TestLandedCostVoucher(FrappeTestCase):
self.assertPurchaseReceiptLCVGLEntries(pr)
def assertPurchaseReceiptLCVGLEntries(self, pr):
gl_entries = get_gl_entries("Purchase Receipt", pr.name)
self.assertTrue(gl_entries)
@@ -170,9 +167,7 @@ class TestLandedCostVoucher(FrappeTestCase):
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)
def test_landed_cost_voucher_for_zero_purchase_rate(self):
@@ -229,7 +224,6 @@ class TestLandedCostVoucher(FrappeTestCase):
)
def test_landed_cost_voucher_against_purchase_invoice(self):
pi = make_purchase_invoice(
update_stock=1,
posting_date=frappe.utils.nowdate(),
@@ -274,9 +268,7 @@ class TestLandedCostVoucher(FrappeTestCase):
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)
@@ -365,9 +357,7 @@ class TestLandedCostVoucher(FrappeTestCase):
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)
@@ -392,7 +382,7 @@ class TestLandedCostVoucher(FrappeTestCase):
do_not_save=True,
)
pr.items[0].cost_center = "Main - TCP1"
for x in range(2):
for _x in range(2):
pr.append(
"items",
{

View File

@@ -33,10 +33,10 @@ class MaterialRequest(BuyingController):
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:
if d.sales_order not in so_items:
so_items[d.sales_order] = {d.item_code: flt(d.qty)}
else:
if not d.item_code in so_items[d.sales_order]:
if d.item_code not in so_items[d.sales_order]:
so_items[d.sales_order][d.item_code] = flt(d.qty)
else:
so_items[d.sales_order][d.item_code] += flt(d.qty)
@@ -61,13 +61,13 @@ class MaterialRequest(BuyingController):
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
)
_(
"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()
super().validate()
self.validate_schedule_date()
self.check_for_on_hold_or_closed_status("Sales Order", "sales_order")
@@ -141,12 +141,8 @@ class MaterialRequest(BuyingController):
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(f"""select TIMEDIFF('{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))
@@ -326,9 +322,7 @@ def update_completed_and_requested_qty(stock_entry, method):
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")
@@ -458,9 +452,7 @@ def make_purchase_order_based_on_supplier(source_name, target_doc=None, args=Non
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
],
[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)
@@ -512,7 +504,7 @@ def get_material_requests_based_on_supplier(doctype, txt, searchfield, start, pa
if filters.get("transaction_date"):
date = filters.get("transaction_date")[1]
conditions += "and mr.transaction_date between '{0}' and '{1}' ".format(date[0], date[1])
conditions += f"and mr.transaction_date between '{date[0]}' and '{date[1]}' "
supplier = filters.get("supplier")
supplier_items = get_items_based_on_default_supplier(supplier)
@@ -524,18 +516,18 @@ def get_material_requests_based_on_supplier(doctype, txt, searchfield, start, pa
"""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})
and mr_item.item_code in ({})
and mr.material_request_type = 'Purchase'
and mr.per_ordered < 99.99
and mr.docstatus = 1
and mr.status != 'Stopped'
and mr.company = %s
{1}
{}
order by mr_item.item_code ASC
limit {2} offset {3} """.format(
limit {} offset {} """.format(
", ".join(["%s"] * len(supplier_items)), conditions, cint(page_len), cint(start)
),
tuple(supplier_items) + (filters.get("company"),),
(*tuple(supplier_items), filters.get("company")),
as_dict=1,
)
@@ -663,7 +655,10 @@ def make_stock_entry(source_name, target_doc=None):
"doctype": "Stock Entry",
"validation": {
"docstatus": ["=", 1],
"material_request_type": ["in", ["Material Transfer", "Material Issue", "Customer Provided"]],
"material_request_type": [
"in",
["Material Transfer", "Material Issue", "Customer Provided"],
],
},
},
"Material Request Item": {
@@ -693,9 +688,7 @@ def raise_work_orders(material_request):
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:

View File

@@ -744,9 +744,7 @@ class TestMaterialRequest(FrappeTestCase):
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")
@@ -810,7 +808,7 @@ class TestMaterialRequest(FrappeTestCase):
)
comapnywise_mr_list.setdefault(mr2.company, []).append(mr2.name)
for company, mr_list in comapnywise_mr_list.items():
for company, _mr_list in comapnywise_mr_list.items():
emails = get_email_list(company)
self.assertTrue(comapnywise_users[company] in emails)
@@ -828,9 +826,7 @@ def get_in_transit_warehouse(company):
}
).insert()
in_transit_warehouse = frappe.db.exists(
"Warehouse", {"warehouse_type": "Transit", "company": company}
)
in_transit_warehouse = frappe.db.exists("Warehouse", {"warehouse_type": "Transit", "company": company})
if not in_transit_warehouse:
in_transit_warehouse = (

View File

@@ -23,9 +23,7 @@ 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)
@@ -244,9 +242,7 @@ def get_packed_item_bin_qty(item, warehouse):
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

View File

@@ -1,7 +1,6 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from typing import List, Optional, Tuple
import frappe
from frappe.tests.utils import FrappeTestCase, change_settings
@@ -15,8 +14,8 @@ from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
def create_product_bundle(
quantities: Optional[List[int]] = None, warehouse: Optional[str] = None
) -> Tuple[str, List[str]]:
quantities: list[int] | None = None, warehouse: str | None = None
) -> tuple[str, list[str]]:
"""Get a new product_bundle for use in tests.
Create 10x required stock if warehouse is specified.
@@ -169,9 +168,7 @@ 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)
@@ -182,10 +179,11 @@ class TestPackedItem(FrappeTestCase):
def assertReturns(self, original, returned):
self.assertEqual(len(original), len(returned))
sort_function = lambda p: (p.parent_item, p.item_code, p.qty)
def sort_function(p):
return 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), strict=False
):
self.assertEqual(sent.item_code, returned.item_code)
self.assertEqual(sent.parent_item, returned.parent_item)

View File

@@ -11,7 +11,7 @@ from erpnext.controllers.status_updater import StatusUpdater
class PackingSlip(StatusUpdater):
def __init__(self, *args, **kwargs) -> None:
super(PackingSlip, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self.status_updater = [
{
"target_dt": "Delivery Note Item",
@@ -64,9 +64,7 @@ class PackingSlip(StatusUpdater):
"""Validate if case nos overlap. If they do, recommend next case no."""
if cint(self.from_case_no) <= 0:
frappe.throw(
_("The 'From Package No.' field must neither be empty nor it's value less than 1.")
)
frappe.throw(_("The 'From Package No.' field must neither be empty nor it's value less than 1."))
elif not self.to_case_no:
self.to_case_no = self.from_case_no
elif cint(self.to_case_no) < cint(self.from_case_no):
@@ -189,9 +187,8 @@ def item_details(doctype, txt, searchfield, start, page_len, filters):
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 offset %s """
% ("%s", searchfield, "%s", get_match_cond(doctype), "%s", "%s"),
where parent= {})
and {} like "{}" {}
limit {} offset {} """.format("%s", searchfield, "%s", get_match_cond(doctype), "%s", "%s"),
((filters or {}).get("delivery_note"), "%%%s%%" % txt, page_len, start),
)

View File

@@ -1,7 +1,6 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase

View File

@@ -4,7 +4,6 @@
import json
from collections import OrderedDict, defaultdict
from itertools import groupby
from typing import Dict, List
import frappe
from frappe import _
@@ -213,9 +212,7 @@ class PickList(Document):
),
)
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
@@ -269,12 +266,10 @@ class PickList(Document):
item_map = OrderedDict()
for item in locations:
if not item.item_code:
frappe.throw("Row #{0}: Item Code is Mandatory".format(item.idx))
frappe.throw(f"Row #{item.idx}: Item Code is Mandatory")
if not cint(
frappe.get_cached_value("Item", item.item_code, "is_stock_item")
) and not frappe.db.exists(
"Product Bundle", {"new_item_code": item.item_code, "disabled": 0}
):
) and not frappe.db.exists("Product Bundle", {"new_item_code": item.item_code, "disabled": 0}):
continue
item_code = item.item_code
reference = item.sales_order_item or item.material_request_item
@@ -395,7 +390,7 @@ class PickList(Document):
return picked_items
def _get_product_bundles(self) -> Dict[str, str]:
def _get_product_bundles(self) -> dict[str, str]:
# Dict[so_item_row: item_code]
product_bundles = {}
for item in self.locations:
@@ -408,13 +403,11 @@ class PickList(Document):
)
return product_bundles
def _get_product_bundle_qty_map(self, bundles: List[str]) -> Dict[str, Dict[str, float]]:
def _get_product_bundle_qty_map(self, bundles: list[str]) -> dict[str, dict[str, float]]:
# bundle_item_code: Dict[component, qty]
product_bundle_qty_map = {}
for bundle_item_code in bundles:
bundle = frappe.get_last_doc(
"Product Bundle", {"new_item_code": bundle_item_code, "disabled": 0}
)
bundle = frappe.get_last_doc("Product Bundle", {"new_item_code": bundle_item_code, "disabled": 0})
product_bundle_qty_map[bundle_item_code] = {item.item_code: item.qty for item in bundle.items}
return product_bundle_qty_map
@@ -440,7 +433,7 @@ def update_pick_list_status(pick_list):
doc.run_method("update_status")
def get_picked_items_qty(items) -> List[Dict]:
def get_picked_items_qty(items) -> list[dict]:
pi_item = frappe.qb.DocType("Pick List Item")
return (
frappe.qb.from_(pi_item)
@@ -470,17 +463,13 @@ def get_items_with_location_and_quantity(item_doc, item_location_map, docstatus)
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 flt(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.get_cached_value("UOM", item_doc.uom, "must_be_whole_number")
@@ -515,7 +504,7 @@ def get_items_with_location_and_quantity(item_doc, item_location_map, docstatus)
if item_location.serial_no:
# set remaining serial numbers
item_location.serial_no = item_location.serial_no[-int(qty_diff) :]
available_locations = [item_location] + available_locations
available_locations = [item_location, *available_locations]
# update available locations for the item
item_location_map[item_doc.item_code] = available_locations
@@ -830,8 +819,7 @@ def create_dn_with_so(sales_dict, pick_list):
"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,
}
for customer in sales_dict:
@@ -852,7 +840,6 @@ def create_dn_with_so(sales_dict, pick_list):
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 or location.product_bundle_item:
continue
@@ -883,9 +870,7 @@ def map_pl_locations(pick_list, item_mapper, delivery_note, sales_order=None):
delivery_note.customer = frappe.get_value("Sales Order", sales_order, "customer")
def add_product_bundles_to_delivery_note(
pick_list: "PickList", delivery_note, item_mapper
) -> None:
def add_product_bundles_to_delivery_note(pick_list: "PickList", delivery_note, item_mapper) -> None:
"""Add product bundles found in pick list to delivery note.
When mapping pick list items, the bundle item itself isn't part of the
@@ -963,7 +948,7 @@ def get_pending_work_orders(doctype, txt, searchfield, start, page_length, filte
& (wo.qty > wo.material_transferred_for_manufacturing)
& (wo.docstatus == 1)
& (wo.company == filters.get("company"))
& (wo.name.like("%{0}%".format(txt)))
& (wo.name.like(f"%{txt}%"))
)
.orderby(Case().when(Locate(txt, wo.name) > 0, Locate(txt, wo.name)).else_(99999))
.orderby(wo.name)
@@ -1030,9 +1015,7 @@ 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")
if not (is_wip_warehouse_group and work_order.skip_transfer):

View File

@@ -139,7 +139,6 @@ class TestPickList(FrappeTestCase):
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",
@@ -274,7 +273,6 @@ class TestPickList(FrappeTestCase):
pr2.cancel()
def test_pick_list_for_items_from_multiple_sales_orders(self):
item_code = make_item().name
try:
frappe.get_doc(
@@ -418,9 +416,7 @@ 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()
@@ -469,7 +465,7 @@ class TestPickList(FrappeTestCase):
_dict(item_code="A", warehouse="X", qty=8, picked_qty=3),
_dict(item_code="B", warehouse="Y", qty=6, picked_qty=4),
]
for expected_item, created_item in zip(expected_items, pl.locations):
for expected_item, created_item in zip(expected_items, pl.locations, strict=False):
_compare_dicts(expected_item, created_item)
def test_multiple_dn_creation(self):
@@ -579,9 +575,7 @@ class TestPickList(FrappeTestCase):
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 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)
@@ -609,7 +603,7 @@ class TestPickList(FrappeTestCase):
quantities = [5, 2]
bundle, components = create_product_bundle(quantities, warehouse=warehouse)
bundle_items = dict(zip(components, quantities))
bundle_items = dict(zip(components, quantities, strict=False))
so = make_sales_order(item_code=bundle, qty=3, rate=42)
@@ -715,7 +709,7 @@ class TestPickList(FrappeTestCase):
for item in items:
for warehouse in warehouses:
se = make_stock_entry(
make_stock_entry(
item=item.get("item_code"),
to_warehouse=warehouse,
qty=5,

View File

@@ -23,7 +23,7 @@ form_grid_templates = {"items": "templates/form_grid/item_grid.html"}
class PurchaseReceipt(BuyingController):
def __init__(self, *args, **kwargs):
super(PurchaseReceipt, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self.status_updater = [
{
"target_dt": "Purchase Order Item",
@@ -116,7 +116,7 @@ class PurchaseReceipt(BuyingController):
def validate(self):
self.validate_posting_time()
super(PurchaseReceipt, self).validate()
super().validate()
if self._action == "submit":
self.make_batches("warehouse")
@@ -148,15 +148,15 @@ class PurchaseReceipt(BuyingController):
# Improves UX by not giving messages of "Assets Created" before throwing error of not finding arbnb account
self.get_company_default("asset_received_but_not_billed")
get_asset_account(
"capital_work_in_progress_account", asset_category=item.asset_category, company=self.company
"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"
)
frappe.db.get_value("Company", self.company, "enable_provisional_accounting_for_non_stock_items")
)
if not provisional_accounting_for_non_stock_items:
@@ -168,7 +168,7 @@ class PurchaseReceipt(BuyingController):
item.provisional_expense_account = default_provisional_account
def validate_with_previous_doc(self):
super(PurchaseReceipt, self).validate_with_previous_doc(
super().validate_with_previous_doc(
{
"Purchase Order": {
"ref_dn_field": "purchase_order",
@@ -229,24 +229,20 @@ class PurchaseReceipt(BuyingController):
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
):
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)
# on submit
def on_submit(self):
super(PurchaseReceipt, self).on_submit()
super().on_submit()
# Check for Approving Authority
frappe.get_doc("Authorization Control").validate_approving_authority(
@@ -283,7 +279,7 @@ class PurchaseReceipt(BuyingController):
frappe.throw(_("Purchase Invoice {0} is already submitted").format(self.submit_rv[0][0]))
def on_cancel(self):
super(PurchaseReceipt, self).on_cancel()
super().on_cancel()
self.check_on_hold_or_closed_status()
# Check if Purchase Invoice has been submitted against current Purchase Order
@@ -325,9 +321,7 @@ class PurchaseReceipt(BuyingController):
)
provisional_accounting_for_non_stock_items = cint(
frappe.db.get_value(
"Company", self.company, "enable_provisional_accounting_for_non_stock_items"
)
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)
@@ -396,7 +390,6 @@ class PurchaseReceipt(BuyingController):
and self.conversion_rate != exchange_rate_map[item.purchase_invoice]
and item.net_rate == net_rate_map[item.purchase_invoice_item]
):
discrepancy_caused_by_exchange_rate_difference = (item.qty * item.net_rate) * (
exchange_rate_map[item.purchase_invoice] - self.conversion_rate
)
@@ -578,10 +571,12 @@ class PurchaseReceipt(BuyingController):
elif warehouse_account.get(d.warehouse):
stock_value_diff = get_stock_value_difference(self.name, d.name, d.warehouse)
stock_asset_account_name = warehouse_account[d.warehouse]["account"]
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 = warehouse_account.get(self.supplier_warehouse, {}).get(
"account"
)
supplier_warehouse_account_currency = warehouse_account.get(
self.supplier_warehouse, {}
).get("account_currency")
# If PR is sub-contracted and fg item rate is zero
# in that case if account for source and target warehouse are same,
@@ -988,9 +983,7 @@ def make_purchase_invoice(source_name, target_doc=None, args=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")
@@ -1002,9 +995,7 @@ def make_purchase_invoice(source_name, target_doc=None, args=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")
@@ -1013,9 +1004,7 @@ def make_purchase_invoice(source_name, target_doc=None, args=None):
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))
@@ -1193,15 +1182,11 @@ def get_item_account_wise_additional_cost(purchase_document):
if total_item_cost > 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
)
]["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
)
]["base_amount"] += account.base_amount * item.get(based_on_field) / total_item_cost
else:
item_account_wise_cost[(item.item_code, item.purchase_receipt_item)][
account.expense_account

View File

@@ -11,7 +11,7 @@ from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.controllers.buying_controller import QtyMismatchError
from erpnext.stock.doctype.item.test_item import create_item, make_item
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice
from erpnext.stock.doctype.serial_no.serial_no import SerialNoDuplicateError, get_serial_nos
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
@@ -37,7 +37,6 @@ class TestPurchaseReceipt(FrappeTestCase):
pr.delete()
def test_reverse_purchase_receipt_sle(self):
pr = make_purchase_receipt(qty=0.5, item_code="_Test Item Home Desktop 200")
sl_entry = frappe.db.get_all(
@@ -124,9 +123,7 @@ class TestPurchaseReceipt(FrappeTestCase):
pi.delete() # draft PI
pr.cancel()
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"
).delete()
frappe.get_doc("Payment Terms Template", "_Test Payment Terms Template For Purchase Invoice").delete()
def test_purchase_receipt_no_gl_entry(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
@@ -273,7 +270,8 @@ class TestPurchaseReceipt(FrappeTestCase):
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
frappe.db.get_value("Serial No", serial_no, "warehouse"),
pr.get("items")[0].rejected_warehouse,
)
pr.cancel()
@@ -613,7 +611,7 @@ class TestPurchaseReceipt(FrappeTestCase):
item_code = "Test Manual Created Serial No"
if not frappe.db.exists("Item", item_code):
item = make_item(item_code, dict(has_serial_no=1))
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)
@@ -759,7 +757,7 @@ class TestPurchaseReceipt(FrappeTestCase):
"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):
for _i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
pr.cancel()
@@ -784,7 +782,7 @@ class TestPurchaseReceipt(FrappeTestCase):
"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):
for _i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
pr.cancel()
@@ -1045,9 +1043,7 @@ class TestPurchaseReceipt(FrappeTestCase):
pr.submit()
# Get exchnage gain and loss account
exchange_gain_loss_account = frappe.db.get_value(
"Company", pr.company, "exchange_gain_loss_account"
)
exchange_gain_loss_account = frappe.db.get_value("Company", pr.company, "exchange_gain_loss_account")
# fetching the latest GL Entry with exchange gain and loss account account
amount = frappe.db.get_value(
@@ -1104,9 +1100,7 @@ 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()
@@ -1221,9 +1215,7 @@ class TestPurchaseReceipt(FrappeTestCase):
from_warehouse = create_warehouse("_Test Internal From Warehouse New", company=company)
to_warehouse = create_warehouse("_Test Internal To Warehouse New", company=company)
rejected_warehouse = create_warehouse(
"_Test Rejected Internal To Warehouse New", company=company
)
rejected_warehouse = create_warehouse("_Test Rejected Internal To Warehouse New", company=company)
item_doc = make_item(
"Test Internal Transfer Item DS",
{
@@ -1616,7 +1608,6 @@ class TestPurchaseReceipt(FrappeTestCase):
)
# Step 4: Create Internal Purchase Receipt
from erpnext.controllers.status_updater import OverAllowanceError
from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
pr = make_inter_company_purchase_receipt(dn.name)
@@ -1766,7 +1757,6 @@ class TestPurchaseReceipt(FrappeTestCase):
)
# Step 4: Create Internal Purchase Receipt
from erpnext.controllers.status_updater import OverAllowanceError
from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
pr = make_inter_company_purchase_receipt(dn.name)
@@ -2072,9 +2062,7 @@ class TestPurchaseReceipt(FrappeTestCase):
)
# Step - 4: Create a Material Issue Stock Entry (Qty = 100, Basic Rate = 18.18 [Auto Fetched])
make_stock_entry(
purpose="Material Issue", item_code=item_code, from_warehouse=warehouse, qty=100
)
make_stock_entry(purpose="Material Issue", item_code=item_code, from_warehouse=warehouse, qty=100)
# Step - 5: Create a Return Purchase Return (Qty = -8, Rate = 100 [Auto fetched])
return_pr = make_purchase_receipt(
@@ -2146,9 +2134,9 @@ class TestPurchaseReceipt(FrappeTestCase):
gl_entries = get_gl_entries(pr_return.doctype, pr_return.name)
# Test - 1: SLE Stock Value Difference should be equal to Qty * Average Rate
average_rate = (
(se.items[0].qty * se.items[0].basic_rate) + (pr.items[0].qty * pr.items[0].rate)
) / (se.items[0].qty + pr.items[0].qty)
average_rate = ((se.items[0].qty * se.items[0].basic_rate) + (pr.items[0].qty * pr.items[0].rate)) / (
se.items[0].qty + pr.items[0].qty
)
expected_stock_value_difference = pr_return.items[0].qty * average_rate
self.assertEqual(
flt(sl_entries[0].stock_value_difference, 2), flt(expected_stock_value_difference, 2)
@@ -2243,7 +2231,7 @@ class TestPurchaseReceipt(FrappeTestCase):
do_not_save=1,
)
for i in range(9):
for _i in range(9):
pr.append(
"items",
{
@@ -2283,7 +2271,7 @@ class TestPurchaseReceipt(FrappeTestCase):
do_not_save=1,
)
for i in range(9):
for _i in range(9):
pr.append(
"items",
{
@@ -2370,7 +2358,6 @@ def get_gl_entries(voucher_type, voucher_no):
def get_taxes(**args):
args = frappe._dict(args)
return [
@@ -2490,15 +2477,14 @@ def make_purchase_receipt(**args):
"rejected_warehouse": args.rejected_warehouse or "_Test Rejected Warehouse - _TC"
if rejected_qty != 0
else "",
"rate": args.rate if args.rate != None else 50,
"rate": args.rate if args.rate is not 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"),
"cost_center": args.cost_center or frappe.get_cached_value("Company", pr.company, "cost_center"),
"asset_location": args.location or "Test Location",
},
)

View File

@@ -109,9 +109,7 @@ def apply_putaway_rule(doctype, items, company, sync=None, purpose=None):
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")
@@ -204,7 +202,7 @@ def _items_changed(old, new, doctype: str) -> bool:
new_sorted = sorted(new, key=sort_key)
# Once sorted by all relevant keys both tables should align if they are same.
for old_item, new_item in zip(old_sorted, new_sorted):
for old_item, new_item in zip(old_sorted, new_sorted, strict=False):
for key in compare_keys:
if old_item.get(key) != new_item.get(key):
return True
@@ -253,9 +251,7 @@ 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
@@ -277,24 +273,20 @@ def show_unassigned_items_message(items_not_accomodated):
for entry in items_not_accomodated:
item_link = frappe.utils.get_link_to_form("Item", entry[0])
formatted_item_rows += """
<td>{0}</td>
<td>{1}</td>
</tr>""".format(
item_link, frappe.bold(entry[1])
)
formatted_item_rows += f"""
<td>{item_link}</td>
<td>{frappe.bold(entry[1])}</td>
</tr>"""
msg += """
<table class="table">
<thead>
<td>{0}</td>
<td>{1}</td>
<td>{}</td>
<td>{}</td>
</thead>
{2}
{}
</table>
""".format(
_("Item"), _("Unassigned Qty"), formatted_item_rows
)
""".format(_("Item"), _("Unassigned Qty"), formatted_item_rows)
frappe.msgprint(msg, title=_("Insufficient Capacity"), is_minimizable=True, wide=True)

View File

@@ -46,9 +46,7 @@ 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_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
)
@@ -69,17 +67,11 @@ 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)
self.assertEqual(len(pr.items), 2)
@@ -97,12 +89,8 @@ 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)
self.assertEqual(len(pr.items), 2)
@@ -123,19 +111,13 @@ class TestPutawayRule(FrappeTestCase):
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",
@@ -167,9 +149,7 @@ class TestPutawayRule(FrappeTestCase):
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)
@@ -199,9 +179,7 @@ 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)
@@ -237,9 +215,7 @@ 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)
self.assertEqual(len(pr.items), 1)
@@ -291,9 +267,7 @@ 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_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
)
@@ -428,9 +402,7 @@ class TestPutawayRule(FrappeTestCase):
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_2 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_2, capacity=100, uom="Kg")
stock_entry = make_stock_entry(
item_code="_Rice",
@@ -480,9 +452,7 @@ 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()

View File

@@ -82,13 +82,11 @@ class QualityInspection(Document):
if self.reference_type == "Job Card":
if self.reference_name:
frappe.db.sql(
"""
UPDATE `tab{doctype}`
f"""
UPDATE `tab{self.reference_type}`
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),
)
@@ -110,9 +108,9 @@ class QualityInspection(Document):
args.append(self.name)
frappe.db.sql(
"""
f"""
UPDATE
`tab{child_doc}` t1, `tab{parent_doc}` t2
`tab{doctype}` t1, `tab{self.reference_type}` t2
SET
t1.quality_inspection = %s, t2.modified = %s
WHERE
@@ -120,9 +118,7 @@ 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,
)
@@ -180,9 +176,9 @@ class QualityInspection(Document):
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
),
_(
"Row #{0}: {1} is not a valid reading field. Please refer to the field description."
).format(reading.idx, field),
title=_("Invalid Formula"),
)
except Exception:
@@ -251,40 +247,26 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
qi_condition = ""
return frappe.db.sql(
"""
f"""
SELECT item_code
FROM `tab{doc}`
FROM `tab{from_doctype}`
WHERE parent=%(parent)s and docstatus < 2 and item_code like %(txt)s
{qi_condition} {cond} {mcond}
ORDER BY item_code limit {page_len} offset {start}
""".format(
doc=from_doctype,
cond=cond,
mcond=mcond,
start=cint(start),
page_len=cint(page_len),
qi_condition=qi_condition,
),
ORDER BY item_code limit {cint(page_len)} offset {cint(start)}
""",
{"parent": filters.get("parent"), "txt": "%%%s%%" % txt},
)
elif filters.get("reference_name"):
return frappe.db.sql(
"""
f"""
SELECT production_item
FROM `tab{doc}`
FROM `tab{from_doctype}`
WHERE name = %(reference_name)s and docstatus < 2 and production_item like %(txt)s
{qi_condition} {cond} {mcond}
ORDER BY production_item
limit {page_len} offset {start}
""".format(
doc=from_doctype,
cond=cond,
mcond=mcond,
start=cint(start),
page_len=cint(page_len),
qi_condition=qi_condition,
),
limit {cint(page_len)} offset {cint(start)}
""",
{"reference_name": filters.get("reference_name"), "txt": "%%%s%%" % txt},
)

View File

@@ -159,9 +159,7 @@ class TestQualityInspection(FrappeTestCase):
do_not_submit=True,
)
readings = [
{"specification": "Iron Content", "min_value": 0.1, "max_value": 0.9, "reading_1": "1.0"}
]
readings = [{"specification": "Iron Content", "min_value": 0.1, "max_value": 0.9, "reading_1": "1.0"}]
qa = create_quality_inspection(
reference_type="Stock Entry", reference_name=se.name, readings=readings, status="Rejected"

View File

@@ -17,17 +17,13 @@ class QuickStockBalance(Document):
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"]
)
out["item"] = frappe.db.get_value("Item Barcode", filters={"barcode": barcode}, fieldname=["parent"])
if not out["item"]:
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)

View File

@@ -117,15 +117,12 @@ class RepostItemValuation(Document):
if not acc_settings.acc_frozen_upto:
return
if getdate(self.posting_date) <= getdate(acc_settings.acc_frozen_upto):
if (
if acc_settings.frozen_accounts_modifier and frappe.session.user in get_users_with_role(
acc_settings.frozen_accounts_modifier
and frappe.session.user in get_users_with_role(acc_settings.frozen_accounts_modifier)
):
frappe.msgprint(_("Caution: This might alter frozen accounts."))
return
frappe.throw(
_("You cannot repost item valuation before {}").format(acc_settings.acc_frozen_upto)
)
frappe.throw(_("You cannot repost item valuation before {}").format(acc_settings.acc_frozen_upto))
def reset_field_values(self):
if self.based_on == "Transaction":

View File

@@ -179,7 +179,6 @@ class TestRepostItemValuation(FrappeTestCase, StockTestMixin):
riv3.set_status("Skipped")
def test_stock_freeze_validation(self):
today = nowdate()
riv = frappe.get_doc(

View File

@@ -3,7 +3,6 @@
import json
from typing import List, Optional, Union
import frappe
from frappe import ValidationError, _
@@ -66,7 +65,7 @@ class SerialNoDuplicateError(ValidationError):
class SerialNo(StockController):
def __init__(self, *args, **kwargs):
super(SerialNo, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self.via_stock_ledger = False
def validate(self):
@@ -123,9 +122,7 @@ class SerialNo(StockController):
"""
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)
)
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
@@ -301,7 +298,8 @@ def validate_serial_no(sle, item_det):
if len(serial_nos) != len(set(serial_nos)):
frappe.throw(
_("Duplicate Serial No entered for Item {0}").format(sle.item_code), SerialNoDuplicateError
_("Duplicate Serial No entered for Item {0}").format(sle.item_code),
SerialNoDuplicateError,
)
allow_existing_serial_no = cint(
@@ -337,7 +335,9 @@ def validate_serial_no(sle, item_det):
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),
_("Serial No {0} does not belong to Item {1}").format(
serial_no, sle.item_code
),
SerialNoItemError,
)
@@ -362,7 +362,9 @@ def validate_serial_no(sle, item_det):
frappe.throw(_(msg), SerialNoDuplicateError)
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))
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
@@ -375,25 +377,32 @@ def validate_serial_no(sle, item_det):
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")
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),
_("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)
frappe.throw(
_("Serial No {0} not in stock").format(serial_no), SerialNoNotExistsError
)
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),
_("Serial No {0} does not belong to Batch {1}").format(
serial_no, sle.batch_no
),
SerialNoBatchError,
)
@@ -408,7 +417,11 @@ def validate_serial_no(sle, item_det):
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},
{
"parent": sle.voucher_no,
"item_code": sle.item_code,
"sales_order": sr.sales_order,
},
):
frappe.throw(
_(
@@ -431,7 +444,11 @@ def validate_serial_no(sle, item_det):
)
if not invoice or frappe.db.exists(
"Sales Invoice Item",
{"parent": invoice, "item_code": sle.item_code, "sales_order": sr.sales_order},
{
"parent": invoice,
"item_code": sle.item_code,
"sales_order": sr.sales_order,
},
):
frappe.throw(
_(
@@ -467,7 +484,9 @@ def validate_serial_no(sle, item_det):
{"parent": sales_invoice, "item_code": sle.item_code},
"sales_order",
)
if sales_order and get_reserved_qty_for_so(sales_order, sle.item_code):
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
@@ -483,9 +502,7 @@ def validate_serial_no(sle, item_det):
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)
@@ -538,9 +555,7 @@ def validate_so_serial_no(sr, sales_order):
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:
@@ -584,7 +599,7 @@ def update_serial_nos(sle, item_det):
def get_auto_serial_nos(serial_no_series, qty):
serial_nos = []
for i in range(cint(qty)):
for _i in range(cint(qty)):
serial_nos.append(get_new_serial_number(serial_no_series))
return "\n".join(serial_nos)
@@ -634,13 +649,11 @@ def auto_make_serial_nos(args):
def get_items_html(serial_nos, item_code):
body = ", ".join(serial_nos)
return """<details><summary>
<b>{0}:</b> {1} Serial Numbers <span class="caret"></span>
return f"""<details><summary>
<b>{item_code}:</b> {len(serial_nos)} Serial Numbers <span class="caret"></span>
</summary>
<div class="small">{2}</div></details>
""".format(
item_code, len(serial_nos), body
)
<div class="small">{body}</div></details>
"""
def get_item_details(item_code):
@@ -657,9 +670,7 @@ 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:
@@ -779,11 +790,9 @@ def update_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`
f""" 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)
),
and sales_invoice is null limit {cint(qty)}""",
{"item_code": item_code, "delivery_note": delivery_note},
)
@@ -798,12 +807,11 @@ def auto_fetch_serial_number(
qty: int,
item_code: str,
warehouse: str,
posting_date: Optional[str] = None,
batch_nos: Optional[Union[str, List[str]]] = None,
for_doctype: Optional[str] = None,
posting_date: str | None = None,
batch_nos: str | list[str] | None = None,
for_doctype: str | None = None,
exclude_sr_nos=None,
) -> List[str]:
) -> list[str]:
filters = frappe._dict({"item_code": item_code, "warehouse": warehouse})
if exclude_sr_nos is None:

View File

@@ -248,16 +248,12 @@ class TestSerialNo(FrappeTestCase):
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(
make_stock_entry(item_code=item_code, to_warehouse=warehouse, qty=1, rate=42, serial_no=serial_nos[0])
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]

View File

@@ -76,9 +76,7 @@ def create_test_shipment(delivery_notes=None):
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_parcel", {"length": 5, "width": 5, "height": 5, "weight": 5, "count": 5})
shipment.insert()
return shipment
@@ -96,9 +94,7 @@ 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}
)
customer_address = frappe.get_all("Address", fields=["name"], filters={"address_title": address_title})
if len(customer_address):
return customer_address[0]
else:
@@ -160,9 +156,7 @@ def create_customer_contact(fname, 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(
"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.insert()
return customer

View File

@@ -68,7 +68,7 @@ form_grid_templates = {"items": "templates/form_grid/stock_entry_grid.html"}
class StockEntry(StockController):
def __init__(self, *args, **kwargs):
super(StockEntry, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
if self.purchase_order:
self.subcontract_data = frappe._dict(
{
@@ -98,9 +98,7 @@ 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)
@@ -268,7 +266,11 @@ class StockEntry(StockController):
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"},
filters={
"docstatus": 0,
"outgoing_stock_entry": self.name,
"purpose": "Receive at Warehouse",
},
):
frappe.delete_doc("Stock Entry", d.name)
@@ -422,7 +424,7 @@ class StockEntry(StockController):
return
precision = frappe.get_precision("Stock Entry Detail", "qty")
fg_item = list(fg_qty.keys())[0]
fg_item = next(iter(fg_qty.keys()))
fg_item_qty = flt(fg_qty[fg_item], precision)
fg_completed_qty = flt(self.fg_completed_qty, precision)
@@ -605,14 +607,13 @@ class StockEntry(StockController):
production_item, qty = frappe.db.get_value(
"Work Order", self.work_order, ["production_item", "qty"]
)
args = other_ste + [production_item]
args = [*other_ste, production_item]
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"),
where parent in ({})
and item_code = {}
and ifnull(s_warehouse,'')='' """.format(", ".join(["%s" * len(other_ste)]), "%s"),
args,
)[0][0]
if fg_qty_already_entered and fg_qty_already_entered >= qty:
@@ -690,9 +691,7 @@ class StockEntry(StockController):
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
)
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)
items = []
@@ -856,8 +855,6 @@ class StockEntry(StockController):
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"]:
serial_nos = []
@@ -895,13 +892,17 @@ class StockEntry(StockController):
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 subcontract_order.supplied_items if d.rm_item_code == item_code]
[
flt(d.required_qty)
for d in subcontract_order.supplied_items
if d.rm_item_code == item_code
]
)
total_allowed = required_qty + (required_qty * (qty_allowance / 100))
if not required_qty:
bom_no = frappe.db.get_value(
frappe.db.get_value(
f"{self.subcontract_data.order_doctype} Item",
{
"parent": self.get(self.subcontract_data.order_field),
@@ -947,7 +948,10 @@ class StockEntry(StockController):
& (se.docstatus == 1)
& (se_detail.item_code == se_item.item_code)
& (
((se.purchase_order == self.purchase_order) & (se_detail.po_detail == se_item.po_detail))
(
(se.purchase_order == self.purchase_order)
& (se_detail.po_detail == se_item.po_detail)
)
if self.subcontract_data.order_doctype == "Purchase Order"
else (
(se.subcontracting_order == self.subcontracting_order)
@@ -1000,7 +1004,9 @@ class StockEntry(StockController):
else:
if not se_item.allow_alternative_item:
frappe.throw(
_("Row {0}# Item {1} not found in 'Raw Materials Supplied' table in {2} {3}").format(
_(
"Row {0}# Item {1} not found in 'Raw Materials Supplied' table in {2} {3}"
).format(
se_item.idx,
se_item.item_code,
self.subcontract_data.order_doctype,
@@ -1187,12 +1193,18 @@ class StockEntry(StockController):
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}
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
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
@@ -1215,7 +1227,7 @@ class StockEntry(StockController):
sl_entries.append(sle)
def get_gl_entries(self, warehouse_account):
gl_entries = super(StockEntry, self).get_gl_entries(warehouse_account)
gl_entries = super().get_gl_entries(warehouse_account)
if self.purpose in ("Repack", "Manufacture"):
total_basic_amount = sum(flt(t.basic_amount) for t in self.get("items") if t.is_finished_item)
@@ -1248,9 +1260,9 @@ class StockEntry(StockController):
flt(t.amount * multiply_based_on) / divide_based_on
)
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
)
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"):
@@ -1282,7 +1294,9 @@ class StockEntry(StockController):
"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
* amount[
"base_amount"
], # put it as negative credit instead of debit purposefully
},
item=d,
)
@@ -1415,7 +1429,10 @@ class StockEntry(StockController):
):
subcontract_items = frappe.get_all(
self.subcontract_data.order_supplied_items_field,
{"parent": self.get(self.subcontract_data.order_field), "rm_item_code": args.get("item_code")},
{
"parent": self.get(self.subcontract_data.order_field),
"rm_item_code": args.get("item_code"),
},
"main_item_code",
)
@@ -1465,7 +1482,6 @@ class StockEntry(StockController):
)
if self.bom_no:
backflush_based_on = frappe.db.get_single_value(
"Manufacturing Settings", "backflush_raw_materials_based_on"
)
@@ -1479,7 +1495,6 @@ class StockEntry(StockController):
"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)
if self.to_warehouse and self.pro_doc:
@@ -1489,7 +1504,10 @@ class StockEntry(StockController):
elif (
self.work_order
and (self.purpose == "Manufacture" or self.purpose == "Material Consumption for Manufacture")
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"
):
@@ -1497,7 +1515,10 @@ class StockEntry(StockController):
elif (
self.work_order
and (self.purpose == "Manufacture" or self.purpose == "Material Consumption for Manufacture")
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
):
@@ -1510,7 +1531,10 @@ class StockEntry(StockController):
item_dict = self.get_bom_raw_materials(self.fg_completed_qty)
# Get Subcontract Order Supplied Items Details
if self.get(self.subcontract_data.order_field) and self.purpose == "Send to Subcontractor":
if (
self.get(self.subcontract_data.order_field)
and self.purpose == "Send to Subcontractor"
):
# Get Subcontract Order Supplied Items Details
parent = frappe.qb.DocType(self.subcontract_data.order_doctype)
child = frappe.qb.DocType(self.subcontract_data.order_supplied_items_field)
@@ -1529,9 +1553,14 @@ class StockEntry(StockController):
if self.pro_doc and cint(self.pro_doc.from_wip_warehouse):
item["from_warehouse"] = self.pro_doc.wip_warehouse
# Get Reserve Warehouse from Subcontract Order
if self.get(self.subcontract_data.order_field) and self.purpose == "Send to Subcontractor":
if (
self.get(self.subcontract_data.order_field)
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)
@@ -1727,9 +1756,7 @@ class StockEntry(StockController):
from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict
if (
frappe.db.get_single_value(
"Manufacturing Settings", "set_op_cost_and_scrape_from_sub_assemblies"
)
frappe.db.get_single_value("Manufacturing Settings", "set_op_cost_and_scrape_from_sub_assemblies")
and self.work_order
and frappe.get_cached_value("Work Order", self.work_order, "use_multi_level_bom")
):
@@ -1885,7 +1912,7 @@ class StockEntry(StockController):
as_dict=1,
)
for key, row in available_materials.items():
for _key, row in available_materials.items():
remaining_qty_to_produce = flt(wo_data.trans_qty) - flt(wo_data.produced_qty)
if remaining_qty_to_produce <= 0 and not self.is_return:
continue
@@ -2029,9 +2056,7 @@ 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
@@ -2046,7 +2071,9 @@ class StockEntry(StockController):
)
item_row["job_card_item"] = job_card_item or None
if d.source_warehouse and not frappe.db.get_value("Warehouse", d.source_warehouse, "is_group"):
if d.source_warehouse and not frappe.db.get_value(
"Warehouse", d.source_warehouse, "is_group"
):
item_row["from_warehouse"] = d.source_warehouse
item_row["to_warehouse"] = wip_warehouse
@@ -2101,9 +2128,9 @@ class StockEntry(StockController):
if item_row.get(field):
se_child.set(field, item_row.get(field))
if se_child.s_warehouse == None:
if se_child.s_warehouse is None:
se_child.s_warehouse = self.from_warehouse
if se_child.t_warehouse == None:
if se_child.t_warehouse is None:
se_child.t_warehouse = self.to_warehouse
# in stock uom
@@ -2159,15 +2186,20 @@ 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_subcontract_order_supplied_items(self):
if self.get(self.subcontract_data.order_field) and (
self.purpose in ["Send to Subcontractor", "Material Transfer"] or self.is_return
):
# Get Subcontract Order Supplied Items Details
order_supplied_items = frappe.db.get_all(
self.subcontract_data.order_supplied_items_field,
@@ -2268,8 +2300,8 @@ class StockEntry(StockController):
cond = ""
for data, transferred_qty in stock_entries.items():
cond += """ WHEN (parent = %s and name = %s) THEN %s
""" % (
cond += """ WHEN (parent = {} and name = {}) THEN {}
""".format(
frappe.db.escape(data[0]),
frappe.db.escape(data[1]),
transferred_qty,
@@ -2325,7 +2357,9 @@ class StockEntry(StockController):
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)
@@ -2530,9 +2564,7 @@ def get_operating_cost_per_unit(work_order=None, bom_no=None):
)
)
):
operating_cost_per_unit += flt(work_order.corrective_operation_cost) / flt(
work_order.produced_qty
)
operating_cost_per_unit += flt(work_order.corrective_operation_cost) / flt(work_order.produced_qty)
return operating_cost_per_unit
@@ -2545,24 +2577,20 @@ def get_used_alternative_items(
if subcontract_order:
cond = f"and ste.purpose = 'Send to Subcontractor' and ste.{subcontract_order_field} = '{subcontract_order}'"
elif work_order:
cond = "and ste.purpose = 'Material Transfer for Manufacture' and ste.work_order = '{0}'".format(
work_order
)
cond = f"and ste.purpose = 'Material Transfer for Manufacture' and ste.work_order = '{work_order}'"
if not cond:
return {}
used_alternative_items = {}
data = frappe.db.sql(
""" select sted.original_item, sted.uom, sted.conversion_factor,
f""" 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
),
{cond} """,
as_dict=1,
)
@@ -2600,9 +2628,7 @@ 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 conversion factor required for UOM: {0} in Item: {1}").format(uom, item_code)
)
frappe.msgprint(_("UOM conversion factor required for UOM: {0} in Item: {1}").format(uom, item_code))
ret = {"uom": ""}
else:
ret = {
@@ -2712,9 +2738,7 @@ def get_supplied_items(
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
@@ -2804,7 +2828,11 @@ def get_stock_entry_data(work_order):
& (stock_entry_detail.s_warehouse.isnotnull())
& (
stock_entry.purpose.isin(
["Manufacture", "Material Consumption for Manufacture", "Material Transfer for Manufacture"]
[
"Manufacture",
"Material Consumption for Manufacture",
"Material Transfer for Manufacture",
]
)
)
)

View File

@@ -2,7 +2,7 @@
# See license.txt
from typing import TYPE_CHECKING, Optional, overload
from typing import TYPE_CHECKING, overload
import frappe
from frappe.utils import cint, flt
@@ -18,15 +18,15 @@ def make_stock_entry(
*,
item_code: str,
qty: float,
company: Optional[str] = None,
from_warehouse: Optional[str] = None,
to_warehouse: Optional[str] = None,
rate: Optional[float] = None,
serial_no: Optional[str] = None,
batch_no: Optional[str] = None,
posting_date: Optional[str] = None,
posting_time: Optional[str] = None,
purpose: Optional[str] = None,
company: str | None = None,
from_warehouse: str | None = None,
to_warehouse: str | None = None,
rate: float | None = None,
serial_no: str | None = None,
batch_no: str | None = None,
posting_date: str | None = None,
posting_time: str | None = None,
purpose: str | None = None,
do_not_save: bool = False,
do_not_submit: bool = False,
inspection_required: bool = False,

View File

@@ -5,7 +5,7 @@
import frappe
from frappe.permissions import add_user_permission, remove_user_permission
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, add_to_date, flt, nowdate, nowtime, today
from frappe.utils import add_days, flt, nowdate, nowtime, today
from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.stock.doctype.item.test_item import (
@@ -14,7 +14,7 @@ from erpnext.stock.doctype.item.test_item import (
make_item_variant,
set_item_variant_settings,
)
from erpnext.stock.doctype.serial_no.serial_no import * # noqa
from erpnext.stock.doctype.serial_no.serial_no import *
from erpnext.stock.doctype.stock_entry.stock_entry import (
FinishedGoodError,
make_stock_in_entry,
@@ -35,7 +35,7 @@ def get_sle(**args):
condition, values = "", []
for key, value in args.items():
condition += " and " if condition else " where "
condition += "`{0}`=%s".format(key)
condition += f"`{key}`=%s"
values.append(value)
return frappe.db.sql(
@@ -136,8 +136,7 @@ class TestStockEntry(FrappeTestCase):
)
projected_qty = (
frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "projected_qty")
or 0
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)
@@ -664,14 +663,10 @@ 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")
@@ -719,9 +714,7 @@ class TestStockEntry(FrappeTestCase):
else:
item = frappe.get_doc("Item", {"item_name": "Batched and Serialised Item"})
se = make_stock_entry(
item_code=item.item_code, target="_Test Warehouse - _TC", qty=1, basic_rate=100
)
se = make_stock_entry(item_code=item.item_code, target="_Test Warehouse - _TC", qty=1, basic_rate=100)
batch_no = se.items[0].batch_no
serial_no = get_serial_nos(se.items[0].serial_no)[0]
batch_qty = get_batch_qty(batch_no, "_Test Warehouse - _TC", item.item_code)
@@ -797,7 +790,7 @@ class TestStockEntry(FrappeTestCase):
self.assertEqual(frappe.db.get_value("Serial No", serial_no2, "status"), "Inactive")
def test_warehouse_company_validation(self):
company = frappe.db.get_value("Warehouse", "_Test Warehouse 2 - _TC1", "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"
)
@@ -903,12 +896,8 @@ 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 = next(filter(lambda x: x.item_code == "_Test FG Item 2", stock_entry.get("items"))).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):
@@ -951,8 +940,8 @@ class TestStockEntry(FrappeTestCase):
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
scrap_cost = list(filter(lambda x: x.is_scrap_item, s.get("items")))[0].amount
fg_cost = next(filter(lambda x: x.item_code == "_Test FG Item", s.get("items"))).amount
scrap_cost = next(filter(lambda x: x.is_scrap_item, s.get("items"))).amount
self.assertEqual(fg_cost, flt(rm_cost - scrap_cost, 2))
# When Stock Entry has only FG + Scrap
@@ -966,13 +955,11 @@ class TestStockEntry(FrappeTestCase):
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 = next(filter(lambda x: x.item_code == "_Test FG Item", s.get("items"))).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
@@ -1073,9 +1060,7 @@ class TestStockEntry(FrappeTestCase):
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
@@ -1126,9 +1111,7 @@ class TestStockEntry(FrappeTestCase):
self.assertRaises(frappe.ValidationError, repack.submit)
def test_customer_provided_parts_se(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)
se = make_stock_entry(
item_code="CUST-0987", purpose="Material Receipt", qty=4, to_warehouse="_Test Warehouse - _TC"
)
@@ -1243,9 +1226,7 @@ class TestStockEntry(FrappeTestCase):
self.check_gl_entries(
"Stock Entry",
se.name,
sorted(
[["Stock Adjustment - TCP1", 100.0, 0.0], ["Miscellaneous Expenses - TCP1", 0.0, 100.0]]
),
sorted([["Stock Adjustment - TCP1", 100.0, 0.0], ["Miscellaneous Expenses - TCP1", 0.0, 100.0]]),
)
def test_conversion_factor_change(self):
@@ -1409,7 +1390,7 @@ class TestStockEntry(FrappeTestCase):
)
batch_nos.append(se2.items[0].batch_no)
with self.assertRaises(NegativeStockError) as nse:
with self.assertRaises(NegativeStockError):
make_stock_entry(
item_code=item_code,
qty=1,
@@ -1455,7 +1436,7 @@ class TestStockEntry(FrappeTestCase):
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):
for row, batch_no in zip(issue.items, batch_nos, strict=False):
row.batch_no = batch_no
issue.save()
issue.submit()
@@ -1663,15 +1644,12 @@ class TestStockEntry(FrappeTestCase):
self.assertRaises(BatchExpiredError, se.save)
def test_negative_stock_reco(self):
from erpnext.controllers.stock_controller import BatchExpiredError
from erpnext.stock.doctype.batch.test_batch import make_new_batch
frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 0)
item_code = "Test Negative Item - 001"
item_doc = create_item(item_code=item_code, is_stock_item=1, valuation_rate=10)
create_item(item_code=item_code, is_stock_item=1, valuation_rate=10)
se1 = make_stock_entry(
make_stock_entry(
item_code=item_code,
posting_date=add_days(today(), -3),
posting_time="00:00:00",
@@ -1680,7 +1658,7 @@ class TestStockEntry(FrappeTestCase):
to_warehouse="_Test Warehouse - _TC",
)
se2 = make_stock_entry(
make_stock_entry(
item_code=item_code,
posting_date=today(),
posting_time="00:00:00",

View File

@@ -222,7 +222,9 @@ class StockLedgerEntry(Document):
)
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),
_("Not allowed to update stock transactions older than {0}").format(
stock_frozen_upto_days
),
StockFreezeError,
)
@@ -240,7 +242,9 @@ class StockLedgerEntry(Document):
expiry_date = frappe.db.get_value("Batch", self.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(self.batch_no, self.item_code))
frappe.throw(
_("Batch {0} of Item {1} has expired.").format(self.batch_no, self.item_code)
)
def validate_and_set_fiscal_year(self):
if not self.fiscal_year:
@@ -273,7 +277,7 @@ class StockLedgerEntry(Document):
(self.item_code, self.warehouse),
)[0][0]
cur_doc_posting_datetime = "%s %s" % (
cur_doc_posting_datetime = "{} {}".format(
self.posting_date,
self.get("posting_time") or "00:00:00",
)
@@ -282,7 +286,9 @@ class StockLedgerEntry(Document):
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)
frappe.bold(self.item_code),
frappe.bold(self.warehouse),
frappe.bold(last_transaction_time),
)
msg += "<br><br>" + _(

View File

@@ -8,7 +8,6 @@ from uuid import uuid4
import frappe
from frappe.core.page.permission_manager.permission_manager import reset
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from frappe.query_builder.functions import CombineDatetime
from frappe.tests.utils import FrappeTestCase, change_settings
from frappe.utils import add_days, add_to_date, flt, today
@@ -35,8 +34,7 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
# delete SLE and BINs for all items
frappe.db.sql(
"delete from `tabStock Ledger Entry` where item_code in (%s)"
% (", ".join(["%s"] * len(items))),
"delete from `tabStock Ledger Entry` where item_code in (%s)" % (", ".join(["%s"] * len(items))),
items,
)
frappe.db.sql(
@@ -122,7 +120,7 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
self.assertEqual(finished_item_sle.get("valuation_rate"), 540)
# Reconciliation for _Test Item for Reposting at Stores on 12-04-2020: Qty = 50, Rate = 150
sr = create_stock_reconciliation(
create_stock_reconciliation(
item_code="_Test Item for Reposting",
warehouse="Stores - _TC",
qty=50,
@@ -484,7 +482,7 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
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):
for dn, incoming_rate in zip(dns, expected_incoming_rates, strict=False):
self.assertEqual(
dn.items[0].incoming_rate,
incoming_rate,
@@ -556,9 +554,7 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
],
)
reciept = make_stock_entry(
item_code=item_code, target=source, batch_no=batches[0], qty=5, rate=10
)
reciept = make_stock_entry(item_code=item_code, target=source, batch_no=batches[0], qty=5, rate=10)
self.assertSLEs(
reciept,
[
@@ -680,9 +676,7 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
],
)
consume_today = make_stock_entry(
item_code=item_code, source=warehouse, batch_no=batches[0], qty=1
)
consume_today = make_stock_entry(item_code=item_code, source=warehouse, batch_no=batches[0], qty=1)
self.assertSLEs(
consume_today,
[
@@ -715,8 +709,8 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
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):
for i, (sle_vals, ex_sle_vals) in enumerate(zip(sle_details, expected_sle_details, strict=False)):
for col, sle_val, ex_sle_val in zip(columns, sle_vals, ex_sle_vals, strict=False):
if col == "stock_queue":
sle_val = get_stock_value_from_q(sle_val)
ex_sle_val = get_stock_value_from_q(ex_sle_val)
@@ -746,9 +740,7 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
se_entry_list_mi = [
(item, warehouses[0], None, batches[1], 1, None, "2021-01-29"),
]
ses = create_stock_entry_entries_for_batchwise_item_valuation_test(
se_entry_list_mi, "Material Issue"
)
ses = create_stock_entry_entries_for_batchwise_item_valuation_test(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))
@@ -771,9 +763,7 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
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
)
old1 = make_stock_entry(item_code=item_code, target=warehouse, batch_no=batches[0], qty=10, rate=10)
self.assertSLEs(
old1,
update_invariants(
@@ -782,20 +772,20 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
]
),
)
old2 = make_stock_entry(
item_code=item_code, target=warehouse, batch_no=batches[1], qty=10, rate=20
)
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]]},
{
"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
)
old3 = make_stock_entry(item_code=item_code, target=warehouse, batch_no=batches[0], qty=5, rate=15)
self.assertSLEs(
old3,
@@ -842,9 +832,7 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
)
# consume old batch as per FIFO
consume_old1 = make_stock_entry(
item_code=item_code, source=warehouse, qty=15, batch_no=batches[0]
)
consume_old1 = make_stock_entry(item_code=item_code, source=warehouse, qty=15, batch_no=batches[0])
self.assertSLEs(
consume_old1,
update_invariants(
@@ -859,22 +847,22 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
)
# consume new batch as per batch
consume_new2 = make_stock_entry(
item_code=item_code, source=warehouse, qty=10, batch_no=batches[-1]
)
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]]},
{
"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]
)
consume_old2 = make_stock_entry(item_code=item_code, source=warehouse, qty=10, batch_no=batches[1])
self.assertSLEs(
consume_old2,
update_invariants(
@@ -885,9 +873,7 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
)
# finish all new batches
consume_new1 = make_stock_entry(
item_code=item_code, source=warehouse, qty=10, batch_no=batches[-2]
)
consume_new1 = make_stock_entry(item_code=item_code, source=warehouse, qty=10, batch_no=batches[-2])
self.assertSLEs(
consume_new1,
update_invariants(
@@ -938,9 +924,7 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
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
@@ -970,9 +954,7 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
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):
"""
@@ -982,7 +964,7 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
item = make_item(properties={"allow_negative_stock": 1}).name
warehouse = "_Test Warehouse - _TC"
receipt = make_stock_entry(item_code=item, target=warehouse, qty=10, rate=10)
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]]}])
@@ -1022,27 +1004,21 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
self.assertEqual(50, _get_stock_credit(depdendent_consumption))
# backdated receipt - should trigger GL repost of all previous stock entries
bd_receipt = make_stock_entry(
item_code=item, to_warehouse=A, qty=5, rate=20, posting_date=_day(-1)
)
bd_receipt = make_stock_entry(item_code=item, to_warehouse=A, qty=5, rate=20, posting_date=_day(-1))
self.assertEqual(100, _get_stock_credit(depdendent_consumption))
# cancelling receipt should reset it back
bd_receipt.cancel()
self.assertEqual(50, _get_stock_credit(depdendent_consumption))
bd_receipt2 = make_stock_entry(
item_code=item, to_warehouse=A, qty=2, rate=20, posting_date=_day(-2)
)
bd_receipt2 = make_stock_entry(item_code=item, to_warehouse=A, qty=2, rate=20, posting_date=_day(-2))
# total as per FIFO -> 2 * 20 + 3 * 10 = 70
self.assertEqual(70, _get_stock_credit(depdendent_consumption))
# transfer WIP material to final destination and consume it all
depdendent_consumption.cancel()
make_stock_entry(item_code=item, from_warehouse=B, to_warehouse=C, qty=5, posting_date=_day(3))
final_consumption = make_stock_entry(
item_code=item, from_warehouse=C, qty=5, posting_date=_day(4)
)
final_consumption = make_stock_entry(item_code=item, from_warehouse=C, qty=5, posting_date=_day(4))
# exact amount gets consumed
self.assertEqual(70, _get_stock_credit(final_consumption))
@@ -1109,11 +1085,10 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
self.assertEqual([1], ordered_qty_after_transaction())
def test_timestamp_clash(self):
item = make_item().name
warehouse = "_Test Warehouse - _TC"
reciept = make_stock_entry(
make_stock_entry(
item_code=item,
to_warehouse=warehouse,
qty=100,
@@ -1122,7 +1097,7 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
posting_time="01:00:00",
)
consumption = make_stock_entry(
make_stock_entry(
item_code=item,
from_warehouse=warehouse,
qty=50,
@@ -1141,11 +1116,10 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
try:
backdated_receipt.cancel()
except Exception as e:
except Exception:
self.fail("Double processing of qty for clashing timestamp.")
def test_previous_sle_with_clashed_timestamp(self):
item = make_item().name
warehouse = "_Test Warehouse - _TC"
@@ -1186,7 +1160,6 @@ class TestStockLedgerEntry(FrappeTestCase, StockTestMixin):
self.assertEqual(sle[0].actual_qty, 5)
def test_backdated_sle_with_same_timestamp(self):
item = make_item().name
warehouse = "_Test Warehouse - _TC"
@@ -1429,12 +1402,13 @@ def create_items(items=None, uoms=None):
def setup_item_valuation_test(
valuation_method="FIFO", suffix=None, use_batchwise_valuation=1, batches_list=["X", "Y"]
valuation_method="FIFO", suffix=None, use_batchwise_valuation=1, batches_list=None
):
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
if batches_list is None:
batches_list = ["X", "Y"]
if not suffix:
suffix = get_unique_suffix()
@@ -1448,7 +1422,7 @@ def setup_item_valuation_test(
for i, batch_id in enumerate(batches):
if not frappe.db.exists("Batch", batch_id):
ubw = use_batchwise_valuation
if isinstance(use_batchwise_valuation, (list, tuple)):
if isinstance(use_batchwise_valuation, list | tuple):
ubw = use_batchwise_valuation[i]
batch = frappe.get_doc(
frappe._dict(
@@ -1479,9 +1453,7 @@ def create_delivery_note_entries_for_batchwise_item_valuation_test(dn_entry_list
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
)
so = make_sales_order(rate=rate, qty=qty, item=item, warehouse=warehouse, against_blanket_order=0)
dn = make_delivery_note(so.name)
dn.items[0].batch_no = batch_no

View File

@@ -1,7 +1,6 @@
# 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 _, bold, msgprint
@@ -27,7 +26,7 @@ class EmptyStockReconciliationItemsError(frappe.ValidationError):
class StockReconciliation(StockController):
def __init__(self, *args, **kwargs):
super(StockReconciliation, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self.head_row = ["Item Code", "Warehouse", "Quantity", "Valuation Rate"]
def validate(self):
@@ -87,7 +86,9 @@ class StockReconciliation(StockController):
if not item.batch_no and not item.serial_no:
for dimension in get_inventory_dimensions():
if item.get(dimension.get("fieldname")):
inventory_dimensions_dict[dimension.get("fieldname")] = item.get(dimension.get("fieldname"))
inventory_dimensions_dict[dimension.get("fieldname")] = item.get(
dimension.get("fieldname")
)
item_dict = get_stock_balance_for(
item.item_code,
@@ -189,19 +190,27 @@ class StockReconciliation(StockController):
# 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.batch_no and frappe.get_cached_value("Batch", row.batch_no, "item") != row.item_code:
self.validation_messages.append(
_get_msg(
row_num,
_("Batch {0} does not belong to item {1}").format(bold(row.batch_no), bold(row.item_code)),
_("Batch {0} does not belong to item {1}").format(
bold(row.batch_no), bold(row.item_code)
),
)
)
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
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
@@ -263,11 +272,10 @@ class StockReconciliation(StockController):
sl_entries = []
has_serial_no = False
has_batch_no = False
for row in self.items:
item = frappe.get_doc("Item", row.item_code)
if item.has_batch_no:
has_batch_no = True
pass
if not row.qty and not row.valuation_rate and not row.current_qty:
self.make_adjustment_entry(row, sl_entries)
@@ -551,9 +559,7 @@ 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().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)):
@@ -648,7 +654,12 @@ class StockReconciliation(StockController):
row.current_valuation_rate = item_dict.get("rate")
else:
current_qty = get_batch_qty_for_stock_reco(
row.item_code, row.warehouse, row.batch_no, self.posting_date, self.posting_time, self.name
row.item_code,
row.warehouse,
row.batch_no,
self.posting_date,
self.posting_time,
self.name,
)
precesion = row.precision("current_qty")
@@ -708,9 +719,7 @@ class StockReconciliation(StockController):
return allow_negative_stock
def get_batch_qty_for_stock_reco(
item_code, warehouse, batch_no, posting_date, posting_time, voucher_no
):
def get_batch_qty_for_stock_reco(item_code, warehouse, batch_no, posting_date, posting_time, voucher_no):
ledger = frappe.qb.DocType("Stock Ledger Entry")
query = (
@@ -740,9 +749,7 @@ def get_batch_qty_for_stock_reco(
@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 = []
if item_code and warehouse:
@@ -913,16 +920,14 @@ def get_stock_balance_for(
warehouse: str,
posting_date,
posting_time,
batch_no: Optional[str] = None,
batch_no: str | None = None,
with_valuation_rate: bool = True,
inventory_dimensions_dict=None,
voucher_no=None,
):
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
)
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
@@ -956,9 +961,7 @@ def get_stock_balance_for(
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}

View File

@@ -101,7 +101,8 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
# no gl entries
self.assertTrue(
frappe.db.get_value(
"Stock Ledger Entry", {"voucher_type": "Stock Reconciliation", "voucher_no": stock_reco.name}
"Stock Ledger Entry",
{"voucher_type": "Stock Reconciliation", "voucher_no": stock_reco.name},
)
)
@@ -147,7 +148,6 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
def test_stock_reco_for_serialized_item(self):
to_delete_records = []
to_delete_serial_nos = []
# Add new serial nos
serial_item_code = "Stock-Reco-Serial-Item-1"
@@ -215,7 +215,7 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
purpose="Opening Stock",
)
for i in range(3):
for _i in range(3):
sr.append(
"items",
{
@@ -394,9 +394,7 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
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
)
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)
@@ -579,18 +577,18 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
item_code = self.make_item().name
warehouse = "_Test Warehouse - _TC"
sr = create_stock_reconciliation(
create_stock_reconciliation(
item_code=item_code, warehouse=warehouse, qty=10, rate=100, posting_date=add_days(nowdate(), 10)
)
dn = create_delivery_note(
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(
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(
@@ -830,7 +828,7 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
warehouse = "_Test Warehouse - _TC"
# Stock Value => 100 * 100 = 10000
se = make_stock_entry(
make_stock_entry(
item_code=item_code,
target=warehouse,
qty=100,
@@ -1118,9 +1116,7 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
self.assertEqual(d.actual_qty, 5.0)
self.assertAlmostEqual(d.stock_value_difference, 500.0)
active_serial_no = frappe.get_all(
"Serial No", filters={"status": "Active", "item_code": item_code}
)
active_serial_no = frappe.get_all("Serial No", filters={"status": "Active", "item_code": item_code})
self.assertEqual(len(active_serial_no), 5)
@@ -1213,9 +1209,7 @@ def create_stock_reconciliation(**args):
)
)
if frappe.get_all("Stock Ledger Entry", {"company": sr.company})
else frappe.get_cached_value(
"Account", {"account_type": "Temporary", "company": sr.company}, "name"
)
else frappe.get_cached_value("Account", {"account_type": "Temporary", "company": sr.company}, "name")
)
sr.cost_center = (
args.cost_center

View File

@@ -61,9 +61,9 @@ class StockSettings(Document):
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)
),
_(
"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"),
)

View File

@@ -1,7 +1,6 @@
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase

View File

@@ -58,7 +58,7 @@ class TestWarehouse(FrappeTestCase):
warehouse_ids.append(warehouse_id)
item_names = [f"_Test Item {i} for Unlinking" for i in range(2)]
for item, warehouse in zip(item_names, warehouse_ids):
for item, warehouse in zip(item_names, warehouse_ids, strict=False):
create_item(item, warehouse=warehouse, company=company)
# Delete warehouses
@@ -78,7 +78,6 @@ class TestWarehouse(FrappeTestCase):
)
def test_group_non_group_conversion(self):
warehouse = frappe.get_doc("Warehouse", create_warehouse("TestGroupConversion"))
convert_to_group_or_ledger(warehouse.name)

View File

@@ -26,9 +26,7 @@ class Warehouse(NestedSet):
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")
):
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:

View File

@@ -189,19 +189,14 @@ def set_valuation_rate(out, args):
def update_bin_details(args, out, doc):
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"):
company = args.company if (doc and doc.get("doctype") == "Purchase Order") else None
# calculate company_total_stock only for po
bin_details = get_bin_details(
args.item_code, out.warehouse, company, include_child_warehouses=True
)
bin_details = get_bin_details(args.item_code, out.warehouse, company, include_child_warehouses=True)
out.update(bin_details)
@@ -308,9 +303,7 @@ def get_basic_details(args, item, overwrite_warehouse=True):
if not item:
item = frappe.get_doc("Item", args.get("item_code"))
if (
item.variant_of and not item.taxes and frappe.db.exists("Item Tax", {"parent": item.variant_of})
):
if item.variant_of and not item.taxes and frappe.db.exists("Item Tax", {"parent": item.variant_of}):
item.update_template_tables()
item_defaults = get_item_defaults(item.name, args.company)
@@ -355,9 +348,7 @@ def get_basic_details(args, item, overwrite_warehouse=True):
# Set stock UOM in args, so that it can be used while fetching item price
args.stock_uom = item.stock_uom
if args.get("batch_no") and item.name != frappe.get_cached_value(
"Batch", args.get("batch_no"), "item"
):
if args.get("batch_no") and item.name != frappe.get_cached_value("Batch", args.get("batch_no"), "item"):
args["batch_no"] = ""
out = frappe._dict(
@@ -378,9 +369,7 @@ def get_basic_details(args, item, overwrite_warehouse=True):
"provisional_expense_account": get_provisional_account(
args, item_defaults, item_group_defaults, brand_defaults
),
"cost_center": get_default_cost_center(
args, item_defaults, item_group_defaults, brand_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"),
@@ -406,9 +395,7 @@ def get_basic_details(args, item, overwrite_warehouse=True):
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,
"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"),
@@ -544,9 +531,7 @@ def get_barcode_data(items_list):
itemwise_barcode = {}
for item in items_list:
barcodes = frappe.db.get_all(
"Item Barcode", filters={"parent": item.item_code}, fields="barcode"
)
barcodes = frappe.db.get_all("Item Barcode", filters={"parent": item.item_code}, fields="barcode")
for barcode in barcodes:
if item.item_code not in itemwise_barcode:
@@ -566,13 +551,13 @@ def get_item_tax_info(company, tax_category, item_codes, item_rates=None, item_t
if item_rates is None:
item_rates = {}
if isinstance(item_codes, (str,)):
if isinstance(item_codes, str):
item_codes = json.loads(item_codes)
if isinstance(item_rates, (str,)):
if isinstance(item_rates, str):
item_rates = json.loads(item_rates)
if isinstance(item_tax_templates, (str,)):
if isinstance(item_tax_templates, str):
item_tax_templates = json.loads(item_tax_templates)
for item_code in item_codes:
@@ -640,9 +625,7 @@ def _get_item_tax_template(args, taxes, out=None, for_validate=False):
taxes_with_no_validity.append(tax)
if taxes_with_validity:
taxes = sorted(
taxes_with_validity, key=lambda i: i.valid_from or tax.maximum_net_rate, reverse=True
)
taxes = sorted(taxes_with_validity, key=lambda i: i.valid_from or tax.maximum_net_rate, reverse=True)
else:
taxes = taxes_with_no_validity
@@ -795,7 +778,7 @@ def get_default_cost_center(args, item=None, item_group=None, brand=None, compan
elif not cost_center and args.get("item_code") and company:
for method in ["get_item_defaults", "get_item_group_defaults", "get_brand_defaults"]:
path = "erpnext.stock.get_item_details.{0}".format(method)
path = f"erpnext.stock.get_item_details.{method}"
data = frappe.get_attr(path)(args.get("item_code"), company)
if data and (data.selling_cost_center or data.buying_cost_center):
@@ -810,11 +793,7 @@ 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:
@@ -824,11 +803,7 @@ def get_default_cost_center(args, item=None, item_group=None, brand=None, compan
def get_default_supplier(args, item, item_group, brand):
return (
item.get("default_supplier")
or item_group.get("default_supplier")
or brand.get("default_supplier")
)
return item.get("default_supplier") or item_group.get("default_supplier") or brand.get("default_supplier")
def get_price_list_rate(args, item_doc, out=None):
@@ -863,9 +838,7 @@ def get_price_list_rate(args, item_doc, out=None):
if not price_list_rate:
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 frappe.db.get_single_value("Buying Settings", "disable_last_purchase_rate"):
return out
@@ -880,9 +853,7 @@ def get_price_list_rate(args, item_doc, out=None):
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(
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"):
@@ -904,7 +875,9 @@ def insert_item_price(args):
):
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),
_("Item Price updated for {0} in Price List {1}").format(
args.item_code, args.price_list
),
alert=True,
)
else:
@@ -1044,11 +1017,7 @@ def validate_conversion_rate(args, meta):
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
):
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
)
@@ -1083,7 +1052,9 @@ def validate_conversion_rate(args, meta):
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})),
get_field_precision(
meta.get_field("plc_conversion_rate"), frappe._dict({"fields": args})
),
)
@@ -1264,9 +1235,7 @@ def get_serial_no_details(item_code, warehouse, stock_qty, 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:
@@ -1276,9 +1245,7 @@ def get_bin_details_and_serial_nos(
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
@@ -1390,9 +1357,7 @@ def get_price_list_currency_and_exchange_rate(args):
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
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 = (
@@ -1414,9 +1379,7 @@ def get_price_list_currency_and_exchange_rate(args):
@frappe.whitelist()
def get_default_bom(item_code=None):
def _get_bom(item):
bom = frappe.get_all(
"BOM", dict(item=item, is_active=True, is_default=True, docstatus=1), limit=1
)
bom = frappe.get_all("BOM", dict(item=item, is_active=True, is_default=True, docstatus=1), limit=1)
return bom[0].name if bom else None
if not item_code:
@@ -1456,7 +1419,7 @@ def get_valuation_rate(item_code, company, warehouse=None):
pi_item = frappe.qb.DocType("Purchase Invoice Item")
valuation_rate = (
frappe.qb.from_(pi_item)
.select((Sum(pi_item.base_net_amount) / Sum(pi_item.qty * pi_item.conversion_factor)))
.select(Sum(pi_item.base_net_amount) / Sum(pi_item.qty * pi_item.conversion_factor))
.where((pi_item.docstatus == 1) & (pi_item.item_code == item_code))
).run()

View File

@@ -58,9 +58,7 @@ def _reorder_item():
item_warehouse_projected_qty.get(kwargs.item_code, {}).get(kwargs.warehouse_group)
)
else:
projected_qty = flt(
item_warehouse_projected_qty.get(kwargs.item_code, {}).get(kwargs.warehouse)
)
projected_qty = flt(item_warehouse_projected_qty.get(kwargs.item_code, {}).get(kwargs.warehouse))
if (reorder_level or reorder_qty) and projected_qty < reorder_level:
deficiency = reorder_level - projected_qty
@@ -188,13 +186,12 @@ def get_item_warehouse_projected_qty(items_to_consider):
for item_code, warehouse, projected_qty in frappe.db.sql(
"""select item_code, warehouse, projected_qty
from tabBin where item_code in ({0})
from tabBin where item_code in ({})
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, {})
@@ -242,7 +239,9 @@ def create_material_request(material_requests):
{
"company": company,
"transaction_date": nowdate(),
"material_request_type": "Material Transfer" if request_type == "Transfer" else request_type,
"material_request_type": "Material Transfer"
if request_type == "Transfer"
else request_type,
}
)
@@ -257,7 +256,9 @@ def create_material_request(material_requests):
if uom != item.stock_uom:
conversion_factor = (
frappe.db.get_value(
"UOM Conversion Detail", {"parent": item.name, "uom": uom}, "conversion_factor"
"UOM Conversion Detail",
{"parent": item.name, "uom": uom},
"conversion_factor",
)
or 1.0
)
@@ -323,9 +324,7 @@ def send_email_notification(company_wise_mr):
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 get_email_list(company):
@@ -378,7 +377,7 @@ def notify_errors(exceptions_list):
for exception in exceptions_list:
try:
exception = json.loads(exception)
error_message = """<div class='small text-muted'>{0}</div><br>""".format(
error_message = """<div class='small text-muted'>{}</div><br>""".format(
_(exception.get("message"))
)
content += error_message

View File

@@ -28,15 +28,15 @@ def validate_filters(filters):
def get_columns():
return (
[_("Item") + ":Link/Item:150"]
+ [_("Item Name") + "::150"]
+ [_("Batch") + ":Link/Batch:150"]
+ [_("Stock UOM") + ":Link/UOM:100"]
+ [_("Quantity") + ":Float:100"]
+ [_("Expires On") + ":Date:100"]
+ [_("Expiry (In Days)") + ":Int:130"]
)
return [
_("Item") + ":Link/Item:150",
_("Item Name") + "::150",
_("Batch") + ":Link/Batch:150",
_("Stock UOM") + ":Link/UOM:100",
_("Quantity") + ":Float:100",
_("Expires On") + ":Date:100",
_("Expiry (In Days)") + ":Int:130",
]
def get_data(filters):
@@ -76,9 +76,7 @@ def get_batch_details(filters):
.where(
(batch.disabled == 0)
& (batch.batch_qty > 0)
& (
(Date(batch.creation) >= filters["from_date"]) & (Date(batch.creation) <= filters["to_date"])
)
& ((Date(batch.creation) >= filters["from_date"]) & (Date(batch.creation) <= filters["to_date"]))
)
.orderby(batch.creation)
)

View File

@@ -69,18 +69,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
@@ -141,7 +141,9 @@ def get_item_warehouse_batch_map(filters, 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)
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)
@@ -154,9 +156,7 @@ def get_item_warehouse_batch_map(filters, float_precision):
def get_item_details(filters):
item_map = {}
for d in (frappe.qb.from_("Item").select("name", "item_name", "description", "stock_uom")).run(
as_dict=1
):
for d in (frappe.qb.from_("Item").select("name", "item_name", "description", "stock_uom")).run(as_dict=1):
item_map.setdefault(d.name, d)
return item_map

View File

@@ -3,7 +3,7 @@
import datetime
from collections import OrderedDict
from typing import Dict, List, Tuple, Union
from typing import Union
import frappe
from frappe import _
@@ -13,15 +13,15 @@ from erpnext.accounts.report.general_ledger.general_ledger import get_gl_entries
Filters = frappe._dict
Row = frappe._dict
Data = List[Row]
Columns = List[Dict[str, str]]
Data = list[Row]
Columns = list[dict[str, str]]
DateTime = Union[datetime.date, datetime.datetime]
FilteredEntries = List[Dict[str, Union[str, float, DateTime, None]]]
ItemGroupsDict = Dict[Tuple[int, int], Dict[str, Union[str, int]]]
SVDList = List[frappe._dict]
FilteredEntries = list[dict[str, str | float | DateTime | None]]
ItemGroupsDict = dict[tuple[int, int], dict[str, str | int]]
SVDList = list[frappe._dict]
def execute(filters: Filters) -> Tuple[Columns, Data]:
def execute(filters: Filters) -> tuple[Columns, Data]:
update_filters_with_account(filters)
validate_filters(filters)
columns = get_columns()
@@ -158,7 +158,7 @@ def assign_item_groups_to_svd_list(svd_list: SVDList) -> None:
item.item_group = ig_map[item.get("item_code")]
def get_item_groups_map(svd_list: SVDList) -> Dict[str, str]:
def get_item_groups_map(svd_list: SVDList) -> dict[str, str]:
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)]
@@ -168,9 +168,7 @@ def get_item_groups_map(svd_list: SVDList) -> Dict[str, str]:
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:

View File

@@ -13,7 +13,7 @@ def execute(filters=None, consolidated=False):
return data, columns
class DelayedItemReport(object):
class DelayedItemReport:
def __init__(self, filters=None):
self.filters = frappe._dict(filters or {})

View File

@@ -20,7 +20,7 @@ class DelayedOrderReport(DelayedItemReport):
return self.get_columns(), self.get_data(consolidated=True) or []
def get_data(self, consolidated=False):
data = super(DelayedOrderReport, self).get_data(consolidated) or []
data = super().get_data(consolidated) or []
so_list = []
result = []

View File

@@ -40,19 +40,18 @@ def get_data(filters):
def get_stock_ledger_entries(filters):
sle_filters = {"is_cancelled": 0}
if filters.warehouse:
children = get_descendants_of("Warehouse", filters.warehouse)
sle_filters["warehouse"] = ("in", children + [filters.warehouse])
sle_filters["warehouse"] = ("in", [*children, filters.warehouse])
if filters.item_code:
sle_filters["item_code"] = filters.item_code
elif filters.get("item_group"):
item_group = filters.get("item_group")
children = get_descendants_of("Item Group", item_group)
item_group_filter = {"item_group": ("in", children + [item_group])}
item_group_filter = {"item_group": ("in", [*children, item_group])}
sle_filters["item_code"] = (
"in",
frappe.get_all("Item", filters=item_group_filter, pluck="name", order_by=None),

View File

@@ -27,7 +27,7 @@ def get_data(filters):
def validate_data(itewise_balance_qty):
res = []
for key, data in itewise_balance_qty.items():
for _key, data in itewise_balance_qty.items():
row = get_incorrect_data(data)
if row:
res.append(row)

View File

@@ -43,11 +43,9 @@ def prepare_serial_nos(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():
for _serial_no, data in serial_nos_data.items():
total_dict = frappe._dict({"qty": 0, "valuation_rate": 0, "serial_no": frappe.bold(_("Total"))})
if check_incorrect_serial_data(data, total_dict):

View File

@@ -16,9 +16,7 @@ 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
)
_("Perpetual inventory required for the company {0} to view this report.").format(filters.company)
)
data = get_data(filters)
@@ -83,9 +81,7 @@ def get_data(report_filters):
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], warehouses=key[1]
)
prev_stock_value = get_stock_value_on(posting_date=closing_date, item_code=key[0], warehouses=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:

View File

@@ -111,9 +111,7 @@ def get_price_list():
).run(as_dict=True)
for d in price_list:
d.update(
{"price": "{0} {1} - {2}".format(d.currency, round(d.price_list_rate, 2), d.price_list)}
)
d.update({"price": f"{d.currency} {round(d.price_list_rate, 2)} - {d.price_list}"})
d.pop("currency")
d.pop("price_list_rate")
d.pop("price_list")

View File

@@ -24,9 +24,7 @@ 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)

View File

@@ -3,7 +3,6 @@
from operator import itemgetter
from typing import Dict, List, Tuple, Union
import frappe
from frappe import _
@@ -14,7 +13,7 @@ from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
Filters = frappe._dict
def execute(filters: Filters = None) -> Tuple:
def execute(filters: Filters = None) -> tuple:
to_date = filters["to_date"]
columns = get_columns(filters)
@@ -26,14 +25,14 @@ 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]:
def format_report_data(filters: Filters, item_details: dict, to_date: str) -> list[dict]:
"Returns ordered, formatted data with ranges."
_func = itemgetter(1)
data = []
precision = cint(frappe.db.get_single_value("System Settings", "float_precision", cache=True))
for item, item_dict in item_details.items():
for _item, item_dict in item_details.items():
if not flt(item_dict.get("total_qty"), precision):
continue
@@ -74,12 +73,12 @@ def format_report_data(filters: Filters, item_details: Dict, to_date: str) -> Li
return data
def get_average_age(fifo_queue: List, to_date: str) -> float:
def get_average_age(fifo_queue: list, to_date: str) -> float:
batch_age = age_qty = total_qty = 0.0
for batch in fifo_queue:
batch_age = date_diff(to_date, batch[1])
if isinstance(batch[0], (int, float)):
if isinstance(batch[0], int | float):
age_qty += batch_age * batch[0]
total_qty += batch[0]
else:
@@ -89,8 +88,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:
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))
range1 = range2 = range3 = above_range3 = 0.0
@@ -111,7 +109,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]:
def get_columns(filters: Filters) -> list[dict]:
range_columns = []
setup_ageing_columns(filters, range_columns)
columns = [
@@ -169,7 +167,7 @@ def get_columns(filters: Filters) -> List[Dict]:
return columns
def get_chart_data(data: List, filters: Filters) -> Dict:
def get_chart_data(data: list, filters: Filters) -> dict:
if not data:
return []
@@ -193,7 +191,7 @@ def get_chart_data(data: List, filters: Filters) -> Dict:
}
def setup_ageing_columns(filters: Filters, range_columns: List):
def setup_ageing_columns(filters: Filters, range_columns: list):
ranges = [
f"0 - {filters['range1']}",
f"{cint(filters['range1']) + 1} - {cint(filters['range2'])}",
@@ -205,23 +203,21 @@ def setup_ageing_columns(filters: Filters, range_columns: List):
add_column(range_columns, label=_("Age ({0})").format(label), fieldname=fieldname)
def add_column(
range_columns: List, label: str, fieldname: str, fieldtype: str = "Float", width: int = 140
):
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 = None, sle: list | None = None):
self.item_details = {}
self.transferred_item_details = {}
self.serial_no_batch_purchase_details = {}
self.filters = filters
self.sle = sle
def generate(self) -> Dict:
def generate(self) -> dict:
"""
Returns dict of the foll.g structure:
Key = Item A / (Item A, Warehouse A)
@@ -257,7 +253,7 @@ class FIFOSlots:
return self.item_details
def __init_key_stores(self, row: Dict) -> Tuple:
def __init_key_stores(self, row: dict) -> tuple:
"Initialise keys and FIFO Queue."
key = (row.name, row.warehouse)
@@ -269,9 +265,7 @@ 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)
@@ -297,9 +291,7 @@ 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]
@@ -325,7 +317,7 @@ class FIFOSlots:
self.transferred_item_details[transfer_key].append([qty_to_pop, slot[1]])
qty_to_pop = 0
def __adjust_incoming_transfer_qty(self, transfer_data: Dict, fifo_queue: List, row: Dict):
def __adjust_incoming_transfer_qty(self, transfer_data: dict, fifo_queue: list, row: dict):
"Add previously removed stock back to FIFO Queue."
transfer_qty_to_pop = flt(row.actual_qty)
@@ -352,7 +344,7 @@ class FIFOSlots:
add_to_fifo_queue([transfer_qty_to_pop, transfer_data[0][1]])
transfer_qty_to_pop = 0
def __update_balances(self, row: Dict, key: Union[Tuple, str]):
def __update_balances(self, row: dict, key: tuple | str):
self.item_details[key]["qty_after_transaction"] = row.qty_after_transaction
if "total_qty" not in self.item_details[key]:
@@ -362,7 +354,7 @@ class FIFOSlots:
self.item_details[key]["has_serial_no"] = row.has_serial_no
def __aggregate_details_by_item(self, wh_wise_data: Dict) -> Dict:
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():
@@ -370,7 +362,12 @@ class FIFOSlots:
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},
{
"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"])
@@ -381,7 +378,7 @@ class FIFOSlots:
return item_aggregated_data
def __get_stock_ledger_entries(self) -> List[Dict]:
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

View File

@@ -1,7 +1,6 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import datetime
from typing import List
import frappe
from frappe import _, scrub
@@ -48,12 +47,10 @@ def get_columns(filters):
ranges = get_period_date_ranges(filters)
for dummy, end_date in ranges:
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
@@ -67,7 +64,7 @@ def get_period_date_ranges(filters):
increment = {"Monthly": 1, "Quarterly": 3, "Half-Yearly": 6, "Yearly": 12}.get(filters.range, 1)
periodic_daterange = []
for dummy in range(1, 53, increment):
for _dummy in range(1, 53, increment):
if filters.range == "Weekly":
period_end_date = from_date + relativedelta(days=6)
else:
@@ -116,9 +113,7 @@ def get_period(posting_date, filters):
elif filters.range == "Monthly":
period = _(str(months[posting_date.month - 1])) + " " + str(posting_date.year)
elif filters.range == "Quarterly":
period = _("Quarter {0} {1}").format(
str(((posting_date.month - 1) // 3) + 1), str(posting_date.year)
)
period = _("Quarter {0} {1}").format(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])
@@ -188,15 +183,13 @@ def get_periodic_data(entry, filters):
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][period][d.warehouse] = periodic_data[d.item_code]["balance"][d.warehouse]
return periodic_data
def fill_intermediate_periods(
periodic_data, item_code: str, current_period: str, all_periods: List[str]
periodic_data, item_code: str, current_period: str, all_periods: list[str]
) -> None:
"""There might be intermediate periods where no stock ledger entry exists, copy previous previous data.
@@ -235,7 +228,7 @@ def get_data(filters):
today = getdate()
for dummy, item_data in item_details.items():
for _dummy, item_data in item_details.items():
row = {
"name": item_data.name,
"item_name": item_data.item_name,
@@ -273,7 +266,7 @@ def get_items(filters):
item_filters = {"is_stock_item": 1}
if item_group := filters.get("item_group"):
children = get_descendants_of("Item Group", item_group, ignore_permissions=True)
item_filters["item_group"] = ("in", children + [item_group])
item_filters["item_group"] = ("in", [*children, item_group])
if brand := filters.get("brand"):
item_filters["brand"] = brand

View File

@@ -3,7 +3,7 @@ import datetime
import frappe
from frappe import _dict
from frappe.tests.utils import FrappeTestCase
from frappe.utils.data import add_to_date, get_datetime, getdate, nowdate
from frappe.utils.data import add_to_date, getdate
from erpnext.accounts.utils import get_fiscal_year
from erpnext.stock.doctype.item.test_item import make_item
@@ -55,7 +55,6 @@ class TestStockAnalyticsReport(FrappeTestCase):
self.assertEqual(actual_buckets, expected_buckets)
def test_get_period_date_ranges(self):
filters = _dict(range="Monthly", from_date="2020-12-28", to_date="2021-02-06")
ranges = get_period_date_ranges(filters)
@@ -69,7 +68,6 @@ class TestStockAnalyticsReport(FrappeTestCase):
self.assertEqual(ranges, expected_ranges)
def test_get_period_date_ranges_yearly(self):
filters = _dict(range="Yearly", from_date="2021-01-28", to_date="2021-02-06")
ranges = get_period_date_ranges(filters)

View File

@@ -14,9 +14,7 @@ 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
)
_("Perpetual inventory required for the company {0} to view this report.").format(filters.company)
)
data = get_data(filters)
@@ -34,7 +32,7 @@ def get_data(report_filters):
"posting_date": ("<=", report_filters.as_on_date),
}
currency_precision = get_currency_precision() or 2
get_currency_precision() or 2
stock_ledger_entries = get_stock_ledger_data(report_filters, filters)
voucher_wise_gl_data = get_gl_data(report_filters, filters)

View File

@@ -3,7 +3,7 @@
from operator import itemgetter
from typing import Any, Dict, List, Optional, TypedDict
from typing import Any, TypedDict
import frappe
from frappe import _
@@ -20,27 +20,27 @@ from erpnext.stock.utils import add_additional_uom_columns
class StockBalanceFilter(TypedDict):
company: Optional[str]
company: str | None
from_date: str
to_date: str
item_group: Optional[str]
item: Optional[str]
warehouse: Optional[str]
warehouse_type: Optional[str]
include_uom: Optional[str] # include extra info in converted UOM
item_group: str | None
item: str | None
warehouse: str | None
warehouse_type: str | None
include_uom: str | None # include extra info in converted UOM
show_stock_ageing_data: bool
show_variant_attributes: bool
SLEntry = Dict[str, Any]
SLEntry = dict[str, Any]
def execute(filters: Optional[StockBalanceFilter] = None):
def execute(filters: StockBalanceFilter | None = None):
return StockBalanceReport(filters).run()
class StockBalanceReport(object):
def __init__(self, filters: Optional[StockBalanceFilter]) -> None:
class StockBalanceReport:
def __init__(self, filters: StockBalanceFilter | None) -> None:
self.filters = filters
self.from_date = getdate(filters.get("from_date"))
self.to_date = getdate(filters.get("to_date"))
@@ -48,7 +48,7 @@ class StockBalanceReport(object):
self.start_from = None
self.data = []
self.columns = []
self.sle_entries: List[SLEntry] = []
self.sle_entries: list[SLEntry] = []
self.set_company_currency()
def set_company_currency(self) -> None:
@@ -104,7 +104,7 @@ class StockBalanceReport(object):
if self.filters.get("show_variant_attributes"):
variant_values = self.get_variant_values_for()
for key, report_data in self.item_warehouse_map.items():
for _key, report_data in self.item_warehouse_map.items():
if variant_data := variant_values.get(report_data.item_code):
report_data.update(variant_data)
@@ -184,7 +184,6 @@ class StockBalanceReport(object):
qty_dict.opening_val += value_diff
elif entry.posting_date >= self.from_date and entry.posting_date <= self.to_date:
if flt(qty_diff, self.float_precision) >= 0:
qty_dict.in_qty += qty_diff
qty_dict.in_val += value_diff
@@ -230,7 +229,7 @@ class StockBalanceReport(object):
return tuple(group_by_key)
def get_closing_balance(self) -> List[Dict[str, Any]]:
def get_closing_balance(self) -> list[dict[str, Any]]:
if self.filters.get("ignore_closing_balance"):
return []
@@ -325,7 +324,7 @@ class StockBalanceReport(object):
def apply_items_filters(self, query, item_table) -> str:
if item_group := self.filters.get("item_group"):
children = get_descendants_of("Item Group", item_group, ignore_permissions=True)
query = query.where(item_table.item_group.isin(children + [item_group]))
query = query.where(item_table.item_group.isin([*children, item_group]))
for field in ["item_code", "brand"]:
if not self.filters.get(field):
@@ -542,7 +541,9 @@ class StockBalanceReport(object):
frappe.qb.from_(sr)
.select(sr.name, Coalesce("Stock Reconciliation").as_("voucher_type"))
.where(
(sr.docstatus == 1) & (sr.posting_date <= self.to_date) & (sr.purpose == "Opening Stock")
(sr.docstatus == 1)
& (sr.posting_date <= self.to_date)
& (sr.purpose == "Opening Stock")
)
)
).select("voucher_type", "name")
@@ -568,7 +569,7 @@ class StockBalanceReport(object):
def filter_items_with_no_transactions(
iwb_map, float_precision: float, inventory_dimensions: list = None
iwb_map, float_precision: float, inventory_dimensions: list | None = None
):
pop_keys = []
for group_by_key in iwb_map:
@@ -605,6 +606,6 @@ def filter_items_with_no_transactions(
return iwb_map
def get_variants_attributes() -> List[str]:
def get_variants_attributes() -> list[str]:
"""Return all item variant attributes."""
return frappe.get_all("Item Attribute", pluck="name")

View File

@@ -1,4 +1,4 @@
from typing import Any, Dict
from typing import Any
import frappe
from frappe import _dict
@@ -32,12 +32,11 @@ class TestStockBalance(FrappeTestCase):
def tearDown(self):
frappe.db.rollback()
def assertPartialDictEq(self, expected: Dict[str, Any], actual: Dict[str, Any]):
def assertPartialDictEq(self, expected: dict[str, Any], actual: dict[str, Any]):
for k, v in expected.items():
self.assertEqual(v, actual[k], msg=f"{expected=}\n{actual=}")
def generate_stock_ledger(self, item_code: str, movements):
for movement in map(_dict, movements):
if "to_warehouse" not in movement:
movement.to_warehouse = "_Test Warehouse - _TC"
@@ -128,7 +127,6 @@ class TestStockBalance(FrappeTestCase):
self.assertPartialDictEq({"opening_qty": 6, "in_qty": 0}, rows[0])
def test_uom_converted_info(self):
self.item.append("uoms", {"conversion_factor": 5, "uom": "Box"})
self.item.save()
@@ -167,8 +165,6 @@ class TestStockBalance(FrappeTestCase):
variant.save()
self.generate_stock_ledger(variant.name, [_dict(qty=5, rate=10)])
rows = stock_balance(
self.filters.update({"show_variant_attributes": 1, "item_code": variant.name})
)
rows = stock_balance(self.filters.update({"show_variant_attributes": 1, "item_code": variant.name}))
self.assertPartialDictEq(attributes, rows[0])
self.assertInvariants(rows)

View File

@@ -437,11 +437,8 @@ 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 f" exists (select name from `tabWarehouse` wh \
where wh.lft >= {warehouse_details.lft} and wh.rgt <= {warehouse_details.rgt} and warehouse = wh.name)"
return ""
@@ -452,22 +449,17 @@ def get_item_group_condition(item_group, item_table=None):
if item_table:
ig = frappe.qb.DocType("Item Group")
return item_table.item_group.isin(
(
frappe.qb.from_(ig)
.select(ig.name)
.where(
(ig.lft >= item_group_details.lft)
& (ig.rgt <= item_group_details.rgt)
& (item_table.item_group == ig.name)
)
frappe.qb.from_(ig)
.select(ig.name)
.where(
(ig.lft >= item_group_details.lft)
& (ig.rgt <= item_group_details.rgt)
& (item_table.item_group == ig.name)
)
)
else:
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 f"item.item_group in (select ig.name from `tabItem Group` ig \
where ig.lft >= {item_group_details.lft} and ig.rgt <= {item_group_details.rgt} and item.item_group = ig.name)"
def check_inventory_dimension_filters_applied(filters) -> bool:

View File

@@ -228,7 +228,7 @@ def get_data(filters=None):
return data
def get_item_warehouse_combinations(filters: dict = None) -> dict:
def get_item_warehouse_combinations(filters: dict | None = None) -> dict:
filters = frappe._dict(filters or {})
bin = frappe.qb.DocType("Bin")
@@ -284,7 +284,5 @@ def has_difference(row, precision, difference_in, valuation_method):
return True
elif difference_in == "Valuation" and valuation_diff:
return True
elif difference_in not in ["Qty", "Value", "Valuation"] and (
qty_diff or value_diff or valuation_diff
):
elif difference_in not in ["Qty", "Value", "Valuation"] and (qty_diff or value_diff or valuation_diff):
return True

View File

@@ -250,9 +250,7 @@ def get_bin_list(filters):
query = query.where(bin.item_code == 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:
wh = frappe.qb.DocType("Warehouse")
@@ -286,7 +284,9 @@ def get_item_map(item_code, include_uom):
(item.is_stock_item == 1)
& (item.disabled == 0)
& (
(item.end_of_life > today()) | (item.end_of_life.isnull()) | (item.end_of_life == "0000-00-00")
(item.end_of_life > today())
| (item.end_of_life.isnull())
| (item.end_of_life == "0000-00-00")
)
& (ExistsCriterion(frappe.qb.from_(bin).select(bin.name).where(bin.item_code == item.name)))
)

View File

@@ -20,7 +20,6 @@ def execute(filters=None):
total_qty = total_amount = 0.0
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_type in ["Delivery Note", "Sales Invoice"]:
delivered_qty += abs(flt(cd.actual_qty))
@@ -54,19 +53,19 @@ def execute(filters=None):
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
@@ -173,9 +172,7 @@ def get_suppliers_details(filters):
def get_material_transfer_vouchers():
se = frappe.qb.DocType("Stock Entry")
query = (
frappe.qb.from_(se)
.select(se.name)
.where((se.purpose == "Material Transfer") & (se.docstatus == 1))
frappe.qb.from_(se).select(se.name).where((se.purpose == "Material Transfer") & (se.docstatus == 1))
)
return [r[0] for r in query.run()]

View File

@@ -1,5 +1,4 @@
import unittest
from typing import List, Tuple
import frappe
@@ -14,7 +13,7 @@ DEFAULT_FILTERS = {
batch = frappe.db.get_value("Batch", fieldname=["name"], as_dict=True, order_by="creation desc")
REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [
REPORT_FILTER_TEST_CASES: list[tuple[ReportName, ReportFilters]] = [
("Stock Ledger", {"_optional": True}),
("Stock Ledger", {"batch_no": batch}),
("Stock Ledger", {"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"}),

View File

@@ -8,7 +8,6 @@ from frappe.query_builder.functions import Sum
def execute(filters=None):
if not filters:
filters = {}
columns = get_columns(filters)
@@ -54,8 +53,8 @@ def get_total_stock(filters):
else:
query = query.select(wh.company).groupby(wh.company)
query = query.select(
item.item_code, item.description, Sum(bin.actual_qty).as_("actual_qty")
).groupby(item.item_code)
query = query.select(item.item_code, item.description, Sum(bin.actual_qty).as_("actual_qty")).groupby(
item.item_code
)
return query.run()

View File

@@ -40,7 +40,7 @@ def execute(filters=None):
item_balance = {}
item_value = {}
for (company, item, warehouse) in sorted(iwb_map):
for company, item, warehouse in sorted(iwb_map):
if not item_map.get(item):
continue
@@ -71,7 +71,7 @@ def execute(filters=None):
row += [average_age]
bal_qty = [sum(bal_qty) for bal_qty in zip(*wh_balance)]
bal_qty = [sum(bal_qty) for bal_qty in zip(*wh_balance, strict=False)]
total_qty = sum(bal_qty)
if len(warehouse_list) > 1:
row += [total_qty]

View File

@@ -1,7 +1,7 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from typing import Any, Dict, List, Optional, TypedDict
from typing import Any, TypedDict
import frappe
from frappe import _
@@ -9,12 +9,12 @@ from frappe.query_builder.functions import Sum
class StockBalanceFilter(TypedDict):
company: Optional[str]
warehouse: Optional[str]
show_disabled_warehouses: Optional[int]
company: str | None
warehouse: str | None
show_disabled_warehouses: int | None
SLEntry = Dict[str, Any]
SLEntry = dict[str, Any]
def execute(filters=None):
@@ -25,7 +25,7 @@ def execute(filters=None):
return columns, data
def get_warehouse_wise_balance(filters: StockBalanceFilter) -> List[SLEntry]:
def get_warehouse_wise_balance(filters: StockBalanceFilter) -> list[SLEntry]:
sle = frappe.qb.DocType("Stock Ledger Entry")
query = (
@@ -95,7 +95,7 @@ def set_balance_in_parent(warehouses):
update_balance(warehouse, warehouse.stock_balance)
def get_columns(filters: StockBalanceFilter) -> List[Dict]:
def get_columns(filters: StockBalanceFilter) -> list[dict]:
columns = [
{
"label": _("Warehouse"),

View File

@@ -15,9 +15,7 @@ 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(
@@ -37,9 +35,7 @@ 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
@@ -51,7 +47,6 @@ def repost_stock(
only_bin=False,
allow_negative_stock=False,
):
if not only_bin:
repost_actual_qty(item_code, warehouse, allow_zero_rate, allow_negative_stock)

View File

@@ -2,7 +2,6 @@
# License: GNU General Public License v3. See license.txt
import json
from typing import Optional, Set, Tuple
import frappe
from frappe import _
@@ -185,9 +184,7 @@ def repost_future_sle(
if not args:
args = [] # set args to empty list if None to avoid enumerate error
items_to_be_repost = get_items_to_be_repost(
voucher_type=voucher_type, voucher_no=voucher_no, doc=doc
)
items_to_be_repost = get_items_to_be_repost(voucher_type=voucher_type, voucher_no=voucher_no, doc=doc)
if items_to_be_repost:
args = items_to_be_repost
@@ -212,12 +209,10 @@ def repost_future_sle(
)
affected_transactions.update(obj.affected_transactions)
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():
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
):
@@ -242,9 +237,7 @@ def validate_item_warehouse(args):
frappe.throw(_(validation_msg))
def update_args_in_repost_item_valuation(
doc, index, args, distinct_item_warehouses, affected_transactions
):
def update_args_in_repost_item_valuation(doc, index, args, distinct_item_warehouses, affected_transactions):
doc.db_set(
{
"items_to_be_repost": json.dumps(args, default=str),
@@ -300,7 +293,7 @@ def get_distinct_item_warehouse(args=None, doc=None):
return distinct_item_warehouses
def get_affected_transactions(doc) -> Set[Tuple[str, str]]:
def get_affected_transactions(doc) -> set[tuple[str, str]]:
if not doc.affected_transactions:
return set()
@@ -313,7 +306,7 @@ def get_current_index(doc=None):
return doc.current_index
class update_entries_after(object):
class update_entries_after:
"""
update valution rate and qty after transaction
from the current time-bucket onwards
@@ -355,7 +348,7 @@ class update_entries_after(object):
self.new_items_found = False
self.distinct_item_warehouses = args.get("distinct_item_warehouses", frappe._dict())
self.affected_transactions: Set[Tuple[str, str]] = set()
self.affected_transactions: set[tuple[str, str]] = set()
self.data = frappe._dict()
self.initialize_previous_data(self.args)
@@ -433,9 +426,7 @@ class update_entries_after(object):
self.process_sle(sle)
def get_sle_against_current_voucher(self):
self.args["posting_datetime"] = get_combine_datetime(
self.args.posting_date, self.args.posting_time
)
self.args["posting_datetime"] = get_combine_datetime(self.args.posting_date, self.args.posting_time)
return frappe.db.sql(
"""
@@ -473,9 +464,7 @@ class update_entries_after(object):
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)
@@ -495,9 +484,7 @@ class update_entries_after(object):
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")
dependent_voucher_detail_nos = self.get_dependent_voucher_detail_nos(key)
@@ -584,7 +571,9 @@ class update_entries_after(object):
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]]
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)
@@ -927,9 +916,7 @@ class update_entries_after(object):
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)
@@ -1044,9 +1031,7 @@ class update_entries_after(object):
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(
@@ -1083,9 +1068,7 @@ 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
@@ -1135,7 +1118,6 @@ class update_entries_after(object):
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"]),
@@ -1201,7 +1183,7 @@ def get_previous_sle_of_current_voucher(args, operator="<", exclude_current_vouc
voucher_condition = f"and voucher_no != '{voucher_no}'"
sle = frappe.db.sql(
"""
f"""
select *, posting_datetime as "timestamp"
from `tabStock Ledger Entry`
where item_code = %(item_code)s
@@ -1213,10 +1195,7 @@ def get_previous_sle_of_current_voucher(args, operator="<", exclude_current_vouc
)
order by posting_datetime desc, creation desc
limit 1
for update""".format(
operator=operator,
voucher_condition=voucher_condition,
),
for update""",
args,
as_dict=1,
)
@@ -1256,7 +1235,7 @@ def get_stock_ledger_entries(
extra_cond=None,
):
"""get stock ledger entries filtered by specific posting datetime conditions"""
conditions = " and posting_datetime {0} %(posting_datetime)s".format(operator)
conditions = f" and posting_datetime {operator} %(posting_datetime)s"
if previous_sle.get("warehouse"):
conditions += " and warehouse = %(warehouse)s"
elif previous_sle.get("warehouse_condition"):
@@ -1268,17 +1247,17 @@ def get_stock_ledger_entries(
conditions += (
""" and
(
serial_no = {0}
or serial_no like {1}
or serial_no like {2}
or serial_no like {3}
serial_no = {}
or serial_no like {}
or serial_no like {}
or serial_no like {}
)
"""
).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)),
frappe.db.escape(f"{serial_no}\n%"),
frappe.db.escape(f"%\n{serial_no}"),
frappe.db.escape(f"%\n{serial_no}\n%"),
)
if not previous_sle.get("posting_date"):
@@ -1301,17 +1280,16 @@ def get_stock_ledger_entries(
"""
select *, posting_datetime as "timestamp"
from `tabStock Ledger Entry`
where item_code = %%(item_code)s
where item_code = %(item_code)s
and is_cancelled = 0
%(conditions)s
order by posting_datetime %(order)s, creation %(order)s
%(limit)s %(for_update)s"""
% {
"conditions": conditions,
"limit": limit or "",
"for_update": for_update and "for update" or "",
"order": order,
},
{conditions}
order by posting_datetime {order}, creation {order}
{limit} {for_update}""".format(
conditions=conditions,
limit=limit or "",
for_update=for_update and "for update" or "",
order=order,
),
previous_sle,
as_dict=1,
debug=debug,
@@ -1336,10 +1314,7 @@ def get_sle_by_voucher_detail_no(voucher_detail_no, excluded_sle=None):
)
def get_batch_incoming_rate(
item_code, warehouse, batch_no, posting_date, posting_time, creation=None
):
def get_batch_incoming_rate(item_code, warehouse, batch_no, posting_date, posting_time, creation=None):
import datetime
sle = frappe.qb.DocType("Stock Ledger Entry")
@@ -1350,9 +1325,9 @@ def get_batch_incoming_rate(
timestamp_condition = sle.posting_datetime < posting_datetime
if creation:
timestamp_condition |= (
sle.posting_datetime == get_combine_datetime(posting_date, posting_time)
) & (sle.creation < creation)
timestamp_condition |= (sle.posting_datetime == get_combine_datetime(posting_date, posting_time)) & (
sle.creation < creation
)
batch_details = (
frappe.qb.from_(sle)
@@ -1381,7 +1356,6 @@ def get_valuation_rate(
raise_error_if_no_rate=True,
batch_no=None,
):
if not company:
company = frappe.get_cached_value("Warehouse", warehouse, "company")
@@ -1457,7 +1431,7 @@ def get_valuation_rate(
solutions += (
"<li>"
+ _("If not, you can Cancel / Submit this entry")
+ " {0} ".format(frappe.bold("after"))
+ " {} ".format(frappe.bold("after"))
+ _("performing either one below:")
+ "</li>"
)
@@ -1591,9 +1565,7 @@ def validate_negative_qty_in_future_sle(args, allow_negative_stock=False):
neg_sle = get_future_sle_with_negative_qty(args)
if is_negative_with_precision(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),
@@ -1609,9 +1581,7 @@ def validate_negative_qty_in_future_sle(args, allow_negative_stock=False):
neg_batch_sle = get_future_sle_with_negative_batch_qty(args)
if is_negative_with_precision(neg_batch_sle, is_batch=True):
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),
@@ -1689,7 +1659,7 @@ def get_future_sle_with_negative_batch_qty(args):
)
def is_negative_stock_allowed(*, item_code: Optional[str] = None) -> bool:
def is_negative_stock_allowed(*, item_code: str | None = None) -> bool:
if cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock", cache=True)):
return True
if item_code and cint(frappe.db.get_value("Item", item_code, "allow_negative_stock", cache=True)):

View File

@@ -1,5 +1,3 @@
import json
import frappe
from frappe.test_runner import make_test_records
from frappe.tests.utils import FrappeTestCase
@@ -16,7 +14,6 @@ class TestGetItemDetail(FrappeTestCase):
super().setUp()
def test_get_item_detail_purchase_order(self):
args = frappe._dict(
{
"item_code": "_Test Item",
@@ -29,7 +26,6 @@ class TestGetItemDetail(FrappeTestCase):
"name": None,
"supplier": "_Test Supplier",
"transaction_date": None,
"conversion_rate": 1.0,
"price_list": "_Test Buying Price List",
"is_subcontracted": 0,
"ignore_pricing_rule": 1,

View File

@@ -28,7 +28,7 @@ class StockTestMixin:
)
self.assertGreaterEqual(len(sles), len(expected_sles))
for exp_sle, act_sle in zip(expected_sles, sles):
for exp_sle, act_sle in zip(expected_sles, sles, strict=False):
for k, v in exp_sle.items():
act_value = act_sle[k]
if k == "stock_queue":
@@ -51,7 +51,7 @@ class StockTestMixin:
order_by=order_by or "posting_date, creation",
)
self.assertGreaterEqual(len(actual_gles), len(expected_gles))
for exp_gle, act_gle in zip(expected_gles, actual_gles):
for exp_gle, act_gle in zip(expected_gles, actual_gles, strict=False):
for k, exp_value in exp_gle.items():
act_value = act_gle[k]
self.assertEqual(exp_value, act_value, msg=f"{k} doesn't match \n{exp_gle}\n{act_gle}")

View File

@@ -28,9 +28,7 @@ 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)
@@ -210,9 +208,7 @@ 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)
@@ -355,7 +351,6 @@ class TestLIFOValuationSLE(FrappeTestCase):
self.assertEqual(stock_queue, expected_queue)
def test_lifo_values(self):
in1 = self._make_stock_entry(1, 1)
self.assertStockQueue(in1, [[1, 1]])

View File

@@ -3,7 +3,6 @@
import json
from typing import Dict, Optional
import frappe
from frappe import _
@@ -14,7 +13,7 @@ import erpnext
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
from erpnext.stock.valuation import FIFOValuation, LIFOValuation
BarcodeScanResult = Dict[str, Optional[str]]
BarcodeScanResult = dict[str, str | None]
class InvalidWarehouseCompany(frappe.ValidationError):
@@ -55,7 +54,7 @@ def get_stock_value_from_bin(warehouse=None, item_code=None):
def get_stock_value_on(
warehouses: list | str = None, posting_date: str = None, item_code: str = None
warehouses: list | str | None = None, posting_date: str | None = None, item_code: str | None = None
) -> float:
if not posting_date:
posting_date = nowdate()
@@ -140,9 +139,7 @@ def get_stock_balance(
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
@@ -205,10 +202,8 @@ def get_latest_stock_qty(item_code, warehouse=None):
condition += " AND warehouse = %s"
actual_qty = frappe.db.sql(
"""select sum(actual_qty) from tabBin
where item_code=%s {0}""".format(
condition
),
f"""select sum(actual_qty) from tabBin
where item_code=%s {condition}""",
values,
)[0][0]
@@ -337,9 +332,7 @@ 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"
)
val_method = frappe.db.get_value("Stock Settings", None, "valuation_method", cache=True) or "FIFO"
return val_method
@@ -414,7 +407,6 @@ def update_included_uom_in_report(columns, result, include_uom, conversion_facto
if not include_uom or not conversion_factors:
return
convertible_cols = {}
is_dict_obj = False
if isinstance(result[0], dict):
is_dict_obj = True
@@ -429,8 +421,8 @@ def update_included_uom_in_report(columns, result, include_uom, conversion_facto
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)),
"label": "{} (per {})".format(d.get("label"), include_uom),
"fieldname": "{}_{}".format(d.get("fieldname"), frappe.scrub(include_uom)),
"fieldtype": "Currency" if d.get("convertible") == "rate" else "Float",
},
)
@@ -453,7 +445,7 @@ def update_included_uom_in_report(columns, result, include_uom, conversion_facto
if not is_dict_obj:
row.insert(key + 1, new_value)
else:
new_key = "{0}_{1}".format(key, frappe.scrub(include_uom))
new_key = f"{key}_{frappe.scrub(include_uom)}"
update_dict_values.append([row, new_key, new_value])
for data in update_dict_values:
@@ -487,9 +479,9 @@ def add_additional_uom_columns(columns, result, include_uom, conversion_factors)
{"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]["label"] += f" (per {include_uom})"
else:
columns[next_col]["label"] += " ({})".format(include_uom)
columns[next_col]["label"] += f" ({include_uom})"
for row_idx, row in enumerate(result):
for convertible_col, data in convertible_column_map.items():
@@ -563,7 +555,7 @@ 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]:
def get_cache() -> BarcodeScanResult | None:
if data := frappe.cache().get_value(f"erpnext:barcode_scan:{search_value}"):
return data
@@ -609,7 +601,7 @@ def scan_barcode(search_value: str) -> BarcodeScanResult:
return {}
def _update_item_info(scan_result: Dict[str, Optional[str]]) -> Dict[str, Optional[str]]:
def _update_item_info(scan_result: dict[str, str | None]) -> dict[str, str | None]:
if item_code := scan_result.get("item_code"):
if item_info := frappe.get_cached_value(
"Item",

View File

@@ -1,9 +1,10 @@
from abc import ABC, abstractmethod, abstractproperty
from typing import Callable, List, NewType, Optional, Tuple
from collections.abc import Callable
from typing import NewType
from frappe.utils import flt
StockBin = NewType("StockBin", List[float]) # [[qty, rate], ...]
StockBin = NewType("StockBin", list[float]) # [[qty, rate], ...]
# Indexes of values inside FIFO bin 2-tuple
QTY = 0
@@ -17,15 +18,15 @@ class BinWiseValuation(ABC):
@abstractmethod
def remove_stock(
self, qty: float, outgoing_rate: float = 0.0, rate_generator: Callable[[], float] = None
) -> List[StockBin]:
self, qty: float, outgoing_rate: float = 0.0, rate_generator: Callable[[], float] | None = None
) -> list[StockBin]:
pass
@abstractproperty
def state(self) -> List[StockBin]:
def state(self) -> list[StockBin]:
pass
def get_total_stock_and_value(self) -> Tuple[float, float]:
def get_total_stock_and_value(self) -> tuple[float, float]:
total_qty = 0.0
total_value = 0.0
@@ -62,11 +63,11 @@ class FIFOValuation(BinWiseValuation):
# ref: https://docs.python.org/3/reference/datamodel.html#slots
__slots__ = ["queue"]
def __init__(self, state: Optional[List[StockBin]]):
self.queue: List[StockBin] = state if state is not None else []
def __init__(self, state: list[StockBin] | None):
self.queue: list[StockBin] = state if state is not None else []
@property
def state(self) -> List[StockBin]:
def state(self) -> list[StockBin]:
"""Get current state of queue."""
return self.queue
@@ -95,8 +96,8 @@ class FIFOValuation(BinWiseValuation):
self.queue[-1][QTY] = qty
def remove_stock(
self, qty: float, outgoing_rate: float = 0.0, rate_generator: Callable[[], float] = None
) -> List[StockBin]:
self, qty: float, outgoing_rate: float = 0.0, rate_generator: Callable[[], float] | None = None
) -> list[StockBin]:
"""Remove stock from the queue and return popped bins.
args:
@@ -166,11 +167,11 @@ class LIFOValuation(BinWiseValuation):
# ref: https://docs.python.org/3/reference/datamodel.html#slots
__slots__ = ["stack"]
def __init__(self, state: Optional[List[StockBin]]):
self.stack: List[StockBin] = state if state is not None else []
def __init__(self, state: list[StockBin] | None):
self.stack: list[StockBin] = state if state is not None else []
@property
def state(self) -> List[StockBin]:
def state(self) -> list[StockBin]:
"""Get current state of stack."""
return self.stack
@@ -201,8 +202,8 @@ class LIFOValuation(BinWiseValuation):
self.stack[-1][QTY] = qty
def remove_stock(
self, qty: float, outgoing_rate: float = 0.0, rate_generator: Callable[[], float] = None
) -> List[StockBin]:
self, qty: float, outgoing_rate: float = 0.0, rate_generator: Callable[[], float] | None = None
) -> list[StockBin]:
"""Remove stock from the stack and return popped bins.
args: