diff --git a/erpnext/accounts/doctype/bank/bank.js b/erpnext/accounts/doctype/bank/bank.js index 77af313dc16..0a268aa88a2 100644 --- a/erpnext/accounts/doctype/bank/bank.js +++ b/erpnext/accounts/doctype/bank/bank.js @@ -3,9 +3,6 @@ frappe.provide("erpnext.integrations"); frappe.ui.form.on("Bank", { - onload: function (frm) { - add_fields_to_mapping_table(frm); - }, refresh: function (frm) { add_fields_to_mapping_table(frm); frm.toggle_display(["address_html", "contact_html"], !frm.doc.__islocal); @@ -37,11 +34,11 @@ let add_fields_to_mapping_table = function (frm) { }); }); - frm.fields_dict.bank_transaction_mapping.grid.update_docfield_property( - "bank_transaction_field", - "options", - options - ); + const grid = frm.fields_dict.bank_transaction_mapping?.grid; + + if (grid) { + grid.update_docfield_property("bank_transaction_field", "options", options); + } }; erpnext.integrations.refreshPlaidLink = class refreshPlaidLink { @@ -116,7 +113,7 @@ erpnext.integrations.refreshPlaidLink = class refreshPlaidLink { "There was an issue connecting to Plaid's authentication server. Check browser console for more information" ) ); - console.log(error); + console.error(error); } plaid_success(token, response) { diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index e7c0832554f..2ed9881772c 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -172,7 +172,7 @@ class JournalEntry(AccountsController): validate_docs_for_deferred_accounting([self.name], []) def submit(self): - if len(self.accounts) > 100: + if len(self.accounts) > 100 and not self.meta.queue_in_background: queue_submission(self, "_submit") else: return self._submit() diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 852fcc6807b..580af69c404 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -400,6 +400,16 @@ frappe.ui.form.on("Payment Entry", { ); frm.refresh_fields(); + + const party_currency = + frm.doc.payment_type === "Receive" ? "paid_from_account_currency" : "paid_to_account_currency"; + + var reference_grid = frm.fields_dict["references"].grid; + ["total_amount", "outstanding_amount", "allocated_amount"].forEach((fieldname) => { + reference_grid.update_docfield_property(fieldname, "options", party_currency); + }); + + reference_grid.refresh(); }, show_general_ledger: function (frm) { @@ -1119,7 +1129,7 @@ frappe.ui.form.on("Payment Entry", { allocate_party_amount_against_ref_docs: async function (frm, paid_amount, paid_amount_change) { await frm.call("allocate_amount_to_references", { - paid_amount: paid_amount, + paid_amount: flt(paid_amount), paid_amount_change: paid_amount_change, allocate_payment_amount: frappe.flags.allocate_payment_amount ?? false, }); diff --git a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json index 30a2da24bbb..80333239ad6 100644 --- a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json +++ b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json @@ -132,6 +132,12 @@ "fieldtype": "Link", "label": "Cost Center", "options": "Cost Center" + }, + { + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project" }, { "fieldname": "due_date", diff --git a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py index c7cc97d7197..bcf3ddec01a 100644 --- a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py +++ b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py @@ -38,6 +38,7 @@ class PaymentLedgerEntry(Document): amount_in_account_currency: DF.Currency company: DF.Link | None cost_center: DF.Link | None + project: DF.Link | None delinked: DF.Check due_date: DF.Date | None finance_book: DF.Link | None diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index c81863f044f..9ec4e0a073a 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -746,7 +746,7 @@ class PaymentReconciliation(Document): ple = qb.DocType("Payment Ledger Entry") for x in self.dimensions: dimension = x.fieldname - if self.get(dimension): + if self.get(dimension) and frappe.db.has_column("Payment Ledger Entry", dimension): self.accounting_dimension_filter_conditions.append(ple[dimension] == self.get(dimension)) def build_qb_filter_conditions(self, get_invoices=False, get_return_invoices=False): diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 9711c4637bc..2c96286d3bb 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -117,12 +117,20 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends ( return item.delivery_note ? true : false; }); - if (!from_delivery_note && !is_delivered_by_supplier) { - cur_frm.add_custom_button( - __("Delivery"), - cur_frm.cscript["Make Delivery Note"], - __("Create") + if (!is_delivered_by_supplier) { + const should_create_delivery_note = doc.items.some( + (item) => + item.qty - item.delivered_qty > 0 && + !item.dn_detail && + !item.delivered_by_supplier ); + if (should_create_delivery_note) { + this.frm.add_custom_button( + __("Delivery Note"), + this.frm.cscript["Make Delivery Note"], + __("Create") + ); + } } } diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 9e15f8701bb..8ed23724dfb 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -2211,7 +2211,9 @@ def make_delivery_note(source_name, target_doc=None): "cost_center": "cost_center", }, "postprocess": update_item, - "condition": lambda doc: doc.delivered_by_supplier != 1, + "condition": lambda doc: doc.delivered_by_supplier != 1 + and not doc.dn_detail + and doc.qty - doc.delivered_qty > 0, }, "Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "reset_value": True}, "Sales Team": { diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 5a7c6ca9055..2a977dd2c03 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1868,6 +1868,7 @@ def get_payment_ledger_entries(gl_entries, cancel=0): account=gle.account, party_type=gle.party_type, party=gle.party, + project=gle.project, cost_center=gle.cost_center, finance_book=gle.finance_book, due_date=gle.due_date, diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index c9968c7a34d..7637192ba9b 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -669,7 +669,10 @@ class Asset(AccountsController): def get_status(self): """Returns status based on whether it is draft, submitted, scrapped or depreciated""" if self.docstatus == 0: - status = "Draft" + if self.is_composite_asset: + status = "Work In Progress" + else: + status = "Draft" elif self.docstatus == 1: status = "Submitted" diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index 674cb3ffa3d..d234b162ba2 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -611,14 +611,21 @@ class AssetCapitalization(StockController): asset_doc = frappe.get_doc("Asset", self.target_asset) if self.docstatus == 2: - asset_doc.gross_purchase_amount -= total_target_asset_value - asset_doc.purchase_amount -= total_target_asset_value + gross_purchase_amount = asset_doc.gross_purchase_amount - total_target_asset_value + purchase_amount = asset_doc.purchase_amount - total_target_asset_value + total_asset_cost = asset_doc.total_asset_cost - total_target_asset_value else: - asset_doc.gross_purchase_amount += total_target_asset_value - asset_doc.purchase_amount += total_target_asset_value - asset_doc.set_status("Work In Progress") - asset_doc.flags.ignore_validate = True - asset_doc.save() + gross_purchase_amount = asset_doc.gross_purchase_amount + total_target_asset_value + purchase_amount = asset_doc.purchase_amount + total_target_asset_value + total_asset_cost = asset_doc.total_asset_cost + total_target_asset_value + + asset_doc.db_set( + { + "gross_purchase_amount": gross_purchase_amount, + "purchase_amount": purchase_amount, + "total_asset_cost": total_asset_cost, + } + ) frappe.msgprint( _("Asset {0} has been updated. Please set the depreciation details if any and submit it.").format( diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 035dfc6e264..bf91f9490a4 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -465,7 +465,7 @@ class StockController(AccountsController): if is_rejected: serial_nos = row.get("rejected_serial_no") type_of_transaction = "Inward" if not self.is_return else "Outward" - qty = row.get("rejected_qty") + qty = row.get("rejected_qty") * row.get("conversion_factor", 1.0) warehouse = row.get("rejected_warehouse") if ( diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 313a9de4e4b..c48c56df3d5 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -553,8 +553,6 @@ var get_bom_material_detail = function (doc, cdt, cdn, scrap_items) { do_not_explode: d.do_not_explode, }, callback: function (r) { - d = locals[cdt][cdn]; - $.extend(d, r.message); refresh_field("items"); refresh_field("scrap_items"); diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 4640f5192dd..f1c9b706ca0 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -3186,6 +3186,53 @@ class TestWorkOrder(FrappeTestCase): allow_overproduction("overproduction_percentage_for_work_order", 0) + def test_reserved_qty_for_pp_with_extra_material_transfer(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import ( + make_stock_entry as make_stock_entry_test_record, + ) + + rm_item_code = make_item( + "_Test Reserved Qty PP Item", + { + "is_stock_item": 1, + }, + ).name + + fg_item_code = make_item( + "_Test Reserved Qty PP FG Item", + { + "is_stock_item": 1, + }, + ).name + + make_stock_entry_test_record( + item_code=rm_item_code, target="_Test Warehouse - _TC", qty=10, basic_rate=100 + ) + + make_bom( + item=fg_item_code, + raw_materials=[rm_item_code], + ) + + wo_order = make_wo_order_test_record( + item=fg_item_code, + qty=1, + source_warehouse="_Test Warehouse - _TC", + skip_transfer=0, + target_warehouse="_Test Warehouse - _TC", + ) + + bin1_at_completion = get_bin(rm_item_code, "_Test Warehouse - _TC") + self.assertEqual(bin1_at_completion.reserved_qty_for_production, 1) + + s = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 1)) + s.items[0].qty += 2 # extra material transfer + s.submit() + + bin1_at_completion = get_bin(rm_item_code, "_Test Warehouse - _TC") + + self.assertEqual(bin1_at_completion.reserved_qty_for_production, 0) + def make_stock_in_entries_and_get_batches(rm_item, source_warehouse, wip_warehouse): from erpnext.stock.doctype.stock_entry.test_stock_entry import ( diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index c26af9a74af..f5a5e2693b9 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -517,6 +517,7 @@ class WorkOrder(Document): self.db_set("status", "Cancelled") self.on_close_or_cancel() + self.delete_job_card() def on_close_or_cancel(self): if self.production_plan and frappe.db.exists( @@ -526,7 +527,6 @@ class WorkOrder(Document): else: self.update_work_order_qty_in_so() - self.delete_job_card() self.update_completed_qty_in_material_request() self.update_planned_qty() self.update_ordered_qty() @@ -1786,6 +1786,9 @@ def get_reserved_qty_for_production( qty_field = wo_item.required_qty else: qty_field = Case() + qty_field = qty_field.when( + ((wo.skip_transfer == 0) & (wo_item.transferred_qty > wo_item.required_qty)), 0.0 + ) qty_field = qty_field.when(wo.skip_transfer == 0, wo_item.required_qty - wo_item.transferred_qty) qty_field = qty_field.else_(wo_item.required_qty - wo_item.consumed_qty) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index cc8f2434513..1f434a485b5 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -603,7 +603,7 @@ def send_project_update_email_to_users(project): "sent": 0, "date": today(), "time": nowtime(), - "naming_series": "UPDATE-.project.-.YY.MM.DD.-", + "naming_series": "UPDATE-.project.-.YY.MM.DD.-.####", } ).insert() diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 0ae1828ee0d..2e782b49e66 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -2726,10 +2726,16 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe set_warehouse() { this.autofill_warehouse(this.frm.doc.items, "warehouse", this.frm.doc.set_warehouse); + this.autofill_warehouse(this.frm.doc.packed_items, "warehouse", this.frm.doc.set_warehouse); } set_target_warehouse() { this.autofill_warehouse(this.frm.doc.items, "target_warehouse", this.frm.doc.set_target_warehouse); + this.autofill_warehouse( + this.frm.doc.packed_items, + "target_warehouse", + this.frm.doc.set_target_warehouse + ); } set_from_warehouse() { diff --git a/erpnext/public/js/utils/sales_common.js b/erpnext/public/js/utils/sales_common.js index 7e2271dc38f..c9b5ed7e6f2 100644 --- a/erpnext/public/js/utils/sales_common.js +++ b/erpnext/public/js/utils/sales_common.js @@ -488,7 +488,30 @@ erpnext.sales_common = { } } - project() { + project(doc, cdt, cdn) { + if (!cdt || !cdn) { + if (this.frm.doc.project) { + $.each(this.frm.doc["items"] || [], function (i, item) { + if (!item.project) { + frappe.model.set_value(item.doctype, item.name, "project", doc.project); + } + }); + } + } else { + const item = frappe.get_doc(cdt, cdn); + if (item.project) { + $.each(this.frm.doc["items"] || [], function (i, other_item) { + if (!other_item.project) { + frappe.model.set_value( + other_item.doctype, + other_item.name, + "project", + item.project + ); + } + }); + } + } let me = this; if (["Delivery Note", "Sales Invoice", "Sales Order"].includes(this.frm.doc.doctype)) { if (this.frm.doc.project) { diff --git a/erpnext/regional/address_template/templates/sweden.html b/erpnext/regional/address_template/templates/sweden.html new file mode 100644 index 00000000000..0c2ed73f0ae --- /dev/null +++ b/erpnext/regional/address_template/templates/sweden.html @@ -0,0 +1,4 @@ +{{ address_line1 }}
+{% if address_line2 %}{{ address_line2 }}
{% endif -%} +{{ pincode }} {{ city | upper }}
+{{ country | upper }} \ No newline at end of file diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json index 7c0676c672f..dd1910ed15a 100644 --- a/erpnext/selling/doctype/customer/customer.json +++ b/erpnext/selling/doctype/customer/customer.json @@ -181,6 +181,7 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Customer Group", + "link_filters": "[[\"Customer Group\", \"is_group\", \"=\", 0]]", "oldfieldname": "customer_group", "oldfieldtype": "Link", "options": "Customer Group", @@ -610,7 +611,7 @@ "link_fieldname": "party" } ], - "modified": "2025-11-25 09:35:56.772949", + "modified": "2026-01-21 17:23:42.151114", "modified_by": "Administrator", "module": "Selling", "name": "Customer", diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 1c1ae08b280..7fb15943c9f 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -114,6 +114,7 @@ class Customer(TransactionBase): set_name_from_naming_options(frappe.get_meta(self.doctype).autoname, self) def get_customer_name(self): + self.customer_name = self.customer_name.strip() if frappe.db.get_value("Customer", self.customer_name) and not frappe.flags.in_import: count = frappe.db.sql( """select ifnull(MAX(CAST(SUBSTRING_INDEX(name, ' ', -1) AS UNSIGNED)), 0) from tabCustomer diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json index 83ba1907671..d2f8945eb29 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.json +++ b/erpnext/selling/doctype/selling_settings/selling_settings.json @@ -51,6 +51,7 @@ "fieldtype": "Link", "in_list_view": 1, "label": "Default Customer Group", + "link_filters": "[[\"Customer Group\", \"is_group\", \"=\", 0]]", "options": "Customer Group" }, { @@ -231,7 +232,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2025-09-23 21:10:14.826653", + "modified": "2026-01-21 17:28:37.027837", "modified_by": "Administrator", "module": "Selling", "name": "Selling Settings", diff --git a/erpnext/setup/setup_wizard/data/country_wise_tax.json b/erpnext/setup/setup_wizard/data/country_wise_tax.json index e82daeb7c72..3616425771c 100644 --- a/erpnext/setup/setup_wizard/data/country_wise_tax.json +++ b/erpnext/setup/setup_wizard/data/country_wise_tax.json @@ -6,14 +6,14 @@ } }, "Algeria": { - "Algeria VAT 17%": { - "account_name": "VAT 17%", - "tax_rate": 17.00, + "Algeria TVA 19%": { + "account_name": "TVA 19%", + "tax_rate": 19.00, "default": 1 }, - "Algeria VAT 7%": { - "account_name": "VAT 7%", - "tax_rate": 7.00 + "Algeria TVA 9%": { + "account_name": "TVA 9%", + "tax_rate": 9.00 } }, diff --git a/erpnext/stock/deprecated_serial_batch.py b/erpnext/stock/deprecated_serial_batch.py index f6984c9edab..69443e3a608 100644 --- a/erpnext/stock/deprecated_serial_batch.py +++ b/erpnext/stock/deprecated_serial_batch.py @@ -78,7 +78,6 @@ class DeprecatedBatchNoValuation: for ledger in entries: self.stock_value_differece[ledger.batch_no] += flt(ledger.batch_value) self.available_qty[ledger.batch_no] += flt(ledger.batch_qty) - self.total_qty[ledger.batch_no] += flt(ledger.batch_qty) @deprecated def get_sle_for_batches(self): @@ -231,7 +230,6 @@ class DeprecatedBatchNoValuation: batch_data = query.run(as_dict=True) for d in batch_data: self.available_qty[d.batch_no] += flt(d.batch_qty) - self.total_qty[d.batch_no] += flt(d.batch_qty) for d in batch_data: if self.available_qty.get(d.batch_no): @@ -332,7 +330,6 @@ class DeprecatedBatchNoValuation: batch_data = query.run(as_dict=True) for d in batch_data: self.available_qty[d.batch_no] += flt(d.batch_qty) - self.total_qty[d.batch_no] += flt(d.batch_qty) if not self.last_sle: return diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index fdaaa23c5af..2f98aff00f2 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -228,8 +228,25 @@ class Item(Document): def validate_description(self): """Clean HTML description if set""" if cint(frappe.db.get_single_value("Stock Settings", "clean_description_html")): + old_desc = self.description self.description = clean_html(self.description) + if ( + old_desc + and self.description + and " { - if (!(r.last_name && r.email && (r.phone || r.mobile_no))) { + if (!(r.full_name && r.email && (r.phone || r.mobile_no))) { if (delivery_type == "Delivery") { frm.set_value("delivery_company", ""); frm.set_value("delivery_contact", ""); @@ -272,9 +272,9 @@ frappe.ui.form.on("Shipment", { frm.set_value("pickup_contact", ""); } frappe.throw( - __("Last Name, Email or Phone/Mobile of the user are mandatory to continue.") + + __("Full Name, Email or Phone/Mobile of the user are mandatory to continue.") + "
" + - __("Please first set Last Name, Email and Phone for the user") + + __("Please first set Full Name, Email and Phone for the user") + ` ${frappe.session.user}` ); } diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 21f3245bbc4..d313c037e02 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -243,6 +243,27 @@ class StockEntry(StockController): self.reset_default_field_value("to_warehouse", "items", "t_warehouse") self.validate_same_source_target_warehouse_during_material_transfer() + self.validate_raw_materials_exists() + + def validate_raw_materials_exists(self): + if self.purpose not in ["Manufacture", "Repack", "Disassemble"]: + return + + if frappe.db.get_single_value("Manufacturing Settings", "material_consumption"): + return + + raw_materials = [] + for row in self.items: + if row.s_warehouse: + raw_materials.append(row.item_code) + + if not raw_materials: + frappe.throw( + _( + "At least one raw material item must be present in the stock entry for the type {0}" + ).format(bold(self.purpose)), + title=_("Raw Materials Missing"), + ) def set_serial_batch_for_disassembly(self): if self.purpose != "Disassemble": diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 0be02756207..2383fabaf89 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -2232,6 +2232,28 @@ class TestStockEntry(FrappeTestCase): se.save() se.submit() + def test_raw_material_missing_validation(self): + original_value = frappe.db.get_single_value("Manufacturing Settings", "material_consumption") + frappe.db.set_single_value("Manufacturing Settings", "material_consumption", 0) + + stock_entry = make_stock_entry( + item_code="_Test Item", + qty=1, + target="_Test Warehouse - _TC", + do_not_save=True, + ) + + stock_entry.purpose = "Manufacture" + stock_entry.stock_entry_type = "Manufacture" + stock_entry.items[0].is_finished_item = 1 + + self.assertRaises( + frappe.ValidationError, + stock_entry.save, + ) + + frappe.db.set_single_value("Manufacturing Settings", "material_consumption", original_value) + def make_serialized_item(**args): args = frappe._dict(args) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index b0ade4324fa..312b8e129f8 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -421,9 +421,10 @@ def get_basic_details(args, item, overwrite_warehouse=True): if not args.get("uom"): if args.get("doctype") in sales_doctypes: args.uom = item.sales_uom if item.sales_uom else item.stock_uom - elif (args.get("doctype") in ["Purchase Order", "Purchase Receipt", "Purchase Invoice"]) or ( - args.get("doctype") == "Material Request" and args.get("material_request_type") == "Purchase" - ): + elif ( + args.get("doctype") + in ["Purchase Order", "Purchase Receipt", "Purchase Invoice", "Supplier Quotation"] + ) or (args.get("doctype") == "Material Request" and args.get("material_request_type") == "Purchase"): args.uom = item.purchase_uom if item.purchase_uom else item.stock_uom else: args.uom = item.stock_uom diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index f2639998d3e..4d7b3d4aaa4 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -804,52 +804,10 @@ class BatchNoValuation(DeprecatedBatchNoValuation): self.stock_value_differece[ledger.batch_no] += flt(ledger.incoming_rate) self.available_qty[ledger.batch_no] += flt(ledger.qty) - entries = self.get_batch_wise_total_available_qty() - for row in entries: - self.total_qty[row.batch_no] += flt(row.total_qty) - self.calculate_avg_rate_from_deprecarated_ledgers() self.calculate_avg_rate_for_non_batchwise_valuation() self.set_stock_value_difference() - def get_batch_wise_total_available_qty(self) -> list[dict]: - # Get total qty of each batch no from Serial and Batch Bundle without checking time condition - if not self.batchwise_valuation_batches: - return [] - - parent = frappe.qb.DocType("Serial and Batch Bundle") - child = frappe.qb.DocType("Serial and Batch Entry") - - query = ( - frappe.qb.from_(parent) - .inner_join(child) - .on(parent.name == child.parent) - .select( - child.batch_no, - Sum(child.qty).as_("total_qty"), - ) - .where( - (parent.warehouse == self.sle.warehouse) - & (parent.item_code == self.sle.item_code) - & (child.batch_no.isin(self.batchwise_valuation_batches)) - & (parent.docstatus == 1) - & (parent.is_cancelled == 0) - & (parent.type_of_transaction.isin(["Inward", "Outward"])) - ) - .for_update() - .groupby(child.batch_no) - ) - - # Important to exclude the current voucher detail no / voucher no to calculate the correct stock value difference - if self.sle.voucher_detail_no: - query = query.where(parent.voucher_detail_no != self.sle.voucher_detail_no) - elif self.sle.voucher_no: - query = query.where(parent.voucher_no != self.sle.voucher_no) - - query = query.where(parent.voucher_type != "Pick List") - - return query.run(as_dict=True) - def get_batch_no_ledgers(self) -> list[dict]: # Get batch wise stock value difference from Serial and Batch Bundle considering time condition if not self.batchwise_valuation_batches: