diff --git a/erpnext/accounts/doctype/budget/budget.py b/erpnext/accounts/doctype/budget/budget.py index d31f72f062e..32225fff93d 100644 --- a/erpnext/accounts/doctype/budget/budget.py +++ b/erpnext/accounts/doctype/budget/budget.py @@ -142,8 +142,10 @@ def validate_expense_against_budget(args, expense_amount=0): if not frappe.get_all("Budget", limit=1): return - if args.get("company") and not args.fiscal_year: + if not args.fiscal_year: args.fiscal_year = get_fiscal_year(args.get("posting_date"), company=args.get("company"))[0] + + if args.get("company"): frappe.flags.exception_approver_role = frappe.get_cached_value( "Company", args.get("company"), "exception_budget_approver_role" ) diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py index c08bd3878d5..2c4e3eb8996 100644 --- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py +++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py @@ -134,7 +134,8 @@ class ExchangeRateRevaluation(Document): accounts = self.get_accounts_data() if accounts: for acc in accounts: - self.append("accounts", acc) + if acc.get("gain_loss"): + self.append("accounts", acc) @frappe.whitelist() def get_accounts_data(self): diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 2e4f9db3539..37ac27f3722 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -311,7 +311,7 @@ def validate_balance_type(account, adv_adj=False): if balance_must_be: balance = frappe.db.sql( """select sum(debit) - sum(credit) - from `tabGL Entry` where account = %s""", + from `tabGL Entry` where is_cancelled = 0 and account = %s""", account, )[0][0] diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py index 7f51f2add59..fba261a1484 100644 --- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py +++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py @@ -75,6 +75,17 @@ class PeriodClosingVoucher(AccountsController): return previous_fiscal_year_start_date = previous_fiscal_year[0][1] + previous_fiscal_year_closed = frappe.db.exists( + "Period Closing Voucher", + { + "period_end_date": ("between", [previous_fiscal_year_start_date, last_year_closing]), + "docstatus": 1, + "company": self.company, + }, + ) + if previous_fiscal_year_closed: + return + gle_exists_in_previous_year = frappe.db.exists( "GL Entry", { @@ -86,16 +97,7 @@ class PeriodClosingVoucher(AccountsController): if not gle_exists_in_previous_year: return - previous_fiscal_year_closed = frappe.db.exists( - "Period Closing Voucher", - { - "period_end_date": ("between", [previous_fiscal_year_start_date, last_year_closing]), - "docstatus": 1, - "company": self.company, - }, - ) - if not previous_fiscal_year_closed: - frappe.throw(_("Previous Year is not closed, please close it first")) + frappe.throw(_("Previous Year is not closed, please close it first")) def block_if_future_closing_voucher_exists(self): future_closing_voucher = self.get_future_closing_voucher() diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index 87360543efd..0196b2b6189 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -668,7 +668,13 @@ class POSInvoice(SalesInvoice): "Account", self.debit_to, "account_currency" ) if not self.due_date and self.customer: - self.due_date = get_due_date(self.posting_date, "Customer", self.customer, self.company) + self.due_date = get_due_date( + self.posting_date, + "Customer", + self.customer, + self.company, + template_name=self.payment_terms_template, + ) super(SalesInvoice, self).set_missing_values(for_validate) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json index c4825a6d519..7a5c6d7713b 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json @@ -174,6 +174,7 @@ }, { "default": "0", + "depends_on": "eval:doc.apply_on != 'Transaction'", "fieldname": "is_cumulative", "fieldtype": "Check", "label": "Is Cumulative" @@ -656,7 +657,7 @@ "icon": "fa fa-gift", "idx": 1, "links": [], - "modified": "2025-02-17 18:15:39.824639", + "modified": "2025-08-20 11:40:07.096854", "modified_by": "Administrator", "module": "Accounts", "name": "Pricing Rule", diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index de63ec3a8c4..fde3b9e48dd 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -583,11 +583,7 @@ def apply_pricing_rule_on_transaction(doc): if not d.get(pr_field): continue - if ( - d.validate_applied_rule - and doc.get(field) is not None - and doc.get(field) < d.get(pr_field) - ): + if d.validate_applied_rule and (doc.get(field) or 0) < d.get(pr_field): frappe.msgprint(_("User has not applied rule on the invoice {0}").format(doc.name)) else: if not d.coupon_code_based: diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.json b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.json index 1d68b23d6c7..c55b0a7d7ff 100644 --- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.json +++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.json @@ -93,12 +93,14 @@ }, { "default": "0", + "depends_on": "eval:doc.apply_on != 'Transaction'", "fieldname": "mixed_conditions", "fieldtype": "Check", "label": "Mixed Conditions" }, { "default": "0", + "depends_on": "eval:doc.apply_on != 'Transaction'", "fieldname": "is_cumulative", "fieldtype": "Check", "label": "Is Cumulative" @@ -278,7 +280,7 @@ } ], "links": [], - "modified": "2021-05-06 16:20:22.039078", + "modified": "2025-08-20 11:48:23.231081", "modified_by": "Administrator", "module": "Accounts", "name": "Promotional Scheme", @@ -336,4 +338,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 17fad2eca43..ad367f3c9cb 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -644,7 +644,7 @@ frappe.ui.form.on("Purchase Invoice", { }, add_custom_buttons: function (frm) { - if (frm.doc.docstatus == 1 && frm.doc.per_received < 100) { + if (frm.doc.docstatus == 1 && frm.doc.per_received < 100 && frm.doc.update_stock == 0) { frm.add_custom_button( __("Purchase Receipt"), () => { diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 4f0d6a64c36..8be04ab67a1 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -340,7 +340,12 @@ class PurchaseInvoice(BuyingController): ) if not self.due_date: self.due_date = get_due_date( - self.posting_date, "Supplier", self.supplier, self.company, self.bill_date + self.posting_date, + "Supplier", + self.supplier, + self.company, + self.bill_date, + template_name=self.payment_terms_template, ) tds_category = frappe.db.get_value("Supplier", self.supplier, "tax_withholding_category") diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 71e541692f8..04068dc08a1 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -532,6 +532,7 @@ def get_accounting_entries( query = query.select(gl_entry.posting_date, gl_entry.is_opening, gl_entry.fiscal_year) query = query.where(gl_entry.is_cancelled == 0) query = query.where(gl_entry.posting_date <= to_date) + query = query.force_index("posting_date_company_index") if ignore_opening_entries and not ignore_is_opening: query = query.where(gl_entry.is_opening == "No") diff --git a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py index d6ffd3db5ac..d234e5bc328 100644 --- a/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py +++ b/erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.py @@ -9,7 +9,6 @@ from frappe.query_builder import functions as fn from frappe.utils import cstr, flt from frappe.utils.nestedset import get_descendants_of from frappe.utils.xlsxutils import handle_html -from pypika import Order from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments from erpnext.accounts.report.utils import get_values_for_columns diff --git a/erpnext/accounts/report/trial_balance_simple/trial_balance_simple.json b/erpnext/accounts/report/trial_balance_simple/trial_balance_simple.json index fab3a76a0c4..6b926649ca6 100644 --- a/erpnext/accounts/report/trial_balance_simple/trial_balance_simple.json +++ b/erpnext/accounts/report/trial_balance_simple/trial_balance_simple.json @@ -4,15 +4,25 @@ "disabled": 0, "docstatus": 0, "doctype": "Report", + "filters": [ + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "mandatory": 1, + "options": "Company", + "wildcard_filter": 0 + } + ], "idx": 0, "is_standard": "Yes", - "modified": "2019-01-17 17:20:42.374958", + "modified": "2025-08-28 19:06:54.273322", "modified_by": "Administrator", "module": "Accounts", "name": "Trial Balance (Simple)", "owner": "Administrator", "prepared_report": 0, - "query": "select fiscal_year as \"Fiscal Year:Data:80\",\n\tcompany as \"Company:Data:220\",\n\tposting_date as \"Posting Date:Date:100\",\n\taccount as \"Account:Data:380\",\n\tsum(debit) as \"Debit:Currency:140\",\n\tsum(credit) as \"Credit:Currency:140\",\n\tfinance_book as \"Finance Book:Link/Finance Book:140\"\nfrom `tabGL Entry`\ngroup by fiscal_year, company, posting_date, account\norder by fiscal_year, company, posting_date, account", + "query": "select fiscal_year as \"Fiscal Year:Data:80\",\n\tcompany as \"Company:Data:220\",\n\tposting_date as \"Posting Date:Date:100\",\n\taccount as \"Account:Data:380\",\n\tsum(debit) as \"Debit:Currency:140\",\n\tsum(credit) as \"Credit:Currency:140\",\n\tfinance_book as \"Finance Book:Link/Finance Book:140\"\nfrom `tabGL Entry`\nwhere is_cancelled = 0 and company = %(company)s\ngroup by fiscal_year, company, posting_date, account\norder by fiscal_year, company, posting_date, account", "ref_doctype": "GL Entry", "report_name": "Trial Balance (Simple)", "report_type": "Query Report", diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json index 9a2d07dbec2..170a68fb394 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.json +++ b/erpnext/buying/doctype/buying_settings/buying_settings.json @@ -39,7 +39,9 @@ "section_break_xcug", "auto_create_subcontracting_order", "column_break_izrr", - "auto_create_purchase_receipt" + "auto_create_purchase_receipt", + "request_for_quotation_tab", + "fixed_email" ], "fields": [ { @@ -255,6 +257,19 @@ "fieldname": "set_valuation_rate_for_rejected_materials", "fieldtype": "Check", "label": "Set Valuation Rate for Rejected Materials" + }, + { + "fieldname": "request_for_quotation_tab", + "fieldtype": "Tab Break", + "label": "Request for Quotation" + }, + { + "description": "If set, the system does not use the user's Email or the standard outgoing Email account for sending request for quotations.", + "fieldname": "fixed_email", + "fieldtype": "Link", + "label": "Fixed Outgoing Email Account", + "link_filters": "[[\"Email Account\",\"enable_outgoing\",\"=\",1]]", + "options": "Email Account" } ], "grid_page_length": 50, @@ -263,7 +278,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2025-05-16 15:56:38.321369", + "modified": "2025-08-20 22:13:38.506889", "modified_by": "Administrator", "module": "Buying", "name": "Buying Settings", diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.py b/erpnext/buying/doctype/buying_settings/buying_settings.py index f3b3dbefff4..8b83418f6f8 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.py +++ b/erpnext/buying/doctype/buying_settings/buying_settings.py @@ -30,6 +30,7 @@ class BuyingSettings(Document): blanket_order_allowance: DF.Float buying_price_list: DF.Link | None disable_last_purchase_rate: DF.Check + fixed_email: DF.Link | None maintain_same_rate: DF.Check maintain_same_rate_action: DF.Literal["Stop", "Warn"] over_transfer_allowance: DF.Float diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py index d11424b555f..e8c5690cf6f 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -289,7 +289,11 @@ class RequestforQuotation(BuyingController): email_template = frappe.get_doc("Email Template", self.email_template) message = frappe.render_template(email_template.response_, doc_args) subject = frappe.render_template(email_template.subject, doc_args) - sender = frappe.session.user not in STANDARD_USERS and frappe.session.user or None + fixed_procurement_email = frappe.db.get_single_value("Buying Settings", "fixed_email") + if fixed_procurement_email: + sender = frappe.db.get_value("Email Account", fixed_procurement_email, "email_id") + else: + sender = frappe.session.user not in STANDARD_USERS and frappe.session.user or None if preview: return {"message": message, "subject": subject} diff --git a/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json b/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json index 510317f5c2e..6b236f2cc60 100644 --- a/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json +++ b/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json @@ -188,30 +188,10 @@ "hidden": 0, "is_query_report": 0, "label": "Payments", - "link_count": 3, + "link_count": 1, "onboard": 0, "type": "Card Break" }, - { - "hidden": 0, - "is_query_report": 0, - "label": "GoCardless Settings", - "link_count": 0, - "link_to": "GoCardless Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, - { - "hidden": 0, - "is_query_report": 0, - "label": "Mpesa Settings", - "link_count": 0, - "link_to": "Mpesa Settings", - "link_type": "DocType", - "onboard": 0, - "type": "Link" - }, { "hidden": 0, "is_query_report": 0, diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 92080c05baf..2f189ba4c46 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -116,6 +116,20 @@ frappe.ui.form.on("Work Order", { frm.set_indicator_formatter("operation", function (doc) { return frm.doc.qty == doc.completed_qty ? "green" : "orange"; }); + + if (frm.doc.docstatus == 0 && frm.doc.bom_no) { + frappe.call({ + method: "erpnext.manufacturing.doctype.work_order.work_order.check_if_scrap_warehouse_mandatory", + args: { + bom_no: frm.doc.bom_no, + }, + callback: function (r) { + if (r.message["set_scrap_wh_mandatory"]) { + frm.toggle_reqd("scrap_warehouse", true); + } + }, + }); + } }, onload: function (frm) { diff --git a/erpnext/patches.txt b/erpnext/patches.txt index ae52e1e3a09..fd3dc3ad3cd 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -390,7 +390,7 @@ erpnext.patches.v15_0.enable_allow_existing_serial_no erpnext.patches.v15_0.update_cc_in_process_statement_of_accounts erpnext.patches.v15_0.update_asset_status_to_work_in_progress erpnext.patches.v15_0.rename_manufacturing_settings_field -erpnext.patches.v15_0.sync_auto_reconcile_config +erpnext.patches.v15_0.sync_auto_reconcile_config #2025-08-26 execute:frappe.db.set_single_value("Accounts Settings", "exchange_gain_loss_posting_date", "Payment") erpnext.patches.v14_0.disable_add_row_in_gross_profit erpnext.patches.v15_0.set_difference_amount_in_asset_value_adjustment diff --git a/erpnext/patches/v15_0/sync_auto_reconcile_config.py b/erpnext/patches/v15_0/sync_auto_reconcile_config.py index be92ad99536..f7ece3ca43e 100644 --- a/erpnext/patches/v15_0/sync_auto_reconcile_config.py +++ b/erpnext/patches/v15_0/sync_auto_reconcile_config.py @@ -11,7 +11,7 @@ def execute(): frappe.db.set_single_value("Accounts Settings", "reconciliation_queue_size", 5) # Create Scheduler Event record if it doesn't exist - if frappe.reload_doc("core", "doctype", "scheduler_event"): + if frappe.reload_doc("core", "doctype", "scheduler_event", force=True): method = "erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation.trigger_reconciliation_for_queued_docs" if not frappe.db.get_all( "Scheduler Event", {"scheduled_against": "Process Payment Reconciliation", "method": method} diff --git a/erpnext/public/js/bank_reconciliation_tool/number_card.js b/erpnext/public/js/bank_reconciliation_tool/number_card.js index d39884c381e..381d94ed785 100644 --- a/erpnext/public/js/bank_reconciliation_tool/number_card.js +++ b/erpnext/public/js/bank_reconciliation_tool/number_card.js @@ -26,7 +26,7 @@ erpnext.accounts.bank_reconciliation.NumberCardManager = class NumberCardManager currency: this.currency, }, { - value: this.bank_statement_closing_balance - this.cleared_balance, + value: flt(this.bank_statement_closing_balance) - flt(this.cleared_balance), label: __("Difference"), datatype: "Currency", currency: this.currency, diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js index a1675be8954..e7ee7d505a1 100644 --- a/erpnext/public/js/controllers/buying.js +++ b/erpnext/public/js/controllers/buying.js @@ -159,8 +159,9 @@ erpnext.buying = { }); } - company(){ - if(!frappe.meta.has_field(this.frm.doc.doctype, "billing_address")) return; + company() { + super.company(); + if (!frappe.meta.has_field(this.frm.doc.doctype, "billing_address")) return; frappe.call({ method: "erpnext.setup.doctype.company.company.get_billing_shipping_address", diff --git a/erpnext/public/js/utils/barcode_scanner.js b/erpnext/public/js/utils/barcode_scanner.js index b8d1b4afa2a..1c3d5d160e6 100644 --- a/erpnext/public/js/utils/barcode_scanner.js +++ b/erpnext/public/js/utils/barcode_scanner.js @@ -459,12 +459,8 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { const item_scanned = row.has_item_scanned; let warehouse_match = true; - if (has_warehouse_field) { - if (warehouse) { - warehouse_match = row[warehouse_field] === warehouse; - } else { - warehouse_match = !row[warehouse_field]; - } + if (has_warehouse_field && warehouse && row[warehouse_field]) { + warehouse_match = row[warehouse_field] === warehouse; } return ( diff --git a/erpnext/regional/__init__.py b/erpnext/regional/__init__.py index bd5d5401035..e2c791071af 100644 --- a/erpnext/regional/__init__.py +++ b/erpnext/regional/__init__.py @@ -19,6 +19,9 @@ def create_transaction_log(doc, method): Appends the transaction to a chain of hashed logs for legal resons. Called on submit of Sales Invoice and Payment Entry. """ + if frappe.conf.get("disable_transaction_log", False): + return + region = get_region() if region not in ["Germany"]: return diff --git a/erpnext/selling/doctype/quotation_item/quotation_item.json b/erpnext/selling/doctype/quotation_item/quotation_item.json index 2818b49913d..74c4670063e 100644 --- a/erpnext/selling/doctype/quotation_item/quotation_item.json +++ b/erpnext/selling/doctype/quotation_item/quotation_item.json @@ -365,6 +365,7 @@ "fieldname": "base_net_rate", "fieldtype": "Currency", "label": "Net Rate (Company Currency)", + "options": "Company:company:default_currency", "print_hide": 1, "read_only": 1 }, @@ -698,7 +699,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2024-12-12 13:49:18.765883", + "modified": "2025-08-26 20:31:47.775890", "modified_by": "Administrator", "module": "Selling", "name": "Quotation Item", diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index ae473e15917..8c4f85ff19f 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -766,6 +766,7 @@ }, { "default": "0", + "description": "Upon enabling this, the JV will be submitted for a different exchange rate.", "fieldname": "submit_err_jv", "fieldtype": "Check", "label": "Submit ERR Journals?" @@ -857,7 +858,7 @@ "image_field": "company_logo", "is_tree": 1, "links": [], - "modified": "2025-01-09 20:12:25.471544", + "modified": "2025-08-25 18:34:03.602046", "modified_by": "Administrator", "module": "Setup", "name": "Company", @@ -919,6 +920,7 @@ "select": 1 } ], + "row_format": "Dynamic", "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "ASC", diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 7df84379c22..b7014b19c4a 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -4146,6 +4146,36 @@ class TestPurchaseReceipt(FrappeTestCase): self.assertTrue(sles) + def test_validate_recreate_stock_ledgers_for_sn_item(self): + item_code = "Test SN Item for Recreate Stock Ledgers" + make_item(item_code, {"has_serial_no": 1, "serial_no_series": "SN-TRSLR-.#####"}) + + pr = make_purchase_receipt(item_code=item_code, qty=10, rate=100) + pr.submit() + + sles = frappe.get_all( + "Stock Ledger Entry", + filters={"voucher_type": pr.doctype, "voucher_no": pr.name}, + pluck="name", + ) + + self.assertTrue(sles) + + repost_doc = frappe.get_doc( + { + "doctype": "Repost Item Valuation", + "based_on": "Transaction", + "voucher_type": pr.doctype, + "voucher_no": pr.name, + "posting_date": pr.posting_date, + "posting_time": pr.posting_time, + "company": pr.company, + "recreate_stock_ledgers": 1, + } + ) + + self.assertRaises(frappe.ValidationError, repost_doc.save) + def test_internal_pr_qty_change_only_single_batch(self): from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js index 6f2548ac8ea..e6547ad6f35 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js @@ -132,4 +132,18 @@ frappe.ui.form.on("Repost Item Valuation", { }, }); }, + + voucher_type: function (frm) { + frm.trigger("set_company_on_transaction"); + }, + + voucher_no: function (frm) { + frm.trigger("set_company_on_transaction"); + }, + + set_company_on_transaction(frm) { + if (frm.doc.voucher_no && frm.doc.voucher_type) { + frm.call("set_company"); + } + }, }); diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 06598aa285b..49ba0bf8358 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -70,12 +70,33 @@ class RepostItemValuation(Document): ) def validate(self): + self.set_company() self.validate_period_closing_voucher() self.set_status(write=False) self.reset_field_values() - self.set_company() self.validate_accounts_freeze() self.reset_recreate_stock_ledgers() + self.validate_recreate_stock_ledgers() + + def validate_recreate_stock_ledgers(self): + if not self.recreate_stock_ledgers: + return + + items = [] + if self.based_on == "Item and Warehouse": + items.append(self.item_code) + else: + items = get_items_to_be_repost(self.voucher_type, self.voucher_no) + items = list(set([d.item_code for d in items])) + + if serial_batch_items := frappe.get_all( + "Item", or_filters={"has_serial_no": 1, "has_batch_no": 1}, filters={"name": ("in", items)} + ): + item_list = ", ".join([d.name for d in serial_batch_items]) + msg = _( + "Since {0} are Serial No/Batch No items, you cannot enable 'Recreate Stock Ledgers' in Repost Item Valuation." + ).format(item_list) + frappe.throw(msg) def validate_period_closing_voucher(self): # Period Closing Voucher @@ -167,6 +188,7 @@ class RepostItemValuation(Document): def on_trash(self): self.clear_attachment() + @frappe.whitelist() def set_company(self): if self.based_on == "Transaction": self.company = frappe.get_cached_value(self.voucher_type, self.voucher_no, "company") diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 67918ee1dfd..ea2ec897d1d 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -1006,7 +1006,7 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle this.barcode_scanner = new erpnext.utils.BarcodeScanner({ frm: this.frm, warehouse_field: (doc) => { - return doc.purpose === "Material Transfer" ? "t_warehouse" : "s_warehouse"; + return doc.purpose === "Material Receipt" ? "t_warehouse" : "s_warehouse"; }, }); diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index a1260c5ffdd..0d1175b2fe0 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -2123,7 +2123,16 @@ class StockEntry(StockController): "Work Order", self.work_order, "allow_alternative_item" ) - item.from_warehouse = self.from_warehouse or item.source_warehouse or item.default_warehouse + item.from_warehouse = ( + frappe.get_value( + "Work Order Item", + {"parent": self.work_order, "item_code": item.item_code}, + "source_warehouse", + ) + if frappe.get_value("Work Order", self.work_order, "skip_transfer") + and not frappe.get_value("Work Order", self.work_order, "from_wip_warehouse") + else self.from_warehouse or item.source_warehouse or item.default_warehouse + ) if item.item_code in used_alternative_items: alternative_item_data = used_alternative_items.get(item.item_code) item.item_code = alternative_item_data.item_code diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py index 21180accf83..2a1fe92ae28 100644 --- a/erpnext/stock/serial_batch_bundle.py +++ b/erpnext/stock/serial_batch_bundle.py @@ -770,12 +770,15 @@ class BatchNoValuation(DeprecatedBatchNoValuation): self.non_batchwise_valuation_batches = self.batches return - batches = frappe.get_all( - "Batch", filters={"name": ("in", self.batches), "use_batchwise_valuation": 1}, fields=["name"] - ) + if get_valuation_method(self.sle.item_code) == "FIFO": + self.batchwise_valuation_batches = self.batches + else: + batches = frappe.get_all( + "Batch", filters={"name": ("in", self.batches), "use_batchwise_valuation": 1}, fields=["name"] + ) - for batch in batches: - self.batchwise_valuation_batches.append(batch.name) + for batch in batches: + self.batchwise_valuation_batches.append(batch.name) self.non_batchwise_valuation_batches = list(set(self.batches) - set(self.batchwise_valuation_batches))