Merge pull request #41355 from frappe/version-15-hotfix

chore: release v15
This commit is contained in:
ruthra kumar
2024-05-09 11:00:41 +05:30
committed by GitHub
29 changed files with 295 additions and 50 deletions

View File

@@ -1102,7 +1102,7 @@ class TestPricingRule(unittest.TestCase):
so.load_from_db() so.load_from_db()
self.assertEqual(so.items[1].is_free_item, 1) self.assertEqual(so.items[1].is_free_item, 1)
self.assertEqual(so.items[1].item_code, "_Test Item") self.assertEqual(so.items[1].item_code, "_Test Item")
self.assertEqual(so.items[1].qty, 4) self.assertEqual(so.items[1].qty, 3)
def test_apply_multiple_pricing_rules_for_discount_percentage_and_amount(self): def test_apply_multiple_pricing_rules_for_discount_percentage_and_amount(self):
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1") frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")

View File

@@ -6,6 +6,7 @@
import copy import copy
import json import json
import math
import frappe import frappe
from frappe import _, bold from frappe import _, bold
@@ -653,7 +654,7 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
if transaction_qty: if transaction_qty:
qty = flt(transaction_qty) * qty / pricing_rule.recurse_for qty = flt(transaction_qty) * qty / pricing_rule.recurse_for
if pricing_rule.round_free_qty: if pricing_rule.round_free_qty:
qty = round(qty) qty = math.floor(qty)
free_item_data_args = { free_item_data_args = {
"item_code": free_item, "item_code": free_item,

View File

@@ -11,13 +11,15 @@
{ {
"fieldname": "cost_center_name", "fieldname": "cost_center_name",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1,
"label": "Cost Center", "label": "Cost Center",
"options": "Cost Center" "options": "Cost Center",
"reqd": 1
} }
], ],
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-08-03 16:56:45.744905", "modified": "2024-05-03 17:16:51.666461",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "PSOA Cost Center", "name": "PSOA Cost Center",

View File

@@ -15,7 +15,7 @@ class PSOACostCenter(Document):
if TYPE_CHECKING: if TYPE_CHECKING:
from frappe.types import DF from frappe.types import DF
cost_center_name: DF.Link | None cost_center_name: DF.Link
parent: DF.Data parent: DF.Data
parentfield: DF.Data parentfield: DF.Data
parenttype: DF.Data parenttype: DF.Data

View File

@@ -1091,7 +1091,7 @@ class PurchaseInvoice(BuyingController):
) )
# check if the exchange rate has changed # check if the exchange rate has changed
if item.get("purchase_receipt"): if item.get("purchase_receipt") and self.auto_accounting_for_stock:
if ( if (
exchange_rate_map[item.purchase_receipt] exchange_rate_map[item.purchase_receipt]
and self.conversion_rate != exchange_rate_map[item.purchase_receipt] and self.conversion_rate != exchange_rate_map[item.purchase_receipt]

View File

@@ -388,6 +388,9 @@ class SalesInvoice(SellingController):
validate_account_head(item.idx, item.income_account, self.company, "Income") validate_account_head(item.idx, item.income_account, self.company, "Income")
def set_tax_withholding(self): def set_tax_withholding(self):
if self.get("is_opening") == "Yes":
return
tax_withholding_details = get_party_tax_withholding_details(self) tax_withholding_details = get_party_tax_withholding_details(self)
if not tax_withholding_details: if not tax_withholding_details:

View File

@@ -1766,6 +1766,49 @@ class TestSalesInvoice(FrappeTestCase):
self.assertTrue(gle) self.assertTrue(gle)
def test_gle_in_transaction_currency(self):
# create multi currency sales invoice with 2 items with same income account
si = create_sales_invoice(
customer="_Test Customer USD",
debit_to="_Test Receivable USD - _TC",
currency="USD",
conversion_rate=50,
do_not_submit=True,
)
# add 2nd item with same income account
si.append(
"items",
{
"item_code": "_Test Item",
"qty": 1,
"rate": 80,
"income_account": "Sales - _TC",
"cost_center": "_Test Cost Center - _TC",
},
)
si.submit()
gl_entries = frappe.db.sql(
"""select transaction_currency, transaction_exchange_rate,
debit_in_transaction_currency, credit_in_transaction_currency
from `tabGL Entry`
where voucher_type='Sales Invoice' and voucher_no=%s and account = 'Sales - _TC'
order by account asc""",
si.name,
as_dict=1,
)
expected_gle = {
"transaction_currency": "USD",
"transaction_exchange_rate": 50,
"debit_in_transaction_currency": 0,
"credit_in_transaction_currency": 180,
}
for gle in gl_entries:
for field in expected_gle:
self.assertEqual(expected_gle[field], gle[field])
def test_invoice_exchange_rate(self): def test_invoice_exchange_rate(self):
si = create_sales_invoice( si = create_sales_invoice(
customer="_Test Customer USD", customer="_Test Customer USD",

View File

@@ -112,11 +112,7 @@ class Subscription(Document):
""" """
_current_invoice_start = None _current_invoice_start = None
if ( if self.trial_period_end and getdate(self.trial_period_end) > getdate(self.start_date):
self.is_new_subscription()
and self.trial_period_end
and getdate(self.trial_period_end) > getdate(self.start_date)
):
_current_invoice_start = add_days(self.trial_period_end, 1) _current_invoice_start = add_days(self.trial_period_end, 1)
elif self.trial_period_start and self.is_trialling(): elif self.trial_period_start and self.is_trialling():
_current_invoice_start = self.trial_period_start _current_invoice_start = self.trial_period_start
@@ -143,7 +139,7 @@ class Subscription(Document):
else: else:
billing_cycle_info = self.get_billing_cycle_data() billing_cycle_info = self.get_billing_cycle_data()
if billing_cycle_info: if billing_cycle_info:
if self.is_new_subscription() and getdate(self.start_date) < getdate(date): if getdate(self.start_date) < getdate(date):
_current_invoice_end = add_to_date(self.start_date, **billing_cycle_info) _current_invoice_end = add_to_date(self.start_date, **billing_cycle_info)
# For cases where trial period is for an entire billing interval # For cases where trial period is for an entire billing interval
@@ -234,14 +230,14 @@ class Subscription(Document):
self.cancelation_date = getdate(posting_date) if self.status == "Cancelled" else None self.cancelation_date = getdate(posting_date) if self.status == "Cancelled" else None
elif self.current_invoice_is_past_due() and not self.is_past_grace_period(): elif self.current_invoice_is_past_due() and not self.is_past_grace_period():
self.status = "Past Due Date" self.status = "Past Due Date"
elif not self.has_outstanding_invoice() or self.is_new_subscription(): elif not self.has_outstanding_invoice():
self.status = "Active" self.status = "Active"
def is_trialling(self) -> bool: def is_trialling(self) -> bool:
""" """
Returns `True` if the `Subscription` is in trial period. Returns `True` if the `Subscription` is in trial period.
""" """
return not self.period_has_passed(self.trial_period_end) and self.is_new_subscription() return not self.period_has_passed(self.trial_period_end)
@staticmethod @staticmethod
def period_has_passed( def period_has_passed(
@@ -288,14 +284,6 @@ class Subscription(Document):
def invoice_document_type(self) -> str: def invoice_document_type(self) -> str:
return "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice" return "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
def is_new_subscription(self) -> bool:
"""
Returns `True` if `Subscription` has never generated an invoice
"""
return self.is_new() or not frappe.db.exists(
{"doctype": self.invoice_document_type, "subscription": self.name}
)
def validate(self) -> None: def validate(self) -> None:
self.validate_trial_period() self.validate_trial_period()
self.validate_plans_billing_cycle(self.get_billing_cycle_and_interval()) self.validate_plans_billing_cycle(self.get_billing_cycle_and_interval())
@@ -604,7 +592,7 @@ class Subscription(Document):
return False return False
if self.generate_invoice_at == "Beginning of the current subscription period" and ( if self.generate_invoice_at == "Beginning of the current subscription period" and (
getdate(posting_date) == getdate(self.current_invoice_start) or self.is_new_subscription() getdate(posting_date) == getdate(self.current_invoice_start)
): ):
return True return True
elif self.generate_invoice_at == "Days before the current subscription period" and ( elif self.generate_invoice_at == "Days before the current subscription period" and (

View File

@@ -445,11 +445,11 @@ class TestSubscription(FrappeTestCase):
# Process subscription and create first invoice # Process subscription and create first invoice
# Subscription status will be unpaid since due date has already passed # Subscription status will be unpaid since due date has already passed
subscription.process() subscription.process(posting_date="2018-01-01")
self.assertEqual(len(subscription.invoices), 1) self.assertEqual(len(subscription.invoices), 1)
self.assertEqual(subscription.status, "Unpaid") self.assertEqual(subscription.status, "Unpaid")
subscription.process() subscription.process(posting_date="2018-04-01")
self.assertEqual(len(subscription.invoices), 1) self.assertEqual(len(subscription.invoices), 1)
def test_multi_currency_subscription(self): def test_multi_currency_subscription(self):
@@ -462,7 +462,7 @@ class TestSubscription(FrappeTestCase):
party=party, party=party,
) )
subscription.process() subscription.process(posting_date="2018-01-01")
self.assertEqual(len(subscription.invoices), 1) self.assertEqual(len(subscription.invoices), 1)
self.assertEqual(subscription.status, "Unpaid") self.assertEqual(subscription.status, "Unpaid")

View File

@@ -9,6 +9,8 @@ from frappe.query_builder import Criterion
from frappe.query_builder.functions import Abs, Sum from frappe.query_builder.functions import Abs, Sum
from frappe.utils import cint, flt, getdate from frappe.utils import cint, flt, getdate
from erpnext.controllers.accounts_controller import validate_account_head
class TaxWithholdingCategory(Document): class TaxWithholdingCategory(Document):
# begin: auto-generated types # begin: auto-generated types
@@ -53,6 +55,7 @@ class TaxWithholdingCategory(Document):
if d.get("account") in existing_accounts: if d.get("account") in existing_accounts:
frappe.throw(_("Account {0} added multiple times").format(frappe.bold(d.get("account")))) frappe.throw(_("Account {0} added multiple times").format(frappe.bold(d.get("account"))))
validate_account_head(d.idx, d.get("account"), d.get("company"))
existing_accounts.append(d.get("account")) existing_accounts.append(d.get("account"))
def validate_thresholds(self): def validate_thresholds(self):

View File

@@ -238,10 +238,16 @@ def merge_similar_entries(gl_map, precision=None):
same_head.debit_in_account_currency = flt(same_head.debit_in_account_currency) + flt( same_head.debit_in_account_currency = flt(same_head.debit_in_account_currency) + flt(
entry.debit_in_account_currency entry.debit_in_account_currency
) )
same_head.debit_in_transaction_currency = flt(same_head.debit_in_transaction_currency) + flt(
entry.debit_in_transaction_currency
)
same_head.credit = flt(same_head.credit) + flt(entry.credit) same_head.credit = flt(same_head.credit) + flt(entry.credit)
same_head.credit_in_account_currency = flt(same_head.credit_in_account_currency) + flt( same_head.credit_in_account_currency = flt(same_head.credit_in_account_currency) + flt(
entry.credit_in_account_currency entry.credit_in_account_currency
) )
same_head.credit_in_transaction_currency = flt(same_head.credit_in_transaction_currency) + flt(
entry.credit_in_transaction_currency
)
else: else:
merged_gl_map.append(entry) merged_gl_map.append(entry)

View File

@@ -720,20 +720,22 @@ class GrossProfitGenerator:
frappe.qb.from_(purchase_invoice_item) frappe.qb.from_(purchase_invoice_item)
.inner_join(purchase_invoice) .inner_join(purchase_invoice)
.on(purchase_invoice.name == purchase_invoice_item.parent) .on(purchase_invoice.name == purchase_invoice_item.parent)
.select(purchase_invoice_item.base_rate / purchase_invoice_item.conversion_factor) .select(
purchase_invoice.name,
purchase_invoice_item.base_rate / purchase_invoice_item.conversion_factor,
)
.where(purchase_invoice.docstatus == 1) .where(purchase_invoice.docstatus == 1)
.where(purchase_invoice.posting_date <= self.filters.to_date) .where(purchase_invoice.posting_date <= self.filters.to_date)
.where(purchase_invoice_item.item_code == item_code) .where(purchase_invoice_item.item_code == item_code)
) )
if row.project: if row.project:
query.where(purchase_invoice_item.project == row.project) query = query.where(purchase_invoice_item.project == row.project)
if row.cost_center: if row.cost_center:
query.where(purchase_invoice_item.cost_center == row.cost_center) query = query.where(purchase_invoice_item.cost_center == row.cost_center)
query.orderby(purchase_invoice.posting_date, order=frappe.qb.desc) query = query.orderby(purchase_invoice.posting_date, order=frappe.qb.desc).limit(1)
query.limit(1)
last_purchase_rate = query.run() last_purchase_rate = query.run()
return flt(last_purchase_rate[0][0]) if last_purchase_rate else 0 return flt(last_purchase_rate[0][0]) if last_purchase_rate else 0

View File

@@ -143,6 +143,10 @@ class AssetCapitalization(StockController):
self.make_gl_entries() self.make_gl_entries()
self.restore_consumed_asset_items() self.restore_consumed_asset_items()
def on_trash(self):
frappe.db.set_value("Asset", self.target_asset, "capitalized_in", None)
super(AssetCapitalization, self).on_trash()
def cancel_target_asset(self): def cancel_target_asset(self):
if self.entry_type == "Capitalization" and self.target_asset: if self.entry_type == "Capitalization" and self.target_asset:
asset_doc = frappe.get_doc("Asset", self.target_asset) asset_doc = frappe.get_doc("Asset", self.target_asset)

View File

@@ -612,6 +612,20 @@ class PurchaseOrder(BuyingController):
return result return result
def update_ordered_qty_in_so_for_removed_items(self, removed_items):
"""
Updates ordered_qty in linked SO when item rows are removed using Update Items
"""
if not self.is_against_so():
return
for item in removed_items:
prev_ordered_qty = frappe.get_cached_value(
"Sales Order Item", item.get("sales_order_item"), "ordered_qty"
)
frappe.db.set_value(
"Sales Order Item", item.get("sales_order_item"), "ordered_qty", prev_ordered_qty - item.qty
)
def auto_create_subcontracting_order(self): def auto_create_subcontracting_order(self):
if self.is_subcontracted and not self.is_old_subcontracting_flow: if self.is_subcontracted and not self.is_old_subcontracting_flow:
if frappe.db.get_single_value("Buying Settings", "auto_create_subcontracting_order"): if frappe.db.get_single_value("Buying Settings", "auto_create_subcontracting_order"):

View File

@@ -3158,6 +3158,9 @@ def validate_and_delete_children(parent, data) -> bool:
d.cancel() d.cancel()
d.delete() d.delete()
if parent.doctype == "Purchase Order":
parent.update_ordered_qty_in_so_for_removed_items(deleted_children)
# need to update ordered qty in Material Request first # need to update ordered qty in Material Request first
# bin uses Material Request Items to recalculate & update # bin uses Material Request Items to recalculate & update
parent.update_prevdoc_status() parent.update_prevdoc_status()

View File

@@ -361,3 +361,5 @@ erpnext.patches.v14_0.migrate_gl_to_payment_ledger
erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20 erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20
erpnext.patches.v14_0.set_maintain_stock_for_bom_item erpnext.patches.v14_0.set_maintain_stock_for_bom_item
erpnext.patches.v15_0.delete_orphaned_asset_movement_item_records erpnext.patches.v15_0.delete_orphaned_asset_movement_item_records
erpnext.patches.v15_0.fix_debit_credit_in_transaction_currency
erpnext.patches.v15_0.remove_cancelled_asset_capitalization_from_asset

View File

@@ -0,0 +1,21 @@
import frappe
def execute():
# update debit and credit in transaction currency:
# if transaction currency is same as account currency,
# then debit and credit in transaction currency is same as debit and credit in account currency
# else debit and credit divided by exchange rate
# nosemgrep
frappe.db.sql(
"""
UPDATE `tabGL Entry`
SET
debit_in_transaction_currency = IF(transaction_currency = account_currency, debit_in_account_currency, debit / transaction_exchange_rate),
credit_in_transaction_currency = IF(transaction_currency = account_currency, credit_in_account_currency, credit / transaction_exchange_rate)
WHERE
transaction_exchange_rate > 0
and transaction_currency is not null
"""
)

View File

@@ -0,0 +1,11 @@
import frappe
def execute():
cancelled_asset_capitalizations = frappe.get_all(
"Asset Capitalization",
filters={"docstatus": 2},
fields=["name", "target_asset"],
)
for asset_capitalization in cancelled_asset_capitalizations:
frappe.db.set_value("Asset", asset_capitalization.target_asset, "capitalized_in", None)

View File

@@ -15,6 +15,9 @@ frappe.ui.form.on("Item", {
frm.add_fetch("tax_type", "tax_rate", "tax_rate"); frm.add_fetch("tax_type", "tax_rate", "tax_rate");
frm.make_methods = { frm.make_methods = {
Quotation: () => {
open_form(frm, "Quotation", "Quotation Item", "items");
},
"Sales Order": () => { "Sales Order": () => {
open_form(frm, "Sales Order", "Sales Order Item", "items"); open_form(frm, "Sales Order", "Sales Order Item", "items");
}, },

View File

@@ -36,6 +36,8 @@
"section_break_11", "section_break_11",
"description", "description",
"brand", "brand",
"unit_of_measure_conversion",
"uoms",
"dashboard_tab", "dashboard_tab",
"inventory_section", "inventory_section",
"inventory_settings_section", "inventory_settings_section",
@@ -52,8 +54,6 @@
"barcodes", "barcodes",
"reorder_section", "reorder_section",
"reorder_levels", "reorder_levels",
"unit_of_measure_conversion",
"uoms",
"serial_nos_and_batches", "serial_nos_and_batches",
"has_batch_no", "has_batch_no",
"create_new_batch", "create_new_batch",
@@ -891,7 +891,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"make_attachments_public": 1, "make_attachments_public": 1,
"modified": "2024-01-08 18:09:30.225085", "modified": "2024-04-30 13:46:39.098753",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Item", "name": "Item",

View File

@@ -65,15 +65,13 @@ class Item(Document):
from erpnext.stock.doctype.item_reorder.item_reorder import ItemReorder from erpnext.stock.doctype.item_reorder.item_reorder import ItemReorder
from erpnext.stock.doctype.item_supplier.item_supplier import ItemSupplier from erpnext.stock.doctype.item_supplier.item_supplier import ItemSupplier
from erpnext.stock.doctype.item_tax.item_tax import ItemTax from erpnext.stock.doctype.item_tax.item_tax import ItemTax
from erpnext.stock.doctype.item_variant_attribute.item_variant_attribute import ( from erpnext.stock.doctype.item_variant_attribute.item_variant_attribute import ItemVariantAttribute
ItemVariantAttribute,
)
from erpnext.stock.doctype.uom_conversion_detail.uom_conversion_detail import UOMConversionDetail from erpnext.stock.doctype.uom_conversion_detail.uom_conversion_detail import UOMConversionDetail
allow_alternative_item: DF.Check allow_alternative_item: DF.Check
allow_negative_stock: DF.Check allow_negative_stock: DF.Check
asset_category: DF.Link | None asset_category: DF.Link | None
asset_naming_series: DF.Literal asset_naming_series: DF.Literal[None]
attributes: DF.Table[ItemVariantAttribute] attributes: DF.Table[ItemVariantAttribute]
auto_create_assets: DF.Check auto_create_assets: DF.Check
barcodes: DF.Table[ItemBarcode] barcodes: DF.Table[ItemBarcode]

View File

@@ -52,10 +52,13 @@
"search_index": 1 "search_index": 1
}, },
{ {
"fetch_from": "item_code.stock_uom",
"fetch_if_empty": 1,
"fieldname": "uom", "fieldname": "uom",
"fieldtype": "Link", "fieldtype": "Link",
"label": "UOM", "label": "UOM",
"options": "UOM" "options": "UOM",
"reqd": 1
}, },
{ {
"default": "0", "default": "0",
@@ -220,7 +223,7 @@
"idx": 1, "idx": 1,
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2024-01-30 14:02:19.304854", "modified": "2024-04-02 22:18:00.450641",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Item Price", "name": "Item Price",

View File

@@ -38,7 +38,7 @@ class ItemPrice(Document):
reference: DF.Data | None reference: DF.Data | None
selling: DF.Check selling: DF.Check
supplier: DF.Link | None supplier: DF.Link | None
uom: DF.Link | None uom: DF.Link
valid_from: DF.Date | None valid_from: DF.Date | None
valid_upto: DF.Date | None valid_upto: DF.Date | None
# end: auto-generated types # end: auto-generated types

View File

@@ -845,6 +845,7 @@ def filter_locations_by_picked_materials(locations, picked_item_details) -> list
picked_qty = picked_item_details.get(key, {}).get("picked_qty", 0) picked_qty = picked_item_details.get(key, {}).get("picked_qty", 0)
if not picked_qty: if not picked_qty:
filterd_locations.append(row)
continue continue
if picked_qty > row.qty: if picked_qty > row.qty:
row.qty = 0 row.qty = 0

View File

@@ -1013,3 +1013,121 @@ class TestPickList(FrappeTestCase):
pl.submit() pl.submit()
self.assertEqual(pl.locations[0].qty, 4.0) self.assertEqual(pl.locations[0].qty, 4.0)
self.assertTrue(hasattr(pl, "locations")) self.assertTrue(hasattr(pl, "locations"))
def test_pick_list_for_multiple_sales_order_with_multiple_batches(self):
warehouse = "_Test Warehouse - _TC"
item = make_item(
"Test Batch Pick List Item For Multiple Batches and Sales Order",
properties={
"is_stock_item": 1,
"has_batch_no": 1,
"batch_number_series": "SN-SOO-BT-SPLIMBATCH-.####",
"create_new_batch": 1,
},
).name
make_stock_entry(item=item, to_warehouse=warehouse, qty=100)
make_stock_entry(item=item, to_warehouse=warehouse, qty=100)
so = make_sales_order(item_code=item, qty=10, rate=100)
pl1 = create_pick_list(so.name)
pl1.save()
self.assertEqual(pl1.locations[0].qty, 10)
so = make_sales_order(item_code=item, qty=110, rate=100)
pl = create_pick_list(so.name)
pl.save()
self.assertEqual(pl.locations[0].qty, 90.0)
self.assertEqual(pl.locations[1].qty, 20.0)
self.assertTrue(hasattr(pl, "locations"))
pl1.submit()
pl.reload()
pl.submit()
self.assertEqual(pl.locations[0].qty, 90.0)
self.assertEqual(pl.locations[1].qty, 20.0)
self.assertTrue(hasattr(pl, "locations"))
def test_pick_list_for_multiple_sales_order_with_multiple_serial_nos(self):
warehouse = "_Test Warehouse - _TC"
item = make_item(
"Test Serial No Pick List Item For Multiple Batches and Sales Order",
properties={
"is_stock_item": 1,
"has_serial_no": 1,
"serial_no_series": "SNNN-SOO-BT-SPLIMBATCH-.####",
},
).name
make_stock_entry(item=item, to_warehouse=warehouse, qty=100)
make_stock_entry(item=item, to_warehouse=warehouse, qty=100)
so = make_sales_order(item_code=item, qty=10, rate=100)
pl1 = create_pick_list(so.name)
pl1.save()
self.assertEqual(pl1.locations[0].qty, 10)
serial_nos = pl1.locations[0].serial_no.split("\n")
self.assertEqual(len(serial_nos), 10)
so = make_sales_order(item_code=item, qty=110, rate=100)
pl = create_pick_list(so.name)
pl.save()
self.assertEqual(pl.locations[0].qty, 110.0)
self.assertTrue(hasattr(pl, "locations"))
new_serial_nos = pl.locations[0].serial_no.split("\n")
self.assertEqual(len(new_serial_nos), 110)
for sn in serial_nos:
self.assertFalse(sn in new_serial_nos)
pl1.submit()
pl.reload()
pl.submit()
self.assertEqual(pl.locations[0].qty, 110.0)
self.assertTrue(hasattr(pl, "locations"))
def test_pick_list_for_multiple_sales_orders_for_non_serialized_item(self):
warehouse = "_Test Warehouse - _TC"
item = make_item(
"Test Non Serialized Pick List Item For Multiple Batches and Sales Order",
properties={
"is_stock_item": 1,
},
).name
make_stock_entry(item=item, to_warehouse=warehouse, qty=100)
make_stock_entry(item=item, to_warehouse=warehouse, qty=100)
so = make_sales_order(item_code=item, qty=10, rate=100)
pl1 = create_pick_list(so.name)
pl1.save()
self.assertEqual(pl1.locations[0].qty, 10)
so = make_sales_order(item_code=item, qty=110, rate=100)
pl = create_pick_list(so.name)
pl.save()
self.assertEqual(pl.locations[0].qty, 110.0)
self.assertTrue(hasattr(pl, "locations"))
pl1.submit()
pl.reload()
pl.submit()
self.assertEqual(pl.locations[0].qty, 110.0)
self.assertTrue(hasattr(pl, "locations"))
so = make_sales_order(item_code=item, qty=110, rate=100)
pl = create_pick_list(so.name)
pl.save()
self.assertEqual(pl.locations[0].qty, 80.0)

View File

@@ -132,7 +132,8 @@
"in_list_view": 1, "in_list_view": 1,
"label": "Item", "label": "Item",
"options": "Item", "options": "Item",
"reqd": 1 "reqd": 1,
"search_index": 1
}, },
{ {
"fieldname": "quantity_section", "fieldname": "quantity_section",
@@ -240,7 +241,7 @@
], ],
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2024-02-04 16:12:16.257951", "modified": "2024-05-07 15:32:42.905446",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Pick List Item", "name": "Pick List Item",

View File

@@ -838,7 +838,12 @@ def insert_item_price(args):
item_price = frappe.db.get_value( item_price = frappe.db.get_value(
"Item Price", "Item Price",
{"item_code": args.item_code, "price_list": args.price_list, "currency": args.currency}, {
"item_code": args.item_code,
"price_list": args.price_list,
"currency": args.currency,
"uom": args.stock_uom,
},
["name", "price_list_rate"], ["name", "price_list_rate"],
as_dict=1, as_dict=1,
) )

View File

@@ -30,8 +30,15 @@ def execute(filters=None):
sle_count = _estimate_table_row_count("Stock Ledger Entry") sle_count = _estimate_table_row_count("Stock Ledger Entry")
if sle_count > SLE_COUNT_LIMIT and not filters.get("item_code") and not filters.get("warehouse"): if (
frappe.throw(_("Please select either the Item or Warehouse filter to generate the report.")) sle_count > SLE_COUNT_LIMIT
and not filters.get("item_code")
and not filters.get("warehouse")
and not filters.get("warehouse_type")
):
frappe.throw(
_("Please select either the Item or Warehouse or Warehouse Type filter to generate the report.")
)
if filters.from_date > filters.to_date: if filters.from_date > filters.to_date:
frappe.throw(_("From Date must be before To Date")) frappe.throw(_("From Date must be before To Date"))

View File

@@ -67,3 +67,9 @@ typing-modules = ["frappe.types.DF"]
quote-style = "double" quote-style = "double"
indent-style = "tab" indent-style = "tab"
docstring-code-format = true docstring-code-format = true
[project.urls]
Homepage = "https://erpnext.com/"
Repository = "https://github.com/frappe/erpnext.git"
"Bug Reports" = "https://github.com/frappe/erpnext/issues"