diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 8b13b00c042..5db9c30135a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -60,7 +60,7 @@ body: description: Share exact version number of Frappe and ERPNext you are using. placeholder: | Frappe version - - ERPNext Verion - + ERPNext version - validations: required: true diff --git a/.github/workflows/server-tests-mariadb-faux.yml b/.github/workflows/server-tests-mariadb-faux.yml index 8df315c13ae..555ce260406 100644 --- a/.github/workflows/server-tests-mariadb-faux.yml +++ b/.github/workflows/server-tests-mariadb-faux.yml @@ -7,6 +7,7 @@ on: paths: - "**.js" - "**.css" + - "**.svg" - "**.md" - "**.html" - 'crowdin.yml' diff --git a/README.md b/README.md index 96ac64c035f..622c004e3ad 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +
ERPNext Logo diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/id_chart_of_accounts.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/id_chart_of_accounts.json index 855feea6920..f569938dfc5 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/id_chart_of_accounts.json +++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/id_chart_of_accounts.json @@ -33,6 +33,17 @@ }, "account_number": "1151.000" }, + "Pajak Dibayar di Muka": { + "PPN Masukan": { + "account_number": "1152.001", + "account_type": "Tax" + }, + "PPh 23 Dibayar di Muka": { + "account_number": "1152.002", + "account_type": "Tax" + }, + "account_number": "1152.000" + }, "account_number": "1150.000" }, "Kas": { @@ -97,17 +108,6 @@ }, "account_number": "1130.000" }, - "Pajak Dibayar di Muka": { - "PPN Masukan": { - "account_number": "1151.001", - "account_type": "Tax" - }, - "PPh 23 Dibayar di Muka": { - "account_number": "1152.001", - "account_type": "Tax" - }, - "account_number": "1150.000" - }, "account_number": "1100.000" }, diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 4ea077f5d8c..dd37c82af0e 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -1691,6 +1691,10 @@ def get_exchange_rate( credit=None, exchange_rate=None, ): + # Ensure exchange_rate is always numeric to avoid calculation errors + if isinstance(exchange_rate, str): + exchange_rate = flt(exchange_rate) or 1 + account_details = frappe.get_cached_value( "Account", account, ["account_type", "root_type", "account_currency", "company"], as_dict=1 ) diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json index 7578150b07e..406e3fa7537 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json @@ -1610,13 +1610,14 @@ "hidden": 1, "label": "Item Wise Tax Details", "no_copy": 1, - "options": "Item Wise Tax Detail" + "options": "Item Wise Tax Detail", + "print_hide": 1 } ], "icon": "fa fa-file-text", "is_submittable": 1, "links": [], - "modified": "2025-08-04 22:22:31.471752", + "modified": "2026-01-29 21:20:51.376875", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice", diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py index 59d47dce726..1a94e1c9990 100644 --- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py @@ -898,6 +898,53 @@ class TestPOSInvoice(IntegrationTestCase): if batch.batch_no == batch_no and batch.warehouse == "_Test Warehouse - _TC": self.assertEqual(batch.qty, 5) + def test_pos_batch_reservation_with_return_qty(self): + """ + Test POS Invoice reserved qty for batch without bundle with return invoices. + """ + from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import ( + get_auto_batch_nos, + ) + from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ( + create_batch_item_with_batch, + ) + + create_batch_item_with_batch("_Batch Item Reserve Return", "TestBatch-RR 01") + se = make_stock_entry( + target="_Test Warehouse - _TC", + item_code="_Batch Item Reserve Return", + qty=30, + basic_rate=100, + ) + + se.reload() + + batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle) + + # POS Invoice for the batch without bundle + pos_inv = create_pos_invoice(item="_Batch Item Reserve Return", rate=300, qty=15, do_not_save=1) + pos_inv.append( + "payments", + {"mode_of_payment": "Cash", "amount": 4500}, + ) + pos_inv.items[0].batch_no = batch_no + pos_inv.save() + pos_inv.submit() + + # POS Invoice return + pos_return = make_sales_return(pos_inv.name) + + pos_return.insert() + pos_return.submit() + + batches = get_auto_batch_nos( + frappe._dict({"item_code": "_Batch Item Reserve Return", "warehouse": "_Test Warehouse - _TC"}) + ) + + for batch in batches: + if batch.batch_no == batch_no and batch.warehouse == "_Test Warehouse - _TC": + self.assertEqual(batch.qty, 30) + def test_pos_batch_item_qty_validation(self): from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import ( BatchNegativeStockError, diff --git a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py index 092081308c9..4f6741f17cd 100644 --- a/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py +++ b/erpnext/accounts/doctype/process_payment_reconciliation/process_payment_reconciliation.py @@ -415,8 +415,9 @@ def reconcile(doc: None | str = None) -> None: for x in allocations: pr.append("allocation", x) + skip_ref_details_update_for_pe = check_multi_currency(pr) # reconcile - pr.reconcile_allocations(skip_ref_details_update_for_pe=True) + pr.reconcile_allocations(skip_ref_details_update_for_pe=skip_ref_details_update_for_pe) # If Payment Entry, update details only for newly linked references # This is for performance @@ -504,6 +505,37 @@ def reconcile(doc: None | str = None) -> None: frappe.db.set_value("Process Payment Reconciliation", doc, "status", "Completed") +def check_multi_currency(pr_doc): + GL = frappe.qb.DocType("GL Entry") + Account = frappe.qb.DocType("Account") + + def get_account_currency(voucher_type, voucher_no): + currency = ( + frappe.qb.from_(GL) + .join(Account) + .on(GL.account == Account.name) + .select(Account.account_currency) + .where( + (GL.voucher_type == voucher_type) + & (GL.voucher_no == voucher_no) + & (Account.account_type.isin(["Payable", "Receivable"])) + ) + .limit(1) + ).run(as_dict=True) + + return currency[0].account_currency if currency else None + + for allocation in pr_doc.allocation: + reference_currency = get_account_currency(allocation.reference_type, allocation.reference_name) + + invoice_currency = get_account_currency(allocation.invoice_type, allocation.invoice_number) + + if reference_currency != invoice_currency: + return True + + return False + + @frappe.whitelist() def is_any_doc_running(for_filter: str | dict | None = None) -> str | None: running_doc = None diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index bd86928766c..980eee2c48c 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -1625,7 +1625,8 @@ "hidden": 1, "label": "Item Wise Tax Details", "no_copy": 1, - "options": "Item Wise Tax Detail" + "options": "Item Wise Tax Detail", + "print_hide": 1 }, { "collapsible": 1, @@ -1667,7 +1668,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2025-12-15 06:41:38.237728", + "modified": "2026-01-29 21:21:53.051193", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 888dbd59438..801820fbdb5 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -36,7 +36,7 @@ from erpnext.accounts.utils import get_account_currency, get_fiscal_year, update from erpnext.assets.doctype.asset.asset import is_cwip_accounting_enabled from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account from erpnext.buying.utils import check_on_hold_or_closed_status -from erpnext.controllers.accounts_controller import validate_account_head +from erpnext.controllers.accounts_controller import merge_taxes, validate_account_head from erpnext.controllers.buying_controller import BuyingController from erpnext.stock.doctype.purchase_receipt.purchase_receipt import ( update_billed_amount_based_on_po, @@ -2005,9 +2005,17 @@ def make_purchase_receipt(source_name, target_doc=None, args=None): args = json.loads(args) def post_parent_process(source_parent, target_parent): - for row in target_parent.get("items"): - if row.get("qty") == 0: - target_parent.remove(row) + remove_items_with_zero_qty(target_parent) + set_missing_values(source_parent, target_parent) + + def remove_items_with_zero_qty(target_parent): + target_parent.items = [row for row in target_parent.get("items") if row.get("qty") != 0] + + def set_missing_values(source_parent, target_parent): + target_parent.run_method("set_missing_values") + if args and args.get("merge_taxes"): + merge_taxes(source_parent, target_parent) + target_parent.run_method("calculate_taxes_and_totals") def update_item(obj, target, source_parent): from erpnext.controllers.sales_and_purchase_return import get_returned_qty_map_for_row @@ -2059,7 +2067,11 @@ def make_purchase_receipt(source_name, target_doc=None, args=None): "postprocess": update_item, "condition": lambda doc: abs(doc.received_qty) < abs(doc.qty) and select_item(doc), }, - "Purchase Taxes and Charges": {"doctype": "Purchase Taxes and Charges"}, + "Purchase Taxes and Charges": { + "doctype": "Purchase Taxes and Charges", + "reset_value": not (args and args.get("merge_taxes")), + "ignore": args.get("merge_taxes") if args else 0, + }, }, target_doc, post_parent_process, diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 0ba6feef6da..6682270b4c9 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -44,6 +44,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends ( "Unreconcile Payment Entries", "Serial and Batch Bundle", "Bank Transaction", + "Packing Slip", ]; if (!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) { diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 2d32832d25c..b372da7f74a 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -1,6 +1,7 @@ { "actions": [], "allow_import": 1, + "allow_rename": 1, "autoname": "naming_series:", "creation": "2022-01-25 10:29:57.771398", "doctype": "DocType", @@ -2250,7 +2251,8 @@ "hidden": 1, "label": "Item Wise Tax Details", "no_copy": 1, - "options": "Item Wise Tax Detail" + "options": "Item Wise Tax Detail", + "print_hide": 1 }, { "default": "0", @@ -2304,7 +2306,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2025-12-24 18:29:50.242618", + "modified": "2026-01-30 16:45:59.682473", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index d14db8be7c6..c00924a4394 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -4745,6 +4745,66 @@ class TestSalesInvoice(ERPNextTestSuite): doc.db_set("do_not_use_batchwise_valuation", original_value) + @change_settings("Selling Settings", {"set_zero_rate_for_expired_batch": True}) + def test_zero_valuation_for_standalone_credit_note_with_expired_batch(self): + item_code = "_Test Item for Expiry Batch Zero Valuation" + make_item_for_si( + item_code, + { + "is_stock_item": 1, + "has_batch_no": 1, + "has_expiry_date": 1, + "shelf_life_in_days": 2, + "create_new_batch": 1, + "batch_number_series": "TBATCH-EBZV.####", + }, + ) + + se = make_stock_entry( + item_code=item_code, + qty=10, + target="_Test Warehouse - _TC", + rate=100, + ) + + # fetch batch no from bundle + batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle) + + si = create_sales_invoice( + posting_date=add_days(nowdate(), 3), + item=item_code, + qty=-10, + rate=100, + is_return=1, + update_stock=1, + use_serial_batch_fields=1, + do_not_save=1, + do_not_submit=1, + ) + + si.items[0].batch_no = batch_no + si.save() + si.submit() + + si.reload() + # check zero incoming rate in voucher + self.assertEqual(si.items[0].incoming_rate, 0.0) + + # chekc zero incoming rate in stock ledger + stock_ledger_entry = frappe.db.get_value( + "Stock Ledger Entry", + { + "voucher_type": "Sales Invoice", + "voucher_no": si.name, + "item_code": item_code, + "warehouse": "_Test Warehouse - _TC", + }, + ["incoming_rate", "valuation_rate"], + as_dict=True, + ) + + self.assertEqual(stock_ledger_entry.incoming_rate, 0.0) + def make_item_for_si(item_code, properties=None): from erpnext.stock.doctype.item.test_item import make_item diff --git a/erpnext/accounts/doctype/transaction_deletion_record_details/transaction_deletion_record_details.json b/erpnext/accounts/doctype/transaction_deletion_record_details/transaction_deletion_record_details.json index 9023ee91000..89fa09b7f95 100644 --- a/erpnext/accounts/doctype/transaction_deletion_record_details/transaction_deletion_record_details.json +++ b/erpnext/accounts/doctype/transaction_deletion_record_details/transaction_deletion_record_details.json @@ -43,16 +43,18 @@ "read_only": 1 } ], + "grid_page_length": 50, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2024-03-27 13:10:55.008837", + "modified": "2025-11-14 16:17:25.584675", "modified_by": "Administrator", "module": "Accounts", "name": "Transaction Deletion Record Details", "owner": "Administrator", "permissions": [], + "row_format": "Dynamic", "sort_field": "creation", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/accounts/print_format/journal_auditing_voucher/journal_auditing_voucher.html b/erpnext/accounts/print_format/journal_auditing_voucher/journal_auditing_voucher.html index 8a6968ee373..ad8c908dbcf 100644 --- a/erpnext/accounts/print_format/journal_auditing_voucher/journal_auditing_voucher.html +++ b/erpnext/accounts/print_format/journal_auditing_voucher/journal_auditing_voucher.html @@ -17,7 +17,7 @@
- +
Date: {{ frappe.utils.format_date(doc.creation) }}
Date: {{ frappe.utils.format_date(doc.posting_date) }}
diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py index c7585d9efd8..9cbdbee6316 100644 --- a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py +++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py @@ -163,11 +163,11 @@ def get_net_profit_loss(income, expense, period_list, company, currency=None, co def get_chart_data(filters, columns, income, expense, net_profit_loss, currency): - labels = [d.get("label") for d in columns[2:]] + labels = [d.get("label") for d in columns[4:]] income_data, expense_data, net_profit = [], [], [] - for p in columns[2:]: + for p in columns[4:]: if income: income_data.append(income[-2].get(p.get("fieldname"))) if expense: diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 294b4436768..0514aa455bc 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -11,6 +11,7 @@ import frappe.defaults from frappe import _, qb, throw from frappe.desk.reportview import build_match_conditions from frappe.model.meta import get_field_precision +from frappe.model.naming import determine_consecutive_week_number from frappe.query_builder import AliasedQuery, Case, Criterion, Field, Table from frappe.query_builder.functions import Count, IfNull, Max, Round, Sum from frappe.query_builder.utils import DocType @@ -25,6 +26,7 @@ from frappe.utils import ( get_number_format_info, getdate, now, + now_datetime, nowdate, ) from frappe.utils.caching import site_cache @@ -66,6 +68,7 @@ def get_fiscal_year( as_dict=False, boolean=None, raise_on_missing=True, + truncate=False, ): if isinstance(raise_on_missing, str): raise_on_missing = loads(raise_on_missing) @@ -79,7 +82,14 @@ def get_fiscal_year( fiscal_years = get_fiscal_years( date, fiscal_year, label, verbose, company, as_dict=as_dict, raise_on_missing=raise_on_missing ) - return False if not fiscal_years else fiscal_years[0] + + if fiscal_years: + fiscal_year = fiscal_years[0] + if truncate: + return ("-".join(y[-2:] for y in fiscal_year[0].split("-")), fiscal_year[1], fiscal_year[2]) + return fiscal_year + + return False def get_fiscal_years( @@ -1501,14 +1511,14 @@ def get_autoname_with_number(number_value, doc_title, company): def parse_naming_series_variable(doc, variable): - if variable == "FY": + if variable in ["FY", "TFY"]: if doc: date = doc.get("posting_date") or doc.get("transaction_date") or getdate() company = doc.get("company") else: date = getdate() company = None - return get_fiscal_year(date=date, company=company)[0] + return get_fiscal_year(date=date, company=company, truncate=variable == "TFY")[0] elif variable == "ABBR": if doc: @@ -1518,6 +1528,18 @@ def parse_naming_series_variable(doc, variable): return frappe.db.get_value("Company", company, "abbr") if company else "" + else: + data = {"YY": "%y", "YYYY": "%Y", "MM": "%m", "DD": "%d", "JJJ": "%j"} + date = ( + ( + getdate(doc.get("posting_date") or doc.get("transaction_date") or doc.get("posting_datetime")) + or now_datetime() + ) + if frappe.get_single_value("Global Defaults", "use_posting_datetime_for_naming_documents") + else now_datetime() + ) + return date.strftime(data[variable]) if variable in data else determine_consecutive_week_number(date) + @frappe.whitelist() def get_coa(doctype, parent, is_root=None, chart=None): diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 7a1b4b70af7..1e0cfedfbb9 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -246,7 +246,9 @@ def _make_journal_entry_for_depreciation( def setup_journal_entry_metadata(je, depr_schedule_doc, depr_series, depr_schedule, asset): je.voucher_type = "Depreciation Entry" - je.naming_series = depr_series + if depr_series: + je.naming_series = depr_series + je.posting_date = depr_schedule.schedule_date je.company = asset.company je.finance_book = depr_schedule_doc.finance_book diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js index 80f1a17f178..12b0a8c301c 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order.js @@ -803,7 +803,7 @@ frappe.ui.form.on("Purchase Order", "is_subcontracted", function (frm) { function prevent_past_schedule_dates(frm) { if (frm.doc.transaction_date) { - frm.fields_dict["schedule_date"].datepicker.update({ + frm.fields_dict["schedule_date"].datepicker?.update({ minDate: new Date(frm.doc.transaction_date), }); } diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index f69be2c8e0b..05d03d47676 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -1301,7 +1301,8 @@ "hidden": 1, "label": "Item Wise Tax Details", "no_copy": 1, - "options": "Item Wise Tax Detail" + "options": "Item Wise Tax Detail", + "print_hide": 1 } ], "grid_page_length": 50, @@ -1309,7 +1310,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2025-09-28 11:00:56.635116", + "modified": "2026-01-29 21:22:54.323838", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 3fbd00e9196..75f552fe195 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -149,6 +149,7 @@ class PurchaseOrder(BuyingController): supplied_items: DF.Table[PurchaseOrderItemSupplied] supplier: DF.Link supplier_address: DF.Link | None + supplier_group: DF.Link | None supplier_name: DF.Data | None supplier_warehouse: DF.Link | None tax_category: DF.Link | None 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 b2f8a1ed1e5..95727472112 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -304,12 +304,17 @@ class RequestforQuotation(BuyingController): else: sender = frappe.session.user not in STANDARD_USERS and frappe.session.user or None + rendered_message = frappe.render_template(self.message_for_supplier, doc_args) + subject_source = ( + self.subject + or frappe.get_value("Email Template", self.email_template, "subject") + or _("Request for Quotation") + ) + rendered_subject = frappe.render_template(subject_source, doc_args) if preview: return { - "message": self.message_for_supplier, - "subject": self.subject - or frappe.get_value("Email Template", self.email_template, "subject") - or _("Request for Quotation"), + "message": rendered_message, + "subject": rendered_subject, } attachments = [] @@ -333,10 +338,8 @@ class RequestforQuotation(BuyingController): self.send_email( data, sender, - self.subject - or frappe.get_value("Email Template", self.email_template, "subject") - or _("Request for Quotation"), - self.message_for_supplier, + rendered_subject, + rendered_message, attachments, ) diff --git a/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json b/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json index f82528e06c6..e96cd169883 100644 --- a/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json +++ b/erpnext/buying/doctype/request_for_quotation_item/request_for_quotation_item.json @@ -63,7 +63,6 @@ "fieldtype": "Column Break" }, { - "fetch_from": "item_code.item_name", "fieldname": "item_name", "fieldtype": "Data", "in_global_search": 1, @@ -262,7 +261,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2025-04-28 23:30:22.927989", + "modified": "2026-01-31 19:46:27.884592", "modified_by": "Administrator", "module": "Buying", "name": "Request for Quotation Item", diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json index c9805ed4065..1328d8aebdc 100644 --- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json +++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json @@ -938,7 +938,8 @@ "hidden": 1, "label": "Item Wise Tax Details", "no_copy": 1, - "options": "Item Wise Tax Detail" + "options": "Item Wise Tax Detail", + "print_hide": 1 } ], "grid_page_length": 50, @@ -947,7 +948,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2025-07-23 02:22:43.526822", + "modified": "2026-01-29 21:23:13.778468", "modified_by": "Administrator", "module": "Buying", "name": "Supplier Quotation", diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index b4f618aecaa..5deaeca975c 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -12,7 +12,7 @@ from frappe.utils import cint, flt, format_datetime, get_datetime import erpnext from erpnext.stock.serial_batch_bundle import get_batches_from_bundle -from erpnext.stock.utils import get_combine_datetime, get_incoming_rate, get_valuation_method +from erpnext.stock.utils import get_combine_datetime, get_incoming_rate, get_valuation_method, getdate class StockOverReturnError(frappe.ValidationError): @@ -759,6 +759,29 @@ def get_rate_for_return( StockLedgerEntry = frappe.qb.DocType("Stock Ledger Entry") select_field = Abs(StockLedgerEntry.stock_value_difference / StockLedgerEntry.actual_qty) + item_details = frappe.get_cached_value("Item", item_code, ["has_batch_no", "has_expiry_date"], as_dict=1) + set_zero_rate_for_expired_batch = frappe.db.get_single_value( + "Selling Settings", "set_zero_rate_for_expired_batch" + ) + + if ( + set_zero_rate_for_expired_batch + and item_details.has_batch_no + and item_details.has_expiry_date + and not return_against + and voucher_type in ["Sales Invoice", "Delivery Note"] + ): + # set incoming_rate zero explicitly for standalone credit note with expired batch + batch_no = frappe.db.get_value(f"{voucher_type} Item", voucher_detail_no, "batch_no") + if batch_no and is_batch_expired(batch_no, sle.get("posting_date")): + frappe.db.set_value( + voucher_type + " Item", + voucher_detail_no, + "incoming_rate", + 0, + ) + return 0 + rate = flt(frappe.db.get_value("Stock Ledger Entry", filters, select_field)) if not (rate and return_against) and voucher_type in ["Sales Invoice", "Delivery Note"]: rate = frappe.db.get_value(f"{voucher_type} Item", voucher_detail_no, "incoming_rate") @@ -823,12 +846,34 @@ def get_filters( if reference_voucher_detail_no: filters["voucher_detail_no"] = reference_voucher_detail_no - if voucher_type in ["Purchase Receipt", "Purchase Invoice"] and item_row and item_row.get("warehouse"): - filters["warehouse"] = item_row.get("warehouse") + warehouses = [] + if voucher_type in ["Purchase Receipt", "Purchase Invoice"] and item_row: + if reference_voucher_detail_no: + warehouses = get_warehouses_for_return(voucher_type, reference_voucher_detail_no) + + if item_row.get("warehouse") and item_row.get("warehouse") in warehouses: + filters["warehouse"] = item_row.get("warehouse") return filters +def get_warehouses_for_return(voucher_type, name): + warehouses = [] + warehouse_details = frappe.get_all( + voucher_type + " Item", + filters={"name": name, "docstatus": 1}, + fields=["warehouse", "rejected_warehouse"], + ) + + for d in warehouse_details: + if d.warehouse: + warehouses.append(d.warehouse) + if d.rejected_warehouse: + warehouses.append(d.rejected_warehouse) + + return warehouses + + def get_returned_serial_nos(child_doc, parent_doc, serial_no_field=None, ignore_voucher_detail_no=None): from erpnext.stock.doctype.serial_no.serial_no import ( get_serial_nos as get_serial_nos_from_serial_no, @@ -1276,3 +1321,17 @@ def get_sales_invoice_item_from_consolidated_invoice(return_against_pos_invoice, return result[0].name if result else None except Exception: return None + + +def is_batch_expired(batch_no, posting_date): + """ + To check whether the batch is expired or not based on the posting date. + """ + expiry_date = frappe.db.get_value("Batch", batch_no, "expiry_date") + if not expiry_date: + return + + if getdate(posting_date) > getdate(expiry_date): + return True + + return False diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 57aeea727bc..0f883eeb50f 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -8,7 +8,7 @@ from frappe.utils import cint, flt, get_link_to_form, nowtime from erpnext.accounts.party import render_address from erpnext.controllers.accounts_controller import get_taxes_and_charges -from erpnext.controllers.sales_and_purchase_return import get_rate_for_return +from erpnext.controllers.sales_and_purchase_return import get_rate_for_return, is_batch_expired from erpnext.controllers.stock_controller import StockController from erpnext.stock.doctype.item.item import set_item_default from erpnext.stock.get_item_details import get_bin_details, get_conversion_factor @@ -296,7 +296,7 @@ class SellingController(StockController): _( """Row #{0}: Selling rate for item {1} is lower than its {2}. Selling {3} should be atleast {4}.

Alternatively, - you can disable selling price validation in {5} to bypass + you can disable '{5}' in {6} to bypass this validation.""" ).format( idx, @@ -304,7 +304,8 @@ class SellingController(StockController): bold(ref_rate_field), bold("net rate"), bold(rate), - get_link_to_form("Selling Settings", "Selling Settings"), + bold(frappe.get_meta("Selling Settings").get_label("validate_selling_price")), + get_link_to_form("Selling Settings"), ), title=_("Invalid Selling Price"), ) @@ -313,7 +314,6 @@ class SellingController(StockController): return is_internal_customer = self.get("is_internal_customer") - valuation_rate_map = {} for item in self.items: if not item.item_code or item.is_free_item: @@ -323,7 +323,9 @@ class SellingController(StockController): "Item", item.item_code, ("last_purchase_rate", "is_stock_item") ) - last_purchase_rate_in_sales_uom = last_purchase_rate * (item.conversion_factor or 1) + last_purchase_rate_in_sales_uom = flt( + last_purchase_rate * (item.conversion_factor or 1), item.precision("base_net_rate") + ) if flt(item.base_net_rate) < flt(last_purchase_rate_in_sales_uom): throw_message(item.idx, item.item_name, last_purchase_rate_in_sales_uom, "last purchase rate") @@ -331,50 +333,16 @@ class SellingController(StockController): if is_internal_customer or not is_stock_item: continue - valuation_rate_map[(item.item_code, item.warehouse)] = None - - if not valuation_rate_map: - return - - or_conditions = ( - f"""(item_code = {frappe.db.escape(valuation_rate[0])} - and warehouse = {frappe.db.escape(valuation_rate[1])})""" - for valuation_rate in valuation_rate_map - ) - - valuation_rates = frappe.db.sql( - f""" - select - item_code, warehouse, valuation_rate - from - `tabBin` - where - ({" or ".join(or_conditions)}) - and valuation_rate > 0 - """, - as_dict=True, - ) - - for rate in valuation_rates: - valuation_rate_map[(rate.item_code, rate.warehouse)] = rate.valuation_rate - - for item in self.items: - if not item.item_code or item.is_free_item: - continue - - last_valuation_rate = valuation_rate_map.get((item.item_code, item.warehouse)) - - if not last_valuation_rate: - continue - - last_valuation_rate_in_sales_uom = last_valuation_rate * (item.conversion_factor or 1) - - if flt(item.base_net_rate) < flt(last_valuation_rate_in_sales_uom): + if item.get("incoming_rate") and item.base_net_rate < ( + valuation_rate := flt( + item.incoming_rate * (item.conversion_factor or 1), item.precision("base_net_rate") + ) + ): throw_message( item.idx, item.item_name, - last_valuation_rate_in_sales_uom, - "valuation rate (Moving Average)", + valuation_rate, + "valuation rate", ) def get_item_list(self): @@ -533,19 +501,37 @@ class SellingController(StockController): if self.doctype not in ("Delivery Note", "Sales Invoice"): return + from erpnext.stock.serial_batch_bundle import get_batch_nos + allow_at_arms_length_price = frappe.get_cached_value( "Stock Settings", None, "allow_internal_transfer_at_arms_length_price" ) + set_zero_rate_for_expired_batch = frappe.db.get_single_value( + "Selling Settings", "set_zero_rate_for_expired_batch" + ) + + old_doc = self.get_doc_before_save() items = self.get("items") + (self.get("packed_items") or []) for d in items: if not frappe.get_cached_value("Item", d.item_code, "is_stock_item"): continue item_details = frappe.get_cached_value( - "Item", d.item_code, ["has_serial_no", "has_batch_no"], as_dict=1 + "Item", d.item_code, ["has_serial_no", "has_batch_no", "has_expiry_date"], as_dict=1 ) - if not self.get("return_against") or ( + if ( + set_zero_rate_for_expired_batch + and item_details.has_batch_no + and item_details.has_expiry_date + and self.get("is_return") + and not self.get("return_against") + and is_batch_expired(d.batch_no, self.get("posting_date")) + ): + # set incoming rate as zero for stand-lone credit note with expired batch + d.incoming_rate = 0 + + elif not self.get("return_against") or ( get_valuation_method(d.item_code, self.company) == "Moving Average" and self.get("is_return") and not item_details.has_serial_no @@ -554,6 +540,29 @@ class SellingController(StockController): # Get incoming rate based on original item cost based on valuation method qty = flt(d.get("stock_qty") or d.get("actual_qty") or d.get("qty")) + if old_doc: + old_item = next( + ( + item + for item in (old_doc.get("items") + (old_doc.get("packed_items") or [])) + if item.name == d.name + ), + None, + ) + if old_item: + old_qty = flt( + old_item.get("stock_qty") or old_item.get("actual_qty") or old_item.get("qty") + ) + if ( + old_item.item_code != d.item_code + or old_item.warehouse != d.warehouse + or old_qty != qty + or old_item.batch_no != d.batch_no + or get_batch_nos(old_item.serial_and_batch_bundle) + != get_batch_nos(d.serial_and_batch_bundle) + ): + d.incoming_rate = 0 + if ( not d.incoming_rate or self.is_internal_transfer() diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index 652ce697711..b16c95722a6 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -91,7 +91,8 @@ status_map = { ], "Delivery Note": [ ["Draft", None], - ["To Bill", "eval:self.per_billed < 100 and self.docstatus == 1"], + ["To Bill", "eval:self.per_billed == 0 and self.docstatus == 1"], + ["Partially Billed", "eval:self.per_billed < 100 and self.per_billed > 0 and self.docstatus == 1"], ["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"], ["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"], ["Return", "eval:self.is_return == 1 and self.per_billed == 0 and self.docstatus == 1"], @@ -443,7 +444,7 @@ class StatusUpdater(Document): ): return - if args["source_dt"] != "Pick List Item": + if args["source_dt"] != "Pick List Item" and args["target_dt"] != "Quotation Item": if qty_or_amount == "qty": action_msg = _( 'To allow over receipt / delivery, update "Over Receipt/Delivery Allowance" in Stock Settings or the Item.' diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index aa09f0ca956..f2e93a4e933 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -552,7 +552,10 @@ 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") * row.get("conversion_factor", 1.0) + qty = flt( + row.get("rejected_qty") * row.get("conversion_factor", 1.0), + frappe.get_precision("Serial and Batch Entry", "qty"), + ) warehouse = row.get("rejected_warehouse") if ( diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index c825159df71..ebfda036044 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -313,10 +313,10 @@ class SubcontractingController(StockController): ): for row in frappe.get_all( f"{self.subcontract_data.order_doctype} Item", - fields=["item_code", {"SUB": ["qty", "received_qty"], "as": "qty"}, "parent", "name"], + fields=["item_code", {"SUB": ["qty", "received_qty"], "as": "qty"}, "parent", "bom"], filters={"docstatus": 1, "parent": ("in", self.subcontract_orders)}, ): - self.qty_to_be_received[(row.item_code, row.parent)] += row.qty + self.qty_to_be_received[(row.item_code, row.parent, row.bom)] += row.qty def __get_transferred_items(self): se = frappe.qb.DocType("Stock Entry") @@ -923,13 +923,17 @@ class SubcontractingController(StockController): self.__set_serial_nos(item_row, rm_obj) def __get_qty_based_on_material_transfer(self, item_row, transfer_item): - key = (item_row.item_code, item_row.get(self.subcontract_data.order_field)) + key = ( + item_row.item_code, + item_row.get(self.subcontract_data.order_field), + item_row.get("bom"), + ) if self.qty_to_be_received == item_row.qty: return transfer_item.qty - if self.qty_to_be_received: - qty = (flt(item_row.qty) * flt(transfer_item.qty)) / flt(self.qty_to_be_received.get(key, 0)) + if self.qty_to_be_received.get(key): + qty = (flt(item_row.qty) * flt(transfer_item.qty)) / flt(self.qty_to_be_received.get(key)) transfer_item.item_details.required_qty = transfer_item.qty if transfer_item.serial_no or frappe.get_cached_value( @@ -978,7 +982,11 @@ class SubcontractingController(StockController): if self.qty_to_be_received: self.qty_to_be_received[ - (row.item_code, row.get(self.subcontract_data.order_field)) + ( + row.item_code, + row.get(self.subcontract_data.order_field), + row.get("bom"), + ) ] -= row.qty def __set_rate_for_serial_and_batch_bundle(self): diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py index 44e3d6a7bf4..553a7246fea 100644 --- a/erpnext/controllers/tests/test_accounts_controller.py +++ b/erpnext/controllers/tests/test_accounts_controller.py @@ -2,8 +2,6 @@ # For license information, please see license.txt -from datetime import datetime - import frappe from frappe import qb from frappe.query_builder.functions import Sum @@ -2480,3 +2478,21 @@ class TestAccountsController(IntegrationTestCase): self.assertRaises(frappe.ValidationError, po.save) po.items[0].delivered_by_supplier = 1 po.save() + + @IntegrationTestCase.change_settings("Global Defaults", {"use_posting_datetime_for_naming_documents": 1}) + def test_document_naming_rule_based_on_posting_date(self): + frappe.new_doc( + "Document Naming Rule", document_type="Sales Invoice", prefix="SI-.MM.-.YYYY.-" + ).submit() + + si = create_sales_invoice(do_not_save=True) + si.set_posting_time = 1 + si.posting_date = "2025-12-31" + si.save() + self.assertEqual(si.name, "SI-12-2025-00001") + + si = create_sales_invoice(do_not_save=True) + si.set_posting_time = 1 + si.posting_date = "2026-01-01" + si.save() + self.assertEqual(si.name, "SI-01-2026-00002") diff --git a/erpnext/hooks.py b/erpnext/hooks.py index ca7efec8e58..9440459fef3 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -402,9 +402,10 @@ doc_events = { } # function should expect the variable and doc as arguments +naming_series_variables_list = ["FY", "TFY", "ABBR", "MM", "DD", "YY", "YYYY", "JJJ", "WW"] naming_series_variables = { - "FY": "erpnext.accounts.utils.parse_naming_series_variable", - "ABBR": "erpnext.accounts.utils.parse_naming_series_variable", + variable: "erpnext.accounts.utils.parse_naming_series_variable" + for variable in naming_series_variables_list } # On cancel event Payment Entry will be exempted and all linked submittable doctype will get cancelled. diff --git a/erpnext/locale/main.pot b/erpnext/locale/main.pot index 86d656e9bac..4a738fff71d 100644 --- a/erpnext/locale/main.pot +++ b/erpnext/locale/main.pot @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: ERPNext VERSION\n" "Report-Msgid-Bugs-To: hello@frappe.io\n" -"POT-Creation-Date: 2026-01-25 09:38+0000\n" -"PO-Revision-Date: 2026-01-25 09:38+0000\n" +"POT-Creation-Date: 2026-02-01 09:43+0000\n" +"PO-Revision-Date: 2026-02-01 09:43+0000\n" "Last-Translator: hello@frappe.io\n" "Language-Team: hello@frappe.io\n" "MIME-Version: 1.0\n" @@ -90,15 +90,15 @@ msgstr "" msgid " Summary" msgstr "" -#: erpnext/stock/doctype/item/item.py:240 +#: erpnext/stock/doctype/item/item.py:257 msgid "\"Customer Provided Item\" cannot be Purchase Item also" msgstr "" -#: erpnext/stock/doctype/item/item.py:242 +#: erpnext/stock/doctype/item/item.py:259 msgid "\"Customer Provided Item\" cannot have Valuation Rate" msgstr "" -#: erpnext/stock/doctype/item/item.py:317 +#: erpnext/stock/doctype/item/item.py:334 msgid "\"Is Fixed Asset\" cannot be unchecked, as Asset record exists against the item" msgstr "" @@ -262,7 +262,7 @@ msgstr "" msgid "'Account' in the Accounting section of Customer {0}" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.py:348 +#: erpnext/selling/doctype/sales_order/sales_order.py:358 msgid "'Allow Multiple Sales Orders Against a Customer's Purchase Order'" msgstr "" @@ -292,7 +292,7 @@ msgstr "" msgid "'From Date' must be after 'To Date'" msgstr "" -#: erpnext/stock/doctype/item/item.py:400 +#: erpnext/stock/doctype/item/item.py:417 msgid "'Has Serial No' can not be 'Yes' for non-stock item" msgstr "" @@ -597,7 +597,7 @@ msgstr "" msgid "<0" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:532 +#: erpnext/assets/doctype/asset/asset.py:534 msgid "Cannot create asset.

You're trying to create {0} asset(s) from {2} {3}.
However, only {1} item(s) were purchased and {4} asset(s) already exist against {5}." msgstr "" @@ -865,7 +865,7 @@ msgstr "" msgid "Quick Access" msgstr "" -#. Header text in the Accounting Workspace +#. Header text in the Invoicing Workspace #. Header text in the Assets Workspace #. Header text in the Buying Workspace #. Header text in the Manufacturing Workspace @@ -874,7 +874,7 @@ msgstr "" #. Header text in the Selling Workspace #. Header text in the Home Workspace #. Header text in the Support Workspace -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json #: erpnext/assets/workspace/assets/assets.json #: erpnext/buying/workspace/buying/buying.json #: erpnext/manufacturing/workspace/manufacturing/manufacturing.json @@ -969,7 +969,7 @@ msgstr "" msgid "A - C" msgstr "" -#: erpnext/selling/doctype/customer/customer.py:328 +#: erpnext/selling/doctype/customer/customer.py:329 msgid "A Customer Group exists with same name please change the Customer name or rename the Customer Group" msgstr "" @@ -1197,7 +1197,7 @@ msgstr "" msgid "According to CEFACT/ICG/2010/IC013 or CEFACT/ICG/2010/IC010" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:973 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:988 msgid "According to the BOM {0}, the Item '{1}' is missing in the stock entry." msgstr "" @@ -1430,7 +1430,7 @@ msgstr "" msgid "Account is not set for the dashboard chart {0}" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:896 +#: erpnext/assets/doctype/asset/asset.py:898 msgid "Account not Found" msgstr "" @@ -1561,7 +1561,6 @@ msgstr "" #. Item' #. Label of the section_break_10 (Section Break) field in DocType 'Shipping #. Rule' -#. Name of a Workspace #. Label of the accounting_tab (Tab Break) field in DocType 'Supplier' #. Label of the accounting_tab (Tab Break) field in DocType 'Customer' #. Label of a Card Break in the Home Workspace @@ -1572,7 +1571,6 @@ msgstr "" #: erpnext/accounts/doctype/pos_profile/pos_profile.json #: erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json #: erpnext/accounts/doctype/shipping_rule/shipping_rule.json -#: erpnext/accounts/workspace/accounting/accounting.json #: erpnext/buying/doctype/supplier/supplier.json #: erpnext/selling/doctype/customer/customer.json #: erpnext/setup/setup_wizard/data/industry_type.txt:1 @@ -1627,22 +1625,22 @@ msgstr "" #. Dimension Filter' #. Label of the accounting_dimension (Link) field in DocType 'Allowed #. Dimension' -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #: erpnext/accounts/doctype/accounting_dimension/accounting_dimension.json #: erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.json #: erpnext/accounts/doctype/allowed_dimension/allowed_dimension.json #: erpnext/accounts/report/profitability_analysis/profitability_analysis.js:32 -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Accounting Dimension" msgstr "" #: erpnext/accounts/doctype/gl_entry/gl_entry.py:213 -#: erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py:149 +#: erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py:150 msgid "Accounting Dimension {0} is required for 'Balance Sheet' account {1}." msgstr "" #: erpnext/accounts/doctype/gl_entry/gl_entry.py:200 -#: erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py:137 +#: erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py:138 msgid "Accounting Dimension {0} is required for 'Profit and Loss' account {1}." msgstr "" @@ -1820,14 +1818,14 @@ msgstr "" msgid "Accounting Entries" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:930 -#: erpnext/assets/doctype/asset/asset.py:945 +#: erpnext/assets/doctype/asset/asset.py:932 +#: erpnext/assets/doctype/asset/asset.py:947 #: erpnext/assets/doctype/asset_capitalization/asset_capitalization.py:562 msgid "Accounting Entry for Asset" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1944 -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1964 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1959 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1979 msgid "Accounting Entry for LCV in Stock Entry {0}" msgstr "" @@ -1848,11 +1846,11 @@ msgstr "" #: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1231 #: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1467 #: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py:1489 -#: erpnext/controllers/stock_controller.py:683 -#: erpnext/controllers/stock_controller.py:700 +#: erpnext/controllers/stock_controller.py:686 +#: erpnext/controllers/stock_controller.py:703 #: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py:930 -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1889 -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1903 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1904 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1918 #: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:708 msgid "Accounting Entry for Stock" msgstr "" @@ -1875,15 +1873,15 @@ msgstr "" msgid "Accounting Ledger" msgstr "" -#. Label of a Card Break in the Accounting Workspace -#: erpnext/accounts/workspace/accounting/accounting.json +#. Label of a Card Break in the Invoicing Workspace +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Accounting Masters" msgstr "" #. Name of a DocType -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #: erpnext/accounts/doctype/accounting_period/accounting_period.json -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Accounting Period" msgstr "" @@ -2022,10 +2020,10 @@ msgid "Accounts Receivable/Payable" msgstr "" #. Name of a DocType -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #. Label of a shortcut in the ERPNext Settings Workspace #: erpnext/accounts/doctype/accounts_settings/accounts_settings.json -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json #: erpnext/setup/workspace/erpnext_settings/erpnext_settings.json msgid "Accounts Settings" msgstr "" @@ -2447,12 +2445,12 @@ msgstr "" msgid "Actual qty in stock" msgstr "" -#: erpnext/accounts/doctype/payment_entry/payment_entry.js:1532 +#: erpnext/accounts/doctype/payment_entry/payment_entry.js:1542 #: erpnext/public/js/controllers/accounts.js:197 msgid "Actual type tax cannot be included in Item rate in row {0}" msgstr "" -#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1005 +#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1021 msgid "Ad-hoc Qty" msgstr "" @@ -2904,7 +2902,7 @@ msgstr "" msgid "Additional Information updated successfully." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:787 +#: erpnext/manufacturing/doctype/work_order/work_order.js:784 msgid "Additional Material Transfer" msgstr "" @@ -3522,7 +3520,7 @@ msgstr "" #: erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js:165 #: erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.js:185 #: erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py:166 -#: erpnext/accounts/utils.py:1532 erpnext/public/js/setup_wizard.js:184 +#: erpnext/accounts/utils.py:1554 erpnext/public/js/setup_wizard.js:184 msgid "All Accounts" msgstr "" @@ -3679,7 +3677,7 @@ msgstr "" msgid "All communications including and above this shall be moved into the new Issue" msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:967 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:969 msgid "All items are already requested" msgstr "" @@ -3691,7 +3689,7 @@ msgstr "" msgid "All items have already been received" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:3095 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:3110 msgid "All items have already been transferred for this Work Order." msgstr "" @@ -3717,7 +3715,7 @@ msgstr "" msgid "All the items have been already returned." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1171 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1168 msgid "All the required items (raw materials) will be fetched from BOM and populated in this table. Here you can also change the Source Warehouse for any item. And during the production, you can track transferred raw materials from this table." msgstr "" @@ -3740,7 +3738,7 @@ msgstr "" msgid "Allocate Advances Automatically (FIFO)" msgstr "" -#: erpnext/accounts/doctype/payment_entry/payment_entry.js:921 +#: erpnext/accounts/doctype/payment_entry/payment_entry.js:931 msgid "Allocate Payment Amount" msgstr "" @@ -3750,7 +3748,7 @@ msgstr "" msgid "Allocate Payment Based On Payment Terms" msgstr "" -#: erpnext/accounts/doctype/payment_entry/payment_entry.js:1722 +#: erpnext/accounts/doctype/payment_entry/payment_entry.js:1732 msgid "Allocate Payment Request" msgstr "" @@ -3776,7 +3774,7 @@ msgstr "" #. Payment Entries' #: erpnext/accounts/doctype/bank_transaction/bank_transaction.json #: erpnext/accounts/doctype/bank_transaction_payments/bank_transaction_payments.json -#: erpnext/accounts/doctype/payment_entry/payment_entry.js:1713 +#: erpnext/accounts/doctype/payment_entry/payment_entry.js:1723 #: erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json #: erpnext/accounts/doctype/process_payment_reconciliation_log_allocations/process_payment_reconciliation_log_allocations.json #: erpnext/accounts/doctype/purchase_invoice_advance/purchase_invoice_advance.json @@ -3802,11 +3800,11 @@ msgstr "" msgid "Allocated amount" msgstr "" -#: erpnext/accounts/utils.py:647 +#: erpnext/accounts/utils.py:657 msgid "Allocated amount cannot be greater than unadjusted amount" msgstr "" -#: erpnext/accounts/utils.py:645 +#: erpnext/accounts/utils.py:655 msgid "Allocated amount cannot be negative" msgstr "" @@ -3908,7 +3906,7 @@ msgstr "" msgid "Allow Item To Be Added Multiple Times in a Transaction" msgstr "" -#: erpnext/controllers/selling_controller.py:838 +#: erpnext/controllers/selling_controller.py:853 msgid "Allow Item to Be Added Multiple Times in a Transaction" msgstr "" @@ -5080,7 +5078,7 @@ msgstr "" msgid "Are" msgstr "" -#: erpnext/public/js/utils/demo.js:20 +#: erpnext/public/js/utils/demo.js:17 msgid "Are you sure you want to clear all demo data?" msgstr "" @@ -5147,7 +5145,7 @@ msgstr "" msgid "As the field {0} is enabled, the value of the field {1} should be more than 1." msgstr "" -#: erpnext/stock/doctype/item/item.py:989 +#: erpnext/stock/doctype/item/item.py:1006 msgid "As there are existing submitted transactions against item {0}, you can not change the value of {1}." msgstr "" @@ -5155,11 +5153,11 @@ msgstr "" msgid "As there are reserved stock, you cannot disable {0}." msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1087 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1089 msgid "As there are sufficient Sub Assembly Items, Work Order is not required for Warehouse {0}." msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1823 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1825 msgid "As there are sufficient raw materials, Material Request is not required for Warehouse {0}." msgstr "" @@ -5287,7 +5285,7 @@ msgstr "" msgid "Asset Category Name" msgstr "" -#: erpnext/stock/doctype/item/item.py:309 +#: erpnext/stock/doctype/item/item.py:326 msgid "Asset Category is mandatory for Fixed Asset item" msgstr "" @@ -5407,7 +5405,7 @@ msgstr "" msgid "Asset Movement Item" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:1176 +#: erpnext/assets/doctype/asset/asset.py:1178 msgid "Asset Movement record {0} created" msgstr "" @@ -5538,31 +5536,31 @@ msgstr "" msgid "Asset Value Analytics" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:271 +#: erpnext/assets/doctype/asset/asset.py:273 msgid "Asset cancelled" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:725 +#: erpnext/assets/doctype/asset/asset.py:727 msgid "Asset cannot be cancelled, as it is already {0}" msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:391 +#: erpnext/assets/doctype/asset/depreciation.py:393 msgid "Asset cannot be scrapped before the last depreciation entry." msgstr "" -#: erpnext/assets/doctype/asset_capitalization/asset_capitalization.py:612 +#: erpnext/assets/doctype/asset_capitalization/asset_capitalization.py:618 msgid "Asset capitalized after Asset Capitalization {0} was submitted" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:280 +#: erpnext/assets/doctype/asset/asset.py:282 msgid "Asset created" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:1416 +#: erpnext/assets/doctype/asset/asset.py:1418 msgid "Asset created after being split from Asset {0}" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:283 +#: erpnext/assets/doctype/asset/asset.py:285 msgid "Asset deleted" msgstr "" @@ -5578,11 +5576,11 @@ msgstr "" msgid "Asset received at Location {0} and issued to Employee {1}" msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:452 +#: erpnext/assets/doctype/asset/depreciation.py:454 msgid "Asset restored" msgstr "" -#: erpnext/assets/doctype/asset_capitalization/asset_capitalization.py:620 +#: erpnext/assets/doctype/asset_capitalization/asset_capitalization.py:626 msgid "Asset restored after Asset Capitalization {0} was cancelled" msgstr "" @@ -5590,11 +5588,11 @@ msgstr "" msgid "Asset returned" msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:439 +#: erpnext/assets/doctype/asset/depreciation.py:441 msgid "Asset scrapped" msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:441 +#: erpnext/assets/doctype/asset/depreciation.py:443 msgid "Asset scrapped via Journal Entry {0}" msgstr "" @@ -5603,7 +5601,7 @@ msgstr "" msgid "Asset sold" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:258 +#: erpnext/assets/doctype/asset/asset.py:260 msgid "Asset submitted" msgstr "" @@ -5611,7 +5609,7 @@ msgstr "" msgid "Asset transferred to Location {0}" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:1425 +#: erpnext/assets/doctype/asset/asset.py:1427 msgid "Asset updated after being split into Asset {0}" msgstr "" @@ -5619,7 +5617,7 @@ msgstr "" msgid "Asset updated due to Asset Repair {0} {1}." msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:373 +#: erpnext/assets/doctype/asset/depreciation.py:375 msgid "Asset {0} cannot be scrapped, as it is already {1}" msgstr "" @@ -5639,12 +5637,12 @@ msgstr "" msgid "Asset {0} does not belong to the location {1}" msgstr "" -#: erpnext/assets/doctype/asset_capitalization/asset_capitalization.py:672 -#: erpnext/assets/doctype/asset_capitalization/asset_capitalization.py:765 +#: erpnext/assets/doctype/asset_capitalization/asset_capitalization.py:678 +#: erpnext/assets/doctype/asset_capitalization/asset_capitalization.py:771 msgid "Asset {0} does not exist" msgstr "" -#: erpnext/assets/doctype/asset_capitalization/asset_capitalization.py:586 +#: erpnext/assets/doctype/asset_capitalization/asset_capitalization.py:592 msgid "Asset {0} has been updated. Please set the depreciation details if any and submit it." msgstr "" @@ -5660,7 +5658,7 @@ msgstr "" msgid "Asset {0} is not submitted. Please submit the asset before proceeding." msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:371 +#: erpnext/assets/doctype/asset/depreciation.py:373 msgid "Asset {0} must be submitted" msgstr "" @@ -5738,7 +5736,7 @@ msgstr "" msgid "At least one account with exchange gain or loss is required" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:1282 +#: erpnext/assets/doctype/asset/asset.py:1284 msgid "At least one asset has to be selected." msgstr "" @@ -5763,7 +5761,7 @@ msgstr "" msgid "At least one of the Selling or Buying must be selected" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:299 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:314 msgid "At least one raw material item must be present in the stock entry for the type {0}" msgstr "" @@ -5771,11 +5769,11 @@ msgstr "" msgid "At least one row is required for a financial report template" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:805 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:820 msgid "At least one warehouse is mandatory" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:707 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:722 msgid "At row #{0}: the Difference Account must not be a Stock type account, please change the Account Type for the account {1} or select a different account" msgstr "" @@ -5783,7 +5781,7 @@ msgstr "" msgid "At row #{0}: the sequence id {1} cannot be less than previous row sequence id {2}" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:718 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:733 msgid "At row #{0}: you have selected the Difference Account {1}, which is a Cost of Goods Sold type account. Please select a different account" msgstr "" @@ -5803,7 +5801,7 @@ msgstr "" msgid "At row {0}: Serial No is mandatory for Item {1}" msgstr "" -#: erpnext/controllers/stock_controller.py:631 +#: erpnext/controllers/stock_controller.py:634 msgid "At row {0}: Serial and Batch Bundle {1} has already created. Please remove the values from the serial no or batch no fields." msgstr "" @@ -5867,7 +5865,7 @@ msgstr "" msgid "Attribute Value" msgstr "" -#: erpnext/stock/doctype/item/item.py:925 +#: erpnext/stock/doctype/item/item.py:942 msgid "Attribute table is mandatory" msgstr "" @@ -5875,11 +5873,11 @@ msgstr "" msgid "Attribute value: {0} must appear only once" msgstr "" -#: erpnext/stock/doctype/item/item.py:929 +#: erpnext/stock/doctype/item/item.py:946 msgid "Attribute {0} selected multiple times in Attributes Table" msgstr "" -#: erpnext/stock/doctype/item/item.py:857 +#: erpnext/stock/doctype/item/item.py:874 msgid "Attributes" msgstr "" @@ -6262,11 +6260,11 @@ msgstr "" msgid "Available Stock for Packing Items" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:376 +#: erpnext/assets/doctype/asset/asset.py:378 msgid "Available for use date is required" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:938 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:953 msgid "Available quantity is {0}, you need {1}" msgstr "" @@ -6279,7 +6277,7 @@ msgstr "" msgid "Available-for-use Date" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:482 +#: erpnext/assets/doctype/asset/asset.py:484 msgid "Available-for-use Date should be after purchase date" msgstr "" @@ -6415,7 +6413,7 @@ msgstr "" msgid "BOM 1" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1697 +#: erpnext/manufacturing/doctype/bom/bom.py:1700 msgid "BOM 1 {0} and BOM 2 {1} should not be same" msgstr "" @@ -6517,7 +6515,7 @@ msgstr "" #: erpnext/manufacturing/doctype/work_order/work_order.json #: erpnext/manufacturing/report/bom_variance_report/bom_variance_report.js:8 #: erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py:31 -#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1066 +#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1082 #: erpnext/selling/doctype/sales_order_item/sales_order_item.json #: erpnext/stock/doctype/material_request_item/material_request_item.json #: erpnext/stock/doctype/quality_inspection/quality_inspection.json @@ -6641,7 +6639,7 @@ msgstr "" msgid "BOM Website Operation" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:2263 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:2278 msgid "BOM and Finished Good Quantity is mandatory for Disassembly" msgstr "" @@ -6856,7 +6854,7 @@ msgstr "" #. Label of the bank (Read Only) field in DocType 'Payment Entry' #. Label of the company_bank (Link) field in DocType 'Payment Order' #. Label of the bank (Link) field in DocType 'Payment Request' -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #. Option for the 'Salary Mode' (Select) field in DocType 'Employee' #: erpnext/accounts/doctype/account/account.json #: erpnext/accounts/doctype/bank/bank.json @@ -6868,7 +6866,7 @@ msgstr "" #: erpnext/accounts/doctype/payment_order/payment_order.json #: erpnext/accounts/doctype/payment_request/payment_request.json #: erpnext/accounts/report/account_balance/account_balance.js:39 -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json #: erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py:99 #: erpnext/setup/doctype/employee/employee.json msgid "Bank" @@ -6895,7 +6893,7 @@ msgstr "" #. Label of the bank_account (Link) field in DocType 'Journal Entry Account' #. Label of the bank_account (Link) field in DocType 'Payment Order Reference' #. Label of the bank_account (Link) field in DocType 'Payment Request' -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #: erpnext/accounts/doctype/bank_account/bank_account.json #: erpnext/accounts/doctype/bank_clearance/bank_clearance.json #: erpnext/accounts/doctype/bank_guarantee/bank_guarantee.json @@ -6909,7 +6907,7 @@ msgstr "" #: erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.js:21 #: erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.js:16 #: erpnext/accounts/report/cheques_and_deposits_incorrectly_cleared/cheques_and_deposits_incorrectly_cleared.js:16 -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Bank Account" msgstr "" @@ -6977,9 +6975,9 @@ msgid "Bank Charges Account" msgstr "" #. Name of a DocType -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #: erpnext/accounts/doctype/bank_clearance/bank_clearance.json -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Bank Clearance" msgstr "" @@ -7049,17 +7047,17 @@ msgid "Bank Overdraft Account" msgstr "" #. Name of a report -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #: erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.html:1 #: erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.json -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Bank Reconciliation Statement" msgstr "" #. Name of a DocType -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #: erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Bank Reconciliation Tool" msgstr "" @@ -7138,9 +7136,9 @@ msgstr "" #. Label of the banking_section (Section Break) field in DocType 'Accounts #. Settings' -#. Label of a Card Break in the Accounting Workspace +#. Label of a Card Break in the Invoicing Workspace #: erpnext/accounts/doctype/accounts_settings/accounts_settings.json -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json #: erpnext/setup/setup_wizard/data/industry_type.txt:8 msgid "Banking" msgstr "" @@ -7150,11 +7148,11 @@ msgstr "" msgid "Barcode Type" msgstr "" -#: erpnext/stock/doctype/item/item.py:477 +#: erpnext/stock/doctype/item/item.py:494 msgid "Barcode {0} already used in Item {1}" msgstr "" -#: erpnext/stock/doctype/item/item.py:492 +#: erpnext/stock/doctype/item/item.py:509 msgid "Barcode {0} is not a valid {1} code" msgstr "" @@ -7445,7 +7443,7 @@ msgstr "" msgid "Batch Nos are created successfully" msgstr "" -#: erpnext/controllers/sales_and_purchase_return.py:1142 +#: erpnext/controllers/sales_and_purchase_return.py:1165 msgid "Batch Not Available for Return" msgstr "" @@ -7503,16 +7501,16 @@ msgstr "" msgid "Batch {0} and Warehouse" msgstr "" -#: erpnext/controllers/sales_and_purchase_return.py:1141 +#: erpnext/controllers/sales_and_purchase_return.py:1164 msgid "Batch {0} is not available in warehouse {1}" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:3272 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:3287 #: erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py:290 msgid "Batch {0} of Item {1} has expired." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:3278 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:3293 msgid "Batch {0} of Item {1} is disabled." msgstr "" @@ -8141,7 +8139,7 @@ msgstr "" #. Label of the budget_section (Section Break) field in DocType 'Accounts #. Settings' #. Name of a DocType -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #: erpnext/accounts/doctype/accounts_settings/accounts_settings.json #: erpnext/accounts/doctype/budget/budget.json #: erpnext/accounts/doctype/cost_center/cost_center.js:45 @@ -8153,7 +8151,7 @@ msgstr "" #: erpnext/accounts/report/budget_variance_report/budget_variance_report.py:324 #: erpnext/accounts/report/budget_variance_report/budget_variance_report.py:334 #: erpnext/accounts/report/budget_variance_report/budget_variance_report.py:448 -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Budget" msgstr "" @@ -8222,10 +8220,10 @@ msgid "Budget Start Date" msgstr "" #. Name of a report -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #: erpnext/accounts/doctype/cost_center/cost_center_tree.js:77 #: erpnext/accounts/report/budget_variance_report/budget_variance_report.json -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Budget Variance Report" msgstr "" @@ -8715,7 +8713,7 @@ msgstr "" msgid "Can only make payment against unbilled {0}" msgstr "" -#: erpnext/accounts/doctype/payment_entry/payment_entry.js:1504 +#: erpnext/accounts/doctype/payment_entry/payment_entry.js:1514 #: erpnext/controllers/accounts_controller.py:3175 #: erpnext/public/js/controllers/accounts.js:103 msgid "Can refer row only if the charge type is 'On Previous Row Amount' or 'Previous Row Total'" @@ -8771,9 +8769,9 @@ msgstr "" msgid "Cannot Create Return" msgstr "" -#: erpnext/stock/doctype/item/item.py:632 -#: erpnext/stock/doctype/item/item.py:645 -#: erpnext/stock/doctype/item/item.py:659 +#: erpnext/stock/doctype/item/item.py:649 +#: erpnext/stock/doctype/item/item.py:662 +#: erpnext/stock/doctype/item/item.py:676 msgid "Cannot Merge" msgstr "" @@ -8797,7 +8795,7 @@ msgstr "" msgid "Cannot apply TDS against multiple parties in one entry" msgstr "" -#: erpnext/stock/doctype/item/item.py:312 +#: erpnext/stock/doctype/item/item.py:329 msgid "Cannot be a fixed asset item as Stock Ledger is created." msgstr "" @@ -8837,11 +8835,11 @@ msgstr "" msgid "Cannot cancel this document as it is linked with the submitted asset {asset_link}. Please cancel the asset to continue." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:479 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:494 msgid "Cannot cancel transaction for Completed Work Order." msgstr "" -#: erpnext/stock/doctype/item/item.py:877 +#: erpnext/stock/doctype/item/item.py:894 msgid "Cannot change Attributes after stock transaction. Make a new Item and transfer stock to the new Item" msgstr "" @@ -8857,7 +8855,7 @@ msgstr "" msgid "Cannot change Service Stop Date for item in row {0}" msgstr "" -#: erpnext/stock/doctype/item/item.py:868 +#: erpnext/stock/doctype/item/item.py:885 msgid "Cannot change Variant properties after stock transaction. You will have to make a new Item to do this." msgstr "" @@ -8889,7 +8887,7 @@ msgstr "" msgid "Cannot create Stock Reservation Entries for future dated Purchase Receipts." msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.py:1877 +#: erpnext/selling/doctype/sales_order/sales_order.py:1888 #: erpnext/stock/doctype/pick_list/pick_list.py:219 msgid "Cannot create a pick list for Sales Order {0} because it has reserved stock. Please unreserve the stock in order to create a pick list." msgstr "" @@ -8915,7 +8913,7 @@ msgstr "" msgid "Cannot deduct when category is for 'Valuation' or 'Valuation and Total'" msgstr "" -#: erpnext/accounts/doctype/payment_entry/payment_entry.js:1819 +#: erpnext/accounts/doctype/payment_entry/payment_entry.js:1829 msgid "Cannot delete Exchange Gain/Loss row" msgstr "" @@ -8943,8 +8941,8 @@ msgstr "" msgid "Cannot enqueue multi docs for one company. {0} is already queued/running for company: {1}" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.py:771 -#: erpnext/selling/doctype/sales_order/sales_order.py:794 +#: erpnext/selling/doctype/sales_order/sales_order.py:782 +#: erpnext/selling/doctype/sales_order/sales_order.py:805 msgid "Cannot ensure delivery by Serial No as Item {0} is added with and without Ensure Delivery by Serial No." msgstr "" @@ -8988,7 +8986,7 @@ msgstr "" msgid "Cannot reduce quantity than ordered or purchased quantity" msgstr "" -#: erpnext/accounts/doctype/payment_entry/payment_entry.js:1517 +#: erpnext/accounts/doctype/payment_entry/payment_entry.js:1527 #: erpnext/controllers/accounts_controller.py:3190 #: erpnext/public/js/controllers/accounts.js:120 msgid "Cannot refer row number greater than or equal to current row number for this Charge type" @@ -9002,8 +9000,8 @@ msgstr "" msgid "Cannot retrieve link token. Check Error Log for more information" msgstr "" -#: erpnext/accounts/doctype/payment_entry/payment_entry.js:1510 -#: erpnext/accounts/doctype/payment_entry/payment_entry.js:1688 +#: erpnext/accounts/doctype/payment_entry/payment_entry.js:1520 +#: erpnext/accounts/doctype/payment_entry/payment_entry.js:1698 #: erpnext/accounts/doctype/payment_entry/payment_entry.py:1811 #: erpnext/controllers/accounts_controller.py:3180 #: erpnext/public/js/controllers/accounts.js:112 @@ -9019,7 +9017,7 @@ msgstr "" msgid "Cannot set authorization on basis of Discount for {0}" msgstr "" -#: erpnext/stock/doctype/item/item.py:723 +#: erpnext/stock/doctype/item/item.py:740 msgid "Cannot set multiple Item Defaults for a company." msgstr "" @@ -9048,7 +9046,7 @@ msgstr "" #. Label of the capacity_per_day (Int) field in DocType 'Item Lead Time' #. Label of the capacity (Float) field in DocType 'Putaway Rule' -#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:947 +#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:963 #: erpnext/stock/doctype/item_lead_time/item_lead_time.json #: erpnext/stock/doctype/putaway_rule/putaway_rule.json msgid "Capacity" @@ -9195,7 +9193,7 @@ msgstr "" msgid "Cash Flow" msgstr "" -#: erpnext/public/js/financial_statements.js:321 +#: erpnext/public/js/financial_statements.js:343 msgid "Cash Flow Statement" msgstr "" @@ -9310,7 +9308,7 @@ msgstr "" msgid "Category-wise Asset Value" msgstr "" -#: erpnext/buying/doctype/purchase_order/purchase_order.py:293 +#: erpnext/buying/doctype/purchase_order/purchase_order.py:294 #: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:130 msgid "Caution" msgstr "" @@ -9425,7 +9423,7 @@ msgstr "" msgid "Change this date manually to setup the next synchronization start date" msgstr "" -#: erpnext/selling/doctype/customer/customer.py:132 +#: erpnext/selling/doctype/customer/customer.py:133 msgid "Changed customer name to '{}' as '{}' already exists." msgstr "" @@ -9493,13 +9491,13 @@ msgstr "" msgid "Chart Tree" msgstr "" -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #. Label of the section_break_28 (Section Break) field in DocType 'Company' #. Label of a Link in the Home Workspace #: erpnext/accounts/doctype/account/account.js:69 #: erpnext/accounts/doctype/account/account_tree.js:5 #: erpnext/accounts/doctype/cost_center/cost_center_tree.js:52 -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json #: erpnext/public/js/setup_wizard.js:43 #: erpnext/setup/doctype/company/company.js:123 #: erpnext/setup/doctype/company/company.json @@ -9508,18 +9506,18 @@ msgid "Chart of Accounts" msgstr "" #. Name of a DocType -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #. Label of a Link in the Home Workspace #: erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.json -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json #: erpnext/setup/workspace/home/home.json msgid "Chart of Accounts Importer" msgstr "" -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #: erpnext/accounts/doctype/account/account_tree.js:196 #: erpnext/accounts/doctype/cost_center/cost_center.js:41 -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Chart of Cost Centers" msgstr "" @@ -9712,7 +9710,7 @@ msgstr "" msgid "Clauses and Conditions" msgstr "" -#: erpnext/public/js/utils/demo.js:11 +#: erpnext/public/js/utils/demo.js:5 msgid "Clear Demo Data" msgstr "" @@ -9761,7 +9759,7 @@ msgstr "" msgid "Clearance date changed from {0} to {1} via Bank Clearance Tool" msgstr "" -#: erpnext/public/js/utils/demo.js:24 +#: erpnext/public/js/utils/demo.js:21 msgid "Clearing Demo Data..." msgstr "" @@ -9831,7 +9829,7 @@ msgstr "" msgid "Closed Work Order can not be stopped or Re-opened" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.py:525 +#: erpnext/selling/doctype/sales_order/sales_order.py:536 msgid "Closed order cannot be cancelled. Unclose to cancel." msgstr "" @@ -10137,7 +10135,7 @@ msgstr "" #. Label of the company (Link) field in DocType 'Tax Withholding Account' #. Label of the company (Link) field in DocType 'Tax Withholding Entry' #. Label of the company (Link) field in DocType 'Unreconcile Payment' -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #. Option for the 'Asset Owner' (Select) field in DocType 'Asset' #. Label of the company (Link) field in DocType 'Asset' #. Label of the company (Link) field in DocType 'Asset Capitalization' @@ -10334,7 +10332,7 @@ msgstr "" #: erpnext/accounts/report/trial_balance/trial_balance.js:8 #: erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.js:8 #: erpnext/accounts/report/voucher_wise_balance/voucher_wise_balance.js:8 -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json #: erpnext/assets/doctype/asset/asset.json #: erpnext/assets/doctype/asset_capitalization/asset_capitalization.json #: erpnext/assets/doctype/asset_category_account/asset_category_account.json @@ -10400,7 +10398,7 @@ msgstr "" #: erpnext/projects/doctype/timesheet/timesheet.json #: erpnext/projects/report/project_summary/project_summary.js:8 #: erpnext/projects/report/project_wise_stock_tracking/project_wise_stock_tracking.py:44 -#: erpnext/public/js/financial_statements.js:333 +#: erpnext/public/js/financial_statements.js:365 #: erpnext/public/js/purchase_trends_filters.js:8 #: erpnext/public/js/sales_trends_filters.js:51 #: erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.json @@ -10696,7 +10694,7 @@ msgstr "" msgid "Company name not same" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:324 +#: erpnext/assets/doctype/asset/asset.py:326 msgid "Company of asset {0} and purchase document {1} doesn't matches." msgstr "" @@ -10728,7 +10726,7 @@ msgid "Company {0} added multiple times" msgstr "" #: erpnext/accounts/doctype/account/account.py:509 -#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1289 +#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1307 msgid "Company {0} does not exist" msgstr "" @@ -10765,7 +10763,7 @@ msgstr "" #. Label of the competitors (Table MultiSelect) field in DocType 'Opportunity' #. Label of the competitors (Table MultiSelect) field in DocType 'Quotation' #: erpnext/crm/doctype/opportunity/opportunity.json -#: erpnext/public/js/utils/sales_common.js:583 +#: erpnext/public/js/utils/sales_common.js:606 #: erpnext/selling/doctype/quotation/quotation.json msgid "Competitors" msgstr "" @@ -10944,7 +10942,7 @@ msgstr "" msgid "Consider Minimum Order Qty" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:993 +#: erpnext/manufacturing/doctype/work_order/work_order.js:990 msgid "Consider Process Loss" msgstr "" @@ -11464,7 +11462,7 @@ msgstr "" msgid "Conversion Rate" msgstr "" -#: erpnext/stock/doctype/item/item.py:395 +#: erpnext/stock/doctype/item/item.py:412 msgid "Conversion factor for default Unit of Measure must be 1 in row {0}" msgstr "" @@ -11720,7 +11718,7 @@ msgstr "" #: erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json #: erpnext/buying/report/procurement_tracker/procurement_tracker.js:15 #: erpnext/buying/report/procurement_tracker/procurement_tracker.py:32 -#: erpnext/public/js/financial_statements.js:426 +#: erpnext/public/js/financial_statements.js:458 #: erpnext/selling/doctype/sales_order/sales_order.json #: erpnext/selling/doctype/sales_order_item/sales_order_item.json #: erpnext/stock/doctype/delivery_note/delivery_note.json @@ -11740,9 +11738,9 @@ msgid "Cost Center" msgstr "" #. Name of a DocType -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #: erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.json -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Cost Center Allocation" msgstr "" @@ -11768,12 +11766,12 @@ msgstr "" msgid "Cost Center Number" msgstr "" -#. Label of a Card Break in the Accounting Workspace -#: erpnext/accounts/workspace/accounting/accounting.json +#. Label of a Card Break in the Invoicing Workspace +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Cost Center and Budgeting" msgstr "" -#: erpnext/public/js/utils/sales_common.js:517 +#: erpnext/public/js/utils/sales_common.js:540 msgid "Cost Center for Item rows has been updated to {0}" msgstr "" @@ -11802,11 +11800,11 @@ msgstr "" msgid "Cost Center {0} cannot be used for allocation as it is used as main cost center in other allocation record." msgstr "" -#: erpnext/assets/doctype/asset/asset.py:352 +#: erpnext/assets/doctype/asset/asset.py:354 msgid "Cost Center {} doesn't belong to Company {}" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:359 +#: erpnext/assets/doctype/asset/asset.py:361 msgid "Cost Center {} is a group cost center and group cost centers cannot be used in transactions" msgstr "" @@ -11848,7 +11846,7 @@ msgstr "" msgid "Cost of Goods Sold" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:721 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:736 msgid "Cost of Goods Sold Account in Items Table" msgstr "" @@ -11925,7 +11923,7 @@ msgstr "" msgid "Could Not Delete Demo Data" msgstr "" -#: erpnext/selling/doctype/quotation/quotation.py:609 +#: erpnext/selling/doctype/quotation/quotation.py:614 msgid "Could not auto create Customer due to the following missing mandatory field(s):" msgstr "" @@ -12132,7 +12130,7 @@ msgstr "" msgid "Create Payment Entry for Consolidated POS Invoices." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:769 +#: erpnext/manufacturing/doctype/work_order/work_order.js:766 msgid "Create Pick List" msgstr "" @@ -12235,7 +12233,7 @@ msgstr "" msgid "Create a variant with the template image." msgstr "" -#: erpnext/stock/stock_ledger.py:2014 +#: erpnext/stock/stock_ledger.py:2013 msgid "Create an incoming stock transaction for the Item." msgstr "" @@ -12463,7 +12461,7 @@ msgstr "" msgid "Credit Limit" msgstr "" -#: erpnext/selling/doctype/customer/customer.py:602 +#: erpnext/selling/doctype/customer/customer.py:603 msgid "Credit Limit Crossed" msgstr "" @@ -12554,16 +12552,16 @@ msgstr "" msgid "Credit in Company Currency" msgstr "" -#: erpnext/selling/doctype/customer/customer.py:568 -#: erpnext/selling/doctype/customer/customer.py:623 +#: erpnext/selling/doctype/customer/customer.py:569 +#: erpnext/selling/doctype/customer/customer.py:624 msgid "Credit limit has been crossed for customer {0} ({1}/{2})" msgstr "" -#: erpnext/selling/doctype/customer/customer.py:357 +#: erpnext/selling/doctype/customer/customer.py:358 msgid "Credit limit is already defined for the Company {0}" msgstr "" -#: erpnext/selling/doctype/customer/customer.py:622 +#: erpnext/selling/doctype/customer/customer.py:623 msgid "Credit limit reached for customer {0}" msgstr "" @@ -12673,9 +12671,9 @@ msgstr "" msgid "Cup" msgstr "" -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #. Name of a DocType -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json #: erpnext/setup/doctype/currency_exchange/currency_exchange.json msgid "Currency Exchange" msgstr "" @@ -12742,7 +12740,7 @@ msgstr "" #: erpnext/accounts/doctype/payment_entry/payment_entry.py:1588 #: erpnext/accounts/doctype/payment_entry/payment_entry.py:1656 -#: erpnext/accounts/utils.py:2432 +#: erpnext/accounts/utils.py:2455 msgid "Currency for {0} must be {1}" msgstr "" @@ -13562,7 +13560,7 @@ msgid "Customer required for 'Customerwise Discount'" msgstr "" #: erpnext/accounts/doctype/sales_invoice/sales_invoice.py:1142 -#: erpnext/selling/doctype/sales_order/sales_order.py:422 +#: erpnext/selling/doctype/sales_order/sales_order.py:432 #: erpnext/stock/doctype/delivery_note/delivery_note.py:424 msgid "Customer {0} does not belong to project {1}" msgstr "" @@ -14072,7 +14070,7 @@ msgstr "" msgid "Decimeter" msgstr "" -#: erpnext/public/js/utils/sales_common.js:610 +#: erpnext/public/js/utils/sales_common.js:633 msgid "Declare Lost" msgstr "" @@ -14168,7 +14166,7 @@ msgstr "" msgid "Default BOM" msgstr "" -#: erpnext/stock/doctype/item/item.py:438 +#: erpnext/stock/doctype/item/item.py:455 msgid "Default BOM ({0}) must be active for this item or its template" msgstr "" @@ -14525,15 +14523,15 @@ msgstr "" msgid "Default Unit of Measure" msgstr "" -#: erpnext/stock/doctype/item/item.py:1272 +#: erpnext/stock/doctype/item/item.py:1289 msgid "Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You need to either cancel the linked documents or create a new Item." msgstr "" -#: erpnext/stock/doctype/item/item.py:1255 +#: erpnext/stock/doctype/item/item.py:1272 msgid "Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You will need to create a new Item to use a different Default UOM." msgstr "" -#: erpnext/stock/doctype/item/item.py:903 +#: erpnext/stock/doctype/item/item.py:920 msgid "Default Unit of Measure for Variant '{0}' must be same as in Template '{1}'" msgstr "" @@ -14878,7 +14876,7 @@ msgstr "" #. Label of the delivery_date (Date) field in DocType 'Sales Order Item' #: erpnext/manufacturing/doctype/master_production_schedule_item/master_production_schedule_item.json #: erpnext/manufacturing/doctype/sales_forecast_item/sales_forecast_item.json -#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1051 +#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1067 #: erpnext/public/js/utils.js:800 #: erpnext/selling/doctype/delivery_schedule_item/delivery_schedule_item.json #: erpnext/selling/doctype/sales_order/sales_order.js:624 @@ -15065,7 +15063,7 @@ msgstr "" msgid "Delivery to" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.py:441 +#: erpnext/selling/doctype/sales_order/sales_order.py:451 msgid "Delivery warehouse required for stock item {0}" msgstr "" @@ -15080,7 +15078,7 @@ msgstr "" #. Label of the demand_qty (Float) field in DocType 'Sales Forecast Item' #: erpnext/manufacturing/doctype/sales_forecast_item/sales_forecast_item.json -#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:999 +#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1015 msgid "Demand Qty" msgstr "" @@ -15098,7 +15096,7 @@ msgstr "" msgid "Demo Company" msgstr "" -#: erpnext/public/js/utils/demo.js:28 +#: erpnext/public/js/utils/demo.js:25 msgid "Demo data cleared" msgstr "" @@ -15217,11 +15215,11 @@ msgstr "" msgid "Depreciation Entry Posting Status" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:1250 +#: erpnext/assets/doctype/asset/asset.py:1252 msgid "Depreciation Entry against asset {0}" msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:253 +#: erpnext/assets/doctype/asset/depreciation.py:255 msgid "Depreciation Entry against {0} worth {1}" msgstr "" @@ -15233,7 +15231,7 @@ msgstr "" msgid "Depreciation Expense Account" msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:300 +#: erpnext/assets/doctype/asset/depreciation.py:302 msgid "Depreciation Expense Account should be an Income or Expense Account." msgstr "" @@ -15264,11 +15262,11 @@ msgstr "" msgid "Depreciation Posting Date cannot be before Available-for-use Date" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:381 +#: erpnext/assets/doctype/asset/asset.py:383 msgid "Depreciation Row {0}: Depreciation Posting Date cannot be before Available-for-use Date" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:710 +#: erpnext/assets/doctype/asset/asset.py:712 msgid "Depreciation Row {0}: Expected value after useful life must be greater than or equal to {1}" msgstr "" @@ -15295,7 +15293,7 @@ msgstr "" msgid "Depreciation Schedule View" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:476 +#: erpnext/assets/doctype/asset/asset.py:478 msgid "Depreciation cannot be calculated for fully depreciated assets" msgstr "" @@ -15321,7 +15319,7 @@ msgstr "" #. Label of the order_lost_reason (Small Text) field in DocType 'Opportunity' #. Label of the order_lost_reason (Small Text) field in DocType 'Quotation' #: erpnext/crm/doctype/opportunity/opportunity.json -#: erpnext/public/js/utils/sales_common.js:589 +#: erpnext/public/js/utils/sales_common.js:612 #: erpnext/selling/doctype/quotation/quotation.json msgid "Detailed Reason" msgstr "" @@ -15375,11 +15373,11 @@ msgstr "" msgid "Difference Account" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:710 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:725 msgid "Difference Account in Items Table" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:699 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:714 msgid "Difference Account must be a Asset/Liability type account (Temporary Opening), since this Stock Entry is an Opening Entry" msgstr "" @@ -16003,7 +16001,7 @@ msgstr "" msgid "Disposal Date" msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:826 +#: erpnext/assets/doctype/asset/depreciation.py:828 msgid "Disposal date {0} cannot be before {1} date {2} of the asset." msgstr "" @@ -16461,7 +16459,7 @@ msgstr "" msgid "Duplicate Entry. Please check Authorization Rule {0}" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:408 +#: erpnext/assets/doctype/asset/asset.py:410 msgid "Duplicate Finance Book" msgstr "" @@ -16807,7 +16805,7 @@ msgstr "" msgid "Email Sent" msgstr "" -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:355 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:358 msgid "Email Sent to Supplier {0}" msgstr "" @@ -17040,7 +17038,7 @@ msgstr "" msgid "Enable Auto Email" msgstr "" -#: erpnext/stock/doctype/item/item.py:1064 +#: erpnext/stock/doctype/item/item.py:1081 msgid "Enable Auto Re-Order" msgstr "" @@ -17237,7 +17235,7 @@ msgstr "" #: erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.js:64 #: erpnext/accounts/report/financial_ratios/financial_ratios.js:25 #: erpnext/assets/report/fixed_asset_register/fixed_asset_register.js:89 -#: erpnext/public/js/financial_statements.js:395 +#: erpnext/public/js/financial_statements.js:427 msgid "End Year" msgstr "" @@ -17358,7 +17356,7 @@ msgstr "" msgid "Enter date to scrap asset" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:474 +#: erpnext/assets/doctype/asset/asset.py:476 msgid "Enter depreciation details" msgstr "" @@ -17397,7 +17395,7 @@ msgstr "" msgid "Enter the quantity of the Item that will be manufactured from this Bill of Materials." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1133 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1130 msgid "Enter the quantity to manufacture. Raw material Items will be fetched only when this is set." msgstr "" @@ -17474,7 +17472,7 @@ msgstr "" msgid "Error in party matching for Bank Transaction {0}" msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:317 +#: erpnext/assets/doctype/asset/depreciation.py:319 msgid "Error while posting depreciation entries" msgstr "" @@ -17493,7 +17491,7 @@ msgid "" "\t\t\t\t\tPlease correct the dates accordingly." msgstr "" -#: erpnext/accounts/doctype/payment_entry/payment_entry.js:973 +#: erpnext/accounts/doctype/payment_entry/payment_entry.js:983 msgid "Error: {0} is mandatory field" msgstr "" @@ -17539,7 +17537,7 @@ msgstr "" msgid "Example URL" msgstr "" -#: erpnext/stock/doctype/item/item.py:995 +#: erpnext/stock/doctype/item/item.py:1012 msgid "Example of a linked document: {0}" msgstr "" @@ -17555,7 +17553,7 @@ msgstr "" msgid "Example: ABCD.#####. If series is set and Batch No is not mentioned in transactions, then automatic batch number will be created based on this series. If you always want to explicitly mention Batch No for this item, leave this blank. Note: this setting will take priority over the Naming Series Prefix in Stock Settings." msgstr "" -#: erpnext/stock/stock_ledger.py:2280 +#: erpnext/stock/stock_ledger.py:2279 msgid "Example: Serial No {0} reserved in {1}." msgstr "" @@ -17676,12 +17674,12 @@ msgstr "" #. Account' #. Option for the 'Journal Entry Type' (Select) field in DocType 'Journal Entry #. Template' -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #: erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.json #: erpnext/accounts/doctype/journal_entry/journal_entry.json #: erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json #: erpnext/accounts/doctype/journal_entry_template/journal_entry_template.json -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Exchange Rate Revaluation" msgstr "" @@ -17819,7 +17817,7 @@ msgstr "" msgid "Expected Delivery Date" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.py:403 +#: erpnext/selling/doctype/sales_order/sales_order.py:413 msgid "Expected Delivery Date should be after Sales Order Date" msgstr "" @@ -17901,7 +17899,7 @@ msgstr "" msgid "Expense" msgstr "" -#: erpnext/controllers/stock_controller.py:897 +#: erpnext/controllers/stock_controller.py:900 msgid "Expense / Difference account ({0}) must be a 'Profit or Loss' account" msgstr "" @@ -17949,7 +17947,7 @@ msgstr "" msgid "Expense Account" msgstr "" -#: erpnext/controllers/stock_controller.py:877 +#: erpnext/controllers/stock_controller.py:880 msgid "Expense Account Missing" msgstr "" @@ -18330,7 +18328,7 @@ msgstr "" #: erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.js:16 #: erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.js:16 -#: erpnext/public/js/financial_statements.js:347 +#: erpnext/public/js/financial_statements.js:379 msgid "Filter Based On" msgstr "" @@ -18397,7 +18395,7 @@ msgstr "" #. Label of the finance_book (Link) field in DocType 'Process Statement Of #. Accounts' #. Label of the finance_book (Link) field in DocType 'Sales Invoice Item' -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #. Label of the finance_book (Link) field in DocType 'Asset Capitalization' #. Label of the finance_book (Link) field in DocType 'Asset Capitalization #. Asset Item' @@ -18427,7 +18425,7 @@ msgstr "" #: erpnext/accounts/report/general_ledger/general_ledger.js:16 #: erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js:31 #: erpnext/accounts/report/trial_balance/trial_balance.js:71 -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json #: erpnext/assets/doctype/asset_capitalization/asset_capitalization.json #: erpnext/assets/doctype/asset_capitalization_asset_item/asset_capitalization_asset_item.json #: erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json @@ -18435,7 +18433,7 @@ msgstr "" #: erpnext/assets/doctype/asset_shift_allocation/asset_shift_allocation.json #: erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.json #: erpnext/assets/report/fixed_asset_register/fixed_asset_register.js:48 -#: erpnext/public/js/financial_statements.js:341 +#: erpnext/public/js/financial_statements.js:373 msgid "Finance Book" msgstr "" @@ -18510,9 +18508,9 @@ msgstr "" msgid "Financial reports will be generated using GL Entry doctypes (should be enabled if Period Closing Voucher is not posted for all years sequentially or missing) " msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:853 -#: erpnext/manufacturing/doctype/work_order/work_order.js:868 -#: erpnext/manufacturing/doctype/work_order/work_order.js:877 +#: erpnext/manufacturing/doctype/work_order/work_order.js:850 +#: erpnext/manufacturing/doctype/work_order/work_order.js:865 +#: erpnext/manufacturing/doctype/work_order/work_order.js:874 msgid "Finish" msgstr "" @@ -18663,7 +18661,7 @@ msgstr "" msgid "Finished Goods based Operating Cost" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1655 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1670 msgid "Finished Item {0} does not match with Work Order {1}" msgstr "" @@ -18725,7 +18723,7 @@ msgstr "" #. Label of the fiscal_year (Link) field in DocType 'GL Entry' #. Label of the fiscal_year (Link) field in DocType 'Monthly Distribution' #. Label of the fiscal_year (Link) field in DocType 'Period Closing Voucher' -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #. Label of the fiscal_year (Link) field in DocType 'Lower Deduction #. Certificate' #. Label of the fiscal_year (Link) field in DocType 'Target Detail' @@ -18740,7 +18738,7 @@ msgstr "" #: erpnext/accounts/report/profitability_analysis/profitability_analysis.js:38 #: erpnext/accounts/report/trial_balance/trial_balance.js:16 #: erpnext/accounts/report/trial_balance_for_party/trial_balance_for_party.js:16 -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json #: erpnext/manufacturing/report/job_card_summary/job_card_summary.js:16 #: erpnext/public/js/purchase_trends_filters.js:28 #: erpnext/public/js/sales_trends_filters.js:44 @@ -18803,7 +18801,7 @@ msgstr "" #. Capitalization Asset Item' #. Label of the fixed_asset_account (Link) field in DocType 'Asset Category #. Account' -#: erpnext/assets/doctype/asset/asset.py:892 +#: erpnext/assets/doctype/asset/asset.py:894 #: erpnext/assets/doctype/asset_capitalization_asset_item/asset_capitalization_asset_item.json #: erpnext/assets/doctype/asset_category_account/asset_category_account.json msgid "Fixed Asset Account" @@ -18814,7 +18812,7 @@ msgstr "" msgid "Fixed Asset Defaults" msgstr "" -#: erpnext/stock/doctype/item/item.py:306 +#: erpnext/stock/doctype/item/item.py:323 msgid "Fixed Asset Item must be a non-stock item." msgstr "" @@ -18905,7 +18903,7 @@ msgstr "" msgid "Following Material Requests have been raised automatically based on Item's re-order level" msgstr "" -#: erpnext/selling/doctype/customer/customer.py:793 +#: erpnext/selling/doctype/customer/customer.py:794 msgid "Following fields are mandatory to create address:" msgstr "" @@ -18966,7 +18964,7 @@ msgstr "" msgid "For Item" msgstr "" -#: erpnext/controllers/stock_controller.py:1556 +#: erpnext/controllers/stock_controller.py:1559 msgid "For Item {0} cannot be received more than {1} qty against the {2} {3}" msgstr "" @@ -18997,7 +18995,7 @@ msgstr "" msgid "For Production" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:822 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:837 msgid "For Quantity (Manufactured Qty) is mandatory" msgstr "" @@ -19090,7 +19088,7 @@ msgstr "" msgid "For projected and forecast quantities, the system will consider all child warehouses under the selected parent warehouse." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1687 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1702 msgid "For quantity {0} should not be greater than allowed quantity {1}" msgstr "" @@ -19099,12 +19097,12 @@ msgstr "" msgid "For reference" msgstr "" -#: erpnext/accounts/doctype/payment_entry/payment_entry.js:1539 +#: erpnext/accounts/doctype/payment_entry/payment_entry.js:1549 #: erpnext/public/js/controllers/accounts.js:204 msgid "For row {0} in {1}. To include {2} in Item rate, rows {3} must also be included" msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1712 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1714 msgid "For row {0}: Enter Planned Qty" msgstr "" @@ -19123,7 +19121,7 @@ msgstr "" msgid "For the convenience of customers, these codes can be used in print formats like Invoices and Delivery Notes" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:962 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:977 msgid "For the item {0}, the consumed quantity should be {1} according to the BOM {2}." msgstr "" @@ -19136,7 +19134,7 @@ msgstr "" msgid "For the {0}, no stock is available for the return in the warehouse {1}." msgstr "" -#: erpnext/controllers/sales_and_purchase_return.py:1193 +#: erpnext/controllers/sales_and_purchase_return.py:1216 msgid "For the {0}, the quantity is required to make the return entry" msgstr "" @@ -19639,6 +19637,10 @@ msgstr "" msgid "Fulfilment Terms and Conditions" msgstr "" +#: erpnext/stock/doctype/shipment/shipment.js:275 +msgid "Full Name, Email or Phone/Mobile of the user are mandatory to continue." +msgstr "" + #. Option for the 'Reference Type' (Select) field in DocType 'Journal Entry #. Account' #: erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json @@ -19721,7 +19723,7 @@ msgstr "" msgid "Future Payments" msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:380 +#: erpnext/assets/doctype/asset/depreciation.py:382 msgid "Future date is not allowed" msgstr "" @@ -20236,7 +20238,7 @@ msgstr "" msgid "Goods Transferred" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:2204 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:2219 msgid "Goods are already received against the outward entry {0}" msgstr "" @@ -20416,7 +20418,7 @@ msgstr "" msgid "Grant Commission" msgstr "" -#: erpnext/accounts/doctype/payment_entry/payment_entry.js:892 +#: erpnext/accounts/doctype/payment_entry/payment_entry.js:902 msgid "Greater Than Amount" msgstr "" @@ -20621,7 +20623,7 @@ msgstr "" #: erpnext/accounts/report/budget_variance_report/budget_variance_report.js:64 #: erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.js:77 #: erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.js:59 -#: erpnext/public/js/financial_statements.js:408 +#: erpnext/public/js/financial_statements.js:440 #: erpnext/public/js/purchase_trends_filters.js:21 #: erpnext/public/js/sales_trends_filters.js:13 #: erpnext/selling/report/sales_partner_target_variance_based_on_item_group/sales_partner_target_variance_based_on_item_group.js:34 @@ -20826,11 +20828,11 @@ msgstr "" msgid "Helps you distribute the Budget/Target across months if you have seasonality in your business." msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:347 +#: erpnext/assets/doctype/asset/depreciation.py:349 msgid "Here are the error logs for the aforementioned failed depreciation entries: {0}" msgstr "" -#: erpnext/stock/stock_ledger.py:1999 +#: erpnext/stock/stock_ledger.py:1998 msgid "Here are the options to proceed:" msgstr "" @@ -21295,6 +21297,12 @@ msgstr "" msgid "If enabled, system will allow user to deliver the entire quantity of the finished goods produced against the Subcontracting Inward Order. If disabled, system will allow delivery of only the ordered quantity." msgstr "" +#. Description of the 'Set Incoming Rate as Zero for Expired Batch' (Check) +#. field in DocType 'Selling Settings' +#: erpnext/selling/doctype/selling_settings/selling_settings.json +msgid "If enabled, system will set incoming rate as zero for stand-alone credit notes with expired batch item." +msgstr "" + #. Description of the 'Deliver Scrap Items' (Check) field in DocType 'Selling #. Settings' #: erpnext/selling/doctype/selling_settings/selling_settings.json @@ -21399,7 +21407,7 @@ msgstr "" msgid "If no taxes are set, and Taxes and Charges Template is selected, the system will automatically apply the taxes from the chosen template." msgstr "" -#: erpnext/stock/stock_ledger.py:2009 +#: erpnext/stock/stock_ledger.py:2008 msgid "If not, you can Cancel / Submit this entry" msgstr "" @@ -21419,7 +21427,7 @@ msgstr "" msgid "If set, the system does not use the user's Email or the standard outgoing Email account for sending request for quotations." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1166 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1163 msgid "If the BOM results in Scrap material, the Scrap Warehouse needs to be selected." msgstr "" @@ -21428,7 +21436,7 @@ msgstr "" msgid "If the account is frozen, entries are allowed to restricted users." msgstr "" -#: erpnext/stock/stock_ledger.py:2002 +#: erpnext/stock/stock_ledger.py:2001 msgid "If the item is transacting as a Zero Valuation Rate item in this entry, please enable 'Allow Zero Valuation Rate' in the {0} Item table." msgstr "" @@ -21438,7 +21446,7 @@ msgstr "" msgid "If the reorder check is set at the Group warehouse level, the available quantity becomes the sum of the projected quantities of all its child warehouses." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1185 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1182 msgid "If the selected BOM has Operations mentioned in it, the system will fetch all Operations from BOM, these values can be changed." msgstr "" @@ -21525,11 +21533,11 @@ msgstr "" msgid "If you need to reconcile particular transactions against each other, then please select accordingly. If not, all the transactions will be allocated in FIFO order." msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1092 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1094 msgid "If you still want to proceed, please disable 'Skip Available Sub Assembly Items' checkbox." msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1828 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1830 msgid "If you still want to proceed, please enable {0}." msgstr "" @@ -21606,7 +21614,7 @@ msgstr "" msgid "Ignore Existing Ordered Qty" msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1820 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1822 msgid "Ignore Existing Projected Quantity" msgstr "" @@ -21693,6 +21701,10 @@ msgstr "" msgid "Ignores legacy Is Opening field in GL Entry that allows adding opening balance post the system is in use while generating reports" msgstr "" +#: erpnext/stock/doctype/item/item.py:245 +msgid "Image in the description has been removed. To disable this behavior, uncheck \"{0}\" in {1}." +msgstr "" + #: erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts.py:135 #: erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts_with_account_number.py:229 msgid "Impairment" @@ -22205,8 +22217,8 @@ msgstr "" msgid "Income Account" msgstr "" -#. Label of a number card in the Accounting Workspace -#: erpnext/accounts/workspace/accounting/accounting.json +#. Label of a number card in the Invoicing Workspace +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Incoming Bills" msgstr "" @@ -22220,8 +22232,8 @@ msgstr "" msgid "Incoming Call Settings" msgstr "" -#. Label of a number card in the Accounting Workspace -#: erpnext/accounts/workspace/accounting/accounting.json +#. Label of a number card in the Invoicing Workspace +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Incoming Payment" msgstr "" @@ -22263,15 +22275,15 @@ msgstr "" msgid "Incorrect Batch Consumed" msgstr "" -#: erpnext/stock/doctype/item/item.py:534 +#: erpnext/stock/doctype/item/item.py:551 msgid "Incorrect Check in (group) Warehouse for Reorder" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:969 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:984 msgid "Incorrect Component Quantity" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:384 +#: erpnext/assets/doctype/asset/asset.py:386 #: erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py:56 msgid "Incorrect Date" msgstr "" @@ -22468,13 +22480,13 @@ msgstr "" msgid "Inspected By" msgstr "" -#: erpnext/controllers/stock_controller.py:1450 +#: erpnext/controllers/stock_controller.py:1453 msgid "Inspection Rejected" msgstr "" #. Label of the inspection_required (Check) field in DocType 'Stock Entry' -#: erpnext/controllers/stock_controller.py:1420 -#: erpnext/controllers/stock_controller.py:1422 +#: erpnext/controllers/stock_controller.py:1423 +#: erpnext/controllers/stock_controller.py:1425 #: erpnext/stock/doctype/stock_entry/stock_entry.json msgid "Inspection Required" msgstr "" @@ -22491,7 +22503,7 @@ msgstr "" msgid "Inspection Required before Purchase" msgstr "" -#: erpnext/controllers/stock_controller.py:1435 +#: erpnext/controllers/stock_controller.py:1438 msgid "Inspection Submission" msgstr "" @@ -22569,13 +22581,13 @@ msgstr "" #: erpnext/stock/doctype/pick_list/pick_list.py:134 #: erpnext/stock/doctype/pick_list/pick_list.py:152 #: erpnext/stock/doctype/pick_list/pick_list.py:1020 -#: erpnext/stock/doctype/stock_entry/stock_entry.py:942 -#: erpnext/stock/serial_batch_bundle.py:1197 erpnext/stock/stock_ledger.py:1711 -#: erpnext/stock/stock_ledger.py:2171 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:957 +#: erpnext/stock/serial_batch_bundle.py:1197 erpnext/stock/stock_ledger.py:1710 +#: erpnext/stock/stock_ledger.py:2170 msgid "Insufficient Stock" msgstr "" -#: erpnext/stock/stock_ledger.py:2186 +#: erpnext/stock/stock_ledger.py:2185 msgid "Insufficient Stock for Batch" msgstr "" @@ -22736,7 +22748,7 @@ msgstr "" msgid "Internal Customer" msgstr "" -#: erpnext/selling/doctype/customer/customer.py:229 +#: erpnext/selling/doctype/customer/customer.py:230 msgid "Internal Customer for company {0} already exists" msgstr "" @@ -22796,7 +22808,7 @@ msgstr "" msgid "Internal Work History" msgstr "" -#: erpnext/controllers/stock_controller.py:1517 +#: erpnext/controllers/stock_controller.py:1520 msgid "Internal transfers can only be done in company's default currency" msgstr "" @@ -22854,13 +22866,13 @@ msgstr "" msgid "Invalid Company for Inter Company Transaction." msgstr "" -#: erpnext/assets/doctype/asset/asset.py:355 -#: erpnext/assets/doctype/asset/asset.py:362 +#: erpnext/assets/doctype/asset/asset.py:357 +#: erpnext/assets/doctype/asset/asset.py:364 #: erpnext/controllers/accounts_controller.py:3227 msgid "Invalid Cost Center" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.py:405 +#: erpnext/selling/doctype/sales_order/sales_order.py:415 msgid "Invalid Delivery Date" msgstr "" @@ -22890,11 +22902,11 @@ msgid "Invalid Group By" msgstr "" #: erpnext/accounts/doctype/pos_invoice/pos_invoice.py:499 -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:955 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:957 msgid "Invalid Item" msgstr "" -#: erpnext/stock/doctype/item/item.py:1410 +#: erpnext/stock/doctype/item/item.py:1427 msgid "Invalid Item Defaults" msgstr "" @@ -22903,7 +22915,7 @@ msgstr "" msgid "Invalid Ledger Entries" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:556 +#: erpnext/assets/doctype/asset/asset.py:558 msgid "Invalid Net Purchase Amount" msgstr "" @@ -22970,8 +22982,8 @@ msgstr "" msgid "Invalid Sales Invoices" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:647 -#: erpnext/assets/doctype/asset/asset.py:675 +#: erpnext/assets/doctype/asset/asset.py:649 +#: erpnext/assets/doctype/asset/asset.py:677 msgid "Invalid Schedule" msgstr "" @@ -22979,12 +22991,12 @@ msgstr "" msgid "Invalid Selling Price" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1730 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1745 msgid "Invalid Serial and Batch Bundle" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1003 -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1025 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1018 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1040 msgid "Invalid Source and Target Warehouse" msgstr "" @@ -23013,7 +23025,7 @@ msgstr "" msgid "Invalid lost reason {0}, please create a new lost reason" msgstr "" -#: erpnext/stock/doctype/item/item.py:410 +#: erpnext/stock/doctype/item/item.py:427 msgid "Invalid naming series (. missing) for {0}" msgstr "" @@ -23033,8 +23045,8 @@ msgstr "" msgid "Invalid search query" msgstr "" -#: erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py:108 -#: erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py:118 +#: erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py:109 +#: erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py:119 #: erpnext/accounts/general_ledger.py:863 #: erpnext/accounts/general_ledger.py:873 msgid "Invalid value {0} for {1} against account {2}" @@ -23276,6 +23288,11 @@ msgstr "" msgid "Invoices and Payments have been Fetched and Allocated" msgstr "" +#. Name of a Workspace +#: erpnext/accounts/workspace/invoicing/invoicing.json +msgid "Invoicing" +msgstr "" + #. Label of the invoicing_features_section (Section Break) field in DocType #. 'Accounts Settings' #: erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -23926,7 +23943,7 @@ msgstr "" msgid "Issuing Date" msgstr "" -#: erpnext/stock/doctype/item/item.py:591 +#: erpnext/stock/doctype/item/item.py:608 msgid "It can take upto few hours for accurate stock values to be visible after merging items." msgstr "" @@ -24277,8 +24294,8 @@ msgstr "" #: erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.js:75 #: erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py:166 #: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.js:30 -#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:935 -#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:971 +#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:951 +#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:987 #: erpnext/manufacturing/report/production_planning_report/production_planning_report.py:364 #: erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.js:27 #: erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py:86 @@ -24754,8 +24771,8 @@ msgstr "" #: erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py:23 #: erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py:106 #: erpnext/manufacturing/report/job_card_summary/job_card_summary.py:158 -#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:942 -#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:978 +#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:958 +#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:994 #: erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py:153 #: erpnext/manufacturing/report/production_planning_report/production_planning_report.py:371 #: erpnext/manufacturing/report/quality_inspection_summary/quality_inspection_summary.py:92 @@ -24980,7 +24997,7 @@ msgstr "" #. Label of the item_tax_template (Link) field in DocType 'Purchase Invoice #. Item' #. Label of the item_tax_template (Link) field in DocType 'Sales Invoice Item' -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #. Label of the item_tax_template (Link) field in DocType 'Purchase Order Item' #. Label of the item_tax_template (Link) field in DocType 'Supplier Quotation #. Item' @@ -24994,7 +25011,7 @@ msgstr "" #: erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json #: erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json #: erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json #: erpnext/buying/doctype/purchase_order_item/purchase_order_item.json #: erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json #: erpnext/selling/doctype/quotation_item/quotation_item.json @@ -25049,7 +25066,7 @@ msgstr "" msgid "Item Variant {0} already exists with same attributes" msgstr "" -#: erpnext/stock/doctype/item/item.py:773 +#: erpnext/stock/doctype/item/item.py:790 msgid "Item Variants updated" msgstr "" @@ -25138,11 +25155,11 @@ msgstr "" msgid "Item and Warranty Details" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:3251 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:3266 msgid "Item for row {0} does not match Material Request" msgstr "" -#: erpnext/stock/doctype/item/item.py:790 +#: erpnext/stock/doctype/item/item.py:807 msgid "Item has variants." msgstr "" @@ -25172,7 +25189,7 @@ msgstr "" msgid "Item qty can not be updated as raw materials are already processed." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1133 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1148 msgid "Item rate has been updated to zero as Allow Zero Valuation Rate is checked for item {0}" msgstr "" @@ -25194,7 +25211,7 @@ msgstr "" msgid "Item valuation reposting in progress. Report might show incorrect item valuation." msgstr "" -#: erpnext/stock/doctype/item/item.py:947 +#: erpnext/stock/doctype/item/item.py:964 msgid "Item variant {0} exists with same attributes" msgstr "" @@ -25210,8 +25227,8 @@ msgstr "" msgid "Item {0} cannot be ordered more than {1} against Blanket Order {2}." msgstr "" -#: erpnext/assets/doctype/asset/asset.py:337 -#: erpnext/stock/doctype/item/item.py:637 +#: erpnext/assets/doctype/asset/asset.py:339 +#: erpnext/stock/doctype/item/item.py:654 msgid "Item {0} does not exist" msgstr "" @@ -25223,7 +25240,7 @@ msgstr "" msgid "Item {0} does not exist." msgstr "" -#: erpnext/controllers/selling_controller.py:835 +#: erpnext/controllers/selling_controller.py:850 msgid "Item {0} entered multiple times." msgstr "" @@ -25231,15 +25248,15 @@ msgstr "" msgid "Item {0} has already been returned" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:339 +#: erpnext/assets/doctype/asset/asset.py:341 msgid "Item {0} has been disabled" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.py:778 +#: erpnext/selling/doctype/sales_order/sales_order.py:789 msgid "Item {0} has no Serial No. Only serialized items can have delivery based on Serial No" msgstr "" -#: erpnext/stock/doctype/item/item.py:1126 +#: erpnext/stock/doctype/item/item.py:1143 msgid "Item {0} has reached its end of life on {1}" msgstr "" @@ -25251,11 +25268,11 @@ msgstr "" msgid "Item {0} is already reserved/delivered against Sales Order {1}." msgstr "" -#: erpnext/stock/doctype/item/item.py:1146 +#: erpnext/stock/doctype/item/item.py:1163 msgid "Item {0} is cancelled" msgstr "" -#: erpnext/stock/doctype/item/item.py:1130 +#: erpnext/stock/doctype/item/item.py:1147 msgid "Item {0} is disabled" msgstr "" @@ -25263,19 +25280,19 @@ msgstr "" msgid "Item {0} is not a serialized Item" msgstr "" -#: erpnext/stock/doctype/item/item.py:1138 +#: erpnext/stock/doctype/item/item.py:1155 msgid "Item {0} is not a stock Item" msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:954 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:956 msgid "Item {0} is not a subcontracted item" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:2116 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:2131 msgid "Item {0} is not active or end of life has been reached" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:341 +#: erpnext/assets/doctype/asset/asset.py:343 msgid "Item {0} must be a Fixed Asset Item" msgstr "" @@ -25287,11 +25304,11 @@ msgstr "" msgid "Item {0} must be a Sub-contracted Item" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:343 +#: erpnext/assets/doctype/asset/asset.py:345 msgid "Item {0} must be a non-stock item" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1466 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1481 msgid "Item {0} not found in 'Raw Materials Supplied' table in {1} {2}" msgstr "" @@ -25299,7 +25316,7 @@ msgstr "" msgid "Item {0} not found." msgstr "" -#: erpnext/buying/doctype/purchase_order/purchase_order.py:320 +#: erpnext/buying/doctype/purchase_order/purchase_order.py:321 msgid "Item {0}: Ordered qty {1} cannot be less than minimum order qty {2} (defined in Item)." msgstr "" @@ -25362,7 +25379,7 @@ msgstr "" msgid "Items Filter" msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1674 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1676 #: erpnext/selling/doctype/sales_order/sales_order.js:1676 msgid "Items Required" msgstr "" @@ -25395,7 +25412,7 @@ msgstr "" msgid "Items not found." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1129 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1144 msgid "Items rate has been updated to zero as Allow Zero Valuation Rate is checked for the following items: {0}" msgstr "" @@ -25405,7 +25422,7 @@ msgstr "" msgid "Items to Be Repost" msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1673 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1675 msgid "Items to Manufacture are required to pull the Raw Materials associated with it." msgstr "" @@ -25631,7 +25648,7 @@ msgstr "" msgid "Journal Entries" msgstr "" -#: erpnext/accounts/utils.py:1053 +#: erpnext/accounts/utils.py:1063 msgid "Journal Entries {0} are un-linked" msgstr "" @@ -25643,7 +25660,7 @@ msgstr "" #. Template' #. Option for the 'Invoice Type' (Select) field in DocType 'Payment #. Reconciliation Invoice' -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #. Group in Asset's connections #. Label of the journal_entry (Link) field in DocType 'Asset Value Adjustment' #. Label of the journal_entry (Link) field in DocType 'Depreciation Schedule' @@ -25652,7 +25669,7 @@ msgstr "" #: erpnext/accounts/doctype/journal_entry_template/journal_entry_template.json #: erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.json #: erpnext/accounts/print_format/journal_auditing_voucher/journal_auditing_voucher.html:10 -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json #: erpnext/assets/doctype/asset/asset.js:345 #: erpnext/assets/doctype/asset/asset.js:354 #: erpnext/assets/doctype/asset/asset.json @@ -25668,9 +25685,9 @@ msgid "Journal Entry Account" msgstr "" #. Name of a DocType -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #: erpnext/accounts/doctype/journal_entry_template/journal_entry_template.json -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Journal Entry Template" msgstr "" @@ -25950,10 +25967,6 @@ msgstr "" msgid "Last Month Downtime Analysis" msgstr "" -#: erpnext/stock/doctype/shipment/shipment.js:275 -msgid "Last Name, Email or Phone/Mobile of the user are mandatory to continue." -msgstr "" - #: erpnext/selling/report/inactive_customers/inactive_customers.py:81 msgid "Last Order Amount" msgstr "" @@ -26112,7 +26125,7 @@ msgstr "" #. Label of the lead_time (Float) field in DocType 'Work Order' #: erpnext/manufacturing/doctype/master_production_schedule_item/master_production_schedule_item.json #: erpnext/manufacturing/doctype/work_order/work_order.json -#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1056 +#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1072 #: erpnext/stock/doctype/item/item_dashboard.py:35 msgid "Lead Time" msgstr "" @@ -26260,7 +26273,7 @@ msgstr "" msgid "Length (cm)" msgstr "" -#: erpnext/accounts/doctype/payment_entry/payment_entry.js:897 +#: erpnext/accounts/doctype/payment_entry/payment_entry.js:907 msgid "Less Than Amount" msgstr "" @@ -26399,7 +26412,7 @@ msgstr "" msgid "Linked Location" msgstr "" -#: erpnext/stock/doctype/item/item.py:999 +#: erpnext/stock/doctype/item/item.py:1016 msgid "Linked with submitted documents" msgstr "" @@ -26580,7 +26593,7 @@ msgstr "" #. 'Quotation' #: erpnext/crm/doctype/opportunity/opportunity.json #: erpnext/crm/report/lost_opportunity/lost_opportunity.py:49 -#: erpnext/public/js/utils/sales_common.js:573 +#: erpnext/public/js/utils/sales_common.js:596 #: erpnext/selling/doctype/quotation/quotation.json msgid "Lost Reasons" msgstr "" @@ -26601,10 +26614,10 @@ msgstr "" #. Withholding Entry' #. Option for the 'Under Withheld Reason' (Select) field in DocType 'Tax #. Withholding Entry' -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #. Name of a DocType #: erpnext/accounts/doctype/tax_withholding_entry/tax_withholding_entry.json -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json #: erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json msgid "Lower Deduction Certificate" msgstr "" @@ -26998,8 +27011,8 @@ msgstr "" #. Label of the make (Data) field in DocType 'Vehicle' #: erpnext/accounts/doctype/journal_entry/journal_entry.js:123 #: erpnext/manufacturing/doctype/job_card/job_card.js:536 -#: erpnext/manufacturing/doctype/work_order/work_order.js:808 -#: erpnext/manufacturing/doctype/work_order/work_order.js:842 +#: erpnext/manufacturing/doctype/work_order/work_order.js:805 +#: erpnext/manufacturing/doctype/work_order/work_order.js:839 #: erpnext/setup/doctype/vehicle/vehicle.json msgid "Make" msgstr "" @@ -27141,7 +27154,7 @@ msgstr "" msgid "Mandatory For Profit and Loss Account" msgstr "" -#: erpnext/selling/doctype/quotation/quotation.py:613 +#: erpnext/selling/doctype/quotation/quotation.py:618 msgid "Mandatory Missing" msgstr "" @@ -27224,8 +27237,8 @@ msgstr "" #: erpnext/stock/doctype/material_request_item/material_request_item.json #: erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json #: erpnext/stock/doctype/stock_entry/stock_entry.json -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1210 -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1226 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1225 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1241 #: erpnext/stock/doctype/stock_entry_type/stock_entry_type.json #: erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json #: erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json @@ -27369,7 +27382,7 @@ msgstr "" msgid "Manufacturing Manager" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:2369 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:2384 msgid "Manufacturing Quantity is mandatory" msgstr "" @@ -27583,7 +27596,7 @@ msgstr "" msgid "Material" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:833 +#: erpnext/manufacturing/doctype/work_order/work_order.js:830 msgid "Material Consumption" msgstr "" @@ -27591,7 +27604,7 @@ msgstr "" #. Option for the 'Purpose' (Select) field in DocType 'Stock Entry Type' #: erpnext/setup/setup_wizard/operations/install_fixtures.py:114 #: erpnext/stock/doctype/stock_entry/stock_entry.json -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1211 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1226 #: erpnext/stock/doctype/stock_entry_type/stock_entry_type.json msgid "Material Consumption for Manufacture" msgstr "" @@ -27761,7 +27774,7 @@ msgstr "" msgid "Material Request Type" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.py:1823 +#: erpnext/selling/doctype/sales_order/sales_order.py:1834 msgid "Material Request not created, as quantity for Raw Materials already available." msgstr "" @@ -27962,7 +27975,7 @@ msgstr "" msgid "Max discount allowed for item: {0} is {1}%" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:985 +#: erpnext/manufacturing/doctype/work_order/work_order.js:982 #: erpnext/stock/doctype/pick_list/pick_list.js:198 msgid "Max: {0}" msgstr "" @@ -27984,11 +27997,11 @@ msgstr "" msgid "Maximum Payment Amount" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:3854 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:3869 msgid "Maximum Samples - {0} can be retained for Batch {1} and Item {2}." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:3845 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:3860 msgid "Maximum Samples - {0} have already been retained for Batch {1} and Item {2} in Batch {3}." msgstr "" @@ -28043,7 +28056,7 @@ msgstr "" msgid "Megawatt" msgstr "" -#: erpnext/stock/stock_ledger.py:2015 +#: erpnext/stock/stock_ledger.py:2014 msgid "Mention Valuation Rate in the Item master." msgstr "" @@ -28302,7 +28315,7 @@ msgid "Min Grade" msgstr "" #. Label of the min_order_qty (Float) field in DocType 'Material Request Item' -#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1046 +#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1062 #: erpnext/stock/doctype/material_request_item/material_request_item.json msgid "Min Order Qty" msgstr "" @@ -28427,7 +28440,7 @@ msgid "Missing Asset" msgstr "" #: erpnext/accounts/doctype/gl_entry/gl_entry.py:186 -#: erpnext/assets/doctype/asset/asset.py:371 +#: erpnext/assets/doctype/asset/asset.py:373 msgid "Missing Cost Center" msgstr "" @@ -28439,11 +28452,11 @@ msgstr "" msgid "Missing Filters" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:416 +#: erpnext/assets/doctype/asset/asset.py:418 msgid "Missing Finance Book" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1665 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1680 msgid "Missing Finished Good" msgstr "" @@ -28451,7 +28464,7 @@ msgstr "" msgid "Missing Formula" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:976 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:991 msgid "Missing Item" msgstr "" @@ -28515,7 +28528,7 @@ msgstr "" #. Label of the mode_of_payment (Link) field in DocType 'POS Payment Method' #. Label of the mode_of_payment (Link) field in DocType 'Purchase Invoice' #. Label of the mode_of_payment (Link) field in DocType 'Sales Invoice Payment' -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #: erpnext/accounts/doctype/cashier_closing_payments/cashier_closing_payments.json #: erpnext/accounts/doctype/journal_entry/journal_entry.json #: erpnext/accounts/doctype/mode_of_payment/mode_of_payment.json @@ -28538,7 +28551,7 @@ msgstr "" #: erpnext/accounts/report/item_wise_sales_register/item_wise_sales_register.js:35 #: erpnext/accounts/report/purchase_register/purchase_register.js:40 #: erpnext/accounts/report/sales_register/sales_register.js:40 -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json #: erpnext/selling/page/point_of_sale/pos_controller.js:33 msgid "Mode of Payment" msgstr "" @@ -28615,10 +28628,10 @@ msgid "Monthly Completed Work Orders" msgstr "" #. Name of a DocType -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #: erpnext/accounts/doctype/cost_center/cost_center_tree.js:69 #: erpnext/accounts/doctype/monthly_distribution/monthly_distribution.json -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Monthly Distribution" msgstr "" @@ -28701,10 +28714,10 @@ msgstr "" #. Label of the multi_currency (Check) field in DocType 'Journal Entry' #. Label of the multi_currency (Check) field in DocType 'Journal Entry #. Template' -#. Label of a Card Break in the Accounting Workspace +#. Label of a Card Break in the Invoicing Workspace #: erpnext/accounts/doctype/journal_entry/journal_entry.json #: erpnext/accounts/doctype/journal_entry_template/journal_entry_template.json -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Multi Currency" msgstr "" @@ -28712,7 +28725,7 @@ msgstr "" msgid "Multi-level BOM Creator" msgstr "" -#: erpnext/selling/doctype/customer/customer.py:402 +#: erpnext/selling/doctype/customer/customer.py:403 msgid "Multiple Loyalty Programs found for Customer {}. Please select manually." msgstr "" @@ -28742,7 +28755,7 @@ msgstr "" msgid "Multiple fiscal years exist for the date {0}. Please set company in Fiscal Year" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1672 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1687 msgid "Multiple items cannot be marked as finished item" msgstr "" @@ -29026,11 +29039,11 @@ msgstr "" msgid "Net Purchase Amount" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:444 +#: erpnext/assets/doctype/asset/asset.py:446 msgid "Net Purchase Amount is mandatory" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:551 +#: erpnext/assets/doctype/asset/asset.py:553 msgid "Net Purchase Amount should be equal to purchase amount of one single Asset." msgstr "" @@ -29350,7 +29363,7 @@ msgstr "" msgid "New Workplace" msgstr "" -#: erpnext/selling/doctype/customer/customer.py:367 +#: erpnext/selling/doctype/customer/customer.py:368 msgid "New credit limit is less than current outstanding amount for the customer. Credit limit has to be atleast {0}" msgstr "" @@ -29477,11 +29490,11 @@ msgstr "" #: erpnext/accounts/doctype/journal_entry/journal_entry.py:1564 #: erpnext/accounts/doctype/journal_entry/journal_entry.py:1624 #: erpnext/accounts/doctype/journal_entry/journal_entry.py:1638 -#: erpnext/stock/doctype/item/item.py:1371 +#: erpnext/stock/doctype/item/item.py:1388 msgid "No Permission" msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:792 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:794 msgid "No Purchase Orders were created" msgstr "" @@ -29499,7 +29512,7 @@ msgstr "" msgid "No Selection" msgstr "" -#: erpnext/controllers/sales_and_purchase_return.py:928 +#: erpnext/controllers/sales_and_purchase_return.py:951 msgid "No Serial / Batches are available for return" msgstr "" @@ -29535,7 +29548,7 @@ msgstr "" msgid "No Unreconciled Payments found for this party" msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:789 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:791 #: erpnext/subcontracting/doctype/subcontracting_inward_order/subcontracting_inward_order.py:249 msgid "No Work Orders were created" msgstr "" @@ -29545,7 +29558,7 @@ msgstr "" msgid "No accounting entries for the following warehouses" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.py:784 +#: erpnext/selling/doctype/sales_order/sales_order.py:795 msgid "No active BOM found for item {0}. Delivery by Serial No cannot be ensured" msgstr "" @@ -29614,7 +29627,7 @@ msgstr "" msgid "No matches occurred via auto reconciliation" msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1036 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1038 msgid "No material request created" msgstr "" @@ -29924,7 +29937,7 @@ msgstr "" msgid "Not in stock" msgstr "" -#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1283 +#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1301 msgid "Not permitted to make Purchase Orders" msgstr "" @@ -29958,7 +29971,7 @@ msgstr "" msgid "Note: This Cost Center is a Group. Cannot make accounting entries against groups." msgstr "" -#: erpnext/stock/doctype/item/item.py:628 +#: erpnext/stock/doctype/item/item.py:645 msgid "Note: To merge the items, create a separate Stock Reconciliation for the old item {0}" msgstr "" @@ -30233,7 +30246,7 @@ msgstr "" msgid "Oldest Of Invoice Or Advance" msgstr "" -#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1020 +#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1036 msgid "On Hand" msgstr "" @@ -30407,7 +30420,7 @@ msgstr "" msgid "Only one of Deposit or Withdrawal should be non-zero when applying an Excluded Fee." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1225 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1240 msgid "Only one {0} entry can be created against the Work Order {1}" msgstr "" @@ -30647,11 +30660,11 @@ msgid "Opening Invoice Creation In Progress" msgstr "" #. Name of a DocType -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #. Label of a Link in the Home Workspace #: erpnext/accounts/doctype/account/account_tree.js:206 #: erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.json -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json #: erpnext/setup/workspace/home/home.json msgid "Opening Invoice Creation Tool" msgstr "" @@ -30702,7 +30715,7 @@ msgstr "" #. Label of the opening_stock (Float) field in DocType 'Item' #. Option for the 'Purpose' (Select) field in DocType 'Stock Reconciliation' -#: erpnext/stock/doctype/item/item.json erpnext/stock/doctype/item/item.py:301 +#: erpnext/stock/doctype/item/item.json erpnext/stock/doctype/item/item.py:318 #: erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json msgid "Opening Stock" msgstr "" @@ -30716,8 +30729,8 @@ msgstr "" msgid "Opening Value" msgstr "" -#. Label of a Card Break in the Accounting Workspace -#: erpnext/accounts/workspace/accounting/accounting.json +#. Label of a Card Break in the Invoicing Workspace +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Opening and Closing" msgstr "" @@ -30754,7 +30767,7 @@ msgstr "" msgid "Operating Cost Per BOM Quantity" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1617 +#: erpnext/manufacturing/doctype/bom/bom.py:1620 msgid "Operating Cost as per Work Order / BOM" msgstr "" @@ -31149,6 +31162,7 @@ msgstr "" #. Label of the ordered_qty (Float) field in DocType 'Production Plan Item' #. Label of the ordered_qty (Float) field in DocType 'Production Plan Sub #. Assembly Item' +#. Label of the ordered_qty (Float) field in DocType 'Quotation Item' #. Label of the ordered_qty (Float) field in DocType 'Sales Order Item' #. Label of the ordered_qty (Float) field in DocType 'Bin' #. Label of the ordered_qty (Float) field in DocType 'Packed Item' @@ -31158,6 +31172,7 @@ msgstr "" #: erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json #: erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json #: erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py:49 +#: erpnext/selling/doctype/quotation_item/quotation_item.json #: erpnext/selling/doctype/sales_order_item/sales_order_item.json #: erpnext/stock/doctype/bin/bin.json #: erpnext/stock/doctype/packed_item/packed_item.json @@ -31177,7 +31192,7 @@ msgstr "" #: erpnext/buying/doctype/supplier/supplier_dashboard.py:11 #: erpnext/selling/doctype/customer/customer_dashboard.py:20 -#: erpnext/selling/doctype/sales_order/sales_order.py:956 +#: erpnext/selling/doctype/sales_order/sales_order.py:967 #: erpnext/setup/doctype/company/company_dashboard.py:23 msgid "Orders" msgstr "" @@ -31330,13 +31345,13 @@ msgstr "" msgid "Outdated POS Opening Entry" msgstr "" -#. Label of a number card in the Accounting Workspace -#: erpnext/accounts/workspace/accounting/accounting.json +#. Label of a number card in the Invoicing Workspace +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Outgoing Bills" msgstr "" -#. Label of a number card in the Accounting Workspace -#: erpnext/accounts/workspace/accounting/accounting.json +#. Label of a number card in the Invoicing Workspace +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Outgoing Payment" msgstr "" @@ -31378,7 +31393,7 @@ msgstr "" #: erpnext/accounts/doctype/cashier_closing/cashier_closing.json #: erpnext/accounts/doctype/discounted_invoice/discounted_invoice.json #: erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.json -#: erpnext/accounts/doctype/payment_entry/payment_entry.js:889 +#: erpnext/accounts/doctype/payment_entry/payment_entry.js:899 #: erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.json #: erpnext/accounts/doctype/payment_request/payment_request.json #: erpnext/accounts/doctype/pos_invoice/pos_invoice.js:300 @@ -31444,7 +31459,7 @@ msgstr "" msgid "Over Picking Allowance" msgstr "" -#: erpnext/controllers/stock_controller.py:1687 +#: erpnext/controllers/stock_controller.py:1690 msgid "Over Receipt" msgstr "" @@ -31917,7 +31932,7 @@ msgstr "" msgid "Packed Items" msgstr "" -#: erpnext/controllers/stock_controller.py:1521 +#: erpnext/controllers/stock_controller.py:1524 msgid "Packed Items cannot be transferred internally" msgstr "" @@ -32670,7 +32685,7 @@ msgstr "" msgid "Party User" msgstr "" -#: erpnext/accounts/doctype/payment_entry/payment_entry.js:465 +#: erpnext/accounts/doctype/payment_entry/payment_entry.js:475 msgid "Party can only be one of {0}" msgstr "" @@ -32876,7 +32891,7 @@ msgstr "" msgid "Payment Entries" msgstr "" -#: erpnext/accounts/utils.py:1140 +#: erpnext/accounts/utils.py:1150 msgid "Payment Entries {0} are un-linked" msgstr "" @@ -32889,7 +32904,7 @@ msgstr "" #. Name of a DocType #. Option for the 'Payment Order Type' (Select) field in DocType 'Payment #. Order' -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #: erpnext/accounts/doctype/bank_clearance_detail/bank_clearance_detail.json #: erpnext/accounts/doctype/bank_transaction_payments/bank_transaction_payments.json #: erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json @@ -32899,7 +32914,7 @@ msgstr "" #: erpnext/accounts/print_format/bank_and_cash_payment_voucher/bank_and_cash_payment_voucher.html:12 #: erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py:29 #: erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.html:8 -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Payment Entry" msgstr "" @@ -32917,7 +32932,7 @@ msgstr "" msgid "Payment Entry already exists" msgstr "" -#: erpnext/accounts/utils.py:639 +#: erpnext/accounts/utils.py:649 msgid "Payment Entry has been modified after you pulled it. Please pull it again." msgstr "" @@ -32960,7 +32975,7 @@ msgstr "" msgid "Payment Gateway Account" msgstr "" -#: erpnext/accounts/utils.py:1426 +#: erpnext/accounts/utils.py:1436 msgid "Payment Gateway Account not created, please create one manually." msgstr "" @@ -33118,7 +33133,7 @@ msgstr "" #. Reference' #. Name of a DocType #: erpnext/accounts/doctype/accounts_settings/accounts_settings.json -#: erpnext/accounts/doctype/payment_entry/payment_entry.js:1713 +#: erpnext/accounts/doctype/payment_entry/payment_entry.js:1723 #: erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json #: erpnext/accounts/doctype/payment_order/payment_order.js:19 #: erpnext/accounts/doctype/payment_order/payment_order.json @@ -33195,7 +33210,7 @@ msgstr "" #. Name of a DocType #. Label of the payment_term (Link) field in DocType 'Payment Terms Template #. Detail' -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #: erpnext/accounts/doctype/overdue_payment/overdue_payment.json #: erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json #: erpnext/accounts/doctype/payment_schedule/payment_schedule.json @@ -33203,7 +33218,7 @@ msgstr "" #: erpnext/accounts/doctype/payment_terms_template_detail/payment_terms_template_detail.json #: erpnext/accounts/report/accounts_receivable/accounts_receivable.py:1214 #: erpnext/accounts/report/gross_profit/gross_profit.py:438 -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json #: erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py:30 msgid "Payment Term" msgstr "" @@ -33307,7 +33322,7 @@ msgstr "" msgid "Payment URL" msgstr "" -#: erpnext/accounts/utils.py:1128 +#: erpnext/accounts/utils.py:1138 msgid "Payment Unlink Error" msgstr "" @@ -33354,7 +33369,7 @@ msgstr "" #. Label of the payments_section (Section Break) field in DocType 'Sales #. Invoice' #. Label of the payments_tab (Tab Break) field in DocType 'Sales Invoice' -#. Label of a Card Break in the Accounting Workspace +#. Label of a Card Break in the Invoicing Workspace #. Option for the 'Hold Type' (Select) field in DocType 'Supplier' #: erpnext/accounts/doctype/cashier_closing/cashier_closing.json #: erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json @@ -33364,7 +33379,7 @@ msgstr "" #: erpnext/accounts/doctype/sales_invoice/sales_invoice.json #: erpnext/accounts/report/sales_payment_summary/sales_payment_summary.py:27 #: erpnext/accounts/report/sales_payment_summary/sales_payment_summary.py:43 -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json #: erpnext/buying/doctype/supplier/supplier.json #: erpnext/buying/doctype/supplier/supplier_dashboard.py:12 #: erpnext/selling/doctype/customer/customer_dashboard.py:21 @@ -33589,10 +33604,10 @@ msgstr "" #. Label of the period_closing_voucher (Link) field in DocType 'Account Closing #. Balance' #. Name of a DocType -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #: erpnext/accounts/doctype/account_closing_balance/account_closing_balance.json #: erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.json -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Period Closing Voucher" msgstr "" @@ -33715,7 +33730,7 @@ msgstr "" #: erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.json #: erpnext/maintenance/doctype/maintenance_schedule_item/maintenance_schedule_item.json #: erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.js:54 -#: erpnext/public/js/financial_statements.js:403 +#: erpnext/public/js/financial_statements.js:435 msgid "Periodicity" msgstr "" @@ -33969,9 +33984,9 @@ msgstr "" msgid "Plaid Secret" msgstr "" -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #. Name of a DocType -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json #: erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json msgid "Plaid Settings" msgstr "" @@ -34040,7 +34055,7 @@ msgstr "" msgid "Planned Operating Cost" msgstr "" -#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1026 +#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1042 msgid "Planned Purchase Order" msgstr "" @@ -34050,7 +34065,7 @@ msgstr "" #. Label of the planned_qty (Float) field in DocType 'Bin' #: erpnext/manufacturing/doctype/master_production_schedule_item/master_production_schedule_item.json #: erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json -#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1014 +#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1030 #: erpnext/stock/doctype/bin/bin.json #: erpnext/stock/report/stock_projected_qty/stock_projected_qty.py:148 msgid "Planned Qty" @@ -34081,7 +34096,7 @@ msgstr "" msgid "Planned Start Time" msgstr "" -#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1031 +#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1047 msgid "Planned Work Order" msgstr "" @@ -34152,7 +34167,7 @@ msgstr "" msgid "Please Set Supplier Group in Buying Settings." msgstr "" -#: erpnext/accounts/doctype/payment_entry/payment_entry.js:1884 +#: erpnext/accounts/doctype/payment_entry/payment_entry.js:1894 msgid "Please Specify Account" msgstr "" @@ -34200,7 +34215,7 @@ msgstr "" msgid "Please add {1} role to user {0}." msgstr "" -#: erpnext/controllers/stock_controller.py:1698 +#: erpnext/controllers/stock_controller.py:1701 msgid "Please adjust the qty or edit {0} to proceed." msgstr "" @@ -34212,7 +34227,7 @@ msgstr "" msgid "Please cancel and amend the Payment Entry" msgstr "" -#: erpnext/accounts/utils.py:1127 +#: erpnext/accounts/utils.py:1137 msgid "Please cancel payment entry manually first" msgstr "" @@ -34222,7 +34237,7 @@ msgid "Please cancel related transaction." msgstr "" #: erpnext/assets/doctype/asset/asset.js:85 -#: erpnext/assets/doctype/asset/asset.py:247 +#: erpnext/assets/doctype/asset/asset.py:249 msgid "Please capitalize this asset before submitting." msgstr "" @@ -34263,7 +34278,7 @@ msgstr "" msgid "Please click on 'Generate Schedule' to get schedule" msgstr "" -#: erpnext/selling/doctype/customer/customer.py:594 +#: erpnext/selling/doctype/customer/customer.py:595 msgid "Please contact any of the following users to extend the credit limits for {0}: {1}" msgstr "" @@ -34271,7 +34286,7 @@ msgstr "" msgid "Please contact any of the following users to {} this transaction." msgstr "" -#: erpnext/selling/doctype/customer/customer.py:587 +#: erpnext/selling/doctype/customer/customer.py:588 msgid "Please contact your administrator to extend the credit limits for {0}." msgstr "" @@ -34279,7 +34294,7 @@ msgstr "" msgid "Please convert the parent account in corresponding child company to a group account." msgstr "" -#: erpnext/selling/doctype/quotation/quotation.py:611 +#: erpnext/selling/doctype/quotation/quotation.py:616 msgid "Please create Customer from Lead {0}." msgstr "" @@ -34295,19 +34310,19 @@ msgstr "" msgid "Please create purchase from internal sale or delivery document itself" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:454 +#: erpnext/assets/doctype/asset/asset.py:456 msgid "Please create purchase receipt or purchase invoice for the item {0}" msgstr "" -#: erpnext/stock/doctype/item/item.py:656 +#: erpnext/stock/doctype/item/item.py:673 msgid "Please delete Product Bundle {0}, before merging {1} into {2}" msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:554 +#: erpnext/assets/doctype/asset/depreciation.py:556 msgid "Please disable workflow temporarily for Journal Entry {0}" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:555 +#: erpnext/assets/doctype/asset/asset.py:557 msgid "Please do not book expense of multiple assets against one single Asset." msgstr "" @@ -34335,7 +34350,7 @@ msgstr "" msgid "Please enable {0} in the {1}." msgstr "" -#: erpnext/controllers/selling_controller.py:837 +#: erpnext/controllers/selling_controller.py:852 msgid "Please enable {} in {} to allow same item in multiple rows" msgstr "" @@ -34355,7 +34370,7 @@ msgstr "" msgid "Please ensure {} account {} is a Receivable account." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:685 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:700 msgid "Please enter Difference Account or set default Stock Adjustment Account for company {0}" msgstr "" @@ -34376,7 +34391,7 @@ msgstr "" msgid "Please enter Cost Center" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.py:409 +#: erpnext/selling/doctype/sales_order/sales_order.py:419 msgid "Please enter Delivery Date" msgstr "" @@ -34535,7 +34550,7 @@ msgid "Please fill the Sales Orders table" msgstr "" #: erpnext/stock/doctype/shipment/shipment.js:277 -msgid "Please first set Last Name, Email and Phone for the user" +msgid "Please first set Full Name, Email and Phone for the user" msgstr "" #: erpnext/telephony/doctype/incoming_call_settings/incoming_call_settings.js:94 @@ -34609,11 +34624,11 @@ msgid "Please select Template Type to download template" msgstr "" #: erpnext/controllers/taxes_and_totals.py:804 -#: erpnext/public/js/controllers/taxes_and_totals.js:781 +#: erpnext/public/js/controllers/taxes_and_totals.js:782 msgid "Please select Apply Discount On" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.py:1771 +#: erpnext/selling/doctype/sales_order/sales_order.py:1782 msgid "Please select BOM against item {0}" msgstr "" @@ -34633,7 +34648,7 @@ msgstr "" msgid "Please select Category first" msgstr "" -#: erpnext/accounts/doctype/payment_entry/payment_entry.js:1495 +#: erpnext/accounts/doctype/payment_entry/payment_entry.js:1505 #: erpnext/public/js/controllers/accounts.js:94 #: erpnext/public/js/controllers/accounts.js:145 msgid "Please select Charge Type first" @@ -34693,7 +34708,7 @@ msgstr "" msgid "Please select Periodic Accounting Entry Difference Account" msgstr "" -#: erpnext/accounts/doctype/payment_entry/payment_entry.js:507 +#: erpnext/accounts/doctype/payment_entry/payment_entry.js:517 msgid "Please select Posting Date before selecting Party" msgstr "" @@ -34705,11 +34720,11 @@ msgstr "" msgid "Please select Price List" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.py:1773 +#: erpnext/selling/doctype/sales_order/sales_order.py:1784 msgid "Please select Qty against item {0}" msgstr "" -#: erpnext/stock/doctype/item/item.py:322 +#: erpnext/stock/doctype/item/item.py:339 msgid "Please select Sample Retention Warehouse in Stock Settings first" msgstr "" @@ -34725,7 +34740,7 @@ msgstr "" msgid "Please select Stock Asset Account" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1589 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1604 msgid "Please select Subcontracting Order instead of Purchase Order {0}" msgstr "" @@ -34920,7 +34935,7 @@ msgstr "" msgid "Please select weekly off day" msgstr "" -#: erpnext/accounts/doctype/payment_entry/payment_entry.js:1212 +#: erpnext/accounts/doctype/payment_entry/payment_entry.js:1222 #: erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py:616 msgid "Please select {0} first" msgstr "" @@ -34929,11 +34944,11 @@ msgstr "" msgid "Please set 'Apply Additional Discount On'" msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:781 +#: erpnext/assets/doctype/asset/depreciation.py:783 msgid "Please set 'Asset Depreciation Cost Center' in Company {0}" msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:779 +#: erpnext/assets/doctype/asset/depreciation.py:781 msgid "Please set 'Gain/Loss Account on Asset Disposal' in Company {0}" msgstr "" @@ -34975,7 +34990,7 @@ msgstr "" msgid "Please set Customer Address to determine if the transaction is an export." msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:743 +#: erpnext/assets/doctype/asset/depreciation.py:745 msgid "Please set Depreciation related Accounts in Asset Category {0} or Company {1}" msgstr "" @@ -34993,7 +35008,7 @@ msgstr "" msgid "Please set Fiscal Code for the public administration '%s'" msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:729 +#: erpnext/assets/doctype/asset/depreciation.py:731 msgid "Please set Fixed Asset Account in Asset Category {0}" msgstr "" @@ -35035,7 +35050,7 @@ msgstr "" msgid "Please set a Company" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:368 +#: erpnext/assets/doctype/asset/asset.py:370 msgid "Please set a Cost Center for the Asset or set an Asset Depreciation Cost Center for the Company {}" msgstr "" @@ -35060,7 +35075,7 @@ msgstr "" msgid "Please set an Address on the Company '%s'" msgstr "" -#: erpnext/controllers/stock_controller.py:872 +#: erpnext/controllers/stock_controller.py:875 msgid "Please set an Expense Account in the Items table" msgstr "" @@ -35092,7 +35107,7 @@ msgstr "" msgid "Please set default Cash or Bank account in Mode of Payments {}" msgstr "" -#: erpnext/accounts/utils.py:2427 +#: erpnext/accounts/utils.py:2450 msgid "Please set default Exchange Gain/Loss Account in Company {}" msgstr "" @@ -35104,7 +35119,7 @@ msgstr "" msgid "Please set default UOM in Stock Settings" msgstr "" -#: erpnext/controllers/stock_controller.py:731 +#: erpnext/controllers/stock_controller.py:734 msgid "Please set default cost of goods sold account in company {0} for booking rounding gain and loss during stock transfer" msgstr "" @@ -35113,7 +35128,7 @@ msgid "Please set default inventory account for item {0}, or their item group or msgstr "" #: erpnext/accounts/doctype/payment_entry/payment_entry.py:278 -#: erpnext/accounts/utils.py:1149 +#: erpnext/accounts/utils.py:1159 msgid "Please set default {0} in Company {1}" msgstr "" @@ -35125,7 +35140,7 @@ msgstr "" msgid "Please set one of the following:" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:638 +#: erpnext/assets/doctype/asset/asset.py:640 msgid "Please set opening number of booked depreciations" msgstr "" @@ -35196,7 +35211,7 @@ msgstr "" msgid "Please setup and enable a group account with the Account Type - {0} for the company {1}" msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:352 +#: erpnext/assets/doctype/asset/depreciation.py:354 msgid "Please share this email with your support team so that they can find and fix the issue." msgstr "" @@ -35380,7 +35395,7 @@ msgstr "" #: erpnext/accounts/doctype/journal_entry/journal_entry.json #: erpnext/accounts/doctype/loyalty_point_entry/loyalty_point_entry.json #: erpnext/accounts/doctype/opening_invoice_creation_tool_item/opening_invoice_creation_tool_item.json -#: erpnext/accounts/doctype/payment_entry/payment_entry.js:876 +#: erpnext/accounts/doctype/payment_entry/payment_entry.js:886 #: erpnext/accounts/doctype/payment_entry/payment_entry.json #: erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.json #: erpnext/accounts/doctype/payment_order/payment_order.json @@ -35518,7 +35533,7 @@ msgstr "" msgid "Posting Time" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:2317 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:2332 msgid "Posting date and posting time is mandatory" msgstr "" @@ -36679,13 +36694,13 @@ msgstr "" #. Option for the 'Report Type' (Select) field in DocType 'Account' #. Option for the 'Report Type' (Select) field in DocType 'Process Period #. Closing Voucher Detail' -#. Label of a chart in the Accounting Workspace #. Label of a chart in the Financial Reports Workspace +#. Label of a chart in the Invoicing Workspace #: erpnext/accounts/doctype/account/account.json #: erpnext/accounts/doctype/process_period_closing_voucher_detail/process_period_closing_voucher_detail.json -#: erpnext/accounts/workspace/accounting/accounting.json #: erpnext/accounts/workspace/financial_reports/financial_reports.json -#: erpnext/public/js/financial_statements.js:316 +#: erpnext/accounts/workspace/invoicing/invoicing.json +#: erpnext/public/js/financial_statements.js:327 msgid "Profit and Loss" msgstr "" @@ -37245,7 +37260,7 @@ msgstr "" msgid "Purchase Invoice Trends" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:330 +#: erpnext/assets/doctype/asset/asset.py:332 msgid "Purchase Invoice cannot be made against an existing asset {0}" msgstr "" @@ -37400,7 +37415,7 @@ msgstr "" msgid "Purchase Order number required for Item {0}" msgstr "" -#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1343 +#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1361 msgid "Purchase Order {0} created" msgstr "" @@ -37408,7 +37423,7 @@ msgstr "" msgid "Purchase Order {0} is not submitted" msgstr "" -#: erpnext/buying/doctype/purchase_order/purchase_order.py:878 +#: erpnext/buying/doctype/purchase_order/purchase_order.py:879 msgid "Purchase Orders" msgstr "" @@ -37423,7 +37438,7 @@ msgstr "" msgid "Purchase Orders Items Overdue" msgstr "" -#: erpnext/buying/doctype/purchase_order/purchase_order.py:281 +#: erpnext/buying/doctype/purchase_order/purchase_order.py:282 msgid "Purchase Orders are not allowed for {0} due to a scorecard standing of {1}." msgstr "" @@ -37590,7 +37605,7 @@ msgstr "" #. Label of the taxes_and_charges (Link) field in DocType 'Purchase Invoice' #. Name of a DocType #. Label of the purchase_tax_template (Link) field in DocType 'Subscription' -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #. Label of the taxes_and_charges (Link) field in DocType 'Purchase Order' #. Label of the taxes_and_charges (Link) field in DocType 'Supplier Quotation' #. Label of a Link in the Buying Workspace @@ -37599,7 +37614,7 @@ msgstr "" #: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json #: erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.json #: erpnext/accounts/doctype/subscription/subscription.json -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json #: erpnext/buying/doctype/purchase_order/purchase_order.json #: erpnext/buying/doctype/supplier_quotation/supplier_quotation.json #: erpnext/buying/workspace/buying/buying.json @@ -37665,7 +37680,7 @@ msgstr "" msgid "Purpose" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:499 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:514 msgid "Purpose must be one of {0}" msgstr "" @@ -37911,7 +37926,7 @@ msgstr "" msgid "Qty for which recursion isn't applicable." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:983 +#: erpnext/manufacturing/doctype/work_order/work_order.js:980 msgid "Qty for {0}" msgstr "" @@ -38439,7 +38454,7 @@ msgstr "" msgid "Quantity must be greater than zero, and less or equal to {0}" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1013 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1010 #: erpnext/stock/doctype/pick_list/pick_list.js:204 msgid "Quantity must not be more than {0}" msgstr "" @@ -38615,11 +38630,11 @@ msgstr "" msgid "Quotation Trends" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.py:473 +#: erpnext/selling/doctype/sales_order/sales_order.py:483 msgid "Quotation {0} is cancelled" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.py:386 +#: erpnext/selling/doctype/sales_order/sales_order.py:396 msgid "Quotation {0} not of type {1}" msgstr "" @@ -39098,7 +39113,7 @@ msgstr "" msgid "Raw Materials Consumption" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:302 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:317 msgid "Raw Materials Missing" msgstr "" @@ -39154,7 +39169,7 @@ msgstr "" #: erpnext/buying/doctype/purchase_order/purchase_order.js:369 #: erpnext/manufacturing/doctype/production_plan/production_plan.js:124 -#: erpnext/manufacturing/doctype/work_order/work_order.js:736 +#: erpnext/manufacturing/doctype/work_order/work_order.js:733 #: erpnext/selling/doctype/sales_order/sales_order.js:968 #: erpnext/selling/doctype/sales_order/sales_order_list.js:70 #: erpnext/stock/doctype/material_request/material_request.js:231 @@ -39963,7 +39978,7 @@ msgstr "" #: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js:310 #: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json #: erpnext/buying/doctype/supplier/supplier.json -#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1061 +#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1077 msgid "Release Date" msgstr "" @@ -40452,9 +40467,8 @@ msgstr "" #. Label of a Link in the Buying Workspace #: erpnext/buying/doctype/buying_settings/buying_settings.json #: erpnext/buying/doctype/request_for_quotation/request_for_quotation.json -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:312 -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:338 -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:410 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:311 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:413 #: erpnext/buying/doctype/supplier_quotation/supplier_quotation.js:88 #: erpnext/buying/doctype/supplier_quotation_item/supplier_quotation_item.json #: erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js:70 @@ -40597,7 +40611,7 @@ msgstr "" #: erpnext/manufacturing/report/bom_stock_report/bom_stock_report.html:21 #: erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py:28 #: erpnext/manufacturing/report/bom_variance_report/bom_variance_report.py:58 -#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1041 +#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1057 #: erpnext/manufacturing/report/production_planning_report/production_planning_report.py:426 #: erpnext/manufacturing/report/work_order_consumed_materials/work_order_consumed_materials.py:139 #: erpnext/subcontracting/doctype/subcontracting_inward_order_received_item/subcontracting_inward_order_received_item.json @@ -40677,7 +40691,7 @@ msgstr "" msgid "Reservation Based On" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:894 +#: erpnext/manufacturing/doctype/work_order/work_order.js:891 #: erpnext/selling/doctype/sales_order/sales_order.js:92 #: erpnext/stock/doctype/pick_list/pick_list.js:148 #: erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js:180 @@ -40722,7 +40736,7 @@ msgstr "" msgid "Reserved" msgstr "" -#: erpnext/controllers/stock_controller.py:1279 +#: erpnext/controllers/stock_controller.py:1282 msgid "Reserved Batch Conflict" msgstr "" @@ -40786,14 +40800,14 @@ msgstr "" msgid "Reserved Quantity for Production" msgstr "" -#: erpnext/stock/stock_ledger.py:2286 +#: erpnext/stock/stock_ledger.py:2285 msgid "Reserved Serial No." msgstr "" #. Label of the reserved_stock (Float) field in DocType 'Bin' #. Name of a report #: erpnext/manufacturing/doctype/plant_floor/stock_summary_template.html:24 -#: erpnext/manufacturing/doctype/work_order/work_order.js:910 +#: erpnext/manufacturing/doctype/work_order/work_order.js:907 #: erpnext/public/js/stock_reservation.js:236 #: erpnext/selling/doctype/sales_order/sales_order.js:120 #: erpnext/selling/doctype/sales_order/sales_order.js:457 @@ -40802,13 +40816,13 @@ msgstr "" #: erpnext/stock/doctype/pick_list/pick_list.js:168 #: erpnext/stock/report/reserved_stock/reserved_stock.json #: erpnext/stock/report/stock_balance/stock_balance.py:497 -#: erpnext/stock/stock_ledger.py:2270 +#: erpnext/stock/stock_ledger.py:2269 #: erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js:205 #: erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js:333 msgid "Reserved Stock" msgstr "" -#: erpnext/stock/stock_ledger.py:2315 +#: erpnext/stock/stock_ledger.py:2314 msgid "Reserved Stock for Batch" msgstr "" @@ -41659,8 +41673,8 @@ msgstr "" msgid "Rounding Loss Allowance should be between 0 and 1" msgstr "" -#: erpnext/controllers/stock_controller.py:743 -#: erpnext/controllers/stock_controller.py:758 +#: erpnext/controllers/stock_controller.py:746 +#: erpnext/controllers/stock_controller.py:761 msgid "Rounding gain/loss Entry for Stock Transfer" msgstr "" @@ -41715,7 +41729,7 @@ msgstr "" msgid "Row #{0} (Payment Table): Amount must be positive" msgstr "" -#: erpnext/stock/doctype/item/item.py:515 +#: erpnext/stock/doctype/item/item.py:532 msgid "Row #{0}: A reorder entry already exists for warehouse {1} with reorder type {2}." msgstr "" @@ -41765,11 +41779,11 @@ msgstr "" msgid "Row #{0}: Asset {1} is already sold" msgstr "" -#: erpnext/buying/doctype/purchase_order/purchase_order.py:329 +#: erpnext/buying/doctype/purchase_order/purchase_order.py:330 msgid "Row #{0}: BOM is not specified for subcontracting item {0}" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.py:288 +#: erpnext/selling/doctype/sales_order/sales_order.py:298 msgid "Row #{0}: BOM not found for FG Item {1}" msgstr "" @@ -41904,11 +41918,11 @@ msgstr "" msgid "Row #{0}: Dates overlapping with other row in group {1}" msgstr "" -#: erpnext/buying/doctype/purchase_order/purchase_order.py:353 +#: erpnext/buying/doctype/purchase_order/purchase_order.py:354 msgid "Row #{0}: Default BOM not found for FG Item {1}" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:674 +#: erpnext/assets/doctype/asset/asset.py:676 msgid "Row #{0}: Depreciation Start Date is required" msgstr "" @@ -41916,11 +41930,11 @@ msgstr "" msgid "Row #{0}: Duplicate entry in References {1} {2}" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.py:318 +#: erpnext/selling/doctype/sales_order/sales_order.py:328 msgid "Row #{0}: Expected Delivery Date cannot be before Purchase Order Date" msgstr "" -#: erpnext/controllers/stock_controller.py:874 +#: erpnext/controllers/stock_controller.py:877 msgid "Row #{0}: Expense Account not set for the Item {1}. {2}" msgstr "" @@ -41928,22 +41942,22 @@ msgstr "" msgid "Row #{0}: Expense account {1} is not valid for Purchase Invoice {2}. Only expense accounts from non-stock items are allowed." msgstr "" -#: erpnext/buying/doctype/purchase_order/purchase_order.py:358 -#: erpnext/selling/doctype/sales_order/sales_order.py:291 +#: erpnext/buying/doctype/purchase_order/purchase_order.py:359 +#: erpnext/selling/doctype/sales_order/sales_order.py:301 msgid "Row #{0}: Finished Good Item Qty can not be zero" msgstr "" -#: erpnext/buying/doctype/purchase_order/purchase_order.py:340 -#: erpnext/selling/doctype/sales_order/sales_order.py:271 +#: erpnext/buying/doctype/purchase_order/purchase_order.py:341 +#: erpnext/selling/doctype/sales_order/sales_order.py:281 msgid "Row #{0}: Finished Good Item is not specified for service item {1}" msgstr "" -#: erpnext/buying/doctype/purchase_order/purchase_order.py:347 -#: erpnext/selling/doctype/sales_order/sales_order.py:278 +#: erpnext/buying/doctype/purchase_order/purchase_order.py:348 +#: erpnext/selling/doctype/sales_order/sales_order.py:288 msgid "Row #{0}: Finished Good Item {1} must be a sub-contracted item" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:455 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:470 msgid "Row #{0}: Finished Good must be {1}" msgstr "" @@ -41964,7 +41978,7 @@ msgstr "" msgid "Row #{0}: For {1}, you can select reference document only if account gets debited" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:657 +#: erpnext/assets/doctype/asset/asset.py:659 msgid "Row #{0}: Frequency of Depreciation must be greater than zero" msgstr "" @@ -41980,7 +41994,7 @@ msgstr "" msgid "Row #{0}: Item added" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1520 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1535 msgid "Row #{0}: Item {1} cannot be transferred more than {2} against {3} {4}" msgstr "" @@ -42037,15 +42051,15 @@ msgstr "" msgid "Row #{0}: Journal Entry {1} does not have account {2} or already matched against another voucher" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:668 +#: erpnext/assets/doctype/asset/asset.py:670 msgid "Row #{0}: Next Depreciation Date cannot be before Available-for-use Date" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:663 +#: erpnext/assets/doctype/asset/asset.py:665 msgid "Row #{0}: Next Depreciation Date cannot be before Purchase Date" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.py:657 +#: erpnext/selling/doctype/sales_order/sales_order.py:668 msgid "Row #{0}: Not allowed to change Supplier as Purchase Order already exists" msgstr "" @@ -42053,11 +42067,11 @@ msgstr "" msgid "Row #{0}: Only {1} available to reserve for the Item {2}" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:631 +#: erpnext/assets/doctype/asset/asset.py:633 msgid "Row #{0}: Opening Accumulated Depreciation must be less than or equal to {1}" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:854 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:869 msgid "Row #{0}: Operation {1} is not completed for {2} qty of finished goods in Work Order {3}. Please update operation status via Job Card {4}." msgstr "" @@ -42066,11 +42080,11 @@ msgstr "" msgid "Row #{0}: Overconsumption of Customer Provided Item {1} against Work Order {2} is not allowed in the Subcontracting Inward process." msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1051 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1053 msgid "Row #{0}: Please select Item Code in Assembly Items" msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1054 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1056 msgid "Row #{0}: Please select the BOM No in Assembly Items" msgstr "" @@ -42078,11 +42092,11 @@ msgstr "" msgid "Row #{0}: Please select the Finished Good Item against which this Customer Provided Item will be used." msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1048 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1050 msgid "Row #{0}: Please select the Sub Assembly Warehouse" msgstr "" -#: erpnext/stock/doctype/item/item.py:522 +#: erpnext/stock/doctype/item/item.py:539 msgid "Row #{0}: Please set reorder quantity" msgstr "" @@ -42103,15 +42117,15 @@ msgstr "" msgid "Row #{0}: Qty should be less than or equal to Available Qty to Reserve (Actual Qty - Reserved Qty) {1} for Iem {2} against Batch {3} in Warehouse {4}." msgstr "" -#: erpnext/controllers/stock_controller.py:1416 +#: erpnext/controllers/stock_controller.py:1419 msgid "Row #{0}: Quality Inspection is required for Item {1}" msgstr "" -#: erpnext/controllers/stock_controller.py:1431 +#: erpnext/controllers/stock_controller.py:1434 msgid "Row #{0}: Quality Inspection {1} is not submitted for the item: {2}" msgstr "" -#: erpnext/controllers/stock_controller.py:1446 +#: erpnext/controllers/stock_controller.py:1449 msgid "Row #{0}: Quality Inspection {1} was rejected for item {2}" msgstr "" @@ -42139,11 +42153,11 @@ msgstr "" msgid "Row #{0}: Rate must be same as {1}: {2} ({3} / {4})" msgstr "" -#: erpnext/accounts/doctype/payment_entry/payment_entry.js:1244 +#: erpnext/accounts/doctype/payment_entry/payment_entry.js:1254 msgid "Row #{0}: Reference Document Type must be one of Purchase Order, Purchase Invoice or Journal Entry" msgstr "" -#: erpnext/accounts/doctype/payment_entry/payment_entry.js:1230 +#: erpnext/accounts/doctype/payment_entry/payment_entry.js:1240 msgid "Row #{0}: Reference Document Type must be one of Sales Order, Sales Invoice, Journal Entry or Dunning" msgstr "" @@ -42215,11 +42229,11 @@ msgstr "" msgid "Row #{0}: Service Start and End Date is required for deferred accounting" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.py:481 +#: erpnext/selling/doctype/sales_order/sales_order.py:491 msgid "Row #{0}: Set Supplier for item {1}" msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1058 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1060 msgid "Row #{0}: Since 'Track Semi Finished Goods' is enabled, the BOM {1} cannot be used for Sub Assembly Items" msgstr "" @@ -42235,11 +42249,11 @@ msgstr "" msgid "Row #{0}: Source Warehouse {1} for item {2} must be same as Source Warehouse {3} in the Work Order." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1000 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1015 msgid "Row #{0}: Source and Target Warehouse cannot be the same for Material Transfer" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1022 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1037 msgid "Row #{0}: Source, Target Warehouse and Inventory Dimensions cannot be the exact same for Material Transfer" msgstr "" @@ -42300,7 +42314,7 @@ msgstr "" msgid "Row #{0}: The batch {1} has already expired." msgstr "" -#: erpnext/stock/doctype/item/item.py:531 +#: erpnext/stock/doctype/item/item.py:548 msgid "Row #{0}: The warehouse {1} is not a child warehouse of a group warehouse {2}" msgstr "" @@ -42308,11 +42322,11 @@ msgstr "" msgid "Row #{0}: Timings conflicts with row {1}" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:644 +#: erpnext/assets/doctype/asset/asset.py:646 msgid "Row #{0}: Total Number of Depreciations cannot be less than or equal to Opening Number of Booked Depreciations" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:653 +#: erpnext/assets/doctype/asset/asset.py:655 msgid "Row #{0}: Total Number of Depreciations must be greater than zero" msgstr "" @@ -42388,7 +42402,7 @@ msgstr "" msgid "Row #{}: Currency of {} - {} doesn't matches company currency." msgstr "" -#: erpnext/assets/doctype/asset/asset.py:415 +#: erpnext/assets/doctype/asset/asset.py:417 msgid "Row #{}: Finance Book should not be empty since you're using multiple." msgstr "" @@ -42408,7 +42422,7 @@ msgstr "" msgid "Row #{}: Please assign task to a member." msgstr "" -#: erpnext/assets/doctype/asset/asset.py:407 +#: erpnext/assets/doctype/asset/asset.py:409 msgid "Row #{}: Please use a different Finance Book." msgstr "" @@ -42437,7 +42451,7 @@ msgstr "" msgid "Row #{}: {} {} does not exist." msgstr "" -#: erpnext/stock/doctype/item/item.py:1403 +#: erpnext/stock/doctype/item/item.py:1420 msgid "Row #{}: {} {} doesn't belong to Company {}. Please select valid {}." msgstr "" @@ -42453,7 +42467,7 @@ msgstr "" msgid "Row {0} picked quantity is less than the required quantity, additional {1} {2} required." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1544 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1559 msgid "Row {0}# Item {1} not found in 'Raw Materials Supplied' table in {2} {3}" msgstr "" @@ -42485,7 +42499,7 @@ msgstr "" msgid "Row {0}: Allocated amount {1} must be less than or equal to remaining payment amount {2}" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1205 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1220 msgid "Row {0}: As {1} is enabled, raw materials cannot be added to {2} entry. Use {3} entry to consume raw materials." msgstr "" @@ -42527,7 +42541,7 @@ msgstr "" msgid "Row {0}: Debit entry can not be linked with a {1}" msgstr "" -#: erpnext/controllers/selling_controller.py:859 +#: erpnext/controllers/selling_controller.py:874 msgid "Row {0}: Delivery Warehouse ({1}) and Customer Warehouse ({2}) can not be same" msgstr "" @@ -42548,11 +42562,11 @@ msgstr "" msgid "Row {0}: Exchange Rate is mandatory" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:602 +#: erpnext/assets/doctype/asset/asset.py:604 msgid "Row {0}: Expected Value After Useful Life cannot be negative" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:605 +#: erpnext/assets/doctype/asset/asset.py:607 msgid "Row {0}: Expected Value After Useful Life must be less than Net Purchase Amount" msgstr "" @@ -42581,7 +42595,7 @@ msgstr "" msgid "Row {0}: From Time and To Time of {1} is overlapping with {2}" msgstr "" -#: erpnext/controllers/stock_controller.py:1512 +#: erpnext/controllers/stock_controller.py:1515 msgid "Row {0}: From Warehouse is mandatory for internal transfers" msgstr "" @@ -42601,7 +42615,7 @@ msgstr "" msgid "Row {0}: Item Tax template updated as per validity and rate applied" msgstr "" -#: erpnext/controllers/selling_controller.py:624 +#: erpnext/controllers/selling_controller.py:639 msgid "Row {0}: Item rate has been updated as per valuation rate since its an internal stock transfer" msgstr "" @@ -42689,7 +42703,7 @@ msgstr "" msgid "Row {0}: Qty cannot be greater than {1} for the Item {2}." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:546 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:561 msgid "Row {0}: Qty in Stock UOM can not be zero." msgstr "" @@ -42701,7 +42715,7 @@ msgstr "" msgid "Row {0}: Quantity cannot be negative." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:928 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:943 msgid "Row {0}: Quantity not available for {4} in warehouse {1} at posting time of the entry ({2} {3})" msgstr "" @@ -42713,11 +42727,11 @@ msgstr "" msgid "Row {0}: Shift cannot be changed since the depreciation has already been processed" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1557 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1572 msgid "Row {0}: Subcontracted Item is mandatory for the raw material {1}" msgstr "" -#: erpnext/controllers/stock_controller.py:1503 +#: erpnext/controllers/stock_controller.py:1506 msgid "Row {0}: Target Warehouse is mandatory for internal transfers" msgstr "" @@ -42729,7 +42743,7 @@ msgstr "" msgid "Row {0}: The entire expense amount for account {1} in {2} has already been allocated." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:592 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:607 msgid "Row {0}: The item {1}, quantity must be positive number" msgstr "" @@ -42741,11 +42755,11 @@ msgstr "" msgid "Row {0}: To set {1} periodicity, difference between from and to date must be greater than or equal to {2}" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:3346 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:3361 msgid "Row {0}: Transferred quantity cannot be greater than the requested quantity." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:540 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:555 msgid "Row {0}: UOM Conversion Factor is mandatory" msgstr "" @@ -42891,7 +42905,7 @@ msgstr "" msgid "SLA Paused On" msgstr "" -#: erpnext/public/js/utils.js:1168 +#: erpnext/public/js/utils.js:1160 msgid "SLA is on hold since {0}" msgstr "" @@ -42937,7 +42951,7 @@ msgstr "" #. Item' #. Label of the safety_stock (Float) field in DocType 'Item' #: erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json -#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1036 +#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1052 #: erpnext/stock/doctype/item/item.json #: erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py:58 msgid "Safety Stock" @@ -43179,7 +43193,7 @@ msgstr "" msgid "Sales Invoice {0} has already been submitted" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.py:576 +#: erpnext/selling/doctype/sales_order/sales_order.py:587 msgid "Sales Invoice {0} must be deleted before cancelling this Sales Order" msgstr "" @@ -43364,7 +43378,7 @@ msgstr "" msgid "Sales Order required for Item {0}" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.py:342 +#: erpnext/selling/doctype/sales_order/sales_order.py:352 msgid "Sales Order {0} already exists against Customer's Purchase Order {1}. To allow multiple Sales Orders, Enable {2} in {3}" msgstr "" @@ -43667,7 +43681,7 @@ msgstr "" #. Label of the taxes_and_charges (Link) field in DocType 'Sales Invoice' #. Name of a DocType #. Label of the sales_tax_template (Link) field in DocType 'Subscription' -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #. Label of the taxes_and_charges (Link) field in DocType 'Quotation' #. Label of the taxes_and_charges (Link) field in DocType 'Sales Order' #. Label of a Link in the Selling Workspace @@ -43677,7 +43691,7 @@ msgstr "" #: erpnext/accounts/doctype/sales_invoice/sales_invoice.json #: erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json #: erpnext/accounts/doctype/subscription/subscription.json -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json #: erpnext/selling/doctype/quotation/quotation.json #: erpnext/selling/doctype/sales_order/sales_order.json #: erpnext/selling/workspace/selling/selling.json @@ -43785,7 +43799,7 @@ msgstr "" msgid "Sample Size" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:3836 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:3851 msgid "Sample quantity {0} cannot be more than received quantity {1}" msgstr "" @@ -44042,7 +44056,7 @@ msgstr "" msgid "Scrap Warehouse" msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:382 +#: erpnext/assets/doctype/asset/depreciation.py:384 msgid "Scrap date cannot be before purchase date" msgstr "" @@ -44289,7 +44303,7 @@ msgstr "" msgid "Select Possible Supplier" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1019 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1016 #: erpnext/stock/doctype/pick_list/pick_list.js:214 msgid "Select Quantity" msgstr "" @@ -44427,7 +44441,7 @@ msgstr "" msgid "Select the Default Workstation where the Operation will be performed. This will be fetched in BOMs and Work Orders." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1121 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1118 msgid "Select the Item to be manufactured." msgstr "" @@ -44808,7 +44822,7 @@ msgstr "" msgid "Serial No Reserved" msgstr "" -#: erpnext/stock/doctype/item/item.py:428 +#: erpnext/stock/doctype/item/item.py:445 msgid "Serial No Series Overlap" msgstr "" @@ -44942,7 +44956,7 @@ msgstr "" msgid "Serial Nos are created successfully" msgstr "" -#: erpnext/stock/stock_ledger.py:2276 +#: erpnext/stock/stock_ledger.py:2275 msgid "Serial Nos are reserved in Stock Reservation Entries, you need to unreserve them before proceeding." msgstr "" @@ -45418,6 +45432,7 @@ msgstr "" #. Label of the set_basic_rate_manually (Check) field in DocType 'Stock Entry #. Detail' +#: erpnext/stock/doctype/stock_entry/stock_entry.py:297 #: erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json msgid "Set Basic Rate Manually" msgstr "" @@ -45452,6 +45467,12 @@ msgstr "" msgid "Set Grand Total to Default Payment Method" msgstr "" +#. Label of the set_zero_rate_for_expired_batch (Check) field in DocType +#. 'Selling Settings' +#: erpnext/selling/doctype/selling_settings/selling_settings.json +msgid "Set Incoming Rate as Zero for Expired Batch" +msgstr "" + #. Description of the 'Territory Targets' (Section Break) field in DocType #. 'Territory' #: erpnext/setup/doctype/territory/territory.json @@ -45529,7 +45550,7 @@ msgstr "" #. Label of the set_warehouse (Link) field in DocType 'Sales Order' #. Label of the set_warehouse (Link) field in DocType 'Delivery Note' #. Label of the set_from_warehouse (Link) field in DocType 'Material Request' -#: erpnext/public/js/utils/sales_common.js:545 +#: erpnext/public/js/utils/sales_common.js:568 #: erpnext/selling/doctype/sales_order/sales_order.json #: erpnext/stock/doctype/delivery_note/delivery_note.json #: erpnext/stock/doctype/material_request/material_request.json @@ -45547,7 +45568,7 @@ msgstr "" #. Label of the set_warehouse (Link) field in DocType 'Subcontracting Order' #: erpnext/accounts/doctype/sales_invoice/sales_invoice.json #: erpnext/buying/doctype/purchase_order/purchase_order.json -#: erpnext/public/js/utils/sales_common.js:542 +#: erpnext/public/js/utils/sales_common.js:565 #: erpnext/stock/doctype/delivery_note/delivery_note.json #: erpnext/stock/doctype/material_request/material_request.json #: erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.json @@ -45579,7 +45600,7 @@ msgstr "" msgid "Set as Completed" msgstr "" -#: erpnext/public/js/utils/sales_common.js:569 +#: erpnext/public/js/utils/sales_common.js:592 #: erpnext/selling/doctype/quotation/quotation.js:145 msgid "Set as Lost" msgstr "" @@ -45632,7 +45653,7 @@ msgstr "" msgid "Set targets Item Group-wise for this Sales Person." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1178 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1175 msgid "Set the Planned Start Date (an Estimated Date at which you want the Production to begin)" msgstr "" @@ -45646,15 +45667,15 @@ msgstr "" msgid "Set this if the customer is a Public Administration company." msgstr "" -#: erpnext/assets/doctype/asset/asset.py:891 +#: erpnext/assets/doctype/asset/asset.py:893 msgid "Set {0} in asset category {1} for company {2}" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:1224 +#: erpnext/assets/doctype/asset/asset.py:1226 msgid "Set {0} in asset category {1} or company {2}" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:1221 +#: erpnext/assets/doctype/asset/asset.py:1223 msgid "Set {0} in company {1}" msgstr "" @@ -45750,33 +45771,33 @@ msgstr "" #. Label of the section_break_3 (Section Break) field in DocType 'Shareholder' #. Label of the share_balance (Table) field in DocType 'Shareholder' #. Name of a report -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #: erpnext/accounts/doctype/share_balance/share_balance.json #: erpnext/accounts/doctype/shareholder/shareholder.js:21 #: erpnext/accounts/doctype/shareholder/shareholder.json #: erpnext/accounts/report/share_balance/share_balance.json -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Share Balance" msgstr "" #. Name of a report -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #: erpnext/accounts/doctype/shareholder/shareholder.js:27 #: erpnext/accounts/report/share_ledger/share_ledger.json -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Share Ledger" msgstr "" -#. Label of a Card Break in the Accounting Workspace -#: erpnext/accounts/workspace/accounting/accounting.json +#. Label of a Card Break in the Invoicing Workspace +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Share Management" msgstr "" #. Name of a DocType -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #: erpnext/accounts/doctype/share_transfer/share_transfer.json #: erpnext/accounts/report/share_ledger/share_ledger.py:59 -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Share Transfer" msgstr "" @@ -45792,13 +45813,13 @@ msgid "Share Type" msgstr "" #. Name of a DocType -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #: erpnext/accounts/doctype/shareholder/shareholder.json #: erpnext/accounts/report/share_balance/share_balance.js:16 #: erpnext/accounts/report/share_balance/share_balance.py:57 #: erpnext/accounts/report/share_ledger/share_ledger.js:16 #: erpnext/accounts/report/share_ledger/share_ledger.py:51 -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Shareholder" msgstr "" @@ -46381,7 +46402,7 @@ msgstr "" msgid "Simultaneous" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:673 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:688 msgid "Since there is a process loss of {0} units for the finished good {1}, you should reduce the quantity by {0} units for the finished good {1} in the Items Table." msgstr "" @@ -46567,7 +46588,7 @@ msgstr "" #: erpnext/manufacturing/doctype/work_order_item/work_order_item.json #: erpnext/manufacturing/doctype/work_order_operation/work_order_operation.json #: erpnext/manufacturing/report/work_order_stock_report/work_order_stock_report.py:126 -#: erpnext/public/js/utils/sales_common.js:541 +#: erpnext/public/js/utils/sales_common.js:564 #: erpnext/stock/dashboard/item_dashboard.js:227 #: erpnext/stock/doctype/material_request_item/material_request_item.json #: erpnext/stock/doctype/stock_entry/stock_entry.js:699 @@ -46598,7 +46619,7 @@ msgstr "" msgid "Source and Target Location cannot be same" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:802 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:817 msgid "Source and target warehouse cannot be same for row {0}" msgstr "" @@ -46611,9 +46632,9 @@ msgstr "" msgid "Source of Funds (Liabilities)" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:768 -#: erpnext/stock/doctype/stock_entry/stock_entry.py:785 -#: erpnext/stock/doctype/stock_entry/stock_entry.py:792 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:783 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:800 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:807 msgid "Source warehouse is mandatory for row {0}" msgstr "" @@ -46686,7 +46707,7 @@ msgstr "" msgid "Split Qty" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:1363 +#: erpnext/assets/doctype/asset/asset.py:1365 msgid "Split Quantity must be less than Asset Quantity" msgstr "" @@ -46767,7 +46788,7 @@ msgstr "" #: erpnext/setup/setup_wizard/operations/defaults_setup.py:70 #: erpnext/setup/setup_wizard/operations/install_fixtures.py:484 -#: erpnext/stock/doctype/item/item.py:250 +#: erpnext/stock/doctype/item/item.py:267 msgid "Standard Selling" msgstr "" @@ -46852,7 +46873,7 @@ msgstr "" #: erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.js:56 #: erpnext/accounts/report/financial_ratios/financial_ratios.js:17 #: erpnext/assets/report/fixed_asset_register/fixed_asset_register.js:81 -#: erpnext/public/js/financial_statements.js:387 +#: erpnext/public/js/financial_statements.js:419 msgid "Start Year" msgstr "" @@ -47059,7 +47080,7 @@ msgstr "" msgid "Stock Details" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:896 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:911 msgid "Stock Entries already created for Work Order {0}: {1}" msgstr "" @@ -47313,7 +47334,7 @@ msgstr "" #. Option for the 'Purpose' (Select) field in DocType 'Stock Reconciliation' #. Label of a Link in the Stock Workspace #: erpnext/setup/workspace/home/home.json -#: erpnext/stock/doctype/item/item.py:619 +#: erpnext/stock/doctype/item/item.py:636 #: erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json #: erpnext/stock/workspace/stock/stock.json msgid "Stock Reconciliation" @@ -47324,7 +47345,7 @@ msgstr "" msgid "Stock Reconciliation Item" msgstr "" -#: erpnext/stock/doctype/item/item.py:619 +#: erpnext/stock/doctype/item/item.py:636 msgid "Stock Reconciliations" msgstr "" @@ -47346,9 +47367,9 @@ msgstr "" #: erpnext/manufacturing/doctype/production_plan/production_plan.js:289 #: erpnext/manufacturing/doctype/production_plan/production_plan.js:297 #: erpnext/manufacturing/doctype/production_plan/production_plan.js:303 -#: erpnext/manufacturing/doctype/work_order/work_order.js:896 -#: erpnext/manufacturing/doctype/work_order/work_order.js:905 -#: erpnext/manufacturing/doctype/work_order/work_order.js:912 +#: erpnext/manufacturing/doctype/work_order/work_order.js:893 +#: erpnext/manufacturing/doctype/work_order/work_order.js:902 +#: erpnext/manufacturing/doctype/work_order/work_order.js:909 #: erpnext/manufacturing/doctype/work_order/work_order_dashboard.py:14 #: erpnext/public/js/stock_reservation.js:12 #: erpnext/selling/doctype/sales_order/sales_order.js:94 @@ -47385,7 +47406,7 @@ msgid "Stock Reservation Entries Cancelled" msgstr "" #: erpnext/controllers/subcontracting_inward_controller.py:1003 -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:2232 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:2234 #: erpnext/manufacturing/doctype/work_order/work_order.py:2065 #: erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py:1777 msgid "Stock Reservation Entries Created" @@ -47450,7 +47471,7 @@ msgstr "" #. Label of a shortcut in the ERPNext Settings Workspace #. Name of a DocType #. Label of a Link in the Stock Workspace -#: erpnext/selling/doctype/selling_settings/selling_settings.py:93 +#: erpnext/selling/doctype/selling_settings/selling_settings.py:94 #: erpnext/setup/doctype/company/company.json #: erpnext/setup/workspace/erpnext_settings/erpnext_settings.json #: erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py:675 @@ -47747,7 +47768,7 @@ msgstr "" #: erpnext/setup/doctype/company/company.py:384 #: erpnext/setup/setup_wizard/operations/defaults_setup.py:33 #: erpnext/setup/setup_wizard/operations/install_fixtures.py:528 -#: erpnext/stock/doctype/item/item.py:287 +#: erpnext/stock/doctype/item/item.py:304 msgid "Stores" msgstr "" @@ -48069,7 +48090,7 @@ msgstr "" msgid "Subcontracting Order Supplied Item" msgstr "" -#: erpnext/buying/doctype/purchase_order/purchase_order.py:915 +#: erpnext/buying/doctype/purchase_order/purchase_order.py:916 msgid "Subcontracting Order {0} created." msgstr "" @@ -48151,7 +48172,7 @@ msgstr "" msgid "Subdivision" msgstr "" -#: erpnext/buying/doctype/purchase_order/purchase_order.py:911 +#: erpnext/buying/doctype/purchase_order/purchase_order.py:912 #: erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py:1047 msgid "Submit Action Failed" msgstr "" @@ -48188,14 +48209,14 @@ msgstr "" #. Invoice' #. Label of the subscription (Link) field in DocType 'Sales Invoice' #. Name of a DocType -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #: erpnext/accounts/doctype/process_subscription/process_subscription.json #: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json #: erpnext/accounts/doctype/purchase_invoice/purchase_invoice_dashboard.py:26 #: erpnext/accounts/doctype/sales_invoice/sales_invoice.json #: erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py:36 #: erpnext/accounts/doctype/subscription/subscription.json -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json #: erpnext/buying/doctype/supplier_quotation/supplier_quotation_dashboard.py:16 #: erpnext/selling/doctype/quotation/quotation_dashboard.py:12 #: erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py:25 @@ -48221,8 +48242,8 @@ msgstr "" msgid "Subscription Invoice" msgstr "" -#. Label of a Card Break in the Accounting Workspace -#: erpnext/accounts/workspace/accounting/accounting.json +#. Label of a Card Break in the Invoicing Workspace +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Subscription Management" msgstr "" @@ -48233,9 +48254,9 @@ msgid "Subscription Period" msgstr "" #. Name of a DocType -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #: erpnext/accounts/doctype/subscription_plan/subscription_plan.json -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Subscription Plan" msgstr "" @@ -48274,9 +48295,9 @@ msgid "Subscription Section" msgstr "" #. Name of a DocType -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #: erpnext/accounts/doctype/subscription_settings/subscription_settings.json -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Subscription Settings" msgstr "" @@ -48328,7 +48349,7 @@ msgstr "" msgid "Successfully Set Supplier" msgstr "" -#: erpnext/stock/doctype/item/item.py:341 +#: erpnext/stock/doctype/item/item.py:358 msgid "Successfully changed Stock UOM, please redefine conversion factors for new UOM." msgstr "" @@ -48808,7 +48829,7 @@ msgstr "" msgid "Supplier Quotation Item" msgstr "" -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:479 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:482 msgid "Supplier Quotation {0} Created" msgstr "" @@ -49262,7 +49283,7 @@ msgstr "" msgid "Target Warehouse is required before Submit" msgstr "" -#: erpnext/controllers/selling_controller.py:865 +#: erpnext/controllers/selling_controller.py:880 msgid "Target Warehouse is set for some items but the customer is not an internal customer." msgstr "" @@ -49270,9 +49291,9 @@ msgstr "" msgid "Target Warehouse {0} must be same as Delivery Warehouse {1} in the Subcontracting Inward Order Item." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:774 -#: erpnext/stock/doctype/stock_entry/stock_entry.py:781 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:789 #: erpnext/stock/doctype/stock_entry/stock_entry.py:796 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:811 msgid "Target warehouse is mandatory for row {0}" msgstr "" @@ -49436,7 +49457,7 @@ msgstr "" #. Template' #. Name of a DocType #. Label of the tax_category (Link) field in DocType 'Tax Rule' -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #. Label of the tax_category (Link) field in DocType 'Purchase Order' #. Label of the tax_category (Link) field in DocType 'Supplier' #. Label of the tax_category (Link) field in DocType 'Supplier Quotation' @@ -49455,7 +49476,7 @@ msgstr "" #: erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json #: erpnext/accounts/doctype/tax_category/tax_category.json #: erpnext/accounts/doctype/tax_rule/tax_rule.json -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json #: erpnext/buying/doctype/purchase_order/purchase_order.json #: erpnext/buying/doctype/supplier/supplier.json #: erpnext/buying/doctype/supplier_quotation/supplier_quotation.json @@ -49517,8 +49538,8 @@ msgstr "" msgid "Tax Id: {0}" msgstr "" -#. Label of a Card Break in the Accounting Workspace -#: erpnext/accounts/workspace/accounting/accounting.json +#. Label of a Card Break in the Invoicing Workspace +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Tax Masters" msgstr "" @@ -49561,9 +49582,9 @@ msgid "Tax Row" msgstr "" #. Name of a DocType -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #: erpnext/accounts/doctype/tax_rule/tax_rule.json -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json msgid "Tax Rule" msgstr "" @@ -49606,7 +49627,7 @@ msgstr "" #. Name of a DocType #. Label of the tax_withholding_category (Link) field in DocType 'Tax #. Withholding Entry' -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #. Label of the tax_withholding_category (Link) field in DocType 'Supplier' #. Label of the tax_withholding_category (Link) field in DocType 'Lower #. Deduction Certificate' @@ -49617,7 +49638,7 @@ msgstr "" #: erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json #: erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json #: erpnext/accounts/doctype/tax_withholding_entry/tax_withholding_entry.json -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json #: erpnext/buying/doctype/supplier/supplier.json #: erpnext/regional/doctype/lower_deduction_certificate/lower_deduction_certificate.json #: erpnext/selling/doctype/customer/customer.json @@ -49880,7 +49901,7 @@ msgstr "" msgid "Taxes and Charges Deducted (Company Currency)" msgstr "" -#: erpnext/stock/doctype/item/item.py:354 +#: erpnext/stock/doctype/item/item.py:371 msgid "Taxes row #{0}: {1} cannot be smaller than {2}" msgstr "" @@ -50047,7 +50068,7 @@ msgstr "" #. Label of the terms (Text Editor) field in DocType 'Purchase Invoice' #. Label of the terms_section_break (Section Break) field in DocType 'Sales #. Invoice' -#. Label of a Link in the Accounting Workspace +#. Label of a Link in the Invoicing Workspace #. Label of the terms (Text Editor) field in DocType 'Purchase Order' #. Label of the terms_section_break (Section Break) field in DocType 'Request #. for Quotation' @@ -50068,7 +50089,7 @@ msgstr "" #: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json #: erpnext/accounts/doctype/sales_invoice/sales_invoice.json #: erpnext/accounts/print_format/sales_invoice_print/sales_invoice_print.html:155 -#: erpnext/accounts/workspace/accounting/accounting.json +#: erpnext/accounts/workspace/invoicing/invoicing.json #: erpnext/buying/doctype/purchase_order/purchase_order.json #: erpnext/buying/doctype/request_for_quotation/request_for_quotation.json #: erpnext/buying/doctype/supplier_quotation/supplier_quotation.json @@ -50245,7 +50266,7 @@ msgstr "" msgid "The 'From Package No.' field must neither be empty nor it's value less than 1." msgstr "" -#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:395 +#: erpnext/buying/doctype/request_for_quotation/request_for_quotation.py:398 msgid "The Access to Request for Quotation From Portal is Disabled. To Allow Access, Enable it in Portal Settings." msgstr "" @@ -50298,7 +50319,7 @@ msgstr "" msgid "The Pick List having Stock Reservation Entries cannot be updated. If you need to make changes, we recommend canceling the existing Stock Reservation Entries before updating the Pick List." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:2593 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:2608 msgid "The Process Loss Qty has reset as per job cards Process Loss Qty" msgstr "" @@ -50314,7 +50335,7 @@ msgstr "" msgid "The Serial No {0} is reserved against the {1} {2} and cannot be used for any other transaction." msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1727 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1742 msgid "The Serial and Batch Bundle {0} is not valid for this transaction. The 'Type of Transaction' should be 'Outward' instead of 'Inward' in Serial and Batch Bundle {0}" msgstr "" @@ -50336,7 +50357,7 @@ msgstr "" msgid "The amount of {0} set in this payment request is different from the calculated amount of all payment plans: {1}. Make sure this is correct before submitting the document." msgstr "" -#: erpnext/controllers/stock_controller.py:1268 +#: erpnext/controllers/stock_controller.py:1271 msgid "The batch {0} is already reserved in {1} {2}. So, cannot proceed with the {3} {4}, which is created against the {5} {6}." msgstr "" @@ -50352,7 +50373,7 @@ msgstr "" msgid "The current POS opening entry is outdated. Please close it and create a new one." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1126 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1123 msgid "The default BOM for that item will be fetched by the system. You can also change the BOM." msgstr "" @@ -50397,7 +50418,7 @@ msgstr "" msgid "The following Purchase Invoices are not submitted:" msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:342 +#: erpnext/assets/doctype/asset/depreciation.py:344 msgid "The following assets have failed to automatically post depreciation entries: {0}" msgstr "" @@ -50409,7 +50430,7 @@ msgstr "" msgid "The following cancelled repost entries exist for {0}:

{1}

Kindly delete these entries before continuing." msgstr "" -#: erpnext/stock/doctype/item/item.py:844 +#: erpnext/stock/doctype/item/item.py:861 msgid "The following deleted attributes exist in Variants but not in the Template. You can either delete the Variants or keep the attribute(s) in template." msgstr "" @@ -50442,7 +50463,7 @@ msgstr "" msgid "The item {item} is not marked as {type_of} item. You can enable it as {type_of} item from its Item master." msgstr "" -#: erpnext/stock/doctype/item/item.py:621 +#: erpnext/stock/doctype/item/item.py:638 msgid "The items {0} and {1} are present in the following {2} :" msgstr "" @@ -50643,15 +50664,15 @@ msgstr "" msgid "The value {0} is already assigned to an existing Item {1}." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1154 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1151 msgid "The warehouse where you store finished Items before they are shipped." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1147 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1144 msgid "The warehouse where you store your raw materials. Each required item can have a separate source warehouse. Group warehouse also can be selected as source warehouse. On submission of the Work Order, the raw materials will be reserved in these warehouses for production usage." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1159 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1156 msgid "The warehouse where your Items will be transferred when you begin production. Group Warehouse can also be selected as a Work in Progress warehouse." msgstr "" @@ -50663,7 +50684,7 @@ msgstr "" msgid "The {0} contains Unit Price Items." msgstr "" -#: erpnext/stock/doctype/item/item.py:425 +#: erpnext/stock/doctype/item/item.py:442 msgid "The {0} prefix '{1}' already exists. Please change the Serial No Series, otherwise you will get a Duplicate Entry error." msgstr "" @@ -50683,7 +50704,7 @@ msgstr "" msgid "Then Pricing Rules are filtered out based on Customer, Customer Group, Territory, Supplier, Supplier Type, Campaign, Sales Partner etc." msgstr "" -#: erpnext/assets/doctype/asset/asset.py:720 +#: erpnext/assets/doctype/asset/asset.py:722 msgid "There are active maintenance or repairs against the asset. You must complete all of them before cancelling the asset." msgstr "" @@ -50739,7 +50760,7 @@ msgstr "" msgid "There is no batch found against the {0}: {1}" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:1664 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:1679 msgid "There must be atleast 1 Finished Good in this Stock Entry" msgstr "" @@ -50760,7 +50781,7 @@ msgstr "" msgid "There was an issue connecting to Plaid's authentication server. Check browser console for more information" msgstr "" -#: erpnext/accounts/utils.py:1125 +#: erpnext/accounts/utils.py:1135 msgid "There were issues unlinking payment entry {0}." msgstr "" @@ -50782,11 +50803,11 @@ msgstr "" msgid "This Month's Summary" msgstr "" -#: erpnext/buying/doctype/purchase_order/purchase_order.py:924 +#: erpnext/buying/doctype/purchase_order/purchase_order.py:925 msgid "This Purchase Order has been fully subcontracted." msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.py:2041 +#: erpnext/selling/doctype/sales_order/sales_order.py:2052 msgid "This Sales Order has been fully subcontracted." msgstr "" @@ -50802,7 +50823,7 @@ msgstr "" msgid "This action will unlink this account from any external service integrating ERPNext with your bank accounts. It cannot be undone. Are you certain ?" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:425 +#: erpnext/assets/doctype/asset/asset.py:427 msgid "This asset category is marked as non-depreciable. Please disable depreciation calculation or choose a different category." msgstr "" @@ -50905,7 +50926,7 @@ msgstr "" msgid "This is done to handle accounting for cases when Purchase Receipt is created after Purchase Invoice" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1140 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1137 msgid "This is enabled by default. If you want to plan materials for sub-assemblies of the Item you're manufacturing leave this enabled. If you plan and manufacture the sub-assemblies separately, you can disable this checkbox." msgstr "" @@ -50947,11 +50968,11 @@ msgstr "" msgid "This schedule was created when Asset {0} was restored due to Sales Invoice {1} cancellation." msgstr "" -#: erpnext/assets/doctype/asset_capitalization/asset_capitalization.py:598 +#: erpnext/assets/doctype/asset_capitalization/asset_capitalization.py:604 msgid "This schedule was created when Asset {0} was restored on Asset Capitalization {1}'s cancellation." msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:456 +#: erpnext/assets/doctype/asset/depreciation.py:458 msgid "This schedule was created when Asset {0} was restored." msgstr "" @@ -50959,11 +50980,11 @@ msgstr "" msgid "This schedule was created when Asset {0} was returned through Sales Invoice {1}." msgstr "" -#: erpnext/assets/doctype/asset/depreciation.py:415 +#: erpnext/assets/doctype/asset/depreciation.py:417 msgid "This schedule was created when Asset {0} was scrapped." msgstr "" -#: erpnext/assets/doctype/asset/asset.py:1497 +#: erpnext/assets/doctype/asset/asset.py:1499 msgid "This schedule was created when Asset {0} was {1} into new Asset {2}." msgstr "" @@ -51011,7 +51032,7 @@ msgstr "" msgid "This will restrict user access to other employee records" msgstr "" -#: erpnext/controllers/selling_controller.py:866 +#: erpnext/controllers/selling_controller.py:881 msgid "This {} will be treated as material transfer." msgstr "" @@ -51494,7 +51515,7 @@ msgstr "" msgid "To include tax in row {0} in Item rate, taxes in rows {1} must also be included" msgstr "" -#: erpnext/stock/doctype/item/item.py:643 +#: erpnext/stock/doctype/item/item.py:660 msgid "To merge, following properties must be same for both items" msgstr "" @@ -52072,7 +52093,7 @@ msgstr "" msgid "Total Payments" msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.py:712 +#: erpnext/selling/doctype/sales_order/sales_order.py:723 msgid "Total Picked Quantity {0} is more than ordered qty {1}. You can set the Over Picking Allowance in Stock Settings." msgstr "" @@ -52360,7 +52381,7 @@ msgstr "" msgid "Total allocated percentage for sales team should be 100" msgstr "" -#: erpnext/selling/doctype/customer/customer.py:167 +#: erpnext/selling/doctype/customer/customer.py:168 msgid "Total contribution percentage should be equal to 100" msgstr "" @@ -53072,7 +53093,7 @@ msgstr "" msgid "UOM Conversion Factor" msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1459 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1461 msgid "UOM Conversion factor ({0} -> {1}) not found for item: {2}" msgstr "" @@ -53085,7 +53106,7 @@ msgstr "" msgid "UOM Name" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:3758 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:3773 msgid "UOM conversion factor required for UOM: {0} in Item: {1}" msgstr "" @@ -53237,7 +53258,7 @@ msgstr "" msgid "Unit of Measure (UOM)" msgstr "" -#: erpnext/stock/doctype/item/item.py:386 +#: erpnext/stock/doctype/item/item.py:403 msgid "Unit of Measure {0} has been entered more than once in Conversion Factor Table" msgstr "" @@ -53367,7 +53388,7 @@ msgstr "" msgid "Unreconciled Entries" msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:903 +#: erpnext/manufacturing/doctype/work_order/work_order.js:900 #: erpnext/selling/doctype/sales_order/sales_order.js:107 #: erpnext/stock/doctype/pick_list/pick_list.js:156 #: erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js:193 @@ -53412,7 +53433,7 @@ msgstr "" msgid "Unsecured Loans" msgstr "" -#: erpnext/accounts/doctype/payment_entry/payment_entry.js:1717 +#: erpnext/accounts/doctype/payment_entry/payment_entry.js:1727 msgid "Unset Matched Payment Request" msgstr "" @@ -53639,7 +53660,7 @@ msgstr "" msgid "Update latest price in all BOMs" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:465 +#: erpnext/assets/doctype/asset/asset.py:467 msgid "Update stock must be enabled for the purchase invoice {0}" msgstr "" @@ -53673,11 +53694,11 @@ msgstr "" msgid "Updating Costing and Billing fields against this Project..." msgstr "" -#: erpnext/stock/doctype/item/item.py:1387 +#: erpnext/stock/doctype/item/item.py:1404 msgid "Updating Variants..." msgstr "" -#: erpnext/manufacturing/doctype/work_order/work_order.js:1102 +#: erpnext/manufacturing/doctype/work_order/work_order.js:1099 msgid "Updating Work Order status" msgstr "" @@ -53804,6 +53825,12 @@ msgstr "" msgid "Use Multi-Level BOM" msgstr "" +#. Label of the use_posting_datetime_for_naming_documents (Check) field in +#. DocType 'Global Defaults' +#: erpnext/setup/doctype/global_defaults/global_defaults.json +msgid "Use Posting Datetime for Naming Documents" +msgstr "" + #. Label of the fallback_to_default_price_list (Check) field in DocType #. 'Selling Settings' #: erpnext/selling/doctype/selling_settings/selling_settings.json @@ -54223,15 +54250,15 @@ msgstr "" msgid "Valuation Rate (In / Out)" msgstr "" -#: erpnext/stock/stock_ledger.py:2018 +#: erpnext/stock/stock_ledger.py:2017 msgid "Valuation Rate Missing" msgstr "" -#: erpnext/stock/stock_ledger.py:1996 +#: erpnext/stock/stock_ledger.py:1995 msgid "Valuation Rate for the Item {0}, is required to do accounting entries for {1} {2}." msgstr "" -#: erpnext/stock/doctype/item/item.py:271 +#: erpnext/stock/doctype/item/item.py:288 msgid "Valuation Rate is mandatory if Opening Stock entered" msgstr "" @@ -54389,7 +54416,7 @@ msgstr "" msgid "Variant" msgstr "" -#: erpnext/stock/doctype/item/item.py:859 +#: erpnext/stock/doctype/item/item.py:876 msgid "Variant Attribute Error" msgstr "" @@ -54408,7 +54435,7 @@ msgstr "" msgid "Variant Based On" msgstr "" -#: erpnext/stock/doctype/item/item.py:887 +#: erpnext/stock/doctype/item/item.py:904 msgid "Variant Based On cannot be changed" msgstr "" @@ -54426,7 +54453,7 @@ msgstr "" msgid "Variant Item" msgstr "" -#: erpnext/stock/doctype/item/item.py:857 +#: erpnext/stock/doctype/item/item.py:874 msgid "Variant Items" msgstr "" @@ -55015,7 +55042,7 @@ msgstr "" msgid "Warehouse {0} is not allowed for Sales Order {1}, it should be {2}" msgstr "" -#: erpnext/controllers/stock_controller.py:771 +#: erpnext/controllers/stock_controller.py:774 msgid "Warehouse {0} is not linked to any account, please mention the account in the warehouse record or set default inventory account in company {1}." msgstr "" @@ -55140,7 +55167,7 @@ msgstr "" msgid "Warning: Quantity exceeds maximum producible quantity based on quantity of raw materials received through the Subcontracting Inward Order {0}." msgstr "" -#: erpnext/selling/doctype/sales_order/sales_order.py:335 +#: erpnext/selling/doctype/sales_order/sales_order.py:345 msgid "Warning: Sales Order {0} already exists against Customer's Purchase Order {1}" msgstr "" @@ -55390,10 +55417,20 @@ msgstr "" msgid "When checked, only transaction threshold will be applied for transaction individually" msgstr "" +#. Description of the 'Use Posting Datetime for Naming Documents' (Check) field +#. in DocType 'Global Defaults' +#: erpnext/setup/doctype/global_defaults/global_defaults.json +msgid "When checked, the system will use the posting datetime of the document for naming the document instead of the creation datetime of the document." +msgstr "" + #: erpnext/stock/doctype/item/item.js:1079 msgid "When creating an Item, entering a value for this field will automatically create an Item Price at the backend." msgstr "" +#: erpnext/stock/doctype/stock_entry/stock_entry.py:294 +msgid "When there are multiple finished goods ({0}) in a Repack stock entry, the basic rate for all finished goods must be set manually. To set rate manually, enable the checkbox 'Set Basic Rate Manually' in the respective finished good row." +msgstr "" + #: erpnext/accounts/doctype/account/account.py:380 msgid "While creating account for Child Company {0}, parent account {1} found as a ledger account." msgstr "" @@ -55621,11 +55658,11 @@ msgstr "" msgid "Work Order not created" msgstr "" -#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1372 +#: erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py:1390 msgid "Work Order {0} created" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:846 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:861 msgid "Work Order {0}: Job Card not found for the operation {1}" msgstr "" @@ -56109,7 +56146,7 @@ msgstr "" msgid "You have enabled {0} and {1} in {2}. This can lead to prices from the default price list being inserted in the transaction price list." msgstr "" -#: erpnext/selling/doctype/selling_settings/selling_settings.py:88 +#: erpnext/selling/doctype/selling_settings/selling_settings.py:89 msgid "You have enabled {0} and {1} in {2}. This can lead to prices from the default price list being inserted into the transaction price list." msgstr "" @@ -56117,7 +56154,7 @@ msgstr "" msgid "You have entered a duplicate Delivery Note on Row" msgstr "" -#: erpnext/stock/doctype/item/item.py:1063 +#: erpnext/stock/doctype/item/item.py:1080 msgid "You have to enable auto re-order in Stock Settings to maintain re-order levels." msgstr "" @@ -56184,7 +56221,7 @@ msgstr "" msgid "Zero Rated" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:546 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:561 msgid "Zero quantity" msgstr "" @@ -56201,7 +56238,7 @@ msgstr "" msgid "`Allow Negative rates for Items`" msgstr "" -#: erpnext/stock/stock_ledger.py:2010 +#: erpnext/stock/stock_ledger.py:2009 msgid "after" msgstr "" @@ -56387,7 +56424,7 @@ msgstr "" msgid "per hour" msgstr "" -#: erpnext/stock/stock_ledger.py:2011 +#: erpnext/stock/stock_ledger.py:2010 msgid "performing either one below:" msgstr "" @@ -56509,7 +56546,7 @@ msgstr "" msgid "{0} '{1}' is disabled" msgstr "" -#: erpnext/accounts/utils.py:187 +#: erpnext/accounts/utils.py:197 msgid "{0} '{1}' not in Fiscal Year {2}" msgstr "" @@ -56545,11 +56582,11 @@ msgstr "" msgid "{0} Digest" msgstr "" -#: erpnext/accounts/utils.py:1486 +#: erpnext/accounts/utils.py:1496 msgid "{0} Number {1} is already used in {2} {3}" msgstr "" -#: erpnext/manufacturing/doctype/bom/bom.py:1575 +#: erpnext/manufacturing/doctype/bom/bom.py:1578 msgid "{0} Operating Cost for operation {1}" msgstr "" @@ -56561,7 +56598,7 @@ msgstr "" msgid "{0} Request for {1}" msgstr "" -#: erpnext/stock/doctype/item/item.py:325 +#: erpnext/stock/doctype/item/item.py:342 msgid "{0} Retain Sample is based on batch, please check Has Batch No to retain sample of item" msgstr "" @@ -56569,11 +56606,11 @@ msgstr "" msgid "{0} Transaction(s) Reconciled" msgstr "" -#: erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py:59 +#: erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py:60 msgid "{0} account is not of company {1}" msgstr "" -#: erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py:62 +#: erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py:63 msgid "{0} account is not of type {1}" msgstr "" @@ -56626,8 +56663,8 @@ msgstr "" msgid "{0} cannot be zero" msgstr "" -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:918 -#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1034 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:920 +#: erpnext/manufacturing/doctype/production_plan/production_plan.py:1036 #: erpnext/stock/doctype/pick_list/pick_list.py:1259 #: erpnext/subcontracting/doctype/subcontracting_inward_order/subcontracting_inward_order.py:322 msgid "{0} created" @@ -56641,7 +56678,7 @@ msgstr "" msgid "{0} currency must be same as company's default currency. Please select another account." msgstr "" -#: erpnext/buying/doctype/purchase_order/purchase_order.py:290 +#: erpnext/buying/doctype/purchase_order/purchase_order.py:291 msgid "{0} currently has a {1} Supplier Scorecard standing, and Purchase Orders to this supplier should be issued with caution." msgstr "" @@ -56662,11 +56699,11 @@ msgid "{0} entered twice in Item Tax" msgstr "" #: erpnext/setup/doctype/item_group/item_group.py:48 -#: erpnext/stock/doctype/item/item.py:456 +#: erpnext/stock/doctype/item/item.py:473 msgid "{0} entered twice {1} in Item Taxes" msgstr "" -#: erpnext/accounts/utils.py:124 +#: erpnext/accounts/utils.py:134 #: erpnext/projects/doctype/activity_cost/activity_cost.py:40 msgid "{0} for {1}" msgstr "" @@ -56709,7 +56746,7 @@ msgstr "" msgid "{0} is blocked so this transaction cannot proceed" msgstr "" -#: erpnext/assets/doctype/asset/asset.py:496 +#: erpnext/assets/doctype/asset/asset.py:498 msgid "{0} is in Draft. Submit it before creating the Asset." msgstr "" @@ -56717,7 +56754,7 @@ msgstr "" msgid "{0} is mandatory for Item {1}" msgstr "" -#: erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py:99 +#: erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py:100 #: erpnext/accounts/general_ledger.py:854 msgid "{0} is mandatory for account {1}" msgstr "" @@ -56730,7 +56767,7 @@ msgstr "" msgid "{0} is mandatory. Maybe Currency Exchange record is not created for {1} to {2}." msgstr "" -#: erpnext/selling/doctype/customer/customer.py:209 +#: erpnext/selling/doctype/customer/customer.py:210 msgid "{0} is not a company bank account" msgstr "" @@ -56738,7 +56775,7 @@ msgstr "" msgid "{0} is not a group node. Please select a group node as parent cost center" msgstr "" -#: erpnext/stock/doctype/stock_entry/stock_entry.py:598 +#: erpnext/stock/doctype/stock_entry/stock_entry.py:613 msgid "{0} is not a stock Item" msgstr "" @@ -56802,7 +56839,7 @@ msgstr "" msgid "{0} payment entries can not be filtered by {1}" msgstr "" -#: erpnext/controllers/stock_controller.py:1690 +#: erpnext/controllers/stock_controller.py:1693 msgid "{0} qty of Item {1} is being received into Warehouse {2} with capacity {3}." msgstr "" @@ -56822,16 +56859,16 @@ msgstr "" msgid "{0} units of {1} are required in {2} with the inventory dimension: {3} on {4} {5} for {6} to complete the transaction." msgstr "" -#: erpnext/stock/stock_ledger.py:1684 erpnext/stock/stock_ledger.py:2162 -#: erpnext/stock/stock_ledger.py:2176 +#: erpnext/stock/stock_ledger.py:1683 erpnext/stock/stock_ledger.py:2161 +#: erpnext/stock/stock_ledger.py:2175 msgid "{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction." msgstr "" -#: erpnext/stock/stock_ledger.py:2263 erpnext/stock/stock_ledger.py:2308 +#: erpnext/stock/stock_ledger.py:2262 erpnext/stock/stock_ledger.py:2307 msgid "{0} units of {1} needed in {2} on {3} {4} to complete this transaction." msgstr "" -#: erpnext/stock/stock_ledger.py:1678 +#: erpnext/stock/stock_ledger.py:1677 msgid "{0} units of {1} needed in {2} to complete this transaction." msgstr "" @@ -56897,8 +56934,8 @@ msgstr "" msgid "{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts." msgstr "" -#: erpnext/buying/doctype/purchase_order/purchase_order.py:430 -#: erpnext/selling/doctype/sales_order/sales_order.py:585 +#: erpnext/buying/doctype/purchase_order/purchase_order.py:431 +#: erpnext/selling/doctype/sales_order/sales_order.py:596 #: erpnext/stock/doctype/material_request/material_request.py:247 msgid "{0} {1} has been modified. Please refresh." msgstr "" @@ -56956,7 +56993,7 @@ msgstr "" msgid "{0} {1} is not associated with {2} {3}" msgstr "" -#: erpnext/accounts/utils.py:120 +#: erpnext/accounts/utils.py:130 msgid "{0} {1} is not in any active Fiscal Year" msgstr "" @@ -56990,17 +57027,17 @@ msgid "{0} {1}: 'Profit and Loss' type account {2} not allowed in Opening Entry" msgstr "" #: erpnext/accounts/doctype/gl_entry/gl_entry.py:251 -#: erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py:85 +#: erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py:86 msgid "{0} {1}: Account {2} does not belong to Company {3}" msgstr "" #: erpnext/accounts/doctype/gl_entry/gl_entry.py:239 -#: erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py:73 +#: erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py:74 msgid "{0} {1}: Account {2} is a Group Account and group accounts cannot be used in transactions" msgstr "" #: erpnext/accounts/doctype/gl_entry/gl_entry.py:246 -#: erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py:80 +#: erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py:81 msgid "{0} {1}: Account {2} is inactive" msgstr "" @@ -57008,7 +57045,7 @@ msgstr "" msgid "{0} {1}: Accounting Entry for {2} can only be made in currency: {3}" msgstr "" -#: erpnext/controllers/stock_controller.py:903 +#: erpnext/controllers/stock_controller.py:906 msgid "{0} {1}: Cost Center is mandatory for Item {2}" msgstr "" @@ -57074,7 +57111,7 @@ msgstr "" msgid "{0}: {1} is a group account." msgstr "" -#: erpnext/accounts/doctype/payment_entry/payment_entry.js:979 +#: erpnext/accounts/doctype/payment_entry/payment_entry.js:989 msgid "{0}: {1} must be less than {2}" msgstr "" @@ -57090,7 +57127,7 @@ msgstr "" msgid "{field_label} is mandatory for sub-contracted {doctype}." msgstr "" -#: erpnext/controllers/stock_controller.py:2095 +#: erpnext/controllers/stock_controller.py:2098 msgid "{item_name}'s Sample Size ({sample_size}) cannot be greater than the Accepted Quantity ({accepted_quantity})" msgstr "" diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 772d9466228..9ae36795fec 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -747,19 +747,21 @@ class ProductionPlan(Document): "project": self.project, } - key = (d.item_code, d.sales_order, d.sales_order_item, d.warehouse) + key = (d.item_code, d.sales_order, d.sales_order_item, d.warehouse, d.planned_start_date) if self.combine_items: - key = (d.item_code, d.sales_order, d.warehouse) + key = (d.item_code, d.sales_order, d.warehouse, d.planned_start_date) if not d.sales_order: - key = (d.name, d.item_code, d.warehouse) + key = (d.name, d.item_code, d.warehouse, d.planned_start_date) if not item_details["project"] and d.sales_order: item_details["project"] = frappe.get_cached_value("Sales Order", d.sales_order, "project") if self.get_items_from == "Material Request": item_details.update({"qty": d.planned_qty}) - item_dict[(d.item_code, d.material_request_item, d.warehouse)] = item_details + item_dict[ + (d.item_code, d.material_request_item, d.warehouse, d.planned_start_date) + ] = item_details else: item_details.update( { diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py index ea318dfa18c..e960e734bc1 100644 --- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py @@ -999,7 +999,7 @@ class TestProductionPlan(IntegrationTestCase): items_data = pln.get_production_items() # Update qty - items_data[(pln.po_items[0].name, item, None)]["qty"] = qty + items_data[(pln.po_items[0].name, item, None, pln.po_items[0].planned_start_date)]["qty"] = qty # Create and Submit Work Order for each item in items_data for _key, item in items_data.items(): diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index e816c4690df..78f2ba090cc 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -710,7 +710,7 @@ erpnext.work_order = { set_custom_buttons: function (frm) { var doc = frm.doc; - if (doc.docstatus === 1 && doc.status !== "Closed") { + if (doc.docstatus === 1 && !["Closed", "Completed"].includes(doc.status)) { frm.add_custom_button( __("Close"), function () { @@ -720,9 +720,6 @@ erpnext.work_order = { }, __("Status") ); - } - - if (doc.docstatus === 1 && !["Closed", "Completed"].includes(doc.status)) { if (doc.status != "Stopped" && doc.status != "Completed") { frm.add_custom_button( __("Stop"), diff --git a/erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py b/erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py index ee93cdc27e5..9a17948362a 100644 --- a/erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py +++ b/erpnext/manufacturing/report/material_requirements_planning_report/material_requirements_planning_report.py @@ -454,7 +454,6 @@ class MaterialRequirementsPlanningReport: row[field] = rm_details.get(field) self.update_required_qty(row) - row.release_date = add_days(row.delivery_date, row.lead_time * -1) if i != 0: data.append(frappe._dict({})) @@ -463,7 +462,15 @@ class MaterialRequirementsPlanningReport: if rm_details.raw_materials: row.capacity = get_item_capacity(row.item_code, self.filters.bucket_size) row.type_of_material = "Manufacture" + if row.lead_time and row.required_qty: + row.lead_time = math.ceil(row.required_qty / row.lead_time) + elif not row.required_qty: + row.lead_time = 0 + if not row.lead_time and rm_details.raw_materials: + row.lead_time = self.get_lead_time_from_raw_materials(rm_details.raw_materials) + + row.release_date = add_days(row.delivery_date, row.lead_time * -1) data.append(row) if rm_details.raw_materials: self.update_rm_details( @@ -472,6 +479,15 @@ class MaterialRequirementsPlanningReport: return data + def get_lead_time_from_raw_materials(self, raw_materials): + lead_time = 0 + for material in raw_materials: + lead_time += math.ceil(material.lead_time) + if material.raw_materials: + lead_time += self.get_lead_time_from_raw_materials(material.raw_materials) + + return lead_time + def add_non_planned_so(self, row): if so_details := self._so_details.get((row.item_code, row.delivery_date)): row.adhoc_qty = so_details.qty @@ -1199,8 +1215,10 @@ def get_item_lead_time(item_code, type_of_material): if type_of_material == "Manufacture": query = query.select( Case() - .when(doctype.manufacturing_time_in_mins.isnull(), 0) - .else_(doctype.manufacturing_time_in_mins / 1440 + doctype.buffer_time) + .when( + (doctype.manufacturing_time_in_mins.isnull() | (doctype.manufacturing_time_in_mins <= 0)), 0 + ) + .else_(1440 / doctype.manufacturing_time_in_mins + doctype.buffer_time) .as_("lead_time") ) else: diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 6978d0a634e..12058846096 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -459,3 +459,6 @@ erpnext.patches.v16_0.fix_barcode_typo erpnext.patches.v16_0.set_post_change_gl_entries_on_pos_settings execute:frappe.delete_doc_if_exists("Workspace Sidebar", "Opening & Closing") erpnext.patches.v15_0.create_accounting_dimensions_in_advance_taxes_and_charges +erpnext.patches.v16_0.set_ordered_qty_in_quotation_item +erpnext.patches.v16_0.migrate_transaction_deletion_task_flags_to_status # 2 +erpnext.patches.v16_0.update_company_custom_field_in_bin diff --git a/erpnext/patches/v16_0/migrate_transaction_deletion_task_flags_to_status.py b/erpnext/patches/v16_0/migrate_transaction_deletion_task_flags_to_status.py new file mode 100644 index 00000000000..1943650565c --- /dev/null +++ b/erpnext/patches/v16_0/migrate_transaction_deletion_task_flags_to_status.py @@ -0,0 +1,42 @@ +import frappe + + +def execute(): + """ + Migrate Transaction Deletion Record boolean task flags to status Select fields. + Renames fields from old names to new names with _status suffix. + Maps: 0 -> "Pending", 1 -> "Completed" + """ + if not frappe.db.table_exists("tabTransaction Deletion Record"): + return + + # Field mapping: old boolean field name -> new status field name + field_mapping = { + "delete_bin_data": "delete_bin_data_status", + "delete_leads_and_addresses": "delete_leads_and_addresses_status", + "reset_company_default_values": "reset_company_default_values_status", + "clear_notifications": "clear_notifications_status", + "initialize_doctypes_table": "initialize_doctypes_table_status", + "delete_transactions": "delete_transactions_status", + } + + # Get all Transaction Deletion Records + records = frappe.db.get_all("Transaction Deletion Record", pluck="name") + + for name in records or []: + updates = {} + + for old_field, new_field in field_mapping.items(): + # Read from old boolean field + current_value = frappe.db.get_value("Transaction Deletion Record", name, old_field) + + # Map to new status and write to new field name + if current_value in (1, "1", True): + updates[new_field] = "Completed" + else: + # Handle 0, "0", False, None, empty string + updates[new_field] = "Pending" + + # Update all fields at once + if updates: + frappe.db.set_value("Transaction Deletion Record", name, updates, update_modified=False) diff --git a/erpnext/patches/v16_0/set_ordered_qty_in_quotation_item.py b/erpnext/patches/v16_0/set_ordered_qty_in_quotation_item.py new file mode 100644 index 00000000000..93a6323eb6f --- /dev/null +++ b/erpnext/patches/v16_0/set_ordered_qty_in_quotation_item.py @@ -0,0 +1,16 @@ +import frappe + + +def execute(): + data = frappe.get_all( + "Sales Order Item", + filters={"quotation_item": ["is", "set"], "docstatus": 1}, + fields=["quotation_item", {"SUM": "stock_qty", "as": "ordered_qty"}], + group_by="quotation_item", + ) + if data: + frappe.db.auto_commit_on_many_writes = 1 + frappe.db.bulk_update( + "Quotation Item", {d.quotation_item: {"ordered_qty": d.ordered_qty} for d in data} + ) + frappe.db.auto_commit_on_many_writes = 0 diff --git a/erpnext/patches/v16_0/update_company_custom_field_in_bin.py b/erpnext/patches/v16_0/update_company_custom_field_in_bin.py new file mode 100644 index 00000000000..e0b36d91fd7 --- /dev/null +++ b/erpnext/patches/v16_0/update_company_custom_field_in_bin.py @@ -0,0 +1,14 @@ +import frappe + + +def execute(): + frappe.reload_doc("stock", "doctype", "bin") + + frappe.db.sql( + """ + UPDATE `tabBin` b + INNER JOIN `tabWarehouse` w ON b.warehouse = w.name + SET b.company = w.company + WHERE b.company IS NULL OR b.company = '' + """ + ) diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 08a1c0433ad..0208bf1e9d0 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -308,6 +308,8 @@ class Project(Document): self.gross_margin = flt(self.total_billed_amount) - expense_amount if self.total_billed_amount: self.per_gross_margin = (self.gross_margin / flt(self.total_billed_amount)) * 100 + else: + self.per_gross_margin = 0 def update_purchase_costing(self): total_purchase_cost = calculate_total_purchase_cost(self.name) diff --git a/erpnext/public/icons/desktop_icons/solid/accounts_setup.svg b/erpnext/public/icons/desktop_icons/solid/accounts_setup.svg new file mode 100644 index 00000000000..e4139c73feb --- /dev/null +++ b/erpnext/public/icons/desktop_icons/solid/accounts_setup.svg @@ -0,0 +1,4 @@ + + + + diff --git a/erpnext/public/icons/desktop_icons/solid/financial_reports.svg b/erpnext/public/icons/desktop_icons/solid/financial_reports.svg index 53a6bf4ea73..0f02331acb5 100644 --- a/erpnext/public/icons/desktop_icons/solid/financial_reports.svg +++ b/erpnext/public/icons/desktop_icons/solid/financial_reports.svg @@ -1,4 +1,4 @@ - + diff --git a/erpnext/public/icons/desktop_icons/solid/invoicing.svg b/erpnext/public/icons/desktop_icons/solid/invoicing.svg index e90d7b7dd59..a7e1cf88b06 100644 --- a/erpnext/public/icons/desktop_icons/solid/invoicing.svg +++ b/erpnext/public/icons/desktop_icons/solid/invoicing.svg @@ -1,4 +1,4 @@ - + diff --git a/erpnext/public/icons/desktop_icons/solid/payments.svg b/erpnext/public/icons/desktop_icons/solid/payments.svg new file mode 100644 index 00000000000..4745da882c1 --- /dev/null +++ b/erpnext/public/icons/desktop_icons/solid/payments.svg @@ -0,0 +1,4 @@ + + + + diff --git a/erpnext/public/icons/desktop_icons/subtle/accounts_setup.svg b/erpnext/public/icons/desktop_icons/subtle/accounts_setup.svg new file mode 100644 index 00000000000..f34a59f4337 --- /dev/null +++ b/erpnext/public/icons/desktop_icons/subtle/accounts_setup.svg @@ -0,0 +1,4 @@ + + + + diff --git a/erpnext/public/icons/desktop_icons/subtle/financial_reports.svg b/erpnext/public/icons/desktop_icons/subtle/financial_reports.svg index 497fa55722d..e942a4a53a7 100644 --- a/erpnext/public/icons/desktop_icons/subtle/financial_reports.svg +++ b/erpnext/public/icons/desktop_icons/subtle/financial_reports.svg @@ -1,4 +1,4 @@ - - + + diff --git a/erpnext/public/icons/desktop_icons/subtle/invoicing.svg b/erpnext/public/icons/desktop_icons/subtle/invoicing.svg new file mode 100644 index 00000000000..468b8f06741 --- /dev/null +++ b/erpnext/public/icons/desktop_icons/subtle/invoicing.svg @@ -0,0 +1,4 @@ + + + + diff --git a/erpnext/public/icons/desktop_icons/subtle/payments.svg b/erpnext/public/icons/desktop_icons/subtle/payments.svg new file mode 100644 index 00000000000..ef36089a18d --- /dev/null +++ b/erpnext/public/icons/desktop_icons/subtle/payments.svg @@ -0,0 +1,4 @@ + + + + diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 4ae844c6116..d4a4577d8a0 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -625,6 +625,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe callback: function (r) { if (!r.exc) { me.frm.refresh_fields(); + me.show_batch_dialog_if_required(item); } }, }); @@ -635,26 +636,13 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe process_item_selection(doc, cdt, cdn) { var item = frappe.get_doc(cdt, cdn); + let update_stock = 0; var me = this; - var update_stock = 0, - show_batch_dialog = 0; item.weight_per_unit = 0; item.weight_uom = ""; item.uom = null; // make UOM blank to update the existing UOM when item changes item.conversion_factor = 0; - - if (["Sales Invoice", "Purchase Invoice"].includes(this.frm.doc.doctype)) { - update_stock = cint(me.frm.doc.update_stock); - show_batch_dialog = update_stock; - } else if (this.frm.doc.doctype === "Purchase Receipt" || this.frm.doc.doctype === "Delivery Note") { - show_batch_dialog = 1; - } - - if (show_batch_dialog && item.use_serial_batch_fields === 1) { - show_batch_dialog = 0; - } - item.barcode = null; if (item.item_code || item.serial_no) { @@ -765,74 +753,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } }, () => me.toggle_conversion_factor(item), - () => { - if (show_batch_dialog && !frappe.flags.trigger_from_barcode_scanner) - return frappe.db - .get_value("Item", item.item_code, [ - "has_batch_no", - "has_serial_no", - ]) - .then((r) => { - if ( - r.message && - (r.message.has_batch_no || r.message.has_serial_no) - ) { - frappe.flags.hide_serial_batch_dialog = false; - } else { - show_batch_dialog = false; - } - }); - }, - () => { - // check if batch serial selector is disabled or not - if (show_batch_dialog && !frappe.flags.hide_serial_batch_dialog) - return frappe.db - .get_single_value( - "Stock Settings", - "disable_serial_no_and_batch_selector" - ) - .then((value) => { - if (value) { - frappe.flags.hide_serial_batch_dialog = true; - } - }); - }, - () => { - if ( - show_batch_dialog && - !frappe.flags.hide_serial_batch_dialog && - !frappe.flags.dialog_set - ) { - var d = locals[cdt][cdn]; - $.each(r.message, function (k, v) { - if (!d[k]) d[k] = v; - }); - - if (d.has_batch_no && d.has_serial_no) { - d.batch_no = undefined; - } - - frappe.flags.dialog_set = true; - erpnext.show_serial_batch_selector( - me.frm, - d, - (item) => { - me.frm.script_manager.trigger("qty", item.doctype, item.name); - if (!me.frm.doc.set_warehouse) - me.frm.script_manager.trigger( - "warehouse", - item.doctype, - item.name - ); - me.apply_price_list(item, true); - }, - undefined, - !frappe.flags.hide_serial_batch_dialog - ); - } else { - frappe.flags.dialog_set = false; - } - }, + () => me.show_batch_dialog_if_required(item), () => me.conversion_factor(doc, cdt, cdn, true), () => me.remove_pricing_rule(item), () => { @@ -853,6 +774,78 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } } + show_batch_dialog_if_required(item) { + let show_batch_dialog = 0; + let update_stock = 0; + let me = this; + + if (!item.item_code) { + return; + } + + if (["Sales Invoice", "Purchase Invoice"].includes(this.frm.doc.doctype)) { + update_stock = cint(me.frm.doc.update_stock); + show_batch_dialog = update_stock; + } else if (this.frm.doc.doctype === "Purchase Receipt" || this.frm.doc.doctype === "Delivery Note") { + show_batch_dialog = 1; + } + + if (show_batch_dialog && item.use_serial_batch_fields === 1) { + show_batch_dialog = 0; + } + + frappe.run_serially([ + () => { + if (show_batch_dialog && !frappe.flags.trigger_from_barcode_scanner) + return frappe.db + .get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"]) + .then((r) => { + if (r.message && (r.message.has_batch_no || r.message.has_serial_no)) { + item.has_serial_no = r.message.has_serial_no; + item.has_batch_no = r.message.has_batch_no; + frappe.flags.hide_serial_batch_dialog = false; + } else { + show_batch_dialog = false; + } + }); + }, + () => { + // check if batch serial selector is disabled or not + if (show_batch_dialog && !frappe.flags.hide_serial_batch_dialog) + return frappe.db + .get_single_value("Stock Settings", "disable_serial_no_and_batch_selector") + .then((value) => { + if (value) { + frappe.flags.hide_serial_batch_dialog = true; + } + }); + }, + () => { + if (show_batch_dialog && !frappe.flags.hide_serial_batch_dialog && !frappe.flags.dialog_set) { + if (item.has_batch_no && item.has_serial_no) { + item.batch_no = undefined; + } + + frappe.flags.dialog_set = true; + erpnext.show_serial_batch_selector( + me.frm, + item, + (item) => { + me.frm.script_manager.trigger("qty", item.doctype, item.name); + if (!me.frm.doc.set_warehouse) + me.frm.script_manager.trigger("warehouse", item.doctype, item.name); + me.apply_price_list(item, true); + }, + undefined, + !frappe.flags.hide_serial_batch_dialog + ); + } else { + frappe.flags.dialog_set = false; + } + }, + ]); + } + price_list_rate(doc, cdt, cdn) { var item = frappe.get_doc(cdt, cdn); frappe.model.round_floats_in(item, ["price_list_rate", "discount_percentage"]); diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 116449a6fa8..355dadbc534 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -994,7 +994,7 @@ erpnext.utils.map_current_doc = function (opts) { if (opts.source_doctype) { let data_fields = []; - if (["Purchase Receipt", "Delivery Note"].includes(opts.source_doctype)) { + if (["Purchase Receipt", "Delivery Note", "Purchase Invoice"].includes(opts.source_doctype)) { let target_meta = frappe.get_meta(cur_frm.doc.doctype); if (target_meta.fields.find((f) => f.fieldname === "taxes")) { data_fields.push({ @@ -1069,17 +1069,9 @@ frappe.form.link_formatters["Project"] = function (value, doc, df) { * @returns {string} - The link value with the added title. */ function add_link_title(value, doc, df, title_field) { - if (doc.doctype != df.parent) { - return ""; - } else if ( - doc && - value && - doc[title_field] && - doc[title_field] !== value && - doc[df.fieldname] === value - ) { + if (doc && value && doc[title_field] && doc[title_field] !== value && doc[df.fieldname] === value) { return value + ": " + doc[title_field]; - } else if (!value && doc.doctype && doc[title_field]) { + } else if (!value && doc.doctype && doc[title_field] && doc.doctype == df.parent) { return doc[title_field]; } else { return value; diff --git a/erpnext/public/js/utils/barcode_scanner.js b/erpnext/public/js/utils/barcode_scanner.js index 9ee1029f345..dd585041d71 100644 --- a/erpnext/public/js/utils/barcode_scanner.js +++ b/erpnext/public/js/utils/barcode_scanner.js @@ -138,14 +138,14 @@ erpnext.utils.BarcodeScanner = class BarcodeScanner { frappe.run_serially([ () => this.set_selector_trigger_flag(data), - () => this.set_serial_no(row, serial_no), - () => this.set_batch_no(row, batch_no), () => this.set_barcode(row, barcode), () => this.set_warehouse(row), () => this.set_item(row, item_code, barcode, batch_no, serial_no).then((qty) => { this.show_scan_message(row.idx, !is_new_row, qty); }), + () => this.set_serial_no(row, serial_no), + () => this.set_batch_no(row, batch_no), () => this.clean_up(), () => this.set_barcode_uom(row, uom), () => this.revert_selector_flag(), diff --git a/erpnext/public/js/utils/demo.js b/erpnext/public/js/utils/demo.js index 97279dbef96..e0da8985e9b 100644 --- a/erpnext/public/js/utils/demo.js +++ b/erpnext/public/js/utils/demo.js @@ -1,11 +1,5 @@ frappe.provide("erpnext.demo"); -$(document).on("toolbar_setup", function () { - if (frappe.boot.sysdefaults.demo_company) { - render_clear_demo_action(); - } -}); - $(document).on("desktop_screen", function (event, data) { data.desktop.add_menu_item({ label: __("Clear Demo Data"), @@ -19,16 +13,6 @@ $(document).on("desktop_screen", function (event, data) { }); }); -function render_clear_demo_action() { - let demo_action = $( - `
- ${__("Clear Demo Data")} - ` - ); - - demo_action.appendTo($("#toolbar-user")); -} - erpnext.demo.clear_demo = function () { frappe.confirm(__("Are you sure you want to clear all demo data?"), () => { frappe.call({ @@ -44,4 +28,4 @@ erpnext.demo.clear_demo = function () { }, }); }); -}; +}; \ No newline at end of file diff --git a/erpnext/regional/italy/utils.py b/erpnext/regional/italy/utils.py index 2e8d38bebde..93e9b60f5a8 100644 --- a/erpnext/regional/italy/utils.py +++ b/erpnext/regional/italy/utils.py @@ -142,7 +142,14 @@ def download_zip(files, output_filename): def get_invoice_summary(items, taxes, item_wise_tax_details): summary_data = frappe._dict() - taxes_wise_tax_details = {d.tax_row: d for d in item_wise_tax_details} + taxes_wise_tax_details = {} + + for d in item_wise_tax_details: + if d.tax_row not in taxes_wise_tax_details: + taxes_wise_tax_details[d.tax_row] = [] + + taxes_wise_tax_details[d.tax_row].append(d) + for tax in taxes: # Include only VAT charges. if tax.charge_type == "Actual": diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 5178ed59fb4..4845b5ea038 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -512,6 +512,9 @@ def _set_missing_values(source, target): if contact: target.contact_person = contact[0].parent + target.contact_display, target.contact_email, target.contact_mobile = frappe.get_value( + "Contact", contact[0].parent, ["full_name", "email_id", "mobile_no"] + ) @frappe.whitelist() diff --git a/erpnext/selling/doctype/quotation/quotation.json b/erpnext/selling/doctype/quotation/quotation.json index e8b8b5106b9..78cf42ae753 100644 --- a/erpnext/selling/doctype/quotation/quotation.json +++ b/erpnext/selling/doctype/quotation/quotation.json @@ -3,7 +3,7 @@ "allow_auto_repeat": 1, "allow_import": 1, "autoname": "naming_series:", - "creation": "2013-05-24 19:29:08", + "creation": "2026-01-29 21:18:32.391385", "doctype": "DocType", "document_type": "Document", "editable_grid": 1, @@ -1115,14 +1115,15 @@ "hidden": 1, "label": "Item Wise Tax Details", "no_copy": 1, - "options": "Item Wise Tax Detail" + "options": "Item Wise Tax Detail", + "print_hide": 1 } ], "icon": "fa fa-shopping-cart", "idx": 82, "is_submittable": 1, "links": [], - "modified": "2025-07-31 17:23:48.875382", + "modified": "2026-01-29 21:18:48.836168", "modified_by": "Administrator", "module": "Selling", "name": "Quotation", diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py index 05a8fb08e50..fd6f6ec812a 100644 --- a/erpnext/selling/doctype/quotation/quotation.py +++ b/erpnext/selling/doctype/quotation/quotation.py @@ -450,7 +450,10 @@ def _make_sales_order(source_name, target_doc=None, ignore_permissions=False, ar "Quotation", source_name, { - "Quotation": {"doctype": "Sales Order", "validation": {"docstatus": ["=", 1]}}, + "Quotation": { + "doctype": "Sales Order", + "validation": {"docstatus": ["=", 1]}, + }, "Quotation Item": { "doctype": "Sales Order Item", "field_map": {"parent": "prevdoc_docname", "name": "quotation_item"}, @@ -553,6 +556,8 @@ def _make_customer(source_name, ignore_permissions=False): if quotation.quotation_to == "Customer": return frappe.get_doc("Customer", quotation.party_name) + elif quotation.quotation_to == "CRM Deal": + return frappe.get_doc("Customer", {"crm_deal": quotation.party_name}) # Check if a Customer already exists for the Lead or Prospect. existing_customer = None @@ -613,27 +618,9 @@ def handle_mandatory_error(e, customer, lead_name): frappe.throw(message, title=_("Mandatory Missing")) -@frappe.whitelist() def get_ordered_items(quotation: str): - """ - Returns a dict of ordered items with their total qty based on quotation row name. - - In `Sales Order Item`, `quotation_item` is the row name of `Quotation Item`. - - Example: - ``` - { - "refsdjhd2": 10, - "ygdhdshrt": 5, - } - ``` - """ return frappe._dict( frappe.get_all( - "Sales Order Item", - filters={"prevdoc_docname": quotation, "docstatus": 1}, - fields=["quotation_item", {"SUM": "qty"}], - group_by="quotation_item", - as_list=1, + "Quotation Item", {"docstatus": 1, "parent": quotation}, ["name", "ordered_qty"], as_list=True ) ) diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py index 043df28d909..eaaa2eb9748 100644 --- a/erpnext/selling/doctype/quotation/test_quotation.py +++ b/erpnext/selling/doctype/quotation/test_quotation.py @@ -934,7 +934,7 @@ class TestQuotation(IntegrationTestCase): # item code same but description different make_item("_Test Item 2", {"is_stock_item": 1}) - quotation = make_quotation(qty=1, rate=100, do_not_submit=1) + quotation = make_quotation(qty=10, rate=100, do_not_submit=1) # duplicate items for qty in [1, 1, 2, 3]: @@ -948,7 +948,7 @@ class TestQuotation(IntegrationTestCase): sales_order.delivery_date = nowdate() self.assertEqual(len(sales_order.items), 6) - self.assertEqual(sales_order.items[0].qty, 1) + self.assertEqual(sales_order.items[0].qty, 10) self.assertEqual(sales_order.items[-1].qty, 5) # Row 1: 10, Row 4: 1, Row 5: 1 @@ -991,6 +991,16 @@ class TestQuotation(IntegrationTestCase): f"Expected conversion rate {expected_rate}, got {quotation.conversion_rate}", ) + def test_over_order_limit(self): + quotation = make_quotation(qty=5) + so1 = make_sales_order(quotation.name) + so2 = make_sales_order(quotation.name) + so1.delivery_date = nowdate() + so2.delivery_date = nowdate() + + so1.submit() + self.assertRaises(frappe.ValidationError, so2.submit) + def enable_calculate_bundle_price(enable=1): selling_settings = frappe.get_doc("Selling Settings") diff --git a/erpnext/selling/doctype/quotation_item/quotation_item.json b/erpnext/selling/doctype/quotation_item/quotation_item.json index 8ff14f2063a..92d7895c57b 100644 --- a/erpnext/selling/doctype/quotation_item/quotation_item.json +++ b/erpnext/selling/doctype/quotation_item/quotation_item.json @@ -27,6 +27,7 @@ "uom", "conversion_factor", "stock_qty", + "ordered_qty", "available_quantity_section", "actual_qty", "column_break_ylrv", @@ -694,19 +695,31 @@ "print_hide": 1, "read_only": 1, "report_hide": 1 + }, + { + "default": "0", + "fieldname": "ordered_qty", + "fieldtype": "Float", + "hidden": 1, + "label": "Ordered Qty", + "no_copy": 1, + "non_negative": 1, + "read_only": 1, + "reqd": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2025-08-26 20:31:47.775890", + "modified": "2026-01-30 12:56:08.320190", "modified_by": "Administrator", "module": "Selling", "name": "Quotation Item", "owner": "Administrator", "permissions": [], + "row_format": "Dynamic", "sort_field": "creation", "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/selling/doctype/quotation_item/quotation_item.py b/erpnext/selling/doctype/quotation_item/quotation_item.py index bbdd8643593..9ab265c885c 100644 --- a/erpnext/selling/doctype/quotation_item/quotation_item.py +++ b/erpnext/selling/doctype/quotation_item/quotation_item.py @@ -48,6 +48,7 @@ class QuotationItem(Document): margin_type: DF.Literal["", "Percentage", "Amount"] net_amount: DF.Currency net_rate: DF.Currency + ordered_qty: DF.Float page_break: DF.Check parent: DF.Data parentfield: DF.Data diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index c76fcff4679..de17b04c450 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -1704,7 +1704,8 @@ "hidden": 1, "label": "Item Wise Tax Details", "no_copy": 1, - "options": "Item Wise Tax Detail" + "options": "Item Wise Tax Detail", + "print_hide": 1 } ], "grid_page_length": 50, @@ -1712,7 +1713,7 @@ "idx": 105, "is_submittable": 1, "links": [], - "modified": "2025-10-12 12:14:29.760988", + "modified": "2026-01-29 21:23:48.362401", "modified_by": "Administrator", "module": "Selling", "name": "Sales Order", diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index a43877f923a..876b11459b4 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -194,6 +194,16 @@ class SalesOrder(SellingController): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + self.status_updater = [ + { + "source_dt": "Sales Order Item", + "target_dt": "Quotation Item", + "join_field": "quotation_item", + "target_field": "ordered_qty", + "target_ref_field": "stock_qty", + "source_field": "stock_qty", + } + ] def onload(self) -> None: super().onload() @@ -481,6 +491,7 @@ class SalesOrder(SellingController): frappe.throw(_("Row #{0}: Set Supplier for item {1}").format(d.idx, d.item_code)) def on_submit(self): + super().update_prevdoc_status() self.check_credit_limit() self.update_reserved_qty() self.delete_removed_delivery_schedule_items() diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index dea7b3bff97..b3cb42906cc 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -57,6 +57,7 @@ class TestSalesOrder(AccountsTestMixin, IntegrationTestCase): frappe.db.rollback() frappe.set_user("Administrator") + @IntegrationTestCase.change_settings("Selling Settings", {"allow_negative_rates_for_items": 1}) def test_sales_order_with_negative_rate(self): """ Test if negative rate is allowed in Sales Order via doc submission and update items diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json index 1b88bf79ac4..c98bba45b6d 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.json +++ b/erpnext/selling/doctype/selling_settings/selling_settings.json @@ -39,6 +39,7 @@ "enable_cutoff_date_on_bulk_delivery_note_creation", "allow_zero_qty_in_quotation", "allow_zero_qty_in_sales_order", + "set_zero_rate_for_expired_batch", "experimental_section", "use_legacy_js_reactivity", "subcontracting_inward_tab", @@ -289,6 +290,13 @@ "fieldname": "use_legacy_js_reactivity", "fieldtype": "Check", "label": "Use Legacy (Client side) Reactivity" + }, + { + "default": "0", + "description": "If enabled, system will set incoming rate as zero for stand-alone credit notes with expired batch item.", + "fieldname": "set_zero_rate_for_expired_batch", + "fieldtype": "Check", + "label": "Set Incoming Rate as Zero for Expired Batch" } ], "grid_page_length": 50, @@ -298,7 +306,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2026-01-21 17:28:37.027837", + "modified": "2026-01-23 00:04:33.105916", "modified_by": "Administrator", "module": "Selling", "name": "Selling Settings", diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.py b/erpnext/selling/doctype/selling_settings/selling_settings.py index 9d343b2c21c..239230de895 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.py +++ b/erpnext/selling/doctype/selling_settings/selling_settings.py @@ -44,6 +44,7 @@ class SellingSettings(Document): role_to_override_stop_action: DF.Link | None sales_update_frequency: DF.Literal["Monthly", "Each Transaction", "Daily"] selling_price_list: DF.Link | None + set_zero_rate_for_expired_batch: DF.Check so_required: DF.Literal["No", "Yes"] territory: DF.Link | None use_legacy_js_reactivity: DF.Check diff --git a/erpnext/setup/demo.py b/erpnext/setup/demo.py index b21fb96546a..7835aeb9a9e 100644 --- a/erpnext/setup/demo.py +++ b/erpnext/setup/demo.py @@ -182,6 +182,10 @@ def create_transaction_deletion_record(company): transaction_deletion_record.company = company transaction_deletion_record.process_in_single_transaction = True transaction_deletion_record.save(ignore_permissions=True) + + transaction_deletion_record.generate_to_delete_list() + transaction_deletion_record.reload() + transaction_deletion_record.submit() transaction_deletion_record.start_deletion_tasks() diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 5a2b6db83f2..5da3ca40904 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -1083,6 +1083,8 @@ def get_billing_shipping_address(name, billing_address=None, shipping_address=No @frappe.whitelist() def create_transaction_deletion_request(company): + frappe.only_for("System Manager") + from erpnext.setup.doctype.transaction_deletion_record.transaction_deletion_record import ( is_deletion_doc_running, ) @@ -1090,12 +1092,16 @@ def create_transaction_deletion_request(company): is_deletion_doc_running(company) tdr = frappe.get_doc({"doctype": "Transaction Deletion Record", "company": company}) + tdr.insert() + + tdr.generate_to_delete_list() + tdr.reload() + tdr.submit() tdr.start_deletion_tasks() frappe.msgprint( - _("A Transaction Deletion Document: {0} is triggered for {0}").format( - get_link_to_form("Transaction Deletion Record", tdr.name) - ), - frappe.bold(company), + _("Transaction Deletion Document {0} has been triggered for company {1}").format( + get_link_to_form("Transaction Deletion Record", tdr.name), frappe.bold(company) + ) ) diff --git a/erpnext/setup/doctype/global_defaults/global_defaults.json b/erpnext/setup/doctype/global_defaults/global_defaults.json index cb43c97ae4c..92bff6d4fbe 100644 --- a/erpnext/setup/doctype/global_defaults/global_defaults.json +++ b/erpnext/setup/doctype/global_defaults/global_defaults.json @@ -13,6 +13,7 @@ "hide_currency_symbol", "disable_rounded_total", "disable_in_words", + "use_posting_datetime_for_naming_documents", "demo_company" ], "fields": [ @@ -80,6 +81,13 @@ "label": "Demo Company", "options": "Company", "read_only": 1 + }, + { + "default": "0", + "description": "When checked, the system will use the posting datetime of the document for naming the document instead of the creation datetime of the document.", + "fieldname": "use_posting_datetime_for_naming_documents", + "fieldtype": "Check", + "label": "Use Posting Datetime for Naming Documents" } ], "grid_page_length": 50, @@ -89,7 +97,7 @@ "in_create": 1, "issingle": 1, "links": [], - "modified": "2026-01-02 18:13:13.421866", + "modified": "2026-01-12 09:45:59.819161", "modified_by": "Administrator", "module": "Setup", "name": "Global Defaults", diff --git a/erpnext/setup/doctype/global_defaults/global_defaults.py b/erpnext/setup/doctype/global_defaults/global_defaults.py index 4b7f642747e..99174c9a0d1 100644 --- a/erpnext/setup/doctype/global_defaults/global_defaults.py +++ b/erpnext/setup/doctype/global_defaults/global_defaults.py @@ -39,6 +39,7 @@ class GlobalDefaults(Document): disable_in_words: DF.Check disable_rounded_total: DF.Check hide_currency_symbol: DF.Literal["", "No", "Yes"] + use_posting_datetime_for_naming_documents: DF.Check # end: auto-generated types def on_update(self): diff --git a/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py index fcfb006860c..b1c96fc66b0 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py @@ -8,38 +8,77 @@ from frappe.tests import IntegrationTestCase class TestTransactionDeletionRecord(IntegrationTestCase): def setUp(self): + # Clear all deletion cache flags from previous tests + self._clear_all_deletion_cache_flags() create_company("Dunder Mifflin Paper Co") def tearDown(self): + # Clean up all deletion cache flags after each test + self._clear_all_deletion_cache_flags() frappe.db.rollback() + def _clear_all_deletion_cache_flags(self): + """Clear all deletion_running_doctype:* cache keys""" + # Get all keys matching the pattern + cache_keys = frappe.cache.get_keys("deletion_running_doctype:*") + if cache_keys: + for key in cache_keys: + # Decode bytes to string if needed + key_str = key.decode() if isinstance(key, bytes) else key + # Extract just the key name (remove site prefix if present) + # Keys are in format: site_prefix|deletion_running_doctype:DocType + if "|" in key_str: + key_name = key_str.split("|")[1] + else: + key_name = key_str + frappe.cache.delete_value(key_name) + def test_doctypes_contain_company_field(self): - tdr = create_transaction_deletion_doc("Dunder Mifflin Paper Co") - for doctype in tdr.doctypes: - contains_company = False - doctype_fields = frappe.get_meta(doctype.doctype_name).as_dict()["fields"] - for doctype_field in doctype_fields: - if doctype_field["fieldtype"] == "Link" and doctype_field["options"] == "Company": - contains_company = True - break - self.assertTrue(contains_company) + """Test that all DocTypes in To Delete list have a valid company link field""" + tdr = create_and_submit_transaction_deletion_doc("Dunder Mifflin Paper Co") + for doctype_row in tdr.doctypes_to_delete: + # If company_field is specified, verify it's a valid Company link field + if doctype_row.company_field: + field_found = False + doctype_fields = frappe.get_meta(doctype_row.doctype_name).as_dict()["fields"] + for doctype_field in doctype_fields: + if ( + doctype_field["fieldname"] == doctype_row.company_field + and doctype_field["fieldtype"] == "Link" + and doctype_field["options"] == "Company" + ): + field_found = True + break + self.assertTrue( + field_found, + f"DocType {doctype_row.doctype_name} should have company field '{doctype_row.company_field}'", + ) def test_no_of_docs_is_correct(self): - for _i in range(5): + """Test that document counts are calculated correctly in To Delete list""" + for _ in range(5): create_task("Dunder Mifflin Paper Co") - tdr = create_transaction_deletion_doc("Dunder Mifflin Paper Co") + tdr = create_and_submit_transaction_deletion_doc("Dunder Mifflin Paper Co") tdr.reload() - for doctype in tdr.doctypes: + + # Check To Delete list has correct count + task_found = False + for doctype in tdr.doctypes_to_delete: if doctype.doctype_name == "Task": - self.assertEqual(doctype.no_of_docs, 5) + self.assertEqual(doctype.document_count, 5) + task_found = True + break + self.assertTrue(task_found, "Task should be in To Delete list") def test_deletion_is_successful(self): + """Test that deletion actually removes documents""" create_task("Dunder Mifflin Paper Co") - create_transaction_deletion_doc("Dunder Mifflin Paper Co") + create_and_submit_transaction_deletion_doc("Dunder Mifflin Paper Co") tasks_containing_company = frappe.get_all("Task", filters={"company": "Dunder Mifflin Paper Co"}) self.assertEqual(tasks_containing_company, []) def test_company_transaction_deletion_request(self): + """Test creation via company deletion request method""" from erpnext.setup.doctype.company.company import create_transaction_deletion_request # don't reuse below company for other test cases @@ -49,15 +88,314 @@ class TestTransactionDeletionRecord(IntegrationTestCase): # below call should not raise any exceptions or throw errors create_transaction_deletion_request(company) + def test_generate_to_delete_list(self): + """Test automatic generation of To Delete list""" + company = "Dunder Mifflin Paper Co" + create_task(company) + + tdr = frappe.new_doc("Transaction Deletion Record") + tdr.company = company + tdr.insert() + + # Generate To Delete list + tdr.generate_to_delete_list() + tdr.reload() + + # Should have at least Task in the list + self.assertGreater(len(tdr.doctypes_to_delete), 0) + task_in_list = any(d.doctype_name == "Task" for d in tdr.doctypes_to_delete) + self.assertTrue(task_in_list, "Task should be in To Delete list") + + def test_validation_prevents_child_tables(self): + """Test that child tables cannot be added to To Delete list""" + company = "Dunder Mifflin Paper Co" + + tdr = frappe.new_doc("Transaction Deletion Record") + tdr.company = company + tdr.append("doctypes_to_delete", {"doctype_name": "Sales Invoice Item"}) # Child table + + # Should throw validation error + with self.assertRaises(frappe.ValidationError): + tdr.insert() + + def test_validation_prevents_protected_doctypes(self): + """Test that protected DocTypes cannot be added to To Delete list""" + company = "Dunder Mifflin Paper Co" + + tdr = frappe.new_doc("Transaction Deletion Record") + tdr.company = company + tdr.append("doctypes_to_delete", {"doctype_name": "DocType"}) # Protected + + # Should throw validation error + with self.assertRaises(frappe.ValidationError): + tdr.insert() + + def test_csv_export_import(self): + """Test CSV export and import functionality with company_field column""" + company = "Dunder Mifflin Paper Co" + create_task(company) + + # Create and generate To Delete list + tdr = frappe.new_doc("Transaction Deletion Record") + tdr.company = company + tdr.insert() + tdr.generate_to_delete_list() + tdr.reload() + + original_count = len(tdr.doctypes_to_delete) + self.assertGreater(original_count, 0) + + # Export as CSV + tdr.export_to_delete_template_method() + csv_content = frappe.response.get("result") + self.assertIsNotNone(csv_content) + self.assertIn("doctype_name", csv_content) + self.assertIn("company_field", csv_content) # New: verify company_field column exists + + # Create new record and import + tdr2 = frappe.new_doc("Transaction Deletion Record") + tdr2.company = company + tdr2.insert() + result = tdr2.import_to_delete_template_method(csv_content) + tdr2.reload() + + # Should have same entries (counts may differ due to new task) + self.assertEqual(len(tdr2.doctypes_to_delete), original_count) + self.assertGreaterEqual(result["imported"], 1) + + # Verify company_field values are preserved + for row in tdr2.doctypes_to_delete: + if row.doctype_name == "Task": + # Task should have company field set + self.assertIsNotNone(row.company_field, "Task should have company_field set after import") + + def test_progress_tracking(self): + """Test that deleted checkbox is marked when DocType deletion completes""" + company = "Dunder Mifflin Paper Co" + create_task(company) + + tdr = create_and_submit_transaction_deletion_doc(company) + tdr.reload() + + # After deletion, Task should be marked as deleted in To Delete list + # Note: Must match using composite key (doctype_name + company_field) + task_row = None + for doctype in tdr.doctypes_to_delete: + if doctype.doctype_name == "Task": + task_row = doctype + break + + if task_row: + self.assertEqual(task_row.deleted, 1, "Task should be marked as deleted") + + def test_composite_key_validation(self): + """Test that duplicate (doctype_name + company_field) combinations are prevented""" + company = "Dunder Mifflin Paper Co" + + tdr = frappe.new_doc("Transaction Deletion Record") + tdr.company = company + tdr.append("doctypes_to_delete", {"doctype_name": "Task", "company_field": "company"}) + tdr.append("doctypes_to_delete", {"doctype_name": "Task", "company_field": "company"}) # Duplicate! + + # Should throw validation error for duplicate composite key + with self.assertRaises(frappe.ValidationError): + tdr.insert() + + def test_same_doctype_different_company_field_allowed(self): + """Test that same DocType can be added with different company_field values""" + company = "Dunder Mifflin Paper Co" + + tdr = frappe.new_doc("Transaction Deletion Record") + tdr.company = company + # Same DocType but one with company field, one without (None) + tdr.append("doctypes_to_delete", {"doctype_name": "Task", "company_field": "company"}) + tdr.append("doctypes_to_delete", {"doctype_name": "Task", "company_field": None}) + + # Should NOT throw error - different company_field values are allowed + try: + tdr.insert() + self.assertEqual( + len(tdr.doctypes_to_delete), + 2, + "Should allow 2 Task entries with different company_field values", + ) + except frappe.ValidationError as e: + self.fail(f"Should allow same DocType with different company_field values, but got error: {e}") + + def test_company_field_validation(self): + """Test that invalid company_field values are rejected""" + company = "Dunder Mifflin Paper Co" + + tdr = frappe.new_doc("Transaction Deletion Record") + tdr.company = company + # Add Task with invalid company field + tdr.append("doctypes_to_delete", {"doctype_name": "Task", "company_field": "nonexistent_field"}) + + # Should throw validation error for invalid company field + with self.assertRaises(frappe.ValidationError): + tdr.insert() + + def test_get_naming_series_prefix_with_dot(self): + """Test prefix extraction for standard dot-separated naming series""" + from erpnext.setup.doctype.transaction_deletion_record.transaction_deletion_record import ( + TransactionDeletionRecord, + ) + + # Standard patterns with dot separator + self.assertEqual(TransactionDeletionRecord.get_naming_series_prefix("TDL.####", "Task"), "TDL") + self.assertEqual(TransactionDeletionRecord.get_naming_series_prefix("PREFIX.#####", "Task"), "PREFIX") + self.assertEqual( + TransactionDeletionRecord.get_naming_series_prefix("TASK-.YYYY.-.#####", "Task"), "TASK-.YYYY.-" + ) + + def test_get_naming_series_prefix_with_brace(self): + """Test prefix extraction for format patterns with brace separators""" + from erpnext.setup.doctype.transaction_deletion_record.transaction_deletion_record import ( + TransactionDeletionRecord, + ) + + # Format patterns with brace separator + self.assertEqual( + TransactionDeletionRecord.get_naming_series_prefix("QA-ACT-{#####}", "Quality Action"), "QA-ACT-" + ) + self.assertEqual( + TransactionDeletionRecord.get_naming_series_prefix("PREFIX-{####}", "Task"), "PREFIX-" + ) + self.assertEqual(TransactionDeletionRecord.get_naming_series_prefix("{####}", "Task"), "") + + def test_get_naming_series_prefix_fallback(self): + """Test prefix extraction fallback for patterns without standard separators""" + from erpnext.setup.doctype.transaction_deletion_record.transaction_deletion_record import ( + TransactionDeletionRecord, + ) + + # Edge case: pattern with # but no dot or brace (shouldn't happen in practice) + self.assertEqual(TransactionDeletionRecord.get_naming_series_prefix("PREFIX####", "Task"), "PREFIX") + # Edge case: pattern with no # at all + self.assertEqual( + TransactionDeletionRecord.get_naming_series_prefix("JUSTPREFIX", "Task"), "JUSTPREFIX" + ) + + def test_cache_flag_management(self): + """Test that cache flags can be set and cleared correctly""" + company = "Dunder Mifflin Paper Co" + create_task(company) + + tdr = frappe.new_doc("Transaction Deletion Record") + tdr.company = company + tdr.insert() + tdr.generate_to_delete_list() + tdr.reload() + + # Test _set_deletion_cache + tdr._set_deletion_cache() + + # Verify flag is set for Task specifically + cached_value = frappe.cache.get_value("deletion_running_doctype:Task") + self.assertEqual(cached_value, tdr.name, "Cache flag should be set for Task") + + # Test _clear_deletion_cache + tdr._clear_deletion_cache() + + # Verify flag is cleared + cached_value = frappe.cache.get_value("deletion_running_doctype:Task") + self.assertIsNone(cached_value, "Cache flag should be cleared for Task") + + def test_check_for_running_deletion_blocks_save(self): + """Test that check_for_running_deletion_job blocks saves when cache flag exists""" + from erpnext.setup.doctype.transaction_deletion_record.transaction_deletion_record import ( + check_for_running_deletion_job, + ) + + company = "Dunder Mifflin Paper Co" + + # Manually set cache flag to simulate running deletion + frappe.cache.set_value("deletion_running_doctype:Task", "TDR-00001", expires_in_sec=60) + + try: + # Try to validate a new Task + new_task = frappe.new_doc("Task") + new_task.company = company + new_task.subject = "Should be blocked" + + # Should throw error when cache flag exists + with self.assertRaises(frappe.ValidationError) as context: + check_for_running_deletion_job(new_task) + + error_message = str(context.exception) + self.assertIn("currently deleting", error_message) + self.assertIn("TDR-00001", error_message) + finally: + # Cleanup: clear the manually set flag + frappe.cache.delete_value("deletion_running_doctype:Task") + + def test_check_for_running_deletion_allows_save_when_no_flag(self): + """Test that documents can be saved when no deletion is running""" + company = "Dunder Mifflin Paper Co" + + # Ensure no cache flag exists + frappe.cache.delete_value("deletion_running_doctype:Task") + + # Try to create and save a new Task + new_task = frappe.new_doc("Task") + new_task.company = company + new_task.subject = "Should be allowed" + + # Should NOT throw error when no cache flag - actually save it + try: + new_task.insert() + # Cleanup + frappe.delete_doc("Task", new_task.name) + except frappe.ValidationError as e: + self.fail(f"Should allow save when no deletion is running, but got: {e}") + + def test_only_one_deletion_allowed_globally(self): + """Test that only one deletion can be submitted at a time (global enforcement)""" + company1 = "Dunder Mifflin Paper Co" + company2 = "Sabre Corporation" + + create_company(company2) + + # Create and submit first deletion (but don't start it) + tdr1 = frappe.new_doc("Transaction Deletion Record") + tdr1.company = company1 + tdr1.insert() + tdr1.append("doctypes_to_delete", {"doctype_name": "Task", "company_field": "company"}) + tdr1.save() + tdr1.submit() # Status becomes "Queued" + + try: + # Try to submit second deletion for different company + tdr2 = frappe.new_doc("Transaction Deletion Record") + tdr2.company = company2 # Different company! + tdr2.insert() + tdr2.append("doctypes_to_delete", {"doctype_name": "Lead", "company_field": "company"}) + tdr2.save() + + # Should throw error - only one deletion allowed globally + with self.assertRaises(frappe.ValidationError) as context: + tdr2.submit() + + self.assertIn("already", str(context.exception).lower()) + self.assertIn(tdr1.name, str(context.exception)) + finally: + # Cleanup + tdr1.cancel() + def create_company(company_name): company = frappe.get_doc({"doctype": "Company", "company_name": company_name, "default_currency": "INR"}) company.insert(ignore_if_duplicate=True) -def create_transaction_deletion_doc(company): +def create_and_submit_transaction_deletion_doc(company): + """Create and execute a transaction deletion record""" tdr = frappe.get_doc({"doctype": "Transaction Deletion Record", "company": company}) tdr.insert() + + tdr.generate_to_delete_list() + tdr.reload() + tdr.process_in_single_transaction = True tdr.submit() tdr.start_deletion_tasks() diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js index 9aa02784165..e1d5c52ba02 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js @@ -2,13 +2,58 @@ // For license information, please see license.txt frappe.ui.form.on("Transaction Deletion Record", { + setup: function (frm) { + // Set up query for DocTypes to exclude child tables and virtual doctypes + // Note: Same DocType can be added multiple times with different company_field values + frm.set_query("doctype_name", "doctypes_to_delete", function () { + // Build exclusion list from protected and ignored doctypes + let excluded_doctypes = ["Transaction Deletion Record"]; // Always exclude self + + // Add protected doctypes (fetched in onload) + if (frm.protected_doctypes_list && frm.protected_doctypes_list.length > 0) { + excluded_doctypes = excluded_doctypes.concat(frm.protected_doctypes_list); + } + + // Add doctypes from the ignore list + if (frm.doc.doctypes_to_be_ignored && frm.doc.doctypes_to_be_ignored.length > 0) { + frm.doc.doctypes_to_be_ignored.forEach((row) => { + if (row.doctype_name) { + excluded_doctypes.push(row.doctype_name); + } + }); + } + + let filters = [ + ["DocType", "istable", "=", 0], // Exclude child tables + ["DocType", "is_virtual", "=", 0], // Exclude virtual doctypes + ]; + + // Only add "not in" filter if we have items to exclude + if (excluded_doctypes.length > 0) { + filters.push(["DocType", "name", "not in", excluded_doctypes]); + } + + return { filters: filters }; + }); + }, + onload: function (frm) { if (frm.doc.docstatus == 0) { - let doctypes_to_be_ignored_array; + // Fetch protected doctypes list for filtering + frappe.call({ + method: "erpnext.setup.doctype.transaction_deletion_record.transaction_deletion_record.get_protected_doctypes", + callback: function (r) { + if (r.message) { + frm.protected_doctypes_list = r.message; + } + }, + }); + + // Fetch ignored doctypes and populate table frappe.call({ method: "erpnext.setup.doctype.transaction_deletion_record.transaction_deletion_record.get_doctypes_to_be_ignored", callback: function (r) { - doctypes_to_be_ignored_array = r.message; + let doctypes_to_be_ignored_array = r.message; populate_doctypes_to_be_ignored(doctypes_to_be_ignored_array, frm); frm.refresh_field("doctypes_to_be_ignored"); }, @@ -17,20 +62,264 @@ frappe.ui.form.on("Transaction Deletion Record", { }, refresh: function (frm) { - if (frm.doc.docstatus == 1 && ["Queued", "Failed"].find((x) => x == frm.doc.status)) { - let execute_btn = frm.doc.status == "Queued" ? __("Start Deletion") : __("Retry"); + // Override submit button to show custom confirmation + if (frm.doc.docstatus === 0 && !frm.is_new()) { + frm.page.clear_primary_action(); + frm.page.set_primary_action(__("Submit"), () => { + if (!frm.doc.doctypes_to_delete || frm.doc.doctypes_to_delete.length === 0) { + frappe.msgprint(__("Please generate the To Delete list before submitting")); + return; + } - frm.add_custom_button(execute_btn, () => { - // Entry point for chain of events + let message = + `
⚠ ${__( + "Warning: This action cannot be undone!" + )}
` + + `
${__( + "You are about to permanently delete data for {0} entries for company {1}.", + [`${frm.doc.doctypes_to_delete.length}`, `${frm.doc.company}`] + )}
` + + `
${__("What will be deleted:")}
` + + `` + + `
` + + `📦 ${__( + "IMPORTANT: Create a backup before proceeding!" + )}` + + `
` + + `
${__( + "Deletion will start automatically after submission." + )}
`; + + frappe.confirm( + message, + () => { + frm.save("Submit"); + }, + () => {} + ); + }); + } + + if (frm.doc.docstatus == 0) { + frm.add_custom_button(__("Generate To Delete List"), () => { + frm.call({ + method: "generate_to_delete_list", + doc: frm.doc, + callback: (r) => { + frappe.show_alert({ + message: __("To Delete list generated with {0} DocTypes", [r.message.count]), + indicator: "green", + }); + frm.refresh(); + }, + }); + }); + + if (frm.doc.doctypes_to_delete && frm.doc.doctypes_to_delete.length > 0) { + frm.add_custom_button( + __("Export"), + () => { + open_url_post( + "/api/method/erpnext.setup.doctype.transaction_deletion_record.transaction_deletion_record.export_to_delete_template", + { + name: frm.doc.name, + } + ); + }, + __("Template") + ); + + frm.add_custom_button(__("Remove Zero Counts"), () => { + let removed_count = 0; + let rows_to_keep = []; + frm.doc.doctypes_to_delete.forEach((row) => { + if (row.document_count && row.document_count > 0) { + rows_to_keep.push(row); + } else { + removed_count++; + } + }); + + if (removed_count === 0) { + frappe.msgprint(__("No rows with zero document count found")); + return; + } + + frm.doc.doctypes_to_delete = rows_to_keep; + frm.refresh_field("doctypes_to_delete"); + frm.dirty(); + + frappe.show_alert({ + message: __( + "Removed {0} rows with zero document count. Please save to persist changes.", + [removed_count] + ), + indicator: "orange", + }); + }); + } + + frm.add_custom_button( + __("Import"), + () => { + new frappe.ui.FileUploader({ + doctype: "Transaction Deletion Record", + docname: frm.doc.name, + folder: "Home/Attachments", + restrictions: { + allowed_file_types: [".csv"], + }, + on_success: (file_doc) => { + frappe.call({ + method: "erpnext.setup.doctype.transaction_deletion_record.transaction_deletion_record.process_import_template", + args: { + transaction_deletion_record_name: frm.doc.name, + file_url: file_doc.file_url, + }, + freeze: true, + freeze_message: __("Processing import..."), + callback: (r) => { + if (r.message) { + frappe.show_alert({ + message: __("Imported {0} DocTypes", [r.message.imported]), + indicator: "green", + }); + + frappe.model.clear_doc(frm.doctype, frm.docname); + frm.reload_doc(); + } + }, + }); + }, + }); + }, + __("Template") + ); + } + + // Only show Retry button for Failed status (deletion starts automatically on submit) + if (frm.doc.docstatus == 1 && frm.doc.status == "Failed") { + frm.add_custom_button(__("Retry"), () => { frm.call({ method: "start_deletion_tasks", doc: frm.doc, + callback: () => { + frappe.show_alert({ + message: __("Deletion process restarted"), + indicator: "blue", + }); + frm.reload_doc(); + }, }); }); } }, }); +frappe.ui.form.on("Transaction Deletion Record To Delete", { + doctype_name: function (frm, cdt, cdn) { + let row = locals[cdt][cdn]; + if (row.doctype_name) { + // Fetch company fields for auto-selection (only if exactly 1 field exists) + frappe.call({ + method: "erpnext.setup.doctype.transaction_deletion_record.transaction_deletion_record.get_company_link_fields", + args: { + doctype_name: row.doctype_name, + }, + callback: function (r) { + if (r.message && r.message.length === 1 && !row.company_field) { + frappe.model.set_value(cdt, cdn, "company_field", r.message[0]); + } else if (r.message && r.message.length > 1) { + // Show message with available options when multiple company fields exist + frappe.show_alert({ + message: __("Multiple company fields available: {0}. Please select manually.", [ + r.message.join(", "), + ]), + indicator: "blue", + }); + } + }, + }); + + // Auto-populate child DocTypes and document count + frm.call({ + method: "populate_doctype_details", + doc: frm.doc, + args: { + doctype_name: row.doctype_name, + company: frm.doc.company, + company_field: row.company_field, + }, + callback: function (r) { + if (r.message) { + if (r.message.error) { + frappe.msgprint({ + title: __("Error"), + indicator: "red", + message: __("Error getting details for {0}: {1}", [ + row.doctype_name, + r.message.error, + ]), + }); + } + frappe.model.set_value(cdt, cdn, "child_doctypes", r.message.child_doctypes || ""); + frappe.model.set_value(cdt, cdn, "document_count", r.message.document_count || 0); + } + }, + }); + } + }, + + company_field: function (frm, cdt, cdn) { + let row = locals[cdt][cdn]; + if (row.doctype_name && row.company_field !== undefined) { + // Check for duplicates using composite key (doctype_name + company_field) + let duplicates = frm.doc.doctypes_to_delete.filter( + (r) => + r.doctype_name === row.doctype_name && + r.company_field === row.company_field && + r.name !== row.name + ); + if (duplicates.length > 0) { + frappe.msgprint( + __("DocType {0} with company field '{1}' is already in the list", [ + row.doctype_name, + row.company_field || __("(none)"), + ]) + ); + frappe.model.set_value(cdt, cdn, "company_field", ""); + return; + } + + // Recalculate document count if company_field changes + if (row.doctype_name) { + frm.call({ + method: "populate_doctype_details", + doc: frm.doc, + args: { + doctype_name: row.doctype_name, + company: frm.doc.company, + company_field: row.company_field, + }, + callback: function (r) { + if (r.message && r.message.document_count !== undefined) { + frappe.model.set_value(cdt, cdn, "document_count", r.message.document_count || 0); + } + }, + }); + } + } + }, +}); + function populate_doctypes_to_be_ignored(doctypes_to_be_ignored_array, frm) { if (frm.doc.doctypes_to_be_ignored.length === 0) { var i; diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json index 16d23f8e3e3..f309139bb5d 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json @@ -11,14 +11,17 @@ "status", "error_log", "tasks_section", - "delete_bin_data", - "delete_leads_and_addresses", - "reset_company_default_values", - "clear_notifications", - "initialize_doctypes_table", - "delete_transactions", + "delete_bin_data_status", + "delete_leads_and_addresses_status", + "column_break_tasks_1", + "reset_company_default_values_status", + "clear_notifications_status", + "column_break_tasks_2", + "initialize_doctypes_table_status", + "delete_transactions_status", "section_break_tbej", "doctypes", + "doctypes_to_delete", "doctypes_to_be_ignored", "amended_from", "process_in_single_transaction" @@ -33,6 +36,7 @@ "reqd": 1 }, { + "depends_on": "eval:doc.docstatus > 0 && (!doc.doctypes_to_delete || doc.doctypes_to_delete.length == 0)", "fieldname": "doctypes", "fieldtype": "Table", "label": "Summary", @@ -41,11 +45,17 @@ "read_only": 1 }, { + "fieldname": "doctypes_to_delete", + "fieldtype": "Table", + "label": "DocTypes To Delete", + "options": "Transaction Deletion Record To Delete" + }, + { + "description": "DocTypes that will NOT be deleted.", "fieldname": "doctypes_to_be_ignored", "fieldtype": "Table", "label": "Excluded DocTypes", - "options": "Transaction Deletion Record Item", - "read_only": 1 + "options": "Transaction Deletion Record Item" }, { "fieldname": "amended_from", @@ -69,56 +79,71 @@ "fieldtype": "Section Break" }, { + "depends_on": "eval:doc.docstatus==1", "fieldname": "tasks_section", "fieldtype": "Section Break", "label": "Tasks" }, { - "default": "0", - "fieldname": "delete_bin_data", - "fieldtype": "Check", + "default": "Pending", + "fieldname": "delete_bin_data_status", + "fieldtype": "Select", "label": "Delete Bins", "no_copy": 1, + "options": "Pending\nCompleted\nSkipped", "read_only": 1 }, { - "default": "0", - "fieldname": "delete_leads_and_addresses", - "fieldtype": "Check", + "default": "Pending", + "fieldname": "delete_leads_and_addresses_status", + "fieldtype": "Select", "label": "Delete Leads and Addresses", "no_copy": 1, + "options": "Pending\nCompleted\nSkipped", "read_only": 1 }, { - "default": "0", - "fieldname": "clear_notifications", - "fieldtype": "Check", - "label": "Clear Notifications", - "no_copy": 1, - "read_only": 1 + "fieldname": "column_break_tasks_1", + "fieldtype": "Column Break" }, { - "default": "0", - "fieldname": "reset_company_default_values", - "fieldtype": "Check", + "default": "Pending", + "fieldname": "reset_company_default_values_status", + "fieldtype": "Select", "label": "Reset Company Default Values", "no_copy": 1, + "options": "Pending\nCompleted\nSkipped", "read_only": 1 }, { - "default": "0", - "fieldname": "delete_transactions", - "fieldtype": "Check", - "label": "Delete Transactions", + "default": "Pending", + "fieldname": "clear_notifications_status", + "fieldtype": "Select", + "label": "Clear Notifications", "no_copy": 1, + "options": "Pending\nCompleted\nSkipped", "read_only": 1 }, { - "default": "0", - "fieldname": "initialize_doctypes_table", - "fieldtype": "Check", + "fieldname": "column_break_tasks_2", + "fieldtype": "Column Break" + }, + { + "default": "Pending", + "fieldname": "initialize_doctypes_table_status", + "fieldtype": "Select", "label": "Initialize Summary Table", "no_copy": 1, + "options": "Pending\nCompleted\nSkipped", + "read_only": 1 + }, + { + "default": "Pending", + "fieldname": "delete_transactions_status", + "fieldtype": "Select", + "label": "Delete Transactions", + "no_copy": 1, + "options": "Pending\nCompleted\nSkipped", "read_only": 1 }, { @@ -144,7 +169,7 @@ "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2024-03-27 13:10:54.828051", + "modified": "2025-11-18 15:02:46.427695", "modified_by": "Administrator", "module": "Setup", "name": "Transaction Deletion Record", @@ -165,8 +190,9 @@ "write": 1 } ], + "row_format": "Dynamic", "sort_field": "creation", "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py index 25459ee8567..b308453c847 100644 --- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py +++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py @@ -7,6 +7,7 @@ import frappe from frappe import _, qb from frappe.desk.notifications import clear_notifications from frappe.model.document import Document +from frappe.query_builder.functions import Max from frappe.utils import cint, comma_and, create_batch, get_link_to_form from frappe.utils.background_jobs import get_job, is_job_enqueued from frappe.utils.caching import request_cache @@ -19,6 +20,95 @@ LEDGER_ENTRY_DOCTYPES = frozenset( ) ) +DELETION_CACHE_TTL = 4 * 60 * 60 # 4 hours in seconds + +PROTECTED_CORE_DOCTYPES = frozenset( + ( + # Core Meta + "DocType", + "DocField", + "Custom Field", + "Property Setter", + "DocPerm", + "Custom DocPerm", + # User & Permissions + "User", + "Role", + "Has Role", + "User Permission", + "User Type", + # System Configuration + "Module Def", + "Workflow", + "Workflow State", + "System Settings", + # Critical System DocTypes + "File", + "Version", + "Activity Log", + "Error Log", + "Scheduled Job Type", + "Scheduled Job Log", + "Server Script", + "Client Script", + "Data Import", + "Data Export", + "Report", + "Print Format", + "Email Template", + "Assignment Rule", + "Workspace", + "Dashboard", + "Access Log", + # Transaction Deletion + "Transaction Deletion Record", + "Company", + ) +) + + +@frappe.whitelist() +def get_protected_doctypes(): + """Get list of protected DocTypes that cannot be deleted (whitelisted for frontend)""" + frappe.only_for("System Manager") + return _get_protected_doctypes_internal() + + +@frappe.whitelist() +def get_company_link_fields(doctype_name): + """Get all Company Link field names for a DocType (whitelisted for frontend autocomplete) + + Args: + doctype_name: The DocType to check + + Returns: + list: List of field names that link to Company DocType, ordered by field index + """ + frappe.only_for("System Manager") + if not doctype_name or not frappe.db.exists("DocType", doctype_name): + return [] + + return frappe.get_all( + "DocField", + filters={"parent": doctype_name, "fieldtype": "Link", "options": "Company"}, + pluck="fieldname", + order_by="idx", + ) + + +def _get_protected_doctypes_internal(): + """Internal method to get protected doctypes""" + protected = [] + + for doctype in PROTECTED_CORE_DOCTYPES: + if frappe.db.exists("DocType", doctype): + protected.append(doctype) + + singles = frappe.get_all("DocType", filters={"issingle": 1}, pluck="name") + protected.extend(singles) + + return protected + class TransactionDeletionRecord(Document): # begin: auto-generated types @@ -35,19 +125,23 @@ class TransactionDeletionRecord(Document): from erpnext.setup.doctype.transaction_deletion_record_item.transaction_deletion_record_item import ( TransactionDeletionRecordItem, ) + from erpnext.setup.doctype.transaction_deletion_record_to_delete.transaction_deletion_record_to_delete import ( + TransactionDeletionRecordToDelete, + ) amended_from: DF.Link | None - clear_notifications: DF.Check + clear_notifications_status: DF.Literal["Pending", "Completed", "Skipped"] company: DF.Link - delete_bin_data: DF.Check - delete_leads_and_addresses: DF.Check - delete_transactions: DF.Check + delete_bin_data_status: DF.Literal["Pending", "Completed", "Skipped"] + delete_leads_and_addresses_status: DF.Literal["Pending", "Completed", "Skipped"] + delete_transactions_status: DF.Literal["Pending", "Completed", "Skipped"] doctypes: DF.Table[TransactionDeletionRecordDetails] doctypes_to_be_ignored: DF.Table[TransactionDeletionRecordItem] + doctypes_to_delete: DF.Table[TransactionDeletionRecordToDelete] error_log: DF.LongText | None - initialize_doctypes_table: DF.Check + initialize_doctypes_table_status: DF.Literal["Pending", "Completed", "Skipped"] process_in_single_transaction: DF.Check - reset_company_default_values: DF.Check + reset_company_default_values_status: DF.Literal["Pending", "Completed", "Skipped"] status: DF.Literal["Queued", "Running", "Failed", "Completed", "Cancelled"] # end: auto-generated types @@ -71,33 +165,90 @@ class TransactionDeletionRecord(Document): def validate(self): frappe.only_for("System Manager") - self.validate_doctypes_to_be_ignored() + self.validate_to_delete_list() - def validate_doctypes_to_be_ignored(self): - doctypes_to_be_ignored_list = get_doctypes_to_be_ignored() - for doctype in self.doctypes_to_be_ignored: - if doctype.doctype_name not in doctypes_to_be_ignored_list: + def validate_to_delete_list(self): + """Validate To Delete list: existence, protection status, child table exclusion, duplicates""" + if not self.doctypes_to_delete: + return + + protected = _get_protected_doctypes_internal() + seen_combinations = set() + + for item in self.doctypes_to_delete: + if not frappe.db.exists("DocType", item.doctype_name): + frappe.throw(_("DocType {0} does not exist").format(item.doctype_name)) + + # Check for duplicates using composite key + composite_key = (item.doctype_name, item.company_field or None) + if composite_key in seen_combinations: + field_desc = f" with company field '{item.company_field}'" if item.company_field else "" frappe.throw( - _( - "DocTypes should not be added manually to the 'Excluded DocTypes' table. You are only allowed to remove entries from it." - ), - title=_("Not Allowed"), + _("Duplicate entry: {0}{1}").format(item.doctype_name, field_desc), + title=_("Duplicate DocType"), + ) + seen_combinations.add(composite_key) + + # Validate protected DocTypes + if item.doctype_name in protected: + frappe.throw( + _("Cannot delete protected core DocType: {0}").format(item.doctype_name), + title=_("Protected DocType"), ) + is_child_table = frappe.db.get_value("DocType", item.doctype_name, "istable") + if is_child_table: + frappe.throw( + _( + "Cannot add child table {0} to deletion list. Child tables are automatically deleted with their parent DocTypes." + ).format(item.doctype_name), + title=_("Child Table Not Allowed"), + ) + + is_virtual = frappe.db.get_value("DocType", item.doctype_name, "is_virtual") + if is_virtual: + frappe.throw( + _( + "Cannot delete virtual DocType: {0}. Virtual DocTypes do not have database tables." + ).format(item.doctype_name), + title=_("Virtual DocType"), + ) + + # Validate company_field if specified + if item.company_field: + valid_company_fields = self._get_company_link_fields(item.doctype_name) + if item.company_field not in valid_company_fields: + frappe.throw( + _("Field '{0}' is not a valid Company link field for DocType {1}").format( + item.company_field, item.doctype_name + ), + title=_("Invalid Company Field"), + ) + + def _is_any_doctype_in_deletion_list(self, doctypes_list): + """Check if any DocType from the list is in the To Delete list""" + if not self.doctypes_to_delete: + return False + + deletion_doctypes = {d.doctype_name for d in self.doctypes_to_delete} + return any(doctype in deletion_doctypes for doctype in doctypes_list) + def generate_job_name_for_task(self, task=None): + """Generate unique job name for a specific task""" method = self.task_to_internal_method_map[task] return f"{self.name}_{method}" def generate_job_name_for_next_tasks(self, task=None): + """Generate job names for all tasks following the specified task""" job_names = [] current_task_idx = list(self.task_to_internal_method_map).index(task) for idx, task in enumerate(self.task_to_internal_method_map.keys(), 0): - # generate job_name for next tasks if idx > current_task_idx: job_names.append(self.generate_job_name_for_task(task)) return job_names def generate_job_name_for_all_tasks(self): + """Generate job names for all tasks in the deletion workflow""" job_names = [] for task in self.task_to_internal_method_map.keys(): job_names.append(self.generate_job_name_for_task(task)) @@ -106,28 +257,28 @@ class TransactionDeletionRecord(Document): def before_submit(self): if queued_docs := frappe.db.get_all( "Transaction Deletion Record", - filters={"company": self.company, "status": ("in", ["Running", "Queued"]), "docstatus": 1}, + filters={"status": ("in", ["Running", "Queued"]), "docstatus": 1}, pluck="name", ): frappe.throw( _( - "Cannot enqueue multi docs for one company. {0} is already queued/running for company: {1}" - ).format( - comma_and([get_link_to_form("Transaction Deletion Record", x) for x in queued_docs]), - frappe.bold(self.company), - ) + "Cannot start deletion. Another deletion {0} is already queued/running. Please wait for it to complete." + ).format(comma_and([get_link_to_form("Transaction Deletion Record", x) for x in queued_docs])) ) + if not self.doctypes_to_delete and not self.doctypes_to_be_ignored: + frappe.throw(_("Please generate To Delete list before submitting")) + if not self.doctypes_to_be_ignored: self.populate_doctypes_to_be_ignored_table() def reset_task_flags(self): - self.clear_notifications = 0 - self.delete_bin_data = 0 - self.delete_leads_and_addresses = 0 - self.delete_transactions = 0 - self.initialize_doctypes_table = 0 - self.reset_company_default_values = 0 + self.clear_notifications_status = "Pending" + self.delete_bin_data_status = "Pending" + self.delete_leads_and_addresses_status = "Pending" + self.delete_transactions_status = "Pending" + self.initialize_doctypes_table_status = "Pending" + self.reset_company_default_values_status = "Pending" def before_save(self): self.status = "" @@ -136,17 +287,288 @@ class TransactionDeletionRecord(Document): def on_submit(self): self.db_set("status", "Queued") + self.start_deletion_tasks() def on_cancel(self): self.db_set("status", "Cancelled") + self._clear_deletion_cache() + + def _set_deletion_cache(self): + """Set Redis cache flags for per-doctype validation""" + for item in self.doctypes_to_delete: + frappe.cache.set_value( + f"deletion_running_doctype:{item.doctype_name}", + self.name, + expires_in_sec=DELETION_CACHE_TTL, + ) + + def _clear_deletion_cache(self): + """Clear Redis cache flags""" + for item in self.doctypes_to_delete: + frappe.cache.delete_value(f"deletion_running_doctype:{item.doctype_name}") + + def _get_child_tables(self, doctype_name): + """Get list of child table DocType names for a given DocType + + Args: + doctype_name: The parent DocType to check + + Returns: + list: List of child table DocType names (Table field options) + """ + return frappe.get_all( + "DocField", filters={"parent": doctype_name, "fieldtype": "Table"}, pluck="options" + ) + + def _get_to_delete_row_infos(self, doctype_name, company_field=None, company=None): + """Get child tables and document count for a To Delete list row + + Args: + doctype_name: The DocType to get information for + company_field: Optional company field name to filter by + company: Optional company value (defaults to self.company) + + Returns: + dict: {"child_doctypes": str, "document_count": int} + """ + company = company or self.company + + child_tables = self._get_child_tables(doctype_name) + child_doctypes_str = ", ".join(child_tables) if child_tables else "" + + if company_field and company: + doc_count = frappe.db.count(doctype_name, filters={company_field: company}) + else: + doc_count = frappe.db.count(doctype_name) + + return { + "child_doctypes": child_doctypes_str, + "document_count": doc_count, + } + + def _has_company_field(self, doctype_name): + """Check if DocType has a field specifically named 'company' linking to Company""" + return frappe.db.exists( + "DocField", + {"parent": doctype_name, "fieldname": "company", "fieldtype": "Link", "options": "Company"}, + ) + + def _get_company_link_fields(self, doctype_name): + """Get all Company Link field names for a DocType + + Args: + doctype_name: The DocType to check + + Returns: + list: List of field names that link to Company DocType, ordered by field index + """ + company_fields = frappe.get_all( + "DocField", + filters={"parent": doctype_name, "fieldtype": "Link", "options": "Company"}, + pluck="fieldname", + order_by="idx", + ) + return company_fields or [] + + @frappe.whitelist() + def generate_to_delete_list(self): + """Generate To Delete list with one row per company field""" + self.doctypes_to_delete = [] + + excluded = [d.doctype_name for d in self.doctypes_to_be_ignored] + excluded.extend(_get_protected_doctypes_internal()) + excluded.append(self.doctype) # Exclude self + + # Get all DocTypes that have Company link fields + doctypes_with_company_field = frappe.get_all( + "DocField", + filters={"fieldtype": "Link", "options": "Company"}, + pluck="parent", + distinct=True, + ) + + # Filter to get only valid DocTypes (not child tables, not virtual, not excluded) + doctypes_with_company = [] + for doctype_name in doctypes_with_company_field: + if doctype_name in excluded: + continue + + # Check if doctype exists and is not a child table or virtual + if frappe.db.exists("DocType", doctype_name): + meta = frappe.get_meta(doctype_name) + if not meta.istable and not meta.is_virtual: + doctypes_with_company.append(doctype_name) + + for doctype_name in doctypes_with_company: + # Get ALL company fields for this DocType + company_fields = self._get_company_link_fields(doctype_name) + + # Get child tables once (same for all company fields of this DocType) + child_tables = self._get_child_tables(doctype_name) + child_doctypes_str = ", ".join(child_tables) if child_tables else "" + + for company_field in company_fields: + doc_count = frappe.db.count(doctype_name, {company_field: self.company}) + + self.append( + "doctypes_to_delete", + { + "doctype_name": doctype_name, + "company_field": company_field, + "document_count": doc_count, + "child_doctypes": child_doctypes_str, + }, + ) + + self.save() + return {"count": len(self.doctypes_to_delete)} + + @frappe.whitelist() + def populate_doctype_details(self, doctype_name, company=None, company_field=None): + """Get child DocTypes and document count for specified DocType + + Args: + doctype_name: The DocType to get details for + company: Optional company value for filtering (defaults to self.company) + company_field: Optional company field name to use for filtering + """ + frappe.only_for("System Manager") + + if not doctype_name: + return {} + + if not frappe.db.exists("DocType", doctype_name): + frappe.throw(_("DocType {0} does not exist").format(doctype_name)) + + is_child_table = frappe.db.get_value("DocType", doctype_name, "istable") + if is_child_table: + return { + "child_doctypes": "", + "document_count": 0, + "error": _("{0} is a child table and will be deleted automatically with its parent").format( + doctype_name + ), + } + + try: + return self._get_to_delete_row_infos(doctype_name, company_field=company_field, company=company) + except Exception as e: + frappe.log_error( + f"Error in populate_doctype_details for {doctype_name}: {e!s}", "Transaction Deletion Record" + ) + return { + "child_doctypes": "", + "document_count": 0, + "error": _("Unable to fetch DocType details. Please contact system administrator."), + } + + def export_to_delete_template_method(self): + """Export To Delete list as CSV template""" + if not self.doctypes_to_delete: + frappe.throw(_("Generate To Delete list first")) + + import csv + from io import StringIO + + output = StringIO() + writer = csv.writer(output) + writer.writerow(["doctype_name", "company_field", "child_doctypes"]) + + for item in self.doctypes_to_delete: + writer.writerow([item.doctype_name, item.company_field or "", item.child_doctypes or ""]) + + frappe.response["result"] = output.getvalue() + frappe.response["type"] = "csv" + frappe.response[ + "doctype" + ] = f"deletion_template_{self.company}_{frappe.utils.now_datetime().strftime('%Y%m%d')}" + + def import_to_delete_template_method(self, csv_content): + """Import CSV template and regenerate counts""" + import csv + from io import StringIO + + reader = csv.DictReader(StringIO(csv_content)) + + if "doctype_name" not in (reader.fieldnames or []): + frappe.throw(_("Invalid CSV format. Expected column: doctype_name")) + + self.doctypes_to_delete = [] + protected = _get_protected_doctypes_internal() + + imported_count = 0 + skipped = [] + + for row in reader: + doctype_name = row.get("doctype_name", "").strip() + company_field = row.get("company_field", "").strip() or None + + if not doctype_name: + continue + + if doctype_name in protected: + skipped.append(_("{0}: Protected DocType").format(doctype_name)) + continue + + if not frappe.db.exists("DocType", doctype_name): + skipped.append(_("{0}: Not found").format(doctype_name)) + continue + + is_child = frappe.db.get_value("DocType", doctype_name, "istable") + if is_child: + skipped.append(_("{0}: Child table (auto-deleted with parent)").format(doctype_name)) + continue + + is_virtual = frappe.db.get_value("DocType", doctype_name, "is_virtual") + if is_virtual: + skipped.append(_("{0}: Virtual DocType (no database table)").format(doctype_name)) + continue + + db_company_fields = self._get_company_link_fields(doctype_name) + import_company_field = "" + if not db_company_fields: # Case no company field exists + details = self._get_to_delete_row_infos(doctype_name) + elif ( + company_field and company_field in db_company_fields + ): # Case it is provided by export and valid + details = self._get_to_delete_row_infos(doctype_name, company_field) + import_company_field = company_field + else: # Company field exists but not provided by export or invalid + if "company" in db_company_fields: # Check if 'company' is a valid field + details = self._get_to_delete_row_infos(doctype_name, "company") + import_company_field = "company" + else: # Fallback to first valid company field + details = self._get_to_delete_row_infos(doctype_name, db_company_fields[0]) + import_company_field = db_company_fields[0] + + self.append( + "doctypes_to_delete", + { + "doctype_name": doctype_name, + "company_field": import_company_field, + "document_count": details["document_count"], + "child_doctypes": details["child_doctypes"], + }, + ) + imported_count += 1 + + self.save() + + if skipped: + frappe.msgprint( + _("Skipped {0} DocType(s):
{1}").format(len(skipped), "
".join(skipped)), + title=_("Import Summary"), + indicator="orange", + ) + + return {"imported": imported_count, "skipped": len(skipped)} def enqueue_task(self, task: str | None = None): + """Enqueue a deletion task for background execution""" if task and task in self.task_to_internal_method_map: - # make sure that none of next tasks are already running job_names = self.generate_job_name_for_next_tasks(task=task) self.validate_running_task_for_doc(job_names=job_names) - # Generate Job Id to uniquely identify each task for this document job_id = self.generate_job_name_for_task(task) if self.process_in_single_transaction: @@ -176,12 +598,13 @@ class TransactionDeletionRecord(Document): message = "Traceback:
" + traceback frappe.db.set_value(self.doctype, self.name, "error_log", message) frappe.db.set_value(self.doctype, self.name, "status", "Failed") + self._clear_deletion_cache() def delete_notifications(self): self.validate_doc_status() - if not self.clear_notifications: + if self.clear_notifications_status == "Pending": clear_notifications() - self.db_set("clear_notifications", 1) + self.db_set("clear_notifications_status", "Completed") self.enqueue_task(task="Initialize Summary Table") def populate_doctypes_to_be_ignored_table(self): @@ -215,23 +638,46 @@ class TransactionDeletionRecord(Document): def start_deletion_tasks(self): # This method is the entry point for the chain of events that follow self.db_set("status", "Running") + self._set_deletion_cache() self.enqueue_task(task="Delete Bins") def delete_bins(self): self.validate_doc_status() - if not self.delete_bin_data: + if self.delete_bin_data_status == "Pending": + stock_related_doctypes = [ + "Item", + "Warehouse", + "Stock Entry", + "Delivery Note", + "Purchase Receipt", + "Stock Reconciliation", + "Material Request", + "Purchase Invoice", + "Sales Invoice", + ] + + if not self._is_any_doctype_in_deletion_list(stock_related_doctypes): + self.db_set("delete_bin_data_status", "Skipped") + self.enqueue_task(task="Delete Leads and Addresses") + return + frappe.db.sql( """delete from `tabBin` where warehouse in (select name from tabWarehouse where company=%s)""", self.company, ) - self.db_set("delete_bin_data", 1) + self.db_set("delete_bin_data_status", "Completed") self.enqueue_task(task="Delete Leads and Addresses") def delete_lead_addresses(self): """Delete addresses to which leads are linked""" self.validate_doc_status() - if not self.delete_leads_and_addresses: + if self.delete_leads_and_addresses_status == "Pending": + if not self._is_any_doctype_in_deletion_list(["Lead"]): + self.db_set("delete_leads_and_addresses_status", "Skipped") + self.enqueue_task(task="Reset Company Values") + return + leads = frappe.db.get_all("Lead", filters={"company": self.company}, pluck="name") addresses = [] if leads: @@ -268,54 +714,94 @@ class TransactionDeletionRecord(Document): customer = qb.DocType("Customer") qb.update(customer).set(customer.lead_name, None).where(customer.lead_name.isin(leads)).run() - self.db_set("delete_leads_and_addresses", 1) + self.db_set("delete_leads_and_addresses_status", "Completed") self.enqueue_task(task="Reset Company Values") def reset_company_values(self): self.validate_doc_status() - if not self.reset_company_default_values: + if self.reset_company_default_values_status == "Pending": + sales_related_doctypes = [ + "Sales Order", + "Sales Invoice", + "Quotation", + "Delivery Note", + ] + + if not self._is_any_doctype_in_deletion_list(sales_related_doctypes): + self.db_set("reset_company_default_values_status", "Skipped") + self.enqueue_task(task="Clear Notifications") + return + company_obj = frappe.get_doc("Company", self.company) company_obj.total_monthly_sales = 0 company_obj.sales_monthly_history = None company_obj.save() - self.db_set("reset_company_default_values", 1) + self.db_set("reset_company_default_values_status", "Completed") self.enqueue_task(task="Clear Notifications") def initialize_doctypes_to_be_deleted_table(self): + """Initialize deletion table from To Delete list or fall back to original logic""" self.validate_doc_status() - if not self.initialize_doctypes_table: - doctypes_to_be_ignored_list = self.get_doctypes_to_be_ignored_list() - docfields = self.get_doctypes_with_company_field(doctypes_to_be_ignored_list) + if self.initialize_doctypes_table_status == "Pending": + # Use To Delete list if available (new behavior) + if not self.doctypes_to_delete: + frappe.throw( + _("No DocTypes in To Delete list. Please generate or import the list before submitting."), + title=_("Empty To Delete List"), + ) tables = self.get_all_child_doctypes() - for docfield in docfields: - if docfield["parent"] != self.doctype: - no_of_docs = self.get_number_of_docs_linked_with_specified_company( - docfield["parent"], docfield["fieldname"] + + for to_delete_item in self.doctypes_to_delete: + if to_delete_item.document_count > 0: + # Add parent DocType only - child tables are handled automatically + # by delete_child_tables() when the parent is deleted + # Use company_field directly from To Delete item + self.populate_doctypes_table( + tables, to_delete_item.doctype_name, to_delete_item.company_field, 0 ) - if no_of_docs > 0: - # Initialize - self.populate_doctypes_table(tables, docfield["parent"], docfield["fieldname"], 0) - self.db_set("initialize_doctypes_table", 1) + self.db_set("initialize_doctypes_table_status", "Completed") self.enqueue_task(task="Delete Transactions") def delete_company_transactions(self): self.validate_doc_status() - if not self.delete_transactions: - doctypes_to_be_ignored_list = self.get_doctypes_to_be_ignored_list() - self.get_doctypes_with_company_field(doctypes_to_be_ignored_list) + if self.delete_transactions_status == "Pending": + protected_doctypes = _get_protected_doctypes_internal() - self.get_all_child_doctypes() for docfield in self.doctypes: if docfield.doctype_name != self.doctype and not docfield.done: - no_of_docs = self.get_number_of_docs_linked_with_specified_company( - docfield.doctype_name, docfield.docfield_name - ) - if no_of_docs > 0: - reference_docs = frappe.get_all( - docfield.doctype_name, - filters={docfield.docfield_name: self.company}, - limit=self.batch_size, + if docfield.doctype_name in protected_doctypes: + error_msg = ( + f"CRITICAL: Attempted to delete protected DocType: {docfield.doctype_name}" ) + frappe.log_error(error_msg, "Transaction Deletion Security") + frappe.throw( + _("Cannot delete protected core DocType: {0}").format(docfield.doctype_name), + title=_("Protected DocType"), + ) + + # Get company_field from stored value (could be any Company link field) + company_field = docfield.docfield_name + + if company_field: + no_of_docs = self.get_number_of_docs_linked_with_specified_company( + docfield.doctype_name, company_field + ) + else: + no_of_docs = frappe.db.count(docfield.doctype_name) + + if no_of_docs > 0: + if company_field: + reference_docs = frappe.get_all( + docfield.doctype_name, + filters={company_field: self.company}, + fields=["name"], + limit=self.batch_size, + ) + else: + reference_docs = frappe.get_all( + docfield.doctype_name, fields=["name"], limit=self.batch_size + ) + reference_doc_names = [r.name for r in reference_docs] self.delete_version_log(docfield.doctype_name, reference_doc_names) @@ -329,26 +815,38 @@ class TransactionDeletionRecord(Document): processed = int(docfield.no_of_docs) + len(reference_doc_names) frappe.db.set_value(docfield.doctype, docfield.name, "no_of_docs", processed) else: - # reset naming series naming_series = frappe.db.get_value("DocType", docfield.doctype_name, "autoname") if naming_series: if "#" in naming_series: self.update_naming_series(naming_series, docfield.doctype_name) frappe.db.set_value(docfield.doctype, docfield.name, "done", 1) + to_delete_row = frappe.db.get_value( + "Transaction Deletion Record To Delete", + { + "parent": self.name, + "doctype_name": docfield.doctype_name, + "company_field": company_field, + }, + "name", + ) + if to_delete_row: + frappe.db.set_value( + "Transaction Deletion Record To Delete", to_delete_row, "deleted", 1 + ) + pending_doctypes = frappe.db.get_all( "Transaction Deletion Record Details", filters={"parent": self.name, "done": 0}, pluck="doctype_name", ) if pending_doctypes: - # as method is enqueued after commit, calling itself will not make validate_doc_status to throw - # recursively call this task to delete all transactions self.enqueue_task(task="Delete Transactions") else: self.db_set("status", "Completed") - self.db_set("delete_transactions", 1) + self.db_set("delete_transactions_status", "Completed") self.db_set("error_log", None) + self._clear_deletion_cache() def get_doctypes_to_be_ignored_list(self): doctypes_to_be_ignored_list = frappe.get_all( @@ -378,18 +876,33 @@ class TransactionDeletionRecord(Document): def get_number_of_docs_linked_with_specified_company(self, doctype, company_fieldname): return frappe.db.count(doctype, {company_fieldname: self.company}) - def populate_doctypes_table(self, tables, doctype, fieldname, no_of_docs): + def get_company_field(self, doctype_name): + """Get company field name for a DocType""" + return frappe.db.get_value( + "DocField", + {"parent": doctype_name, "fieldtype": "Link", "options": "Company"}, + "fieldname", + ) + + def populate_doctypes_table(self, tables, doctype, company_field, no_of_docs): + """Add doctype to processing tracker + + Args: + tables: List of child table DocType names (to exclude) + doctype: DocType name to track + company_field: Company link field name (or None) + no_of_docs: Initial count + """ self.flags.ignore_validate_update_after_submit = True if doctype not in tables: self.append( - "doctypes", {"doctype_name": doctype, "docfield_name": fieldname, "no_of_docs": no_of_docs} + "doctypes", + {"doctype_name": doctype, "docfield_name": company_field, "no_of_docs": no_of_docs}, ) self.save(ignore_permissions=True) def delete_child_tables(self, doctype, reference_doc_names): - child_tables = frappe.get_all( - "DocField", filters={"fieldtype": "Table", "parent": doctype}, pluck="options" - ) + child_tables = self._get_child_tables(doctype) for table in child_tables: frappe.db.delete(table, {"parent": ["in", reference_doc_names]}) @@ -397,22 +910,52 @@ class TransactionDeletionRecord(Document): def delete_docs_linked_with_specified_company(self, doctype, reference_doc_names): frappe.db.delete(doctype, {"name": ("in", reference_doc_names)}) - def update_naming_series(self, naming_series, doctype_name): + @staticmethod + def get_naming_series_prefix(naming_series: str, doctype_name: str) -> str: + """Extract the static prefix from an autoname pattern. + + Args: + naming_series: The autoname pattern (e.g., "PREFIX.####", "format:PRE-{####}") + doctype_name: DocType name for error logging + + Returns: + The static prefix before the counter placeholders + """ if "." in naming_series: - prefix, hashes = naming_series.rsplit(".", 1) + prefix = naming_series.rsplit(".", 1)[0] + elif "{" in naming_series: + prefix = naming_series.rsplit("{", 1)[0] else: - prefix, hashes = naming_series.rsplit("{", 1) - last = frappe.db.sql( - f"""select max(name) from `tab{doctype_name}` - where name like %s""", - prefix + "%", + # Fallback for unexpected patterns (shouldn't happen with valid Frappe naming series) + frappe.log_error( + title=_("Unexpected Naming Series Pattern"), + message=_( + "Naming series '{0}' for DocType '{1}' does not contain standard '.' or '{{' separator. Using fallback extraction." + ).format(naming_series, doctype_name), + ) + prefix = naming_series.split("#", 1)[0] if "#" in naming_series else naming_series + + return prefix + + def update_naming_series(self, naming_series, doctype_name): + # Derive a static prefix from the autoname pattern + prefix = self.get_naming_series_prefix(naming_series, doctype_name) + + # Find the highest number used in the naming series to reset the counter + doctype_table = qb.DocType(doctype_name) + result = ( + qb.from_(doctype_table) + .select(Max(doctype_table.name)) + .where(doctype_table.name.like(prefix + "%")) + .run() ) - if last and last[0][0]: - last = cint(last[0][0].replace(prefix, "")) + + if result and result[0][0]: + last = cint(result[0][0].replace(prefix, "")) else: last = 0 - frappe.db.sql("""update `tabSeries` set current = %s where name=%s""", (last, prefix)) + frappe.db.set_value("Series", prefix, "current", last, update_modified=False) def delete_version_log(self, doctype, docnames): versions = qb.DocType("Version") @@ -487,15 +1030,61 @@ def get_doctypes_to_be_ignored(): return doctypes_to_be_ignored +@frappe.whitelist() +def export_to_delete_template(name): + """Export To Delete list as CSV via URL access""" + frappe.only_for("System Manager") + doc = frappe.get_doc("Transaction Deletion Record", name) + doc.check_permission("read") + return doc.export_to_delete_template_method() + + +@frappe.whitelist() +def process_import_template(transaction_deletion_record_name, file_url): + """Import CSV template and populate To Delete list""" + import os + + doc = frappe.get_doc("Transaction Deletion Record", transaction_deletion_record_name) + doc.check_permission("write") + + if not file_url or ".." in file_url: + frappe.throw(_("Invalid file URL")) + + try: + file_doc = frappe.get_doc("File", {"file_url": file_url}) + except frappe.DoesNotExistError: + frappe.throw(_("File not found")) + + if ( + file_doc.attached_to_doctype != "Transaction Deletion Record" + or file_doc.attached_to_name != transaction_deletion_record_name + ): + frappe.throw(_("File does not belong to this Transaction Deletion Record")) + + if not file_doc.file_name or not file_doc.file_name.lower().endswith(".csv"): + frappe.throw(_("Only CSV files are allowed")) + + file_path = file_doc.get_full_path() + + if not os.path.isfile(file_path): + frappe.throw(_("File not found on server")) + + with open(file_path, encoding="utf-8") as f: + csv_content = f.read() + + return doc.import_to_delete_template_method(csv_content) + + @frappe.whitelist() @request_cache def is_deletion_doc_running(company: str | None = None, err_msg: str | None = None): - if not company: - return + """Check if any deletion is running globally + The company parameter is kept for backwards compatibility but is now ignored. + """ running_deletion_job = frappe.db.get_value( "Transaction Deletion Record", - {"docstatus": 1, "company": company, "status": "Running"}, + {"docstatus": 1, "status": ("in", ["Running", "Queued"])}, "name", ) @@ -504,17 +1093,28 @@ def is_deletion_doc_running(company: str | None = None, err_msg: str | None = No frappe.throw( title=_("Deletion in Progress!"), - msg=_("Transaction Deletion Document: {0} is running for this Company. {1}").format( + msg=_("Transaction Deletion Record {0} is already running. {1}").format( get_link_to_form("Transaction Deletion Record", running_deletion_job), err_msg or "" ), ) def check_for_running_deletion_job(doc, method=None): - # Check if DocType has 'company' field - if doc.doctype in LEDGER_ENTRY_DOCTYPES or not doc.meta.has_field("company"): + """Hook function called on document validate - checks Redis cache for running deletions""" + if doc.doctype in LEDGER_ENTRY_DOCTYPES: return - is_deletion_doc_running( - doc.company, _("Cannot make any transactions until the deletion job is completed") - ) + if doc.doctype in PROTECTED_CORE_DOCTYPES: + return + + deletion_name = frappe.cache.get_value(f"deletion_running_doctype:{doc.doctype}") + + if deletion_name: + frappe.throw( + title=_("Deletion in Progress!"), + msg=_( + "Transaction Deletion Record {0} is currently deleting {1}. Cannot save documents until deletion completes." + ).format( + get_link_to_form("Transaction Deletion Record", deletion_name), frappe.bold(doc.doctype) + ), + ) diff --git a/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json index ee9cc968c17..70688b7a860 100644 --- a/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json +++ b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json @@ -17,17 +17,19 @@ "reqd": 1 } ], + "grid_page_length": 50, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2024-03-27 13:10:55.128861", + "modified": "2025-11-14 16:17:47.755531", "modified_by": "Administrator", "module": "Setup", "name": "Transaction Deletion Record Item", "owner": "Administrator", "permissions": [], + "row_format": "Dynamic", "sort_field": "creation", "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/setup/doctype/transaction_deletion_record_to_delete/__init__.py b/erpnext/setup/doctype/transaction_deletion_record_to_delete/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/setup/doctype/transaction_deletion_record_to_delete/transaction_deletion_record_to_delete.json b/erpnext/setup/doctype/transaction_deletion_record_to_delete/transaction_deletion_record_to_delete.json new file mode 100644 index 00000000000..2cc94b3ee7d --- /dev/null +++ b/erpnext/setup/doctype/transaction_deletion_record_to_delete/transaction_deletion_record_to_delete.json @@ -0,0 +1,67 @@ +{ + "actions": [], + "creation": "2025-11-14 00:00:00", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "doctype_name", + "company_field", + "document_count", + "child_doctypes", + "deleted" + ], + "fields": [ + { + "fieldname": "doctype_name", + "fieldtype": "Link", + "in_list_view": 1, + "label": "DocType", + "options": "DocType" + }, + { + "description": "Company link field name used for filtering (optional - leave empty to delete all records)", + "fieldname": "company_field", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Company Field" + }, + { + "fieldname": "document_count", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Document Count", + "read_only": 1 + }, + { + "description": "Child tables that will also be deleted", + "fieldname": "child_doctypes", + "fieldtype": "Small Text", + "in_list_view": 1, + "label": "Child DocTypes", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "deleted", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Deleted", + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2025-11-14 16:17:04.494126", + "modified_by": "Administrator", + "module": "Setup", + "name": "Transaction Deletion Record To Delete", + "owner": "Administrator", + "permissions": [], + "row_format": "Dynamic", + "sort_field": "creation", + "sort_order": "DESC", + "states": [], + "track_changes": 1 +} diff --git a/erpnext/setup/doctype/transaction_deletion_record_to_delete/transaction_deletion_record_to_delete.py b/erpnext/setup/doctype/transaction_deletion_record_to_delete/transaction_deletion_record_to_delete.py new file mode 100644 index 00000000000..e7883eaa0d0 --- /dev/null +++ b/erpnext/setup/doctype/transaction_deletion_record_to_delete/transaction_deletion_record_to_delete.py @@ -0,0 +1,27 @@ +# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class TransactionDeletionRecordToDelete(Document): + # begin: auto-generated types + # This code is auto-generated. Do not modify anything in this block. + + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from frappe.types import DF + + child_doctypes: DF.SmallText | None + company_field: DF.Data | None + deleted: DF.Check + doctype_name: DF.Link | None + document_count: DF.Int + parent: DF.Data + parentfield: DF.Data + parenttype: DF.Data + # end: auto-generated types + + pass diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py index 3cdd7833908..122503027b1 100644 --- a/erpnext/setup/utils.py +++ b/erpnext/setup/utils.py @@ -202,7 +202,7 @@ def enable_all_roles_and_domains(): def _enable_all_roles_for_admin(): from frappe.desk.page.setup_wizard.setup_wizard import add_all_roles_to - all_roles = set(frappe.db.get_values("Role", pluck="name")) + all_roles = set(frappe.get_all("Role", pluck="name")) admin_roles = set( frappe.db.get_values("Has Role", {"parent": "Administrator"}, fieldname="role", pluck="role") ) diff --git a/erpnext/stock/doctype/bin/bin.json b/erpnext/stock/doctype/bin/bin.json index 7de1aa9d189..b0668896597 100644 --- a/erpnext/stock/doctype/bin/bin.json +++ b/erpnext/stock/doctype/bin/bin.json @@ -22,6 +22,7 @@ "reserved_stock", "section_break_pmrs", "stock_uom", + "company", "column_break_0slj", "valuation_rate", "stock_value" @@ -132,6 +133,14 @@ "options": "UOM", "read_only": 1 }, + { + "fetch_from": "warehouse.company", + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "read_only": 1 + }, { "fieldname": "valuation_rate", "fieldtype": "Float", @@ -186,7 +195,7 @@ "idx": 1, "in_create": 1, "links": [], - "modified": "2024-03-27 13:06:39.414036", + "modified": "2026-02-01 08:11:46.824913", "modified_by": "Administrator", "module": "Stock", "name": "Bin", @@ -231,8 +240,9 @@ } ], "quick_entry": 1, + "row_format": "Dynamic", "search_fields": "item_code,warehouse", "sort_field": "creation", "sort_order": "ASC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py index 62c4528f432..ae1c44d6419 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -19,6 +19,7 @@ class Bin(Document): from frappe.types import DF actual_qty: DF.Float + company: DF.Link | None indented_qty: DF.Float item_code: DF.Link ordered_qty: DF.Float diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index 82c2851a181..9abe8c8c409 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -1070,7 +1070,7 @@ "no_copy": 1, "oldfieldname": "status", "oldfieldtype": "Select", - "options": "\nDraft\nTo Bill\nCompleted\nReturn\nReturn Issued\nCancelled\nClosed", + "options": "\nDraft\nTo Bill\nPartially Billed\nCompleted\nReturn\nReturn Issued\nCancelled\nClosed", "print_hide": 1, "print_width": "150px", "read_only": 1, @@ -1426,14 +1426,15 @@ "hidden": 1, "label": "Item Wise Tax Details", "no_copy": 1, - "options": "Item Wise Tax Detail" + "options": "Item Wise Tax Detail", + "print_hide": 1 } ], "icon": "fa fa-truck", "idx": 146, "is_submittable": 1, "links": [], - "modified": "2025-12-02 23:55:25.415443", + "modified": "2026-02-03 12:27:19.055918", "modified_by": "Administrator", "module": "Stock", "name": "Delivery Note", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 3b52f91b492..07c1623a182 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -127,7 +127,15 @@ class DeliveryNote(SellingController): shipping_address_name: DF.Link | None shipping_rule: DF.Link | None status: DF.Literal[ - "", "Draft", "To Bill", "Completed", "Return", "Return Issued", "Cancelled", "Closed" + "", + "Draft", + "To Bill", + "Partially Billed", + "Completed", + "Return", + "Return Issued", + "Cancelled", + "Closed", ] tax_category: DF.Link | None tax_id: DF.Data | None diff --git a/erpnext/stock/doctype/delivery_note/delivery_note_list.js b/erpnext/stock/doctype/delivery_note/delivery_note_list.js index fccc401931e..56698ccf76b 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note_list.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note_list.js @@ -18,8 +18,10 @@ frappe.listview_settings["Delivery Note"] = { return [__("Closed"), "green", "status,=,Closed"]; } else if (doc.status === "Return Issued") { return [__("Return Issued"), "grey", "status,=,Return Issued"]; - } else if (flt(doc.per_billed, 2) < 100) { - return [__("To Bill"), "orange", "per_billed,<,100|docstatus,=,1"]; + } else if (flt(doc.per_billed) == 0) { + return [__("To Bill"), "orange", "per_billed,=,0|docstatus,=,1"]; + } else if (flt(doc.per_billed, 2) > 0 && flt(doc.per_billed, 2) < 100) { + return [__("Partially Billed"), "yellow", "per_billed,<,100|docstatus,=,1"]; } else if (flt(doc.per_billed, 2) === 100) { return [__("Completed"), "green", "per_billed,=,100|docstatus,=,1"]; } diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index d85a76f9f75..71ed8b753cb 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -1101,7 +1101,8 @@ class TestDeliveryNote(IntegrationTestCase): self.assertEqual(dn2.get("items")[0].billed_amt, 400) self.assertEqual(dn2.per_billed, 80) - self.assertEqual(dn2.status, "To Bill") + # Since 20% of DN2 is yet to be billed, it should be classified as partially billed. + self.assertEqual(dn2.status, "Partially Billed") def test_dn_billing_status_case4(self): # SO -> SI -> DN @@ -2864,6 +2865,23 @@ class TestDeliveryNote(IntegrationTestCase): for entry in sabb.entries: self.assertEqual(entry.incoming_rate, 200) + @IntegrationTestCase.change_settings("Selling Settings", {"validate_selling_price": 1}) + def test_validate_selling_price(self): + item_code = make_item("VSP Item", properties={"is_stock_item": 1}).name + make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=1, basic_rate=10) + make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=1, basic_rate=1) + + dn = create_delivery_note( + item_code=item_code, + qty=1, + rate=9, + do_not_save=True, + ) + self.assertRaises(frappe.ValidationError, dn.save) + dn.items[0].incoming_rate = 0 + dn.items[0].stock_qty = 2 + dn.save() + def create_delivery_note(**args): dn = frappe.new_doc("Delivery Note") diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json index 5586f1355c9..1e93e50b10d 100755 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json @@ -1282,7 +1282,8 @@ "hidden": 1, "label": "Item Wise Tax Details", "no_copy": 1, - "options": "Item Wise Tax Detail" + "options": "Item Wise Tax Detail", + "print_hide": 1 } ], "grid_page_length": 50, @@ -1290,7 +1291,7 @@ "idx": 261, "is_submittable": 1, "links": [], - "modified": "2025-11-27 16:46:30.210628", + "modified": "2026-01-29 21:24:30.652933", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt", diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index b426b333c02..df0b28f696e 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -5036,6 +5036,206 @@ class TestPurchaseReceipt(IntegrationTestCase): return_pr = make_return_doc("Purchase Receipt", pr.name) self.assertRaises(frappe.ValidationError, return_pr.submit) + def test_internal_purchase_receipt_incoming_rate_with_lcv(self): + """ + To test inter branch transaction incoming rate calculation with lcv after item reposting + """ + 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 + + prepare_data_for_internal_transfer() + customer = "_Test Internal Customer 2" + company = "_Test Company with perpetual inventory" + item_doc = create_item("_Test Internal PR LCV Item") + lcv_expense_account = "Expenses Included In Valuation - TCP1" + + from_warehouse = create_warehouse("_Test Internal From Warehouse LCV", company=company) + to_warehouse = create_warehouse("_Test Internal To Warehouse LCV", company=company) + + # inward qty for internal transactions + make_purchase_receipt( + item_code=item_doc.item_code, + qty=5, + rate=100, + company="_Test Company with perpetual inventory", + warehouse=from_warehouse, + ) + + idn = create_delivery_note( + item_code=item_doc.name, + company=company, + customer=customer, + cost_center="Main - TCP1", + expense_account="Cost of Goods Sold - TCP1", + qty=5, + rate=100, + warehouse=from_warehouse, + target_warehouse=to_warehouse, + ) + self.assertEqual(idn.items[0].rate, 100) + + ipr = make_inter_company_purchase_receipt(idn.name) + ipr.items[0].warehouse = from_warehouse + self.assertEqual(ipr.items[0].rate, 100) + ipr.submit() + + self.create_lcv(ipr.doctype, ipr.name, company, lcv_expense_account, charges=100) + ipr.reload() + + self.assertEqual(ipr.items[0].landed_cost_voucher_amount, 100) + self.assertEqual(ipr.items[0].valuation_rate, 120) + + # repost the receipt and check the stock ledger values + repost_doc = frappe.new_doc("Repost Item Valuation") + repost_doc.update( + { + "based_on": "Transaction", + "voucher_type": ipr.doctype, + "voucher_no": ipr.name, + "posting_date": ipr.posting_date, + "posting_time": ipr.posting_time, + "company": ipr.company, + "allow_negative_stock": 1, + "via_landed_cost_voucher": 0, + "allow_zero_rate": 0, + } + ) + repost_doc.save() + repost_doc.submit() + + stk_ledger = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": "Purchase Receipt", "voucher_no": ipr.name, "warehouse": from_warehouse}, + ["incoming_rate", "stock_value_difference"], + as_dict=True, + ) + + # check the incoming rate and stock value change + self.assertEqual(stk_ledger.incoming_rate, 120) + self.assertEqual(stk_ledger.stock_value_difference, 600) + + def test_negative_stock_error_for_purchase_return_when_stock_exists_in_future_date(self): + from erpnext.controllers.sales_and_purchase_return import make_return_doc + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + from erpnext.stock.stock_ledger import NegativeStockError + + item_code = make_item( + "Test Negative Stock for Purchase Return with Future Stock Item", + { + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "TNSPFPRI.#####", + }, + ).name + + make_purchase_receipt( + item_code=item_code, + posting_date=add_days(today(), -4), + qty=100, + rate=100, + warehouse="_Test Warehouse - _TC", + ) + + pr1 = make_purchase_receipt( + item_code=item_code, + posting_date=add_days(today(), -3), + qty=100, + rate=100, + warehouse="_Test Warehouse - _TC", + ) + + batch1 = get_batch_from_bundle(pr1.items[0].serial_and_batch_bundle) + + pr2 = make_purchase_receipt( + item_code=item_code, + posting_date=add_days(today(), -2), + qty=100, + rate=100, + warehouse="_Test Warehouse - _TC", + ) + + batch2 = get_batch_from_bundle(pr2.items[0].serial_and_batch_bundle) + + make_stock_entry( + item_code=item_code, + qty=100, + posting_date=add_days(today(), -1), + source="_Test Warehouse - _TC", + target="_Test Warehouse 1 - _TC", + batch_no=batch1, + use_serial_batch_fields=1, + ) + + make_stock_entry( + item_code=item_code, + qty=100, + posting_date=add_days(today(), -1), + source="_Test Warehouse - _TC", + target="_Test Warehouse 1 - _TC", + batch_no=batch2, + use_serial_batch_fields=1, + ) + + make_stock_entry( + item_code=item_code, + qty=100, + posting_date=today(), + source="_Test Warehouse 1 - _TC", + target="_Test Warehouse - _TC", + batch_no=batch1, + use_serial_batch_fields=1, + ) + + make_purchase_entry = make_return_doc("Purchase Receipt", pr1.name) + make_purchase_entry.set_posting_time = 1 + make_purchase_entry.posting_date = pr1.posting_date + self.assertRaises(NegativeStockError, make_purchase_entry.submit) + + def test_purchase_return_from_different_warehouse(self): + from erpnext.controllers.sales_and_purchase_return import make_return_doc + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + + item_code = make_item( + "Test Purchase Return From Different Warehouse Item", + { + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "TPRFDWU.#####", + }, + ).name + + pr1 = make_purchase_receipt( + item_code=item_code, + posting_date=add_days(today(), -4), + qty=100, + rate=100, + warehouse="_Test Warehouse - _TC", + ) + + batch1 = get_batch_from_bundle(pr1.items[0].serial_and_batch_bundle) + + make_stock_entry( + item_code=item_code, + qty=100, + posting_date=add_days(today(), -1), + source="_Test Warehouse - _TC", + target="_Test Warehouse 1 - _TC", + batch_no=batch1, + use_serial_batch_fields=1, + ) + + make_purchase_entry = make_return_doc("Purchase Receipt", pr1.name) + make_purchase_entry.items[0].warehouse = "_Test Warehouse 1 - _TC" + make_purchase_entry.submit() + make_purchase_entry.reload() + + sabb = frappe.get_doc("Serial and Batch Bundle", make_purchase_entry.items[0].serial_and_batch_bundle) + for row in sabb.entries: + self.assertEqual(row.warehouse, "_Test Warehouse 1 - _TC") + self.assertEqual(row.incoming_rate, 100) + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 464a56e68b4..7ac65775b23 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -17,6 +17,7 @@ from frappe.utils import ( cint, cstr, flt, + get_datetime, get_link_to_form, getdate, now, @@ -439,6 +440,8 @@ class SerialandBatchBundle(Document): ) def get_valuation_rate_for_return_entry(self, return_against): + from erpnext.controllers.sales_and_purchase_return import get_warehouses_for_return + if not self.voucher_detail_no: return {} @@ -468,9 +471,11 @@ class SerialandBatchBundle(Document): ["Serial and Batch Bundle", "voucher_detail_no", "=", return_against_voucher_detail_no], ] + # Added to handle rejected warehouse case if self.voucher_type in ["Purchase Receipt", "Purchase Invoice"]: - # Added to handle rejected warehouse case - filters.append(["Serial and Batch Entry", "warehouse", "=", self.warehouse]) + warehouses = get_warehouses_for_return(self.voucher_type, return_against_voucher_detail_no) + if self.warehouse in warehouses: + filters.append(["Serial and Batch Entry", "warehouse", "=", self.warehouse]) bundle_data = frappe.get_all( "Serial and Batch Bundle", @@ -1452,31 +1457,44 @@ class SerialandBatchBundle(Document): for d in self.entries: available_qty = batch_wise_available_qty.get(d.batch_no, 0) if flt(available_qty, precision) < 0: - frappe.throw( - _( - """ - The Batch {0} of an item {1} has negative stock in the warehouse {2}. Please add a stock quantity of {3} to proceed with this entry.""" - ).format( - bold(d.batch_no), - bold(self.item_code), - bold(self.warehouse), - bold(abs(flt(available_qty, precision))), - ), - title=_("Negative Stock Error"), - ) + self.throw_negative_batch(d.batch_no, available_qty, precision) + + def throw_negative_batch(self, batch_no, available_qty, precision): + from erpnext.stock.stock_ledger import NegativeStockError + + frappe.throw( + _( + """ + The Batch {0} of an item {1} has negative stock in the warehouse {2}. Please add a stock quantity of {3} to proceed with this entry.""" + ).format( + bold(batch_no), + bold(self.item_code), + bold(self.warehouse), + bold(abs(flt(available_qty, precision))), + ), + title=_("Negative Stock Error"), + exc=NegativeStockError, + ) def get_batchwise_available_qty(self): - available_qty = self.get_available_qty_from_sabb() - available_qty_from_ledger = self.get_available_qty_from_stock_ledger() + batchwise_entries = self.get_available_qty_from_sabb() + batchwise_entries.extend(self.get_available_qty_from_stock_ledger()) - if not available_qty_from_ledger: - return available_qty + available_qty = frappe._dict({}) + batchwise_entries = sorted( + batchwise_entries, + key=lambda x: (get_datetime(x.get("posting_datetime")), get_datetime(x.get("creation"))), + ) - for batch_no, qty in available_qty_from_ledger.items(): - if batch_no in available_qty: - available_qty[batch_no] += qty + precision = frappe.get_precision("Serial and Batch Entry", "qty") + for row in batchwise_entries: + if row.batch_no in available_qty: + available_qty[row.batch_no] += flt(row.qty) else: - available_qty[batch_no] = qty + available_qty[row.batch_no] = flt(row.qty) + + if flt(available_qty[row.batch_no], precision) < 0: + self.throw_negative_batch(row.batch_no, available_qty[row.batch_no], precision) return available_qty @@ -1489,7 +1507,9 @@ class SerialandBatchBundle(Document): frappe.qb.from_(sle) .select( sle.batch_no, - Sum(sle.actual_qty).as_("available_qty"), + sle.actual_qty.as_("qty"), + sle.posting_datetime, + sle.creation, ) .where( (sle.item_code == self.item_code) @@ -1501,12 +1521,9 @@ class SerialandBatchBundle(Document): & (sle.batch_no.isnotnull()) ) .for_update() - .groupby(sle.batch_no) ) - res = query.run(as_list=True) - - return frappe._dict(res) if res else frappe._dict() + return query.run(as_dict=True) def get_available_qty_from_sabb(self): batches = [d.batch_no for d in self.entries if d.batch_no] @@ -1517,7 +1534,9 @@ class SerialandBatchBundle(Document): frappe.qb.from_(child) .select( child.batch_no, - Sum(child.qty).as_("available_qty"), + child.qty, + child.posting_datetime, + child.creation, ) .where( (child.item_code == self.item_code) @@ -1528,13 +1547,10 @@ class SerialandBatchBundle(Document): & (child.type_of_transaction.isin(["Inward", "Outward"])) ) .for_update() - .groupby(child.batch_no) ) query = query.where(child.voucher_type != "Pick List") - res = query.run(as_list=True) - - return frappe._dict(res) if res else frappe._dict() + return query.run(as_dict=True) def validate_voucher_no_docstatus(self): if self.voucher_type == "POS Invoice": @@ -2597,11 +2613,11 @@ def get_reserved_batches_for_pos(kwargs) -> dict: key = (row.batch_no, row.warehouse) if key in pos_batches: - pos_batches[key]["qty"] -= row.qty * -1 if row.is_return else row.qty + pos_batches[key]["qty"] += row.qty * -1 else: pos_batches[key] = frappe._dict( { - "qty": (row.qty * -1 if not row.is_return else row.qty), + "qty": row.qty * -1, "warehouse": row.warehouse, } ) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 39db384abd2..f5f724efe42 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -258,6 +258,7 @@ class StockEntry(StockController, SubcontractingInwardController): self.validate_job_card_item() self.set_purpose_for_stock_entry() self.clean_serial_nos() + self.validate_repack_entry() if not self.from_bom: self.fg_completed_qty = 0.0 @@ -282,6 +283,20 @@ class StockEntry(StockController, SubcontractingInwardController): super().validate_subcontracting_inward() + def validate_repack_entry(self): + if self.purpose != "Repack": + return + + fg_items = {row.item_code: row for row in self.items if row.is_finished_item} + + if len(fg_items) > 1 and not all(row.set_basic_rate_manually for row in fg_items.values()): + frappe.throw( + _( + "When there are multiple finished goods ({0}) in a Repack stock entry, the basic rate for all finished goods must be set manually. To set rate manually, enable the checkbox 'Set Basic Rate Manually' in the respective finished good row." + ).format(", ".join(fg_items)), + title=_("Set Basic Rate Manually"), + ) + def validate_raw_materials_exists(self): if self.purpose not in ["Manufacture", "Repack", "Disassemble"]: return diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index 8f9fbfc434e..04f87d3348e 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -413,6 +413,10 @@ class TestStockEntry(IntegrationTestCase): }, ) repack.set_stock_entry_type() + for row in repack.items: + if row.t_warehouse: + row.set_basic_rate_manually = 1 + repack.insert() self.assertEqual(repack.items[1].is_finished_item, 1) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 59acb04e8ea..c7d8461704f 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -1272,15 +1272,11 @@ def get_items(warehouse, posting_date, posting_time, company, item_code=None, ig for d in items: if (d.item_code, d.warehouse) in itemwise_batch_data: - valuation_rate = get_stock_balance( - d.item_code, d.warehouse, posting_date, posting_time, with_valuation_rate=True - )[1] - for row in itemwise_batch_data.get((d.item_code, d.warehouse)): if ignore_empty_stock and not row.qty: continue - args = get_item_data(row, row.qty, valuation_rate) + args = get_item_data(row, row.qty, row.valuation_rate) res.append(args) else: stock_bal = get_stock_balance( @@ -1414,6 +1410,7 @@ def get_itemwise_batch(warehouse, posting_date, company, item_code=None): "item_code": row[0], "warehouse": row[3], "qty": row[8], + "valuation_rate": row[9], "item_name": row[1], "batch_no": row[4], } diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index 026a064c7dc..559e1b31a36 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -282,7 +282,11 @@ class StockBalanceReport: for field in self.inventory_dimensions: qty_dict[field] = entry.get(field) - if entry.voucher_type == "Stock Reconciliation" and (not entry.batch_no or entry.serial_no): + if ( + entry.voucher_type == "Stock Reconciliation" + and frappe.get_cached_value(entry.voucher_type, entry.voucher_no, "purpose") != "Opening Stock" + and (not entry.batch_no or entry.serial_no) + ): qty_diff = flt(entry.qty_after_transaction) - flt(qty_dict.bal_qty) else: qty_diff = flt(entry.actual_qty) diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py index e650fb607ba..b2401da4f8f 100644 --- a/erpnext/stock/stock_balance.py +++ b/erpnext/stock/stock_balance.py @@ -3,6 +3,7 @@ import frappe +from frappe.query_builder.functions import Coalesce, Sum from frappe.utils import cstr, flt, now, nowdate, nowtime from erpnext.controllers.stock_controller import create_repost_item_valuation_entry @@ -182,18 +183,67 @@ def get_indented_qty(item_code, warehouse): def get_ordered_qty(item_code, warehouse): - ordered_qty = frappe.db.sql( - """ - select sum((po_item.qty - po_item.received_qty)*po_item.conversion_factor) - from `tabPurchase Order Item` po_item, `tabPurchase Order` po - where po_item.item_code=%s and po_item.warehouse=%s - and po_item.qty > po_item.received_qty and po_item.parent=po.name - and po.status not in ('Closed', 'Delivered') and po.docstatus=1 - and po_item.delivered_by_supplier = 0""", - (item_code, warehouse), + """Return total pending ordered quantity for an item in a warehouse. + Includes outstanding quantities from Purchase Orders and Subcontracting Orders""" + + purchase_order_qty = get_purchase_order_qty(item_code, warehouse) + subcontracting_order_qty = get_subcontracting_order_qty(item_code, warehouse) + + return flt(purchase_order_qty) + flt(subcontracting_order_qty) + + +def get_purchase_order_qty(item_code, warehouse): + PurchaseOrder = frappe.qb.DocType("Purchase Order") + PurchaseOrderItem = frappe.qb.DocType("Purchase Order Item") + + purchase_order_qty = ( + frappe.qb.from_(PurchaseOrderItem) + .join(PurchaseOrder) + .on(PurchaseOrderItem.parent == PurchaseOrder.name) + .select( + Sum( + (PurchaseOrderItem.qty - PurchaseOrderItem.received_qty) * PurchaseOrderItem.conversion_factor + ) + ) + .where( + (PurchaseOrderItem.item_code == item_code) + & (PurchaseOrderItem.warehouse == warehouse) + & (PurchaseOrderItem.qty > PurchaseOrderItem.received_qty) + & (PurchaseOrder.status.notin(["Closed", "Delivered"])) + & (PurchaseOrder.docstatus == 1) + & (Coalesce(PurchaseOrderItem.delivered_by_supplier, 0) == 0) + ) + .run() ) - return flt(ordered_qty[0][0]) if ordered_qty else 0 + return purchase_order_qty[0][0] if purchase_order_qty else 0 + + +def get_subcontracting_order_qty(item_code, warehouse): + SubcontractingOrder = frappe.qb.DocType("Subcontracting Order") + SubcontractingOrderItem = frappe.qb.DocType("Subcontracting Order Item") + + subcontracting_order_qty = ( + frappe.qb.from_(SubcontractingOrderItem) + .join(SubcontractingOrder) + .on(SubcontractingOrderItem.parent == SubcontractingOrder.name) + .select( + Sum( + (SubcontractingOrderItem.qty - SubcontractingOrderItem.received_qty) + * SubcontractingOrderItem.conversion_factor + ) + ) + .where( + (SubcontractingOrderItem.item_code == item_code) + & (SubcontractingOrderItem.warehouse == warehouse) + & (SubcontractingOrderItem.qty > SubcontractingOrderItem.received_qty) + & (SubcontractingOrder.status.notin(["Closed", "Completed"])) + & (SubcontractingOrder.docstatus == 1) + ) + .run() + ) + + return subcontracting_order_qty[0][0] if subcontracting_order_qty else 0 def get_planned_qty(item_code, warehouse): diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 7a9a2ad273a..39cfbf5c516 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -827,7 +827,6 @@ class update_entries_after: if not self.validate_negative_stock(sle): self.wh_data.qty_after_transaction += flt(sle.actual_qty) return - # Get dynamic incoming/outgoing rate if not self.args.get("sle_id"): self.get_dynamic_incoming_outgoing_rate(sle) @@ -2328,6 +2327,7 @@ def get_incoming_rate_for_inter_company_transfer(sle) -> float: For inter company transfer, incoming rate is the average of the outgoing rate """ rate = 0.0 + lcv_rate = 0.0 field = "delivery_note_item" if sle.voucher_type == "Purchase Receipt" else "sales_invoice_item" @@ -2342,7 +2342,15 @@ def get_incoming_rate_for_inter_company_transfer(sle) -> float: "incoming_rate", ) - return rate + # add lcv amount in incoming_rate + lcv_amount = frappe.db.get_value( + f"{sle.voucher_type} Item", sle.voucher_detail_no, "landed_cost_voucher_amount" + ) + + if lcv_amount: + lcv_rate = flt(lcv_amount / abs(sle.actual_qty)) + + return rate + lcv_rate def is_internal_transfer(sle): diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py index ee9cf7a8ee5..8eb369d120f 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py @@ -12,7 +12,7 @@ from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry impor StockReservation, has_reserved_stock, ) -from erpnext.stock.stock_balance import update_bin_qty +from erpnext.stock.stock_balance import get_ordered_qty, update_bin_qty from erpnext.stock.utils import get_bin @@ -234,30 +234,7 @@ class SubcontractingOrder(SubcontractingController): ): item_wh_list.append([item.item_code, item.warehouse]) for item_code, warehouse in item_wh_list: - update_bin_qty(item_code, warehouse, {"ordered_qty": self.get_ordered_qty(item_code, warehouse)}) - - @staticmethod - def get_ordered_qty(item_code, warehouse): - table = frappe.qb.DocType("Subcontracting Order") - child = frappe.qb.DocType("Subcontracting Order Item") - - query = ( - frappe.qb.from_(table) - .inner_join(child) - .on(table.name == child.parent) - .select((child.qty - child.received_qty) * child.conversion_factor) - .where( - (table.docstatus == 1) - & (child.item_code == item_code) - & (child.warehouse == warehouse) - & (child.qty > child.received_qty) - & (table.status != "Completed") - ) - ) - - query = query.run() - - return flt(query[0][0]) if query else 0 + update_bin_qty(item_code, warehouse, {"ordered_qty": get_ordered_qty(item_code, warehouse)}) def update_reserved_qty_for_subcontracting(self, sco_item_rows=None): for item in self.supplied_items: diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index 5bf2a6dd6fe..489f2bf82cd 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -617,6 +617,117 @@ class TestSubcontractingReceipt(IntegrationTestCase): for item in scr.supplied_items: self.assertFalse(item.available_qty_for_consumption) + def test_supplied_items_consumed_qty_for_similar_finished_goods(self): + """ + Test that supplied raw material consumption is calculated correctly + when multiple subcontracted service items use the same finished good + but different BOMs. + """ + + from erpnext.controllers.subcontracting_controller import ( + make_rm_stock_entry as make_subcontract_transfer_entry, + ) + from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom + + # Configuration: Backflush based on subcontract material transfer + set_backflush_based_on("Material Transferred for Subcontract") + + # Create Raw Materials + raw_material_1 = make_item("_RM Item 1", properties={"is_stock_item": 1}).name + + raw_material_2 = make_item("_RM Item 2", properties={"is_stock_item": 1}).name + + # Create Subcontracted Finished Good + finished_good = make_item("_Finished Good Item", properties={"is_stock_item": 1}) + finished_good.is_sub_contracted_item = 1 + finished_good.save() + + # Receive Raw Materials into Warehouse + for raw_material in (raw_material_1, raw_material_2): + make_stock_entry( + item_code=raw_material, + qty=10, + target="_Test Warehouse - _TC", + basic_rate=100, + ) + + # Create BOMs for the same Finished Good with different RMs + bom_rm_1 = make_bom( + item=finished_good.name, + quantity=1, + raw_materials=[raw_material_1], + ).name + + _bom_rm_2 = make_bom( + item=finished_good.name, + quantity=1, + raw_materials=[raw_material_2], + ).name + + # Define Subcontracted Service Items + service_items = [ + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 1", + "qty": 1, + "rate": 100, + "fg_item": finished_good.name, + "fg_item_qty": 10, + }, + { + "warehouse": "_Test Warehouse - _TC", + "item_code": "Subcontracted Service Item 1", + "qty": 1, + "rate": 150, + "fg_item": finished_good.name, + "fg_item_qty": 10, + }, + ] + + # Create Subcontracting Order + subcontracting_order = get_subcontracting_order( + service_items=service_items, + do_not_save=True, + ) + + # Assign BOM only to the first service item + subcontracting_order.items[0].bom = bom_rm_1 + subcontracting_order.save() + subcontracting_order.submit() + + # Prepare Raw Material Transfer Items + raw_material_transfer_items = [] + for supplied_item in subcontracting_order.supplied_items: + raw_material_transfer_items.append( + { + "item_code": supplied_item.main_item_code, + "rm_item_code": supplied_item.rm_item_code, + "qty": supplied_item.required_qty, + "warehouse": "_Test Warehouse - _TC", + "stock_uom": "Nos", + } + ) + + # Transfer Raw Materials to Subcontractor Warehouse + stock_entry = frappe.get_doc( + make_subcontract_transfer_entry( + subcontracting_order.name, + raw_material_transfer_items, + ) + ) + stock_entry.to_warehouse = "_Test Warehouse 1 - _TC" + stock_entry.save() + stock_entry.submit() + + # Create Subcontracting Receipt + subcontracting_receipt = make_subcontracting_receipt(subcontracting_order.name) + subcontracting_receipt.save() + + # Check consumed_qty for each supplied item + self.assertEqual(len(subcontracting_receipt.supplied_items), 2) + self.assertEqual(subcontracting_receipt.supplied_items[0].consumed_qty, 10) + self.assertEqual(subcontracting_receipt.supplied_items[1].consumed_qty, 10) + def test_supplied_items_cost_after_reposting(self): # Set Backflush Based On as "BOM" set_backflush_based_on("BOM") diff --git a/erpnext/workspace_sidebar/invoicing.json b/erpnext/workspace_sidebar/invoicing.json index 104c8547d5f..99a6b367953 100644 --- a/erpnext/workspace_sidebar/invoicing.json +++ b/erpnext/workspace_sidebar/invoicing.json @@ -219,7 +219,7 @@ "collapsible": 1, "indent": 0, "keep_closed": 0, - "label": "Payment Reconciliaition", + "label": "Payment Reconciliation", "link_to": "Payment Reconciliation", "link_type": "DocType", "show_arrow": 0, diff --git a/erpnext/workspace_sidebar/manufacturing.json b/erpnext/workspace_sidebar/manufacturing.json index 3c2a4a3dd9d..3570184eb5e 100644 --- a/erpnext/workspace_sidebar/manufacturing.json +++ b/erpnext/workspace_sidebar/manufacturing.json @@ -89,6 +89,18 @@ "show_arrow": 0, "type": "Section Break" }, + { + "child": 1, + "collapsible": 1, + "icon": "", + "indent": 0, + "keep_closed": 0, + "label": "Item Lead Time", + "link_to": "Item Lead Time", + "link_type": "DocType", + "show_arrow": 0, + "type": "Link" + }, { "child": 1, "collapsible": 1, @@ -425,7 +437,7 @@ "type": "Link" } ], - "modified": "2026-01-10 00:06:13.058137", + "modified": "2026-01-29 16:41:40.416652", "modified_by": "Administrator", "module": "Manufacturing", "name": "Manufacturing", diff --git a/erpnext/workspace_sidebar/taxes.json b/erpnext/workspace_sidebar/taxes.json index 5cf65ff3c67..64b343ca215 100644 --- a/erpnext/workspace_sidebar/taxes.json +++ b/erpnext/workspace_sidebar/taxes.json @@ -13,7 +13,7 @@ "indent": 0, "keep_closed": 0, "label": "Sales Tax Template", - "link_to": "Item Tax Template", + "link_to": "Sales Taxes and Charges Template", "link_type": "DocType", "navigate_to_tab": "", "show_arrow": 0, @@ -148,7 +148,7 @@ "type": "Link" } ], - "modified": "2026-01-10 00:06:13.005238", + "modified": "2026-02-01 00:00:00.000000", "modified_by": "Administrator", "module": "Accounts", "name": "Taxes", diff --git a/transaction-deletion-import-logic-summary.md b/transaction-deletion-import-logic-summary.md new file mode 100644 index 00000000000..85ec35ef198 --- /dev/null +++ b/transaction-deletion-import-logic-summary.md @@ -0,0 +1,230 @@ +# Transaction Deletion CSV Import Logic - Updated Behavior + +## Auto-Detection of Company Field + +When importing a CSV without a `company_field` column or with empty values, the system uses smart auto-detection: + +### Priority Order: + +1. **"company" field** (most common convention) + - Check if a field named `company` exists that links to Company DocType + - ✅ Use "company" if found + +2. **First Company link field** (custom fields) + - If no "company" field, get all fields linking to Company DocType + - ✅ Use the first one (sorted by field index) + +3. **No company field** (DocTypes without company filtering) + - If no Company link fields exist at all + - ✅ Leave `company_field` as None/empty + - ✅ Delete ALL records (no company filtering) + +## Import CSV Format + +### Minimal Format (Auto-Detection) +```csv +doctype_name,child_doctypes +Sales Order,Sales Order Item +Note, +Task, +``` + +**Result:** +- `Sales Order`: Auto-detects "company" field → Filters by company +- `Note`: No company field → Deletes all Note records +- `Task`: Has "company" field → Filters by company + +### Explicit Format (Recommended) +```csv +doctype_name,company_field,child_doctypes +Sales Order,company,Sales Order Item +Sales Contract,primary_company,Sales Contract Item +Sales Contract,billing_company,Sales Contract Item +Note,, +``` + +**Result:** +- `Sales Order`: Uses "company" field explicitly +- `Sales Contract` (row 1): Uses "primary_company" field +- `Sales Contract` (row 2): Uses "billing_company" field (separate row!) +- `Note`: No company field, deletes all records + +### Multiple Company Fields Example +```csv +doctype_name,company_field,child_doctypes +Customer Invoice,head_office,Customer Invoice Item +Customer Invoice,billing_company,Customer Invoice Item +``` + +**Deletion Process:** +1. Row 1 deletes: `WHERE head_office = 'ABC Company'` +2. Row 2 deletes: `WHERE billing_company = 'ABC Company'` +3. Documents with both fields = ABC get deleted in first pass +4. Documents with only billing_company = ABC get deleted in second pass + +## Validation Rules + +### ✅ Accepted Cases + +1. **DocType with "company" field** - Auto-detected +2. **DocType with custom Company link field** - Auto-detected (first field used) +3. **DocType with multiple Company fields** - Auto-detected (first field used), but user can add multiple rows +4. **DocType with NO Company fields** - Accepted! Deletes ALL records +5. **Explicit company_field provided** - Validated and used + +### ❌ Rejected Cases + +1. **Protected DocTypes** - User, Role, DocType, etc. +2. **Child tables** - Auto-deleted with parent +3. **Virtual DocTypes** - No database table +4. **Invalid company_field** - Field doesn't exist or isn't a Company link +5. **DocType doesn't exist** - Not found in system + +## Code Flow + +```python +# 1. Read company_field from CSV (may be empty) +company_field = row.get("company_field", "").strip() + +# 2. Auto-detect if not provided +if not company_field: + # Try "company" first + if exists("company" field linking to Company): + company_field = "company" + else: + # Check for other Company link fields + company_fields = get_all_company_link_fields() + if company_fields: + company_field = company_fields[0] # Use first + # else: company_field stays empty + +# 3. Validate if company_field was provided/detected +if company_field: + if not is_valid_company_link_field(company_field): + skip_with_error() + +# 4. Count documents +if company_field: + count = count(WHERE company_field = self.company) +else: + count = count(all records) + +# 5. Store in To Delete list +append({ + "doctype_name": doctype_name, + "company_field": company_field or None, # Store None if empty + "document_count": count +}) +``` + +## Examples + +### Example 1: Standard DocType with "company" Field + +**CSV:** +```csv +doctype_name,company_field,child_doctypes +Sales Order,, +``` + +**Auto-Detection:** +- Finds "company" field linking to Company +- Sets `company_field = "company"` +- Counts: `WHERE company = 'Test Company'` +- Result: Deletes only Test Company's Sales Orders + +### Example 2: Custom Company Field + +**CSV:** +```csv +doctype_name,company_field,child_doctypes +Project Contract,, +``` + +**Auto-Detection:** +- No "company" field found +- Finds "contracting_company" field linking to Company +- Sets `company_field = "contracting_company"` +- Counts: `WHERE contracting_company = 'Test Company'` +- Result: Deletes only Test Company's Project Contracts + +### Example 3: No Company Field (Global DocType) + +**CSV:** +```csv +doctype_name,company_field,child_doctypes +Note,, +Global Settings,, +``` + +**Auto-Detection:** +- No Company link fields found +- Sets `company_field = None` +- Counts: All records +- Result: Deletes ALL Note and Global Settings records + +### Example 4: Multiple Company Fields (Explicit) + +**CSV:** +```csv +doctype_name,company_field,child_doctypes +Sales Contract,primary_company,Sales Contract Item +Sales Contract,billing_company,Sales Contract Item +``` + +**No Auto-Detection:** +- Row 1: Uses "primary_company" explicitly +- Row 2: Uses "billing_company" explicitly +- Both rows validated as valid Company link fields +- Result: Two separate deletion passes + +### Example 5: Mixed Approaches + +**CSV:** +```csv +doctype_name,company_field,child_doctypes +Sales Order,,Sales Order Item +Sales Contract,billing_company,Sales Contract Item +Note,, +``` + +**Result:** +- Row 1: Auto-detects "company" field +- Row 2: Uses "billing_company" explicitly +- Row 3: No company field (deletes all) + +## User Benefits + +✅ **Flexible**: Supports auto-detection and explicit specification +✅ **Safe**: Validates all fields before processing +✅ **Clear**: Empty company_field means "delete all" +✅ **Powerful**: Can target specific company fields in multi-company setups +✅ **Backward Compatible**: Old CSVs (without company_field column) still work + +## Migration from Old Format + +**Old CSV (without company_field):** +```csv +doctype_name,child_doctypes +Sales Order,Sales Order Item +``` + +**New System Behavior:** +- Auto-detects "company" field +- Works identically to before +- ✅ Backward compatible + +**New CSV (with company_field):** +```csv +doctype_name,company_field,child_doctypes +Sales Order,company,Sales Order Item +``` + +**Benefits:** +- Explicit and clear +- Supports multiple rows per DocType +- Can specify custom company fields + +--- + +*Generated for Transaction Deletion Record enhancement*