mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-23 00:28:30 +00:00
Merge pull request #41355 from frappe/version-15-hotfix
chore: release v15
This commit is contained in:
@@ -1102,7 +1102,7 @@ class TestPricingRule(unittest.TestCase):
|
||||
so.load_from_db()
|
||||
self.assertEqual(so.items[1].is_free_item, 1)
|
||||
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):
|
||||
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1")
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import copy
|
||||
import json
|
||||
import math
|
||||
|
||||
import frappe
|
||||
from frappe import _, bold
|
||||
@@ -653,7 +654,7 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
|
||||
if transaction_qty:
|
||||
qty = flt(transaction_qty) * qty / pricing_rule.recurse_for
|
||||
if pricing_rule.round_free_qty:
|
||||
qty = round(qty)
|
||||
qty = math.floor(qty)
|
||||
|
||||
free_item_data_args = {
|
||||
"item_code": free_item,
|
||||
|
||||
@@ -11,13 +11,15 @@
|
||||
{
|
||||
"fieldname": "cost_center_name",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Cost Center",
|
||||
"options": "Cost Center"
|
||||
"options": "Cost Center",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-03 16:56:45.744905",
|
||||
"modified": "2024-05-03 17:16:51.666461",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "PSOA Cost Center",
|
||||
@@ -27,4 +29,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ class PSOACostCenter(Document):
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
cost_center_name: DF.Link | None
|
||||
cost_center_name: DF.Link
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
|
||||
@@ -1091,7 +1091,7 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
|
||||
# check if the exchange rate has changed
|
||||
if item.get("purchase_receipt"):
|
||||
if item.get("purchase_receipt") and self.auto_accounting_for_stock:
|
||||
if (
|
||||
exchange_rate_map[item.purchase_receipt]
|
||||
and self.conversion_rate != exchange_rate_map[item.purchase_receipt]
|
||||
|
||||
@@ -388,6 +388,9 @@ class SalesInvoice(SellingController):
|
||||
validate_account_head(item.idx, item.income_account, self.company, "Income")
|
||||
|
||||
def set_tax_withholding(self):
|
||||
if self.get("is_opening") == "Yes":
|
||||
return
|
||||
|
||||
tax_withholding_details = get_party_tax_withholding_details(self)
|
||||
|
||||
if not tax_withholding_details:
|
||||
|
||||
@@ -1766,6 +1766,49 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
|
||||
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):
|
||||
si = create_sales_invoice(
|
||||
customer="_Test Customer USD",
|
||||
|
||||
@@ -112,11 +112,7 @@ class Subscription(Document):
|
||||
"""
|
||||
_current_invoice_start = None
|
||||
|
||||
if (
|
||||
self.is_new_subscription()
|
||||
and self.trial_period_end
|
||||
and getdate(self.trial_period_end) > getdate(self.start_date)
|
||||
):
|
||||
if self.trial_period_end and getdate(self.trial_period_end) > getdate(self.start_date):
|
||||
_current_invoice_start = add_days(self.trial_period_end, 1)
|
||||
elif self.trial_period_start and self.is_trialling():
|
||||
_current_invoice_start = self.trial_period_start
|
||||
@@ -143,7 +139,7 @@ class Subscription(Document):
|
||||
else:
|
||||
billing_cycle_info = self.get_billing_cycle_data()
|
||||
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)
|
||||
|
||||
# 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
|
||||
elif self.current_invoice_is_past_due() and not self.is_past_grace_period():
|
||||
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"
|
||||
|
||||
def is_trialling(self) -> bool:
|
||||
"""
|
||||
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
|
||||
def period_has_passed(
|
||||
@@ -288,14 +284,6 @@ class Subscription(Document):
|
||||
def invoice_document_type(self) -> str:
|
||||
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:
|
||||
self.validate_trial_period()
|
||||
self.validate_plans_billing_cycle(self.get_billing_cycle_and_interval())
|
||||
@@ -604,7 +592,7 @@ class Subscription(Document):
|
||||
return False
|
||||
|
||||
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
|
||||
elif self.generate_invoice_at == "Days before the current subscription period" and (
|
||||
|
||||
@@ -445,11 +445,11 @@ class TestSubscription(FrappeTestCase):
|
||||
|
||||
# Process subscription and create first invoice
|
||||
# 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(subscription.status, "Unpaid")
|
||||
|
||||
subscription.process()
|
||||
subscription.process(posting_date="2018-04-01")
|
||||
self.assertEqual(len(subscription.invoices), 1)
|
||||
|
||||
def test_multi_currency_subscription(self):
|
||||
@@ -462,7 +462,7 @@ class TestSubscription(FrappeTestCase):
|
||||
party=party,
|
||||
)
|
||||
|
||||
subscription.process()
|
||||
subscription.process(posting_date="2018-01-01")
|
||||
self.assertEqual(len(subscription.invoices), 1)
|
||||
self.assertEqual(subscription.status, "Unpaid")
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ from frappe.query_builder import Criterion
|
||||
from frappe.query_builder.functions import Abs, Sum
|
||||
from frappe.utils import cint, flt, getdate
|
||||
|
||||
from erpnext.controllers.accounts_controller import validate_account_head
|
||||
|
||||
|
||||
class TaxWithholdingCategory(Document):
|
||||
# begin: auto-generated types
|
||||
@@ -53,6 +55,7 @@ class TaxWithholdingCategory(Document):
|
||||
if d.get("account") in existing_accounts:
|
||||
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"))
|
||||
|
||||
def validate_thresholds(self):
|
||||
|
||||
@@ -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(
|
||||
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_in_account_currency = flt(same_head.credit_in_account_currency) + flt(
|
||||
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:
|
||||
merged_gl_map.append(entry)
|
||||
|
||||
|
||||
@@ -720,20 +720,22 @@ class GrossProfitGenerator:
|
||||
frappe.qb.from_(purchase_invoice_item)
|
||||
.inner_join(purchase_invoice)
|
||||
.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.posting_date <= self.filters.to_date)
|
||||
.where(purchase_invoice_item.item_code == item_code)
|
||||
)
|
||||
|
||||
if row.project:
|
||||
query.where(purchase_invoice_item.project == row.project)
|
||||
query = query.where(purchase_invoice_item.project == row.project)
|
||||
|
||||
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.limit(1)
|
||||
query = query.orderby(purchase_invoice.posting_date, order=frappe.qb.desc).limit(1)
|
||||
last_purchase_rate = query.run()
|
||||
|
||||
return flt(last_purchase_rate[0][0]) if last_purchase_rate else 0
|
||||
|
||||
@@ -143,6 +143,10 @@ class AssetCapitalization(StockController):
|
||||
self.make_gl_entries()
|
||||
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):
|
||||
if self.entry_type == "Capitalization" and self.target_asset:
|
||||
asset_doc = frappe.get_doc("Asset", self.target_asset)
|
||||
|
||||
@@ -612,6 +612,20 @@ class PurchaseOrder(BuyingController):
|
||||
|
||||
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):
|
||||
if self.is_subcontracted and not self.is_old_subcontracting_flow:
|
||||
if frappe.db.get_single_value("Buying Settings", "auto_create_subcontracting_order"):
|
||||
|
||||
@@ -3158,6 +3158,9 @@ def validate_and_delete_children(parent, data) -> bool:
|
||||
d.cancel()
|
||||
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
|
||||
# bin uses Material Request Items to recalculate & update
|
||||
parent.update_prevdoc_status()
|
||||
|
||||
@@ -360,4 +360,6 @@ erpnext.patches.v15_0.create_accounting_dimensions_in_payment_request
|
||||
erpnext.patches.v14_0.migrate_gl_to_payment_ledger
|
||||
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.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
|
||||
|
||||
@@ -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
|
||||
"""
|
||||
)
|
||||
@@ -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)
|
||||
@@ -15,6 +15,9 @@ frappe.ui.form.on("Item", {
|
||||
frm.add_fetch("tax_type", "tax_rate", "tax_rate");
|
||||
|
||||
frm.make_methods = {
|
||||
Quotation: () => {
|
||||
open_form(frm, "Quotation", "Quotation Item", "items");
|
||||
},
|
||||
"Sales Order": () => {
|
||||
open_form(frm, "Sales Order", "Sales Order Item", "items");
|
||||
},
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
"section_break_11",
|
||||
"description",
|
||||
"brand",
|
||||
"unit_of_measure_conversion",
|
||||
"uoms",
|
||||
"dashboard_tab",
|
||||
"inventory_section",
|
||||
"inventory_settings_section",
|
||||
@@ -52,8 +54,6 @@
|
||||
"barcodes",
|
||||
"reorder_section",
|
||||
"reorder_levels",
|
||||
"unit_of_measure_conversion",
|
||||
"uoms",
|
||||
"serial_nos_and_batches",
|
||||
"has_batch_no",
|
||||
"create_new_batch",
|
||||
@@ -891,7 +891,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"make_attachments_public": 1,
|
||||
"modified": "2024-01-08 18:09:30.225085",
|
||||
"modified": "2024-04-30 13:46:39.098753",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Item",
|
||||
@@ -964,4 +964,4 @@
|
||||
"states": [],
|
||||
"title_field": "item_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,15 +65,13 @@ class Item(Document):
|
||||
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_tax.item_tax import ItemTax
|
||||
from erpnext.stock.doctype.item_variant_attribute.item_variant_attribute import (
|
||||
ItemVariantAttribute,
|
||||
)
|
||||
from erpnext.stock.doctype.item_variant_attribute.item_variant_attribute import ItemVariantAttribute
|
||||
from erpnext.stock.doctype.uom_conversion_detail.uom_conversion_detail import UOMConversionDetail
|
||||
|
||||
allow_alternative_item: DF.Check
|
||||
allow_negative_stock: DF.Check
|
||||
asset_category: DF.Link | None
|
||||
asset_naming_series: DF.Literal
|
||||
asset_naming_series: DF.Literal[None]
|
||||
attributes: DF.Table[ItemVariantAttribute]
|
||||
auto_create_assets: DF.Check
|
||||
barcodes: DF.Table[ItemBarcode]
|
||||
|
||||
@@ -52,10 +52,13 @@
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.stock_uom",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "uom",
|
||||
"fieldtype": "Link",
|
||||
"label": "UOM",
|
||||
"options": "UOM"
|
||||
"options": "UOM",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@@ -220,7 +223,7 @@
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2024-01-30 14:02:19.304854",
|
||||
"modified": "2024-04-02 22:18:00.450641",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Item Price",
|
||||
|
||||
@@ -38,7 +38,7 @@ class ItemPrice(Document):
|
||||
reference: DF.Data | None
|
||||
selling: DF.Check
|
||||
supplier: DF.Link | None
|
||||
uom: DF.Link | None
|
||||
uom: DF.Link
|
||||
valid_from: DF.Date | None
|
||||
valid_upto: DF.Date | None
|
||||
# end: auto-generated types
|
||||
|
||||
@@ -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)
|
||||
if not picked_qty:
|
||||
filterd_locations.append(row)
|
||||
continue
|
||||
if picked_qty > row.qty:
|
||||
row.qty = 0
|
||||
|
||||
@@ -1013,3 +1013,121 @@ class TestPickList(FrappeTestCase):
|
||||
pl.submit()
|
||||
self.assertEqual(pl.locations[0].qty, 4.0)
|
||||
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)
|
||||
|
||||
@@ -132,7 +132,8 @@
|
||||
"in_list_view": 1,
|
||||
"label": "Item",
|
||||
"options": "Item",
|
||||
"reqd": 1
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "quantity_section",
|
||||
@@ -240,7 +241,7 @@
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-02-04 16:12:16.257951",
|
||||
"modified": "2024-05-07 15:32:42.905446",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Pick List Item",
|
||||
@@ -251,4 +252,4 @@
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -838,7 +838,12 @@ def insert_item_price(args):
|
||||
|
||||
item_price = frappe.db.get_value(
|
||||
"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"],
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
@@ -30,8 +30,15 @@ def execute(filters=None):
|
||||
|
||||
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"):
|
||||
frappe.throw(_("Please select either the Item or Warehouse filter to generate the report."))
|
||||
if (
|
||||
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:
|
||||
frappe.throw(_("From Date must be before To Date"))
|
||||
|
||||
@@ -67,3 +67,9 @@ typing-modules = ["frappe.types.DF"]
|
||||
quote-style = "double"
|
||||
indent-style = "tab"
|
||||
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"
|
||||
|
||||
Reference in New Issue
Block a user