From c2c4e5ee8b2bfe6ae1ee3dc5bfee9ab950886ea5 Mon Sep 17 00:00:00 2001 From: ervishnucs Date: Wed, 5 Aug 2026 19:00:51 +0530 Subject: [PATCH 01/59] fix(assets): split FIFO/LIFO rate across grouped stock item rows (cherry picked from commit a05ec49062526d75fcb526fc543775e78dcb23d6) --- .../asset_capitalization.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py index 15128607e5b..9ac50458e1e 100644 --- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py @@ -166,6 +166,8 @@ class AssetCapitalization(StockController): if d.meta.has_field(k) and (not d.get(k) or k in force_fields): d.set(k, v) + self.split_valuation_rate_for_grouped_stock_items() + for d in self.asset_items: args = self.as_dict() args.update(d.as_dict()) @@ -187,6 +189,30 @@ class AssetCapitalization(StockController): if d.meta.has_field(k) and (not d.get(k) or k in force_fields): d.set(k, v) + def split_valuation_rate_for_grouped_stock_items(self): + groups = {} + for d in self.stock_items: + if d.item_code and d.warehouse and not (d.serial_no or d.batch_no or d.serial_and_batch_bundle): + groups.setdefault((d.item_code, d.warehouse), []).append(d) + + for rows in groups.values(): + if len(rows) < 2: + continue + + cumulative_qty = 0.0 + prev_cumulative_value = 0.0 + for d in rows: + cumulative_qty += flt(d.stock_qty) + args = self.get_args_for_incoming_rate(d) + args["qty"] = -1 * cumulative_qty + cumulative_rate = flt(get_incoming_rate(args, raise_error_if_no_rate=False)) + cumulative_value = cumulative_rate * cumulative_qty + + row_value = cumulative_value - prev_cumulative_value + d.valuation_rate = flt(row_value / d.stock_qty) if flt(d.stock_qty) else 0.0 + d.amount = flt(flt(d.stock_qty) * d.valuation_rate, d.precision("amount")) + prev_cumulative_value = cumulative_value + def validate_target_item(self): target_item = frappe.get_cached_doc("Item", self.target_item_code) @@ -338,6 +364,8 @@ class AssetCapitalization(StockController): warehouse_details = get_warehouse_details(args) d.update(warehouse_details) + self.split_valuation_rate_for_grouped_stock_items() + @frappe.whitelist() def set_asset_values(self): for d in self.get("asset_items"): From 570038498954db6a16890d3351681038a591bacc Mon Sep 17 00:00:00 2001 From: pandiyan Date: Wed, 12 Aug 2026 15:54:53 +0530 Subject: [PATCH 02/59] test: budget against a balance sheet account --- erpnext/accounts/doctype/budget/test_budget.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/erpnext/accounts/doctype/budget/test_budget.py b/erpnext/accounts/doctype/budget/test_budget.py index 6d256382042..6cd7aa766bc 100644 --- a/erpnext/accounts/doctype/budget/test_budget.py +++ b/erpnext/accounts/doctype/budget/test_budget.py @@ -357,6 +357,16 @@ class TestBudget(unittest.TestCase): self.assertRaises(BudgetError, jv.submit) + def test_budget_against_balance_sheet_account(self): + budget = frappe.new_doc("Budget") + budget.budget_against = "Cost Center" + budget.cost_center = "_Test Cost Center - _TC" + budget.company = "_Test Company" + budget.fiscal_year = get_fiscal_year(nowdate())[0] + budget.append("accounts", {"account": "_Test Bank - _TC", "budget_amount": 200000}) + + self.assertRaisesRegex(frappe.ValidationError, "_Test Bank - _TC", budget.insert) + def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None): if budget_against_field == "project": From 2095411a28645fc6ecc3f23eb479af36aca23079 Mon Sep 17 00:00:00 2001 From: pandiyan Date: Wed, 12 Aug 2026 15:58:45 +0530 Subject: [PATCH 03/59] fix: attributeerror on budget against a non profit and loss account --- erpnext/accounts/doctype/budget/budget.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/budget/budget.py b/erpnext/accounts/doctype/budget/budget.py index e1038b6af33..ae891fb0a1f 100644 --- a/erpnext/accounts/doctype/budget/budget.py +++ b/erpnext/accounts/doctype/budget/budget.py @@ -98,13 +98,13 @@ class Budget(Document): frappe.throw(_("Budget cannot be assigned against Group Account {0}").format(d.account)) elif account_details.company != self.company: frappe.throw( - _("Account {0} does not belongs to company {1}").format(d.account, self.company) + _("Account {0} does not belong to company {1}").format(d.account, self.company) ) elif account_details.report_type != "Profit and Loss": frappe.throw( _( "Budget cannot be assigned against {0}, as its Root Type is not of Income or Expense" - ).format(self.account) + ).format(d.account) ) if d.account in account_list: From 8c8a4b6f206b7b5db548a57a7f233c2e28ed6e7d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 12 Aug 2026 17:33:56 +0530 Subject: [PATCH 04/59] fix: filter available batch report by company (backport #57995) (#58076) Co-authored-by: Krishna Shirsath --- .../report/available_batch_report/available_batch_report.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erpnext/stock/report/available_batch_report/available_batch_report.py b/erpnext/stock/report/available_batch_report/available_batch_report.py index e5f14315773..84a671431ed 100644 --- a/erpnext/stock/report/available_batch_report/available_batch_report.py +++ b/erpnext/stock/report/available_batch_report/available_batch_report.py @@ -154,6 +154,9 @@ def get_batchwise_data_from_serial_batch_bundle(batchwise_data, filters): def get_query_based_on_filters(query, batch, table, filters): + if filters.company: + query = query.where(table.company == filters.company) + if filters.item_code: query = query.where(table.item_code == filters.item_code) From bfa3edbf95c54b35912999092a2c5a255352fe5b Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 13 Aug 2026 04:42:02 +0000 Subject: [PATCH 05/59] Fix/item description in the item price list (backport #58084) (#58101) Co-authored-by: Mohsin Akhtar <167299936+akhtarmohsin@users.noreply.github.com> Co-authored-by: Mihir Kandoi --- erpnext/stock/doctype/item_price/item_price.json | 6 +++--- erpnext/stock/doctype/item_price/item_price.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/stock/doctype/item_price/item_price.json b/erpnext/stock/doctype/item_price/item_price.json index c6950b9a10f..95d5dbe9147 100644 --- a/erpnext/stock/doctype/item_price/item_price.json +++ b/erpnext/stock/doctype/item_price/item_price.json @@ -89,7 +89,7 @@ }, { "fieldname": "item_description", - "fieldtype": "Text", + "fieldtype": "Text Editor", "label": "Item Description", "read_only": 1 }, @@ -224,7 +224,7 @@ "idx": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2024-04-02 22:18:00.450641", + "modified": "2026-08-12 13:14:41.847412", "modified_by": "Administrator", "module": "Stock", "name": "Item Price", @@ -264,4 +264,4 @@ "states": [], "title_field": "item_name", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/stock/doctype/item_price/item_price.py b/erpnext/stock/doctype/item_price/item_price.py index dc693890cd7..c982a2f706f 100644 --- a/erpnext/stock/doctype/item_price/item_price.py +++ b/erpnext/stock/doctype/item_price/item_price.py @@ -28,7 +28,7 @@ class ItemPrice(Document): currency: DF.Link | None customer: DF.Link | None item_code: DF.Link - item_description: DF.Text | None + item_description: DF.TextEditor | None item_name: DF.Data | None lead_time_days: DF.Int note: DF.Text | None From 6650ac8c36919fd0495d71a9b851827fcbbded01 Mon Sep 17 00:00:00 2001 From: khushi8112 Date: Tue, 28 Jul 2026 17:47:26 +0530 Subject: [PATCH 06/59] refactor: split make_depreciation_entry into public and internal helpers --- erpnext/assets/doctype/asset/depreciation.py | 28 ++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index ea8059b68a5..561de38e217 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -81,7 +81,7 @@ def post_depreciation_entries(date=None): ) try: - make_depreciation_entry( + _make_depreciation_entry( asset_depr_schedule_name, date, sch_start_idx, @@ -139,7 +139,7 @@ def get_depreciable_asset_depr_schedules_data(date): def make_depreciation_entry_for_all_asset_depr_schedules(asset_doc, date=None): for row in asset_doc.get("finance_books"): asset_depr_schedule_name = get_asset_depr_schedule_name(asset_doc.name, "Active", row.finance_book) - make_depreciation_entry(asset_depr_schedule_name, date) + _make_depreciation_entry(asset_depr_schedule_name, date) def get_acc_frozen_upto(): @@ -193,6 +193,30 @@ def make_depreciation_entry( credit_and_debit_accounts=None, depreciation_cost_center_and_depreciation_series=None, accounting_dimensions=None, +): + asset_depr_schedule_doc = frappe.get_doc("Asset Depreciation Schedule", asset_depr_schedule_name) + frappe.has_permission("Asset Depreciation Schedule", "write", asset_depr_schedule_doc, throw=True) + frappe.has_permission("Asset", "write", asset_depr_schedule_doc.asset, throw=True) + + return _make_depreciation_entry( + asset_depr_schedule_name, + date, + sch_start_idx, + sch_end_idx, + credit_and_debit_accounts, + depreciation_cost_center_and_depreciation_series, + accounting_dimensions, + ) + + +def _make_depreciation_entry( + asset_depr_schedule_name, + date=None, + sch_start_idx=None, + sch_end_idx=None, + credit_and_debit_accounts=None, + depreciation_cost_center_and_depreciation_series=None, + accounting_dimensions=None, ): frappe.has_permission("Journal Entry", throw=True) From 1e23d48a5b072e4644eca66b42c6660e197f7f39 Mon Sep 17 00:00:00 2001 From: ervishnucs Date: Thu, 13 Aug 2026 12:01:19 +0530 Subject: [PATCH 07/59] test(assets): cover grouped stock item rows splitting FIFO rate (cherry picked from commit 2cbc5b89d64c4afef35ad64b6f9db176f81a76e9) # Conflicts: # erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py --- .../test_asset_capitalization.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py index e0ff6102046..bdd61e6b29a 100644 --- a/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py +++ b/erpnext/assets/doctype/asset_capitalization/test_asset_capitalization.py @@ -10,12 +10,14 @@ from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries from erpnext.assets.doctype.asset.test_asset import ( create_asset, create_asset_data, + create_fixed_asset_item, set_depreciation_settings_in_company, ) from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import ( get_asset_depr_schedule_doc, ) from erpnext.stock.doctype.item.test_item import create_item +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import ( make_serial_batch_bundle, ) @@ -340,6 +342,33 @@ class TestAssetCapitalization(unittest.TestCase): self.assertFalse(get_actual_gle_dict(asset_capitalization.name)) self.assertFalse(get_actual_sle_dict(asset_capitalization.name)) + def test_grouped_stock_item_rows_split_fifo_rate(self): + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + + company = "_Test Company" + warehouse = create_warehouse("_Test Warehouse for Grouped FIFO Rows", company=company) + item = create_item( + "_Test Grouped FIFO Rows Item", is_stock_item=1, is_fixed_asset=0, is_purchase_item=1 + ) + target_item = create_fixed_asset_item("_Test Grouped FIFO Rows Target Item") + + make_purchase_receipt(item_code=item.item_code, qty=1, rate=100, company=company, warehouse=warehouse) + make_purchase_receipt(item_code=item.item_code, qty=1, rate=200, company=company, warehouse=warehouse) + + asset_capitalization = frappe.new_doc("Asset Capitalization") + asset_capitalization.company = company + asset_capitalization.target_item_code = target_item.name + asset_capitalization.append( + "stock_items", {"item_code": item.item_code, "warehouse": warehouse, "stock_qty": 1} + ) + asset_capitalization.append( + "stock_items", {"item_code": item.item_code, "warehouse": warehouse, "stock_qty": 1} + ) + asset_capitalization.insert() + + rates = [d.valuation_rate for d in asset_capitalization.stock_items] + self.assertEqual(rates, [100, 200]) + def create_asset_capitalization_data(): create_item("Capitalization Target Stock Item", is_stock_item=1, is_fixed_asset=0, is_purchase_item=0) From 4cd39aa14722f7a1c0584ef3c95459a404dafff3 Mon Sep 17 00:00:00 2001 From: Shllokkk <140623894+Shllokkk@users.noreply.github.com> Date: Wed, 3 Jun 2026 10:51:44 +0530 Subject: [PATCH 08/59] fix: remove ignore_permissions from get_party_details signature (#55491) (cherry picked from commit efb8336bf89b6bbf89d22e3e786e32571c798b1a) # Conflicts: # erpnext/accounts/doctype/sales_invoice/sales_invoice.py # erpnext/accounts/party.py --- .../doctype/sales_invoice/sales_invoice.py | 6 ++++- erpnext/accounts/party.py | 22 ++++++++++++++++--- .../request_for_quotation.py | 4 ++-- .../buying/doctype/supplier/test_supplier.py | 6 ++--- erpnext/controllers/buying_controller.py | 4 ++-- .../selling/doctype/customer/test_customer.py | 12 +++++----- .../customer_wise_item_price.py | 4 ++-- 7 files changed, 39 insertions(+), 19 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 53e35e7407f..1041ee7172b 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -23,12 +23,16 @@ from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category get_party_tax_withholding_details, ) from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center +<<<<<<< HEAD from erpnext.accounts.party import ( CROSS_PARTY_FIELD_NO_MAP, get_due_date, get_party_account, get_party_details, ) +======= +from erpnext.accounts.party import _get_party_details, get_due_date, get_party_account +>>>>>>> efb8336bf8 (fix: remove ignore_permissions from get_party_details signature (#55491)) from erpnext.accounts.utils import ( cancel_exchange_gain_loss_journal, get_account_currency, @@ -2737,7 +2741,7 @@ def update_taxes( master_doctype=None, ): # Update Party Details - party_details = get_party_details( + party_details = _get_party_details( party=party, party_type=party_type, company=company, diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 170a39582af..2192e40d637 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -74,6 +74,7 @@ class DuplicatePartyAccountError(frappe.ValidationError): @frappe.whitelist() def get_party_details( +<<<<<<< HEAD party=None, account=None, party_type="Customer", @@ -90,11 +91,26 @@ def get_party_details( shipping_address=None, dispatch_address=None, pos_profile=None, +======= + party: str | None = None, + account: str | None = None, + party_type: str = "Customer", + company: str | None = None, + posting_date: str | None = None, + bill_date: str | None = None, + price_list: str | None = None, + currency: str | None = None, + doctype: str | None = None, + fetch_payment_terms_template: bool = True, + party_address: str | None = None, + company_address: str | None = None, + shipping_address: str | None = None, + dispatch_address: str | None = None, + pos_profile: str | None = None, +>>>>>>> efb8336bf8 (fix: remove ignore_permissions from get_party_details signature (#55491)) ): if not party: return frappe._dict() - if not frappe.db.exists(party_type, party): - frappe.throw(_("{0}: {1} does not exists").format(party_type, party)) return _get_party_details( party, account, @@ -105,7 +121,7 @@ def get_party_details( price_list, currency, doctype, - ignore_permissions, + False, fetch_payment_terms_template, party_address, company_address, 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 c4ada801cd2..cc1919afd57 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -15,7 +15,7 @@ from frappe.utils import get_url from frappe.utils.print_format import download_pdf from frappe.utils.user import get_user_fullname -from erpnext.accounts.party import get_party_account_currency, get_party_details +from erpnext.accounts.party import _get_party_details, get_party_account_currency from erpnext.buying.utils import validate_for_items from erpnext.controllers.buying_controller import BuyingController from erpnext.stock.doctype.material_request.material_request import set_missing_values @@ -443,7 +443,7 @@ def make_supplier_quotation_from_rfq(source_name, target_doc=None, for_supplier= def postprocess(source, target_doc): if for_supplier: target_doc.supplier = for_supplier - args = get_party_details(for_supplier, party_type="Supplier", ignore_permissions=True) + args = _get_party_details(for_supplier, party_type="Supplier", ignore_permissions=True) target_doc.currency = args.currency or get_party_account_currency( "Supplier", for_supplier, source.company ) diff --git a/erpnext/buying/doctype/supplier/test_supplier.py b/erpnext/buying/doctype/supplier/test_supplier.py index e0a2a379ed8..69c81eb2e80 100644 --- a/erpnext/buying/doctype/supplier/test_supplier.py +++ b/erpnext/buying/doctype/supplier/test_supplier.py @@ -125,12 +125,12 @@ class TestSupplier(FrappeTestCase): self.assertEqual(supplier.country, "Greece") def test_party_details_tax_category(self): - from erpnext.accounts.party import get_party_details + from erpnext.accounts.party import _get_party_details frappe.delete_doc_if_exists("Address", "_Test Address With Tax Category-Billing") # Tax Category without Address - details = get_party_details("_Test Supplier With Tax Category", party_type="Supplier") + details = _get_party_details("_Test Supplier With Tax Category", party_type="Supplier") self.assertEqual(details.tax_category, "_Test Tax Category 1") address = frappe.get_doc( @@ -147,7 +147,7 @@ class TestSupplier(FrappeTestCase): ).insert() # Tax Category with Address - details = get_party_details("_Test Supplier With Tax Category", party_type="Supplier") + details = _get_party_details("_Test Supplier With Tax Category", party_type="Supplier") self.assertEqual(details.tax_category, "_Test Tax Category 2") # Rollback diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 5b8df2cf767..1bbec4b5196 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -11,7 +11,7 @@ from frappe.utils.data import nowtime import erpnext from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget -from erpnext.accounts.party import get_party_details +from erpnext.accounts.party import _get_party_details from erpnext.buying.utils import update_last_purchase_rate, validate_for_items from erpnext.controllers.accounts_controller import get_taxes_and_charges from erpnext.controllers.sales_and_purchase_return import get_rate_for_return @@ -165,7 +165,7 @@ class BuyingController(SubcontractingController): # set contact and address details for supplier, if they are not mentioned if getattr(self, "supplier", None): self.update_if_missing( - get_party_details( + _get_party_details( self.supplier, party_type="Supplier", doctype=self.doctype, diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py index e6ed7acb508..cc6c55a37f1 100644 --- a/erpnext/selling/doctype/customer/test_customer.py +++ b/erpnext/selling/doctype/customer/test_customer.py @@ -67,7 +67,7 @@ class TestCustomer(FrappeTestCase): doc.delete() def test_party_details(self): - from erpnext.accounts.party import get_party_details + from erpnext.accounts.party import _get_party_details to_check = { "selling_price_list": None, @@ -91,7 +91,7 @@ class TestCustomer(FrappeTestCase): "Contact", "_Test Contact for _Test Customer-_Test Customer", "is_primary_contact", 1 ) - details = get_party_details("_Test Customer") + details = _get_party_details("_Test Customer") for key, value in to_check.items(): val = details.get(key) @@ -101,13 +101,13 @@ class TestCustomer(FrappeTestCase): self.assertEqual(value, val) def test_party_details_tax_category(self): - from erpnext.accounts.party import get_party_details + from erpnext.accounts.party import _get_party_details frappe.delete_doc_if_exists("Address", "_Test Address With Tax Category-Billing") frappe.delete_doc_if_exists("Address", "_Test Address With Tax Category-Shipping") # Tax Category without Address - details = get_party_details("_Test Customer With Tax Category") + details = _get_party_details("_Test Customer With Tax Category") self.assertEqual(details.tax_category, "_Test Tax Category 1") billing_address = frappe.get_doc( @@ -141,13 +141,13 @@ class TestCustomer(FrappeTestCase): # Tax Category from Billing Address settings.determine_address_tax_category_from = "Billing Address" settings.save() - details = get_party_details("_Test Customer With Tax Category") + details = _get_party_details("_Test Customer With Tax Category") self.assertEqual(details.tax_category, "_Test Tax Category 2") # Tax Category from Shipping Address settings.determine_address_tax_category_from = "Shipping Address" settings.save() - details = get_party_details("_Test Customer With Tax Category") + details = _get_party_details("_Test Customer With Tax Category") self.assertEqual(details.tax_category, "_Test Tax Category 3") # Rollback diff --git a/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.py b/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.py index 84da765d930..46056c94129 100644 --- a/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.py +++ b/erpnext/selling/report/customer_wise_item_price/customer_wise_item_price.py @@ -7,7 +7,7 @@ from frappe import _, qb from frappe.query_builder import Criterion from erpnext import get_default_company -from erpnext.accounts.party import get_party_details +from erpnext.accounts.party import _get_party_details def execute(filters=None): @@ -125,7 +125,7 @@ def get_data(filters=None): def get_customer_details(filters): - customer_details = get_party_details(party=filters.get("customer"), party_type="Customer") + customer_details = _get_party_details(party=filters.get("customer"), party_type="Customer") customer_details.update( {"company": get_default_company(), "price_list": customer_details.get("selling_price_list")} ) From 23919967f69ad94e0a9c35b8de6c66b9eb6297f9 Mon Sep 17 00:00:00 2001 From: diptanilsaha Date: Thu, 13 Aug 2026 13:01:53 +0530 Subject: [PATCH 09/59] chore: resolve conflicts --- .../doctype/sales_invoice/sales_invoice.py | 12 ++++-------- erpnext/accounts/party.py | 19 ------------------- 2 files changed, 4 insertions(+), 27 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 1041ee7172b..59e72ced503 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -23,16 +23,12 @@ from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category get_party_tax_withholding_details, ) from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center -<<<<<<< HEAD from erpnext.accounts.party import ( CROSS_PARTY_FIELD_NO_MAP, + _get_party_details, get_due_date, get_party_account, - get_party_details, ) -======= -from erpnext.accounts.party import _get_party_details, get_due_date, get_party_account ->>>>>>> efb8336bf8 (fix: remove ignore_permissions from get_party_details signature (#55491)) from erpnext.accounts.utils import ( cancel_exchange_gain_loss_journal, get_account_currency, @@ -2270,9 +2266,9 @@ def make_delivery_note(source_name, target_doc=None): "cost_center": "cost_center", }, "postprocess": update_item, - "condition": lambda doc: doc.delivered_by_supplier != 1 - and not doc.dn_detail - and doc.qty - doc.delivered_qty > 0, + "condition": lambda doc: ( + doc.delivered_by_supplier != 1 and not doc.dn_detail and doc.qty - doc.delivered_qty > 0 + ), }, "Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "reset_value": True}, "Sales Team": { diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 2192e40d637..1747fe63480 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -74,7 +74,6 @@ class DuplicatePartyAccountError(frappe.ValidationError): @frappe.whitelist() def get_party_details( -<<<<<<< HEAD party=None, account=None, party_type="Customer", @@ -84,30 +83,12 @@ def get_party_details( price_list=None, currency=None, doctype=None, - ignore_permissions=False, fetch_payment_terms_template=True, party_address=None, company_address=None, shipping_address=None, dispatch_address=None, pos_profile=None, -======= - party: str | None = None, - account: str | None = None, - party_type: str = "Customer", - company: str | None = None, - posting_date: str | None = None, - bill_date: str | None = None, - price_list: str | None = None, - currency: str | None = None, - doctype: str | None = None, - fetch_payment_terms_template: bool = True, - party_address: str | None = None, - company_address: str | None = None, - shipping_address: str | None = None, - dispatch_address: str | None = None, - pos_profile: str | None = None, ->>>>>>> efb8336bf8 (fix: remove ignore_permissions from get_party_details signature (#55491)) ): if not party: return frappe._dict() From a1ecea1794c6a9011ae48d3097f2d4cd09ee3067 Mon Sep 17 00:00:00 2001 From: Mihir Kandoi Date: Thu, 13 Aug 2026 15:09:21 +0530 Subject: [PATCH 10/59] fix(manufacturing): derive material transfers from actual coverage (#58114) --- .../doctype/work_order/services/__init__.py | 1 + .../work_order/services/material_coverage.py | 22 +++ .../doctype/work_order/test_work_order.py | 138 +++++++++++++++++- .../doctype/work_order/work_order.py | 29 ++-- erpnext/patches.txt | 1 + .../repair_work_order_material_transfer.py | 65 +++++++++ .../stock/doctype/stock_entry/stock_entry.py | 65 +++++++++ 7 files changed, 302 insertions(+), 19 deletions(-) create mode 100644 erpnext/manufacturing/doctype/work_order/services/__init__.py create mode 100644 erpnext/manufacturing/doctype/work_order/services/material_coverage.py create mode 100644 erpnext/patches/v16_0/repair_work_order_material_transfer.py diff --git a/erpnext/manufacturing/doctype/work_order/services/__init__.py b/erpnext/manufacturing/doctype/work_order/services/__init__.py new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/erpnext/manufacturing/doctype/work_order/services/__init__.py @@ -0,0 +1 @@ + diff --git a/erpnext/manufacturing/doctype/work_order/services/material_coverage.py b/erpnext/manufacturing/doctype/work_order/services/material_coverage.py new file mode 100644 index 00000000000..8363e0c1284 --- /dev/null +++ b/erpnext/manufacturing/doctype/work_order/services/material_coverage.py @@ -0,0 +1,22 @@ +# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from collections.abc import Mapping + +from frappe.utils import flt + + +def get_minimum_material_coverage_fraction( + required_qty: Mapping[str, float], transferred_qty: Mapping[str, float], precision: int +) -> float: + """Return the least-covered component ratio at the configured quantity precision.""" + coverage = [] + for item_code, required in required_qty.items(): + transferred = flt(transferred_qty.get(item_code)) + # Stored values can differ after the digits that the user can enter or see. + if flt(transferred, precision) == flt(required, precision): + coverage.append(1.0) + else: + coverage.append(transferred / required) + + return min(coverage, default=0.0) diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 0d421ed4a63..e453dcc26c3 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -1461,9 +1461,11 @@ class TestWorkOrder(FrappeTestCase): del transfer_entry.get("items")[0] # transfer only one RM transfer_entry.submit() - # WO's "Material Transferred for Mfg" shows all is transferred, one RM is pending + # One required item is still missing, so no finished-good quantity is covered yet. work_order.reload() - self.assertEqual(work_order.material_transferred_for_manufacturing, 1) + self.assertEqual(transfer_entry.fg_completed_qty, 0) + self.assertEqual(work_order.material_transferred_for_manufacturing, 0) + self.assertEqual(work_order.status, "In Process") self.assertEqual(work_order.required_items[0].transferred_qty, 0) self.assertEqual(work_order.required_items[1].transferred_qty, 2) @@ -1483,6 +1485,47 @@ class TestWorkOrder(FrappeTestCase): self.assertEqual(work_order.required_items[0].transferred_qty, 1) self.assertEqual(work_order.required_items[1].transferred_qty, 2) + def test_material_transfer_claim_follows_actual_coverage(self): + work_order = make_wo_order_test_record(planned_start_date=now(), qty=4) + test_stock_entry.make_stock_entry( + item_code="_Test Item", target="_Test Warehouse - _TC", qty=10, basic_rate=5000.0 + ) + test_stock_entry.make_stock_entry( + item_code="_Test Item Home Desktop 100", + target="_Test Warehouse - _TC", + qty=20, + basic_rate=1000.0, + ) + + transfer_entry = frappe.get_doc( + make_stock_entry(work_order.name, "Material Transfer for Manufacture", 4) + ) + for row in transfer_entry.items: + if row.item_code == "_Test Item": + row.qty = 1 + transfer_entry.submit() + + work_order.reload() + self.assertEqual(transfer_entry.fg_completed_qty, 1) + self.assertEqual(work_order.material_transferred_for_manufacturing, 1) + + remainder_entry = frappe.get_doc( + make_stock_entry(work_order.name, "Material Transfer for Manufacture", 3) + ) + remainder_entry.submit() + + work_order.reload() + self.assertEqual(remainder_entry.fg_completed_qty, 3) + self.assertEqual(work_order.material_transferred_for_manufacturing, 4) + + def test_material_coverage_cap_skips_manufacture_entry(self): + work_order = make_wo_order_test_record(planned_start_date=now(), qty=1) + manufacture_entry = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 1)) + manufacture_entry.pro_doc = work_order + manufacture_entry._action = "submit" + + self.assertFalse(manufacture_entry._should_cap_completed_qty()) + def test_material_transferred_min_fraction_on_partial_pick_list(self): """Pick-list flow (fg_completed_qty = 0): 'Material Transferred for Manufacturing' must reflect the least-transferred required item (the bottleneck), instead of being @@ -1545,6 +1588,97 @@ class TestWorkOrder(FrappeTestCase): work_order.reload() self.assertEqual(work_order.material_transferred_for_manufacturing, 2.0) + def test_material_transferred_ignores_hidden_precision_difference(self): + work_order = make_wo_order_test_record(planned_start_date=now(), qty=2) + test_stock_entry.make_stock_entry( + item_code="_Test Item", target="_Test Warehouse - _TC", qty=10, basic_rate=5000.0 + ) + test_stock_entry.make_stock_entry( + item_code="_Test Item Home Desktop 100", + target="_Test Warehouse - _TC", + qty=10, + basic_rate=1000.0, + ) + + precision = work_order.precision("required_qty", "required_items") + hidden_difference = 4 / (10 ** (precision + 1)) + row = work_order.required_items[0] + row.db_set("required_qty", flt(row.required_qty) + hidden_difference, update_modified=False) + work_order.reload() + required_qty = {row.item_code: flt(row.required_qty) for row in work_order.required_items} + + transfer_entry = frappe.get_doc( + make_stock_entry(work_order.name, "Material Transfer for Manufacture", 0) + ) + for item in transfer_entry.items: + item.qty = flt(required_qty[item.item_code], precision) + item.transfer_qty = item.qty + transfer_entry.submit() + + work_order.reload() + self.assertEqual( + flt(work_order.required_items[0].required_qty, precision), + flt(work_order.required_items[0].transferred_qty, precision), + ) + self.assertEqual(work_order.material_transferred_for_manufacturing, work_order.qty) + + def test_repair_material_transfer_precision_patch(self): + from erpnext.patches.v16_0.repair_work_order_material_transfer import ( + execute, + get_precision_affected_work_orders, + ) + + precision = frappe.get_precision("Work Order Item", "required_qty") + hidden_difference = 4 / (10 ** (precision + 1)) + work_order = make_wo_order_test_record(planned_start_date=now(), qty=2) + for index, row in enumerate(work_order.required_items): + required_qty = flt(row.required_qty) + (hidden_difference if index == 0 else 0) + row.db_set( + { + "required_qty": required_qty, + "transferred_qty": flt(required_qty, precision), + }, + update_modified=False, + ) + work_order.db_set("material_transferred_for_manufacturing", 1.99, update_modified=False) + + partial_work_order = make_wo_order_test_record(planned_start_date=now(), qty=2) + for row in partial_work_order.required_items: + row.db_set("transferred_qty", row.required_qty, update_modified=False) + partial_row = partial_work_order.required_items[0] + partial_row.db_set( + "transferred_qty", + flt(partial_row.required_qty, precision) - (1 / (10**precision)), + update_modified=False, + ) + partial_work_order.db_set("material_transferred_for_manufacturing", 1.99, update_modified=False) + + terminal_work_orders = [] + for status in ("Stopped", "Closed", "Completed"): + terminal_work_order = make_wo_order_test_record(planned_start_date=now(), qty=2) + for row in terminal_work_order.required_items: + row.db_set("transferred_qty", row.required_qty, update_modified=False) + terminal_work_order.db_set( + {"material_transferred_for_manufacturing": 1.99, "status": status}, + update_modified=False, + ) + terminal_work_orders.append(terminal_work_order) + + updates = get_precision_affected_work_orders() + self.assertIn(work_order.name, updates) + self.assertNotIn(partial_work_order.name, updates) + for terminal_work_order in terminal_work_orders: + self.assertNotIn(terminal_work_order.name, updates) + + execute() + work_order.reload() + partial_work_order.reload() + self.assertEqual(work_order.material_transferred_for_manufacturing, work_order.qty) + self.assertEqual(partial_work_order.material_transferred_for_manufacturing, 1.99) + for terminal_work_order in terminal_work_orders: + terminal_work_order.reload() + self.assertEqual(terminal_work_order.material_transferred_for_manufacturing, 1.99) + def test_status_in_process_when_only_one_required_item_transferred(self): """Stock Entry created from a Pick List that picked only one of the required items: min-fraction keeps material_transferred_for_manufacturing at 0, but the work order must diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index d1bfa8edc0d..ae3520ab222 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -31,6 +31,9 @@ from erpnext.manufacturing.doctype.bom.bom import ( from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import ( get_mins_between_operations, ) +from erpnext.manufacturing.doctype.work_order.services.material_coverage import ( + get_minimum_material_coverage_fraction, +) from erpnext.stock.doctype.batch.batch import make_batch from erpnext.stock.doctype.item.item import get_item_defaults, validate_end_of_life from erpnext.stock.doctype.serial_no.serial_no import get_available_serial_nos, get_serial_nos @@ -436,8 +439,7 @@ class WorkOrder(Document): return status def has_transferred_material(self): - """True if any raw material was transferred against this work order via a pick list - (these leave material_transferred_for_manufacturing at 0 via the min-fraction rule).""" + """True if any raw material was transferred against this work order.""" ste = frappe.qb.DocType("Stock Entry") ste_child = frappe.qb.DocType("Stock Entry Detail") qty = ( @@ -450,7 +452,6 @@ class WorkOrder(Document): & (ste.docstatus == 1) & (ste.purpose == "Material Transfer for Manufacture") & (ste.is_return == 0) - & (ste.pick_list.isnotnull()) ) ).run()[0][0] return flt(qty) > 0 @@ -1274,20 +1275,13 @@ class WorkOrder(Document): self.recompute_material_transferred_for_manufacturing(transferred_items) def recompute_material_transferred_for_manufacturing(self, transferred_items): - """Set material_transferred_for_manufacturing based on actual item-level transfers, not fg_completed_qty.""" + """Set transferred quantity from the raw materials that have actually moved.""" # Job Card transfers use the minimum completed quantity across operations. if self.operations and self.transfer_material_against == "Job Card": return - # When fg_completed_qty > 0 (direct stock entries, excess transfer), preserve the - # SUM(fg_completed_qty) approach so excess-transfer tracking works correctly. - sum_fg_completed_qty = self.get_transferred_or_manufactured_qty("Material Transfer for Manufacture") - if sum_fg_completed_qty: - self.db_set("material_transferred_for_manufacturing", sum_fg_completed_qty) - return + claimed_qty = self.get_transferred_or_manufactured_qty("Material Transfer for Manufacture") - # Pick list flow sets fg_completed_qty=0; use min-fraction of actual item transfers - # so partial availability does not prematurely mark the work order as fully transferred. required_by_item = {} for row in self.required_items: if not row.include_item_in_manufacturing or flt(row.required_qty) <= 0: @@ -1297,12 +1291,13 @@ class WorkOrder(Document): if not required_by_item: return - min_fraction = min( - flt(transferred_items.get(item_code) or 0) / required_qty - for item_code, required_qty in required_by_item.items() + min_fraction = get_minimum_material_coverage_fraction( + required_by_item, + transferred_items, + self.precision("required_qty", "required_items"), ) - min_fraction = min(min_fraction, 1.0) - material_transferred = min_fraction * flt(self.qty) + covered_qty = min_fraction * flt(self.qty) + material_transferred = min(covered_qty, max(flt(self.qty), claimed_qty)) self.db_set("material_transferred_for_manufacturing", material_transferred) def update_returned_qty(self): diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 9ca27a734d8..4517df7184d 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -447,3 +447,4 @@ erpnext.patches.v16_0.rename_ar_ap_ageing_filter erpnext.patches.v15_0.fix_titles erpnext.patches.v16_0.backfill_repost_accounting_ledger_status erpnext.patches.v16_0.merge_seeded_item_group_root +erpnext.patches.v16_0.repair_work_order_material_transfer diff --git a/erpnext/patches/v16_0/repair_work_order_material_transfer.py b/erpnext/patches/v16_0/repair_work_order_material_transfer.py new file mode 100644 index 00000000000..31e119f8442 --- /dev/null +++ b/erpnext/patches/v16_0/repair_work_order_material_transfer.py @@ -0,0 +1,65 @@ +import frappe +from frappe.utils import flt +from pypika import functions as fn + +from erpnext.manufacturing.doctype.work_order.services.material_coverage import ( + get_minimum_material_coverage_fraction, +) + + +def execute(): + updates = get_precision_affected_work_orders() + frappe.db.bulk_update("Work Order", updates, update_modified=False) + + +def get_precision_affected_work_orders(): + """Return Work Orders whose components cover the plan at quantity precision.""" + work_orders = {} + for row in _get_candidate_rows(): + work_order = work_orders.setdefault( + row.work_order, + {"qty": flt(row.qty), "required_qty": {}, "transferred_qty": {}}, + ) + item_code = row.item_code + work_order["required_qty"][item_code] = work_order["required_qty"].get(item_code, 0.0) + flt( + row.required_qty + ) + work_order["transferred_qty"][item_code] = max( + work_order["transferred_qty"].get(item_code, 0.0), flt(row.transferred_qty) + ) + + precision = frappe.get_precision("Work Order Item", "required_qty") + return { + name: {"material_transferred_for_manufacturing": values["qty"]} + for name, values in work_orders.items() + if get_minimum_material_coverage_fraction( + values["required_qty"], values["transferred_qty"], precision + ) + >= 1.0 + } + + +def _get_candidate_rows(): + work_order = frappe.qb.DocType("Work Order") + required_item = frappe.qb.DocType("Work Order Item") + return ( + frappe.qb.from_(work_order) + .inner_join(required_item) + .on(required_item.parent == work_order.name) + .select( + work_order.name.as_("work_order"), + work_order.qty, + required_item.item_code, + required_item.required_qty, + required_item.transferred_qty, + ) + .where( + (work_order.docstatus == 1) + & (work_order.status.notin(["Stopped", "Closed", "Completed"])) + & (fn.Coalesce(work_order.skip_transfer, 0) == 0) + & (fn.Coalesce(work_order.material_transferred_for_manufacturing, 0) < work_order.qty) + & (fn.Coalesce(work_order.transfer_material_against, "") != "Job Card") + & (required_item.include_item_in_manufacturing == 1) + & (required_item.required_qty > 0) + ) + ).run(as_dict=True) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index c50671d9d3f..0541193184c 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -32,6 +32,9 @@ from erpnext.manufacturing.doctype.bom.bom import ( get_scrap_items_from_sub_assemblies, validate_bom_no, ) +from erpnext.manufacturing.doctype.work_order.services.material_coverage import ( + get_minimum_material_coverage_fraction, +) from erpnext.setup.doctype.brand.brand import get_brand_defaults from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults from erpnext.stock.doctype.batch.batch import get_batch_qty @@ -262,6 +265,7 @@ class StockEntry(StockController): self.calculate_rate_and_amount() self.validate_putaway_capacity() self.validate_component_and_quantities() + self._cap_completed_qty_to_material_coverage() self.validate_finished_good_serial_batch_for_work_order() if not self.get("purpose") == "Manufacture": @@ -1186,6 +1190,67 @@ class StockEntry(StockController): title=_("Missing Item"), ) + def _cap_completed_qty_to_material_coverage(self): + if not self._should_cap_completed_qty(): + return + # Keep an excessive claim intact so the Work Order allowance check can reject it. + max_qty = flt(self.pro_doc.qty) + overproduction_percentage = flt( + frappe.db.get_single_value("Manufacturing Settings", "overproduction_percentage_for_work_order") + ) + to_transfer_qty = flt(self.pro_doc.material_transferred_for_manufacturing) + flt( + self.fg_completed_qty + ) + transfer_limit_qty = max_qty + (max_qty * overproduction_percentage / 100) + if transfer_limit_qty < to_transfer_qty: + return + + required_qty, transferred_qty = self._get_work_order_material_qty() + if not required_qty: + return + + covered_before = self._get_covered_work_order_qty(required_qty, transferred_qty) + for row in self.items: + item_code = row.original_item or row.item_code + if row.s_warehouse and item_code in required_qty: + transferred_qty[item_code] += flt(row.qty) * flt(row.conversion_factor or 1) + + covered_after = self._get_covered_work_order_qty(required_qty, transferred_qty) + covered_by_entry = flt(max(covered_after - covered_before, 0), self.precision("fg_completed_qty")) + self.fg_completed_qty = min(flt(self.fg_completed_qty), covered_by_entry) + + def _should_cap_completed_qty(self): + if self.get("_action") != "submit": + return False + if self.purpose != "Material Transfer for Manufacture": + return False + if not self.pro_doc or not self.fg_completed_qty: + return False + if self.is_return or self.get("is_additional_transfer_entry"): + return False + return not (self.pro_doc.operations and self.pro_doc.transfer_material_against == "Job Card") + + def _get_work_order_material_qty(self): + required_qty = {} + transferred_qty = {} + for row in self.pro_doc.required_items: + if not row.include_item_in_manufacturing or flt(row.required_qty) <= 0: + continue + required_qty[row.item_code] = required_qty.get(row.item_code, 0.0) + flt(row.required_qty) + # Duplicate required-item rows each hold the aggregate transferred quantity. + transferred_qty[row.item_code] = max( + transferred_qty.get(row.item_code, 0.0), flt(row.transferred_qty) + ) + return required_qty, transferred_qty + + def _get_covered_work_order_qty(self, required_qty, transferred_qty): + min_fraction = get_minimum_material_coverage_fraction( + required_qty, + transferred_qty, + self.pro_doc.precision("required_qty", "required_items"), + ) + return min_fraction * flt(self.pro_doc.qty) + def _validate_no_excess_transfer(self): if self.is_return: return From 954a5ec006f1d73bd9dddc17aea99364cd246a99 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 13 Aug 2026 16:40:26 +0530 Subject: [PATCH 11/59] fix(stock): confirm before changing item qty from the batch selector (backport #58123) (#58124) fix(stock): confirm before changing item qty from the batch selector (#58123) the batch selector silently overwrote the item qty with the bundle total, so editing a row qty in the dialog changed the delivered qty without any warning. prompt for confirmation when the rows do not add up to the qty to fetch, and only proceed if the user agrees. (cherry picked from commit a2976dd29e9429a43f345a9e693c07aabb2ace9e) Co-authored-by: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com> --- .../js/utils/serial_no_batch_selector.js | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 91cda58718c..211059ca603 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -658,6 +658,27 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { frappe.throw(__("Rejected Warehouse and Accepted Warehouse cannot be same.")); } + let qty_to_fetch = flt(this.dialog.get_value("qty")); + let total_qty = entries.reduce((total, row) => total + (flt(row.qty) || 1.0), 0); + + if (flt(total_qty, 6) !== flt(qty_to_fetch, 6)) { + const confirm_dialog = frappe.confirm( + __( + "Total qty of the rows ({0}) does not match the Qty to Fetch ({1}). Qty of the item will be changed to {0}. Are you sure want to proceed?", + [format_number(total_qty), format_number(qty_to_fetch)] + ), + () => this.create_bundle_entries(entries, warehouse) + ); + confirm_dialog.indicator = "blue"; + confirm_dialog.set_indicator(); + + return; + } + + this.create_bundle_entries(entries, warehouse); + } + + create_bundle_entries(entries, warehouse) { frappe .call({ method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.add_serial_batch_ledgers", From eb85ca68f60356ce31eef6cb4fa19940f648b4d3 Mon Sep 17 00:00:00 2001 From: diptanilsaha Date: Thu, 13 Aug 2026 22:44:23 +0530 Subject: [PATCH 12/59] fix(crm_settings): create custom fields for Frappe CRM on enabling synchronization (cherry picked from commit be2dea0ba2f108f388e39e316d95d4a83442a1a3) --- erpnext/crm/doctype/crm_settings/crm_settings.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erpnext/crm/doctype/crm_settings/crm_settings.py b/erpnext/crm/doctype/crm_settings/crm_settings.py index 379c55ae5b3..73c0156e291 100644 --- a/erpnext/crm/doctype/crm_settings/crm_settings.py +++ b/erpnext/crm/doctype/crm_settings/crm_settings.py @@ -69,6 +69,13 @@ class CRMSettings(Document): self.allowed_users = [] def custom_fields_for_frappe_crm_data_sync(self): + custom_fields = self.get_frappe_crm_custom_fields() + + if self.enable_frappe_crm_data_synchronization: + create_custom_fields(custom_fields, ignore_validate=True) + + @staticmethod + def get_frappe_crm_custom_fields(): custom_fields = { "Quotation": [ { @@ -88,4 +95,4 @@ class CRMSettings(Document): ], } - create_custom_fields(custom_fields, ignore_validate=True) + return custom_fields From ff1a1914a1f10e32d2227c010c5c44a8f8f5a423 Mon Sep 17 00:00:00 2001 From: diptanilsaha Date: Thu, 13 Aug 2026 22:51:24 +0530 Subject: [PATCH 13/59] fix: patch to delete the `crm_deal` custom fields (cherry picked from commit 9613d72d8182305148cf0fb4915cb013ea37839d) --- erpnext/patches.txt | 1 + .../v16_0/remove_frappe_crm_custom_fields.py | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 erpnext/patches/v16_0/remove_frappe_crm_custom_fields.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 4517df7184d..5535458d3ed 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -448,3 +448,4 @@ erpnext.patches.v15_0.fix_titles erpnext.patches.v16_0.backfill_repost_accounting_ledger_status erpnext.patches.v16_0.merge_seeded_item_group_root erpnext.patches.v16_0.repair_work_order_material_transfer +erpnext.patches.v16_0.remove_frappe_crm_custom_fields diff --git a/erpnext/patches/v16_0/remove_frappe_crm_custom_fields.py b/erpnext/patches/v16_0/remove_frappe_crm_custom_fields.py new file mode 100644 index 00000000000..3f1c8e1a6f4 --- /dev/null +++ b/erpnext/patches/v16_0/remove_frappe_crm_custom_fields.py @@ -0,0 +1,27 @@ +import frappe +from frappe.custom.doctype.custom_field.custom_field import delete_custom_fields + +from erpnext.crm.doctype.crm_settings.crm_settings import CRMSettings + + +def execute(): + """Delete the `crm_deal` fields on Quotation and Customer if Frappe CRM Data Synchronization is disabled and there's no data on those fields.""" + + crm_deal_exists_in_quotation = frappe.db.has_column("Quotation", "crm_deal") and frappe.get_all( + "Quotation", filters={"crm_deal": ["is", "set"]}, limit=1 + ) + + crm_deal_exists_in_customer = frappe.db.has_column("Customer", "crm_deal") and frappe.get_all( + "Customer", filters={"crm_deal": ["is", "set"]}, limit=1 + ) + + enable_frappe_crm_data_sync = frappe.get_single_value( + "CRM Settings", "enable_frappe_crm_data_synchronization" + ) + + if enable_frappe_crm_data_sync or crm_deal_exists_in_quotation or crm_deal_exists_in_customer: + return + + custom_fields = CRMSettings.get_frappe_crm_custom_fields() + + delete_custom_fields(custom_fields) From 167cc1e5b95154da2de29763c40740b1ed190363 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 13 Aug 2026 18:41:15 +0000 Subject: [PATCH 14/59] fix: asset scrap flow related changes (backport #55126) (#58144) Co-authored-by: khushi8112 --- erpnext/assets/doctype/asset/depreciation.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 561de38e217..1abb8b73277 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -419,6 +419,7 @@ def get_comma_separated_links(names, doctype): @frappe.whitelist() def scrap_asset(asset_name, scrap_date=None): + frappe.has_permission("Asset", "write", asset_name, throw=True) asset = frappe.get_doc("Asset", asset_name) if asset.docstatus != 1: @@ -496,6 +497,7 @@ def validate_scrap_date(scrap_date, today_date, purchase_date, calculate_depreci @frappe.whitelist() def restore_asset(asset_name): + frappe.has_permission("Asset", "write", asset_name, throw=True) asset = frappe.get_doc("Asset", asset_name) reverse_depreciation_entry_made_after_disposal(asset, asset.disposal_date) From 81c53931b57e0d2e5003bc6017bcc96b879f5050 Mon Sep 17 00:00:00 2001 From: Pandiyan P Date: Fri, 14 Aug 2026 09:01:03 +0530 Subject: [PATCH 15/59] fix(buying): allow purchase returns against a closed purchase order (#58140) --- .../purchase_invoice/purchase_invoice.py | 3 ++ .../purchase_invoice/test_purchase_invoice.py | 33 +++++++++++++++++ erpnext/controllers/buying_controller.py | 2 +- .../purchase_receipt/purchase_receipt.py | 3 ++ .../purchase_receipt/test_purchase_receipt.py | 37 +++++++++++++++++++ 5 files changed, 77 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index afc08c0e4d6..79385db3a24 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -396,6 +396,9 @@ class PurchaseInvoice(BuyingController): self.party_account_currency = account.account_currency def check_on_hold_or_closed_status(self): + if self.get("is_return"): + return + check_list = [] for d in self.get("items"): diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index ae9b8442c34..50d6f76e3dc 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -2609,6 +2609,39 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): self.assertEqual(row.serial_no, "\n".join(serial_nos[:2])) self.assertEqual(row.rejected_serial_no, serial_nos[2]) + def test_purchase_invoice_return_against_closed_purchase_order(self): + from erpnext.controllers.sales_and_purchase_return import make_return_doc + + po = create_purchase_order(qty=2, rate=100) + + invoices = [] + for _ in range(2): + pi = make_pi_from_po(po.name) + pi.items[0].qty = 1 + pi.submit() + invoices.append(pi) + + make_return_doc("Purchase Invoice", invoices[0].name).submit() + + po.reload() + po.update_status("Closed") + + # a debit note against a closed Purchase Order should still go through, + # the same way a Sales Invoice return does against a closed Sales Order + debit_note = make_return_doc("Purchase Invoice", invoices[1].name) + debit_note.submit() + + self.assertEqual(debit_note.docstatus, 1) + self.assertEqual(frappe.db.get_value("Purchase Order", po.name, "status"), "Closed") + + # cancelling the debit note runs the same check on the closed order + debit_note.reload() + debit_note.cancel() + + # a regular invoice against the closed order must still be blocked + blocked_pi = make_pi_from_po(po.name) + self.assertRaisesRegex(frappe.InvalidStatusError, "Closed", blocked_pi.save) + def test_make_pr_and_pi_from_po(self): from erpnext.assets.doctype.asset.test_asset import create_asset_category diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 1bbec4b5196..a4323365201 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -755,7 +755,7 @@ class BuyingController(SubcontractingController): if po and po_item_rows: po_obj = frappe.get_doc("Purchase Order", po) - if po_obj.status in ["Closed", "Cancelled"]: + if po_obj.status == "Cancelled" or (po_obj.status == "Closed" and not self.get("is_return")): frappe.throw( _("{0} {1} is cancelled or closed").format(_("Purchase Order"), po), frappe.InvalidStatusError, diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 25c6fd987f5..f7df8ecd9f3 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -372,6 +372,9 @@ class PurchaseReceipt(BuyingController): # Check for Closed status def check_on_hold_or_closed_status(self): + if self.get("is_return"): + return + check_list = [] for d in self.get("items"): if d.meta.get_field("purchase_order") and d.purchase_order and d.purchase_order not in check_list: diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index 40cf324010d..f30158b4f51 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -708,6 +708,43 @@ class TestPurchaseReceipt(FrappeTestCase): update_purchase_receipt_status(pr.name, "Closed") self.assertEqual(frappe.db.get_value("Purchase Receipt", pr.name, "status"), "Closed") + def test_purchase_return_against_closed_purchase_order(self): + from erpnext.buying.doctype.purchase_order.purchase_order import ( + make_purchase_receipt as make_pr_from_po, + ) + from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order + from erpnext.controllers.sales_and_purchase_return import make_return_doc + + po = create_purchase_order(qty=2, rate=100) + + receipts = [] + for _ in range(2): + pr = make_pr_from_po(po.name) + pr.items[0].qty = pr.items[0].received_qty = 1 + pr.submit() + receipts.append(pr) + + first_return = make_return_doc("Purchase Receipt", receipts[0].name) + first_return.submit() + + po.reload() + po.update_status("Closed") + + # a return against a closed Purchase Order should still go through, + # the same way a Delivery Note return does against a closed Sales Order + second_return = make_return_doc("Purchase Receipt", receipts[1].name) + second_return.submit() + + self.assertEqual(second_return.docstatus, 1) + self.assertEqual(frappe.db.get_value("Purchase Order", po.name, "status"), "Closed") + + # cancelling the return runs the same check on the closed order + second_return.cancel() + + # a regular receipt against the closed order must still be blocked + blocked_pr = make_pr_from_po(po.name) + self.assertRaisesRegex(frappe.InvalidStatusError, "Closed", blocked_pr.save) + def test_pr_billing_status(self): """Flow: 1. PO -> PR1 -> PI From 9cc9aa0fa52bd548c51554d579add2a2a6c9f43d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 05:48:33 +0000 Subject: [PATCH 16/59] fix: ignore historical negative batch stock in outward validation (backport #58148) (#58150) fix: ignore historical negative batch stock in outward validation (#58148) (cherry picked from commit 9239d1c2a3f4d922f44c624425746519bf44c956) Co-authored-by: rohitwaghchaure --- .../serial_and_batch_bundle.py | 5 +- .../test_serial_and_batch_bundle.py | 152 ++++++++++++++++++ 2 files changed, 156 insertions(+), 1 deletion(-) 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 0b614be1596..8c28f98a0d7 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 @@ -1661,13 +1661,16 @@ class SerialandBatchBundle(Document): ) precision = frappe.get_precision("Serial and Batch Entry", "qty") + posting_datetime = get_datetime(self.posting_datetime) if self.posting_datetime else None for row in batchwise_entries: if row.batch_no in available_qty: available_qty[row.batch_no] += flt(row.qty) else: available_qty[row.batch_no] = flt(row.qty) - if flt(available_qty[row.batch_no], precision) < 0: + if flt(available_qty[row.batch_no], precision) < 0 and ( + not posting_datetime or get_datetime(row.posting_datetime) >= posting_datetime + ): self.throw_negative_batch( row.batch_no, available_qty[row.batch_no], precision, row.posting_datetime ) diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py index ff6e9012633..17a720f7430 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/test_serial_and_batch_bundle.py @@ -1335,6 +1335,158 @@ class TestSerialandBatchBundle(FrappeTestCase): batch_no="LSBRV-BATCH-0001", ) + def _setup_negative_batch_item(self, item_code, batches): + make_item(item_code, properties={"is_stock_item": 1, "has_batch_no": 1}) + for batch_no in batches: + if not frappe.db.exists("Batch", batch_no): + frappe.get_doc( + {"doctype": "Batch", "batch_id": batch_no, "item": item_code, "company": "_Test Company"} + ).insert(ignore_permissions=True) + + def _allow_negative_stock_temporarily(self): + for field in ("allow_negative_stock", "allow_negative_stock_for_batch"): + original = frappe.db.get_single_value("Stock Settings", field) + frappe.db.set_single_value("Stock Settings", field, 1) + self.addCleanup(frappe.db.set_single_value, "Stock Settings", field, original) + + def _disable_negative_stock(self): + frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 0) + frappe.db.set_single_value("Stock Settings", "allow_negative_stock_for_batch", 0) + + def test_historical_negative_batch_stock_does_not_block_outward(self): + from unittest.mock import patch + + from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import ( + BatchNegativeStockError, + SerialandBatchBundle, + ) + + item_code = "Test Hist Neg Batch Item" + ballast_batch, batch_no = "THNB-BALLAST-001", "THNB-BATCH-001" + self._setup_negative_batch_item(item_code, [ballast_batch, batch_no]) + warehouse = "_Test Warehouse - _TC" + + self._allow_negative_stock_temporarily() + make_stock_entry( + item_code=item_code, + qty=1000, + rate=100, + target=warehouse, + use_serial_batch_fields=True, + batch_no=ballast_batch, + posting_date=add_days(today(), -730), + posting_time="10:00:00", + ) + make_stock_entry( + item_code=item_code, + qty=100, + rate=100, + target=warehouse, + use_serial_batch_fields=True, + batch_no=batch_no, + posting_date=add_days(today(), -365), + posting_time="10:00:00", + ) + with patch.object(SerialandBatchBundle, "validate_negative_batch"): + make_stock_entry( + item_code=item_code, + qty=5, + source=warehouse, + use_serial_batch_fields=True, + batch_no=batch_no, + posting_date=add_days(today(), -730), + posting_time="11:00:00", + ) + self._disable_negative_stock() + + make_stock_entry( + item_code=item_code, + qty=10, + source=warehouse, + use_serial_batch_fields=True, + batch_no=batch_no, + ) + + outward = make_stock_entry( + item_code=item_code, + qty=200, + source=warehouse, + use_serial_batch_fields=True, + batch_no=batch_no, + do_not_submit=True, + ) + self.assertRaises(BatchNegativeStockError, outward.submit) + + def test_backdated_outward_cannot_make_future_batch_stock_negative(self): + from erpnext.stock.stock_ledger import NegativeStockError + + item_code = "Test Future Neg Batch Item" + ballast_batch, batch_no = "TFNB-BALLAST-001", "TFNB-BATCH-001" + self._setup_negative_batch_item(item_code, [ballast_batch, batch_no]) + warehouse = "_Test Warehouse - _TC" + + make_stock_entry( + item_code=item_code, + qty=1000, + rate=100, + target=warehouse, + use_serial_batch_fields=True, + batch_no=ballast_batch, + posting_date=add_days(today(), -365), + posting_time="10:00:00", + ) + make_stock_entry( + item_code=item_code, + qty=100, + rate=100, + target=warehouse, + use_serial_batch_fields=True, + batch_no=batch_no, + posting_date=add_days(today(), -365), + posting_time="11:00:00", + ) + make_stock_entry( + item_code=item_code, + qty=90, + source=warehouse, + use_serial_batch_fields=True, + batch_no=batch_no, + posting_date=add_days(today(), -180), + posting_time="10:00:00", + ) + make_stock_entry( + item_code=item_code, + qty=60, + rate=100, + target=warehouse, + use_serial_batch_fields=True, + batch_no=batch_no, + posting_date=add_days(today(), -30), + posting_time="10:00:00", + ) + + make_stock_entry( + item_code=item_code, + qty=5, + source=warehouse, + use_serial_batch_fields=True, + batch_no=batch_no, + posting_date=add_days(today(), -240), + posting_time="10:00:00", + ) + + backdated = make_stock_entry( + item_code=item_code, + qty=50, + source=warehouse, + use_serial_batch_fields=True, + batch_no=batch_no, + posting_date=add_days(today(), -240), + posting_time="11:00:00", + do_not_submit=True, + ) + self.assertRaises(NegativeStockError, backdated.submit) + def get_batch_from_bundle(bundle): from erpnext.stock.serial_batch_bundle import get_batch_nos From faaaa0776d4eca38d93f962884018d2960fdf896 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Fri, 14 Aug 2026 11:51:18 +0530 Subject: [PATCH 17/59] feat: sync serial no status from stock ledger in Stock Qty vs Serial No Count report (version-15-hotfix) (#57865) * feat: sync serial no status from stock ledger in Stock Qty vs Serial No Count report * fix: pick last bundle move in SQL ordered by posting datetime and SLE creation * fix: derive synced serial no status from stock ledger helper and validate sync args * fix: use posting datetime for last bundle move after version-15 field rename * fix: order last bundle moves by bundle posting datetime --- .../stock_qty_vs_serial_no_count.js | 24 +++ .../stock_qty_vs_serial_no_count.py | 175 ++++++++++++++++++ .../test_stock_qty_vs_serial_no_count.py | 41 ++++ 3 files changed, 240 insertions(+) create mode 100644 erpnext/stock/report/stock_qty_vs_serial_no_count/test_stock_qty_vs_serial_no_count.py diff --git a/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.js b/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.js index c38f0237436..6df8458fd3c 100644 --- a/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.js +++ b/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.js @@ -2,6 +2,30 @@ // For license information, please see license.txt frappe.query_reports["Stock Qty vs Serial No Count"] = { + onload: function (report) { + report.page.add_inner_button(__("Sync Serial No Status"), () => { + const warehouse = report.get_filter_value("warehouse"); + if (!warehouse) { + frappe.msgprint(__("Please select a warehouse first.")); + return; + } + + frappe.confirm( + __( + "This will update the warehouse and status of Serial Nos counted in {0} to match the stock ledger. Continue?", + [warehouse.bold()] + ), + () => { + frappe.call({ + method: "erpnext.stock.report.stock_qty_vs_serial_no_count.stock_qty_vs_serial_no_count.sync_serial_no_status", + args: { warehouse: warehouse }, + freeze: true, + }); + } + ); + }); + }, + filters: [ { fieldname: "company", diff --git a/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.py b/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.py index 6087c747374..001ae8f1a53 100644 --- a/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.py +++ b/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.py @@ -4,6 +4,12 @@ import frappe from frappe import _ +from frappe.query_builder import Order +from frappe.query_builder.functions import Coalesce +from frappe.utils import cstr, flt +from pypika import analytics as an + +from erpnext.stock.serial_batch_bundle import get_serial_no_status def execute(filters=None): @@ -77,3 +83,172 @@ def get_data(warehouse, show_disabled_items): data.append(row) return data + + +SYNC_CHUNK_SIZE = 1000 + + +@frappe.whitelist(methods=["POST"]) +def sync_serial_no_status(warehouse: str, item_code: str | None = None): + if not frappe.has_permission("Serial No", "write"): + frappe.throw(_("Not permitted to update Serial No"), frappe.PermissionError) + + warehouse = cstr(warehouse) + item_code = cstr(item_code) if item_code else None + if not frappe.db.exists("Warehouse", warehouse): + frappe.throw(_("Warehouse {0} does not exist").format(warehouse)) + + if item_code and not frappe.db.exists("Item", item_code): + frappe.throw(_("Item {0} does not exist").format(item_code)) + + frappe.enqueue( + sync_serial_no_status_for_warehouse, + queue="long", + warehouse=warehouse, + item_code=item_code, + ) + frappe.msgprint( + _("Serial No status sync has been queued. Reload the report after a few minutes."), + alert=True, + ) + + +def sync_serial_no_status_for_warehouse(warehouse, item_code=None): + filters = {"has_serial_no": 1} + if item_code: + filters["name"] = item_code + + for item in frappe.get_all("Item", filters=filters, pluck="name"): + sync_serial_no_status_for_item(item, warehouse) + + +def sync_serial_no_status_for_item(item_code, warehouse): + """Correct Serial No records this report counts in the warehouse but whose last + stock ledger movement says the stock left it. Reposting rebuilds qty and valuation + from the ledger but never rewrites Serial No warehouse/status, so records orphaned + by cancelled or amended vouchers keep inflating the serial count.""" + serial_nos = frappe.get_all( + "Serial No", + filters={"item_code": item_code, "warehouse": warehouse, "status": ("in", ["Active", "Expired"])}, + pluck="name", + ) + if not serial_nos: + return + + last_moves = get_last_ledger_moves(item_code, serial_nos) + for serial_no in serial_nos: + row = last_moves.get(serial_no) + if row and flt(row.qty) > 0 and row.warehouse == warehouse: + continue + + set_serial_no_state_from_ledger(serial_no, row) + + +def set_serial_no_state_from_ledger(serial_no, row): + if not row: + frappe.db.set_value( + "Serial No", serial_no, {"warehouse": None, "status": "Inactive"}, update_modified=False + ) + return + + status = get_serial_no_status( + frappe._dict( + actual_qty=flt(row.qty), + warehouse=row.warehouse, + voucher_type=row.voucher_type, + voucher_no=row.voucher_no, + is_cancelled=0, + ) + ) + warehouse = row.warehouse if status == "Active" else None + frappe.db.set_value( + "Serial No", serial_no, {"warehouse": warehouse, "status": status}, update_modified=False + ) + + +def get_last_ledger_moves(item_code, serial_nos): + last_moves = get_last_bundle_moves(item_code, serial_nos) + if missing := [serial_no for serial_no in serial_nos if serial_no not in last_moves]: + set_legacy_last_moves(item_code, missing, last_moves) + + return last_moves + + +def get_last_bundle_moves(item_code, serial_nos): + last_moves = {} + for start in range(0, len(serial_nos), SYNC_CHUNK_SIZE): + for row in get_last_bundle_moves_chunk(item_code, serial_nos[start : start + SYNC_CHUNK_SIZE]): + last_moves[row.serial_no] = row + + return last_moves + + +def get_last_bundle_moves_chunk(item_code, serial_nos): + """A bundle can be created much before its Stock Ledger Entry, so same-posting-datetime + ties are broken on the creation of the bundle's own SLE. The SLE join also keeps only + real stock movements - reservation bundles (Pick List) carry no SLE.""" + entry = frappe.qb.DocType("Serial and Batch Entry") + bundle = frappe.qb.DocType("Serial and Batch Bundle") + sle = frappe.qb.DocType("Stock Ledger Entry") + + row_number = ( + an.RowNumber() + .over(entry.serial_no) + .orderby(bundle.posting_datetime, order=Order.desc) + .orderby(sle.creation, order=Order.desc) + ) + + ranked = ( + frappe.qb.from_(entry) + .inner_join(bundle) + .on(entry.parent == bundle.name) + .inner_join(sle) + .on(sle.serial_and_batch_bundle == bundle.name) + .select( + entry.serial_no, + entry.qty, + Coalesce(entry.warehouse, bundle.warehouse).as_("warehouse"), + bundle.voucher_type, + bundle.voucher_no, + row_number.as_("row_no"), + ) + .where( + (bundle.docstatus == 1) + & (Coalesce(bundle.is_cancelled, 0) == 0) + & (sle.is_cancelled == 0) + & (bundle.item_code == item_code) + & (entry.serial_no.isin(serial_nos)) + ) + ).as_("ranked") + + return ( + frappe.qb.from_(ranked) + .select(ranked.serial_no, ranked.qty, ranked.warehouse, ranked.voucher_type, ranked.voucher_no) + .where(ranked.row_no == 1) + .run(as_dict=True) + ) + + +def set_legacy_last_moves(item_code, serial_nos, last_moves): + """Movements posted before Serial and Batch Bundle exist only as newline-separated + text on Stock Ledger Entry.""" + from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos + + pending = set(serial_nos) + rows = frappe.get_all( + "Stock Ledger Entry", + filters={"item_code": item_code, "is_cancelled": 0, "serial_no": ("is", "set")}, + fields=["serial_no", "actual_qty", "warehouse", "voucher_type", "voucher_no"], + order_by="posting_datetime asc, creation asc", + ) + + for row in rows: + qty = 1 if flt(row.actual_qty) > 0 else -1 + for serial_no in get_serial_nos(row.serial_no): + if serial_no in pending: + last_moves[serial_no] = frappe._dict( + qty=qty, + warehouse=row.warehouse, + voucher_type=row.voucher_type, + voucher_no=row.voucher_no, + ) diff --git a/erpnext/stock/report/stock_qty_vs_serial_no_count/test_stock_qty_vs_serial_no_count.py b/erpnext/stock/report/stock_qty_vs_serial_no_count/test_stock_qty_vs_serial_no_count.py new file mode 100644 index 00000000000..f61ed70ef72 --- /dev/null +++ b/erpnext/stock/report/stock_qty_vs_serial_no_count/test_stock_qty_vs_serial_no_count.py @@ -0,0 +1,41 @@ +# Copyright (c) 2026, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt + +import frappe +from frappe.tests.utils import FrappeTestCase + +from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry + + +class TestStockQtyVsSerialNoCount(FrappeTestCase): + def test_sync_serial_no_status(self): + from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note + from erpnext.stock.report.stock_qty_vs_serial_no_count.stock_qty_vs_serial_no_count import ( + sync_serial_no_status_for_warehouse, + ) + + item = "_Test Serialized Item With Series" + warehouse = "Stores - _TC" + se = make_stock_entry(item_code=item, to_warehouse=warehouse, qty=2, rate=100) + serial_no = frappe.get_all( + "Serial and Batch Entry", + {"parent": se.items[0].serial_and_batch_bundle}, + pluck="serial_no", + )[0] + + create_delivery_note( + item_code=item, + warehouse=warehouse, + qty=1, + serial_no=serial_no, + use_serial_batch_fields=1, + ) + self.assertEqual(frappe.db.get_value("Serial No", serial_no, "status"), "Delivered") + + frappe.db.set_value("Serial No", serial_no, {"status": "Active", "warehouse": warehouse}) + + sync_serial_no_status_for_warehouse(warehouse, item_code=item) + + details = frappe.db.get_value("Serial No", serial_no, ["status", "warehouse"], as_dict=True) + self.assertEqual(details.status, "Delivered") + self.assertFalse(details.warehouse) From 8c9e941614b10438d36f6a55d6470f60628c5516 Mon Sep 17 00:00:00 2001 From: Smit Vora Date: Tue, 28 Jul 2026 17:21:55 +0530 Subject: [PATCH 18/59] feat: taxable-base resolver hook for custom charge types (#56175) (cherry picked from commit 986cea2331ccd9965eff94893cc4fdd482326eaf) # Conflicts: # erpnext/controllers/taxes_and_totals.py # erpnext/controllers/tests/test_taxes_and_totals.py # erpnext/public/js/controllers/taxes_and_totals.js --- erpnext/controllers/taxes_and_totals.py | 129 ++++++++++++++---- .../tests/test_taxes_and_totals.py | 106 ++++++++++++++ .../public/js/controllers/taxes_and_totals.js | 95 +++++++++++-- 3 files changed, 291 insertions(+), 39 deletions(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index a8a48140bdd..7b659341b97 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -298,33 +298,37 @@ class calculate_taxes_and_totals: for item in self.doc.items: item_tax_map = self._load_item_tax_rate(item.item_tax_rate) - cumulated_tax_fraction = 0 - total_inclusive_tax_amount_per_qty = 0 + total_tax_slope = 0 + total_tax_intercept = 0 for i, tax in enumerate(self.doc.get("taxes")): ( tax.tax_fraction_for_current_item, - inclusive_tax_amount_per_qty, - ) = self.get_current_tax_fraction(tax, item_tax_map) + tax_intercept_per_qty, + ) = self.get_current_tax_fraction(tax, item_tax_map, item) + tax.inclusive_amount_per_qty = tax_intercept_per_qty if i == 0: tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item + tax.grand_total_amount_per_qty = tax_intercept_per_qty else: + prev = self.doc.get("taxes")[i - 1] tax.grand_total_fraction_for_current_item = ( - self.doc.get("taxes")[i - 1].grand_total_fraction_for_current_item - + tax.tax_fraction_for_current_item + prev.grand_total_fraction_for_current_item + tax.tax_fraction_for_current_item ) + tax.grand_total_amount_per_qty = prev.grand_total_amount_per_qty + tax_intercept_per_qty - cumulated_tax_fraction += tax.tax_fraction_for_current_item - total_inclusive_tax_amount_per_qty += inclusive_tax_amount_per_qty * flt(item.qty) + total_tax_slope += tax.tax_fraction_for_current_item + total_tax_intercept += tax_intercept_per_qty * flt(item.qty) - if ( - not self.discount_amount_applied - and item.qty - and (cumulated_tax_fraction or total_inclusive_tax_amount_per_qty) - ): - amount = flt(item.amount) - total_inclusive_tax_amount_per_qty + if not self.discount_amount_applied and item.qty and (total_tax_slope or total_tax_intercept): + amount = flt(item.amount) - total_tax_intercept +<<<<<<< HEAD item.net_amount = flt(amount / (1 + cumulated_tax_fraction), item.precision("net_amount")) +======= + item._unrounded_net_amount = amount / (1 + total_tax_slope) + item.net_amount = flt(item._unrounded_net_amount, item.precision("net_amount")) +>>>>>>> 986cea2331 (feat: taxable-base resolver hook for custom charge types (#56175)) item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate")) item.discount_percentage = flt( item.discount_percentage, item.precision("discount_percentage") @@ -335,38 +339,51 @@ class calculate_taxes_and_totals: def _load_item_tax_rate(self, item_tax_rate): return json.loads(item_tax_rate) if item_tax_rate else {} - def get_current_tax_fraction(self, tax, item_tax_map): + def get_current_tax_fraction(self, tax, item_tax_map, item): """ - Get tax fraction for calculating tax exclusive amount - from tax inclusive amount + tax = slope * net + intercept. + Returns (slope, intercept_per_qty) """ - current_tax_fraction = 0 - inclusive_tax_amount_per_qty = 0 + tax_slope = 0 + tax_intercept = 0 if cint(tax.included_in_print_rate): tax_rate = self._get_tax_rate(tax, item_tax_map) +<<<<<<< HEAD +======= + if tax_rate == NOT_APPLICABLE_TAX: + return tax_slope, tax_intercept + +>>>>>>> 986cea2331 (feat: taxable-base resolver hook for custom charge types (#56175)) if tax.charge_type == "On Net Total": - current_tax_fraction = tax_rate / 100.0 + tax_slope = tax_rate / 100.0 elif tax.charge_type == "On Previous Row Amount": - current_tax_fraction = (tax_rate / 100.0) * self.doc.get("taxes")[ - cint(tax.row_id) - 1 - ].tax_fraction_for_current_item + row = self.doc.get("taxes")[cint(tax.row_id) - 1] + tax_slope = (tax_rate / 100.0) * row.tax_fraction_for_current_item + tax_intercept = (tax_rate / 100.0) * flt(getattr(row, "inclusive_amount_per_qty", 0)) elif tax.charge_type == "On Previous Row Total": - current_tax_fraction = (tax_rate / 100.0) * self.doc.get("taxes")[ - cint(tax.row_id) - 1 - ].grand_total_fraction_for_current_item + row = self.doc.get("taxes")[cint(tax.row_id) - 1] + tax_slope = (tax_rate / 100.0) * row.grand_total_fraction_for_current_item + tax_intercept = (tax_rate / 100.0) * flt(getattr(row, "grand_total_amount_per_qty", 0)) elif tax.charge_type == "On Item Quantity": - inclusive_tax_amount_per_qty = flt(tax_rate) + tax_intercept = flt(tax_rate) + + else: + # Custom charge_type: the rate applies to a resolved (fixed) base, + # e.g. a tax on MRP included in the printed price. + qty = flt(item.qty) or 1 + base = self.get_item_taxable_base(item, tax) + tax_intercept = (tax_rate / 100.0) * base / qty if getattr(tax, "add_deduct_tax", None) and tax.add_deduct_tax == "Deduct": - current_tax_fraction *= -1.0 - inclusive_tax_amount_per_qty *= -1.0 + tax_slope *= -1.0 + tax_intercept *= -1.0 - return current_tax_fraction, inclusive_tax_amount_per_qty + return tax_slope, tax_intercept def _get_tax_rate(self, tax, item_tax_map): if tax.account_head in item_tax_map: @@ -529,7 +546,21 @@ class calculate_taxes_and_totals: ) elif tax.charge_type == "On Net Total": +<<<<<<< HEAD current_tax_amount = (tax_rate / 100.0) * item.net_amount +======= + if tax.account_head in item_tax_map: + current_net_amount = item.net_amount + # Use unrounded net for inclusive taxes to avoid double rounding + if ( + cint(tax.included_in_print_rate) + and not self.discount_amount_applied + and item._unrounded_net_amount is not None + ): + current_tax_amount = (tax_rate / 100.0) * item._unrounded_net_amount + else: + current_tax_amount = (tax_rate / 100.0) * item.net_amount +>>>>>>> 986cea2331 (feat: taxable-base resolver hook for custom charge types (#56175)) elif tax.charge_type == "On Previous Row Amount": current_tax_amount = (tax_rate / 100.0) * self.doc.get("taxes")[ cint(tax.row_id) - 1 @@ -540,13 +571,51 @@ class calculate_taxes_and_totals: ].grand_total_for_current_item elif tax.charge_type == "On Item Quantity": current_tax_amount = tax_rate * item.qty + else: + # Custom charge_type: rate applies to the resolver-provided base. + base = self.get_item_taxable_base(item, tax) + current_net_amount = base + current_tax_amount = (tax_rate / 100.0) * base if not (self.doc.get("is_consolidated") or tax.get("dont_recompute_tax")): self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount) return current_tax_amount +<<<<<<< HEAD def set_item_wise_tax(self, item, tax, tax_rate, current_tax_amount): +======= + def get_item_taxable_base(self, item, tax): + """Per-item base a custom charge_type's rate is applied to. + + Override the base (gross, MRP, net of other taxes, …) via the + `erpnext_taxable_base_resolvers` hook + + Register a resolver in `hooks.py`, keyed by charge_type: + + erpnext_taxable_base_resolvers = {"On Gross Amount": "my_app.taxes.gross_base"} + + It receives (calc, item, tax) — calc is this instance, calc.doc the parent — + and returns the base (flt-coerced by the caller): + + def gross_base(calc, item, tax): + return item.custom_field_mrp * item.qty + + A resolver may stamp transient attributes on `item`; it can be called more than once + per item, so such stamping must be idempotent. + """ + resolvers = frappe.get_hooks("erpnext_taxable_base_resolvers") or {} + path = resolvers.get(tax.charge_type) + + if path: + method = path[-1] if isinstance(path, list | tuple) else path + return flt(frappe.get_attr(method)(self, item, tax)) + + # fallback + return flt(item.net_amount) + + def set_item_wise_tax(self, item, tax, tax_rate, current_tax_amount, current_net_amount): +>>>>>>> 986cea2331 (feat: taxable-base resolver hook for custom charge types (#56175)) # store tax breakup for each item key = item.item_code or item.item_name item_wise_tax_amount = current_tax_amount * self.doc.conversion_rate diff --git a/erpnext/controllers/tests/test_taxes_and_totals.py b/erpnext/controllers/tests/test_taxes_and_totals.py index 715acf8782f..a17263863e1 100644 --- a/erpnext/controllers/tests/test_taxes_and_totals.py +++ b/erpnext/controllers/tests/test_taxes_and_totals.py @@ -1,13 +1,32 @@ +from unittest import mock from unittest.mock import patch import frappe +<<<<<<< HEAD from frappe.tests.utils import FrappeTestCase +======= +from frappe.utils import flt +>>>>>>> 986cea2331 (feat: taxable-base resolver hook for custom charge types (#56175)) from erpnext.controllers.taxes_and_totals import calculate_taxes_and_totals from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order +<<<<<<< HEAD class TestTaxesAndTotals(FrappeTestCase): +======= +def resolve_on_gross(calc, item, tax): + # base = gross printed line amount + return flt(item.amount) + + +def resolve_on_mrp(calc, item, tax): + # base = MRP, not net + return flt(item.price_list_rate) * flt(item.qty) + + +class TestTaxesAndTotals(ERPNextTestSuite): +>>>>>>> 986cea2331 (feat: taxable-base resolver hook for custom charge types (#56175)) def test_regional_round_off_accounts(self): """ Regional overrides cannot extend the list in-place — the return @@ -30,6 +49,93 @@ class TestTaxesAndTotals(FrappeTestCase): self.assertIn(test_account, frappe.flags.round_off_applicable_accounts) + def test_exclusive_custom_charge_on_resolved_base(self): + """Added (exclusive) custom charge_type whose base is resolved by the + `erpnext_taxable_base_resolvers` hook. IPI 10% on the gross product value 1000 + -> tax 100, net 1000, grand 1100.""" + so = make_sales_order(do_not_save=True) + so.items = [] + so.append( + "items", + { + "item_code": "_Test Item", + "qty": 1, + "rate": 1000, + "price_list_rate": 1000, + "warehouse": "_Test Warehouse - _TC", + }, + ) + so.set("taxes", []) + so.append( + "taxes", + { + "charge_type": "On Gross Value", + "account_head": "_Test Account Excise Duty - _TC", + "description": "IPI 10% on gross product value", + "rate": 10, + "cost_center": "_Test Cost Center - _TC", + }, + ) + + real_get_hooks = frappe.get_hooks + + def fake_get_hooks(hook=None, *args, **kwargs): + if hook == "erpnext_taxable_base_resolvers": + return { + "On Gross Value": ["erpnext.controllers.tests.test_taxes_and_totals.resolve_on_gross"] + } + return real_get_hooks(hook, *args, **kwargs) + + with mock.patch("frappe.get_hooks", side_effect=fake_get_hooks): + calculate_taxes_and_totals(so) + + self.assertEqual(so.net_total, 1000.0) + self.assertEqual(so.taxes[0].tax_amount, 100.0) + self.assertEqual(so.grand_total, 1100.0) + + def test_inclusive_custom_charge_on_resolved_base(self): + """Inclusive custom charge on a resolved base backs out non-compounding + (tax = rate x resolved base) — a resolved base is fixed, so it never + compounds. MRP 1200, printed 1000, rate 10%: tax 120, net 880.""" + so = make_sales_order(do_not_save=True) + so.items = [] + so.append( + "items", + { + "item_code": "_Test Item", + "qty": 1, + "rate": 1000, + "price_list_rate": 1200, + "warehouse": "_Test Warehouse - _TC", + }, + ) + so.set("taxes", []) + so.append( + "taxes", + { + "charge_type": "On MRP", + "account_head": "_Test Account VAT - _TC", + "description": "Tax 10% on MRP, inclusive", + "rate": 10, + "included_in_print_rate": 1, + "cost_center": "_Test Cost Center - _TC", + }, + ) + + real_get_hooks = frappe.get_hooks + + def fake_get_hooks(hook=None, *args, **kwargs): + if hook == "erpnext_taxable_base_resolvers": + return {"On MRP": ["erpnext.controllers.tests.test_taxes_and_totals.resolve_on_mrp"]} + return real_get_hooks(hook, *args, **kwargs) + + with mock.patch("frappe.get_hooks", side_effect=fake_get_hooks): + calculate_taxes_and_totals(so) + + self.assertEqual(so.taxes[0].tax_amount, 120.0) + self.assertEqual(so.net_total, 880.0) + self.assertEqual(so.grand_total, 1000.0) + def test_disabling_rounded_total_resets_base_fields(self): """Disabling rounded total should also clear base rounded values.""" so = make_sales_order(do_not_save=True) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 4d980d7e277..e6aad4684a5 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -1,6 +1,16 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt +<<<<<<< HEAD +======= +const NOT_APPLICABLE_TAX = "N/A"; + +// Per-charge_type base resolvers, mirror of the `erpnext_taxable_base_resolvers` +// server hook. A localization registers `fn(calc, item, tax)` returning the per-item +// base, so the client preview matches the server for custom charge types. +erpnext.taxable_base_resolvers = erpnext.taxable_base_resolvers || {}; + +>>>>>>> 986cea2331 (feat: taxable-base resolver hook for custom charge types (#56175)) erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { setup() { this.fetch_round_off_accounts(); @@ -252,28 +262,53 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { $.each(this.frm.doc.items || [], function(n, item) { var item_tax_map = me._load_item_tax_rate(item.item_tax_rate); +<<<<<<< HEAD var cumulated_tax_fraction = 0.0; var total_inclusive_tax_amount_per_qty = 0; $.each(me.frm.doc["taxes"] || [], function(i, tax) { var current_tax_fraction = me.get_current_tax_fraction(tax, item_tax_map); tax.tax_fraction_for_current_item = current_tax_fraction[0]; var inclusive_tax_amount_per_qty = current_tax_fraction[1]; +======= + var total_tax_slope = 0.0; + var total_tax_intercept = 0; + $.each(me.frm.doc["taxes"] || [], function (i, tax) { + var tax_contribution = me.get_current_tax_fraction(tax, item_tax_map, item); + tax.tax_fraction_for_current_item = tax_contribution[0]; + var tax_intercept_per_qty = tax_contribution[1]; + tax.inclusive_amount_per_qty = tax_intercept_per_qty; +>>>>>>> 986cea2331 (feat: taxable-base resolver hook for custom charge types (#56175)) if(i==0) { tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item; + tax.grand_total_amount_per_qty = tax_intercept_per_qty; } else { + var prev = me.frm.doc["taxes"][i - 1]; tax.grand_total_fraction_for_current_item = +<<<<<<< HEAD me.frm.doc["taxes"][i-1].grand_total_fraction_for_current_item + tax.tax_fraction_for_current_item; +======= + prev.grand_total_fraction_for_current_item + tax.tax_fraction_for_current_item; + tax.grand_total_amount_per_qty = + flt(prev.grand_total_amount_per_qty) + tax_intercept_per_qty; +>>>>>>> 986cea2331 (feat: taxable-base resolver hook for custom charge types (#56175)) } - cumulated_tax_fraction += tax.tax_fraction_for_current_item; - total_inclusive_tax_amount_per_qty += inclusive_tax_amount_per_qty * flt(item.qty); + total_tax_slope += tax.tax_fraction_for_current_item; + total_tax_intercept += tax_intercept_per_qty * flt(item.qty); }); +<<<<<<< HEAD if(!me.discount_amount_applied && item.qty && (total_inclusive_tax_amount_per_qty || cumulated_tax_fraction)) { var amount = flt(item.amount) - total_inclusive_tax_amount_per_qty; item.net_amount = flt(amount / (1 + cumulated_tax_fraction), precision("net_amount", item)); +======= + if (!me.discount_amount_applied && item.qty && (total_tax_intercept || total_tax_slope)) { + var amount = flt(item.amount) - total_tax_intercept; + item._unrounded_net_amount = amount / (1 + total_tax_slope); + item.net_amount = flt(item._unrounded_net_amount, precision("net_amount", item)); +>>>>>>> 986cea2331 (feat: taxable-base resolver hook for custom charge types (#56175)) item.net_rate = item.qty ? flt(item.net_amount / item.qty, precision("net_rate", item)) : 0; me.set_in_company_currency(item, ["net_rate", "net_amount"]); @@ -281,15 +316,16 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { }); } - get_current_tax_fraction(tax, item_tax_map) { - // Get tax fraction for calculating tax exclusive amount - // from tax inclusive amount - var current_tax_fraction = 0.0; - var inclusive_tax_amount_per_qty = 0; + get_current_tax_fraction(tax, item_tax_map, item) { + // tax = slope * net + intercept. + // Returns [slope, intercept_per_qty] + var tax_slope = 0.0; + var tax_intercept = 0; if(cint(tax.included_in_print_rate)) { var tax_rate = this._get_tax_rate(tax, item_tax_map); +<<<<<<< HEAD if(tax.charge_type == "On Net Total") { current_tax_fraction = (tax_rate / 100.0); @@ -300,16 +336,52 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { } else if(tax.charge_type == "On Previous Row Total") { current_tax_fraction = (tax_rate / 100.0) * this.frm.doc["taxes"][cint(tax.row_id) - 1].grand_total_fraction_for_current_item; +======= + if (tax_rate === NOT_APPLICABLE_TAX) { + return [tax_slope, tax_intercept]; + } + + if (tax.charge_type == "On Net Total") { + tax_slope = tax_rate / 100.0; + } else if (tax.charge_type == "On Previous Row Amount") { + const row = this.frm.doc["taxes"][cint(tax.row_id) - 1]; + tax_slope = (tax_rate / 100.0) * row.tax_fraction_for_current_item; + tax_intercept = (tax_rate / 100.0) * flt(row.inclusive_amount_per_qty); + } else if (tax.charge_type == "On Previous Row Total") { + const row = this.frm.doc["taxes"][cint(tax.row_id) - 1]; + tax_slope = (tax_rate / 100.0) * row.grand_total_fraction_for_current_item; + tax_intercept = (tax_rate / 100.0) * flt(row.grand_total_amount_per_qty); +>>>>>>> 986cea2331 (feat: taxable-base resolver hook for custom charge types (#56175)) } else if (tax.charge_type == "On Item Quantity") { - inclusive_tax_amount_per_qty = flt(tax_rate); + tax_intercept = flt(tax_rate); + } else { + // Custom charge_type: the rate applies to a resolved (fixed) base, + // e.g. a tax on MRP included in the printed price. + const qty = flt(item.qty) || 1; + const base = this.get_item_taxable_base(item, tax); + tax_intercept = ((tax_rate / 100.0) * base) / qty; } } +<<<<<<< HEAD if(tax.add_deduct_tax && tax.add_deduct_tax == "Deduct") { current_tax_fraction *= -1; inclusive_tax_amount_per_qty *= -1; +======= + if (tax.add_deduct_tax && tax.add_deduct_tax == "Deduct") { + tax_slope *= -1; + tax_intercept *= -1; +>>>>>>> 986cea2331 (feat: taxable-base resolver hook for custom charge types (#56175)) } - return [current_tax_fraction, inclusive_tax_amount_per_qty]; + return [tax_slope, tax_intercept]; + } + + get_item_taxable_base(item, tax) { + // Mirror of the server get_item_taxable_base: a custom charge_type's resolver + // overrides the base value; otherwise the net amount. + const resolver = erpnext.taxable_base_resolvers[tax.charge_type]; + if (resolver) return flt(resolver(this, item, tax)); + return flt(item.net_amount); } _get_tax_rate(tax, item_tax_map) { @@ -526,6 +598,11 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { } else if (tax.charge_type == "On Item Quantity") { // don't sum current net amount due to the field being a currency field current_tax_amount = tax_rate * item.qty; + } else { + // Custom charge_type: rate applies to the resolver-provided base. + var resolved_base = this.get_item_taxable_base(item, tax); + current_net_amount = resolved_base; + current_tax_amount = (tax_rate / 100.0) * resolved_base; } if (!tax.dont_recompute_tax) { From e7e2358cb5b04e3696dadcf927d415bb30694ca4 Mon Sep 17 00:00:00 2001 From: vorasmit Date: Fri, 14 Aug 2026 15:12:42 +0530 Subject: [PATCH 19/59] chore: resolve conflicts --- erpnext/controllers/taxes_and_totals.py | 37 +----------- .../tests/test_taxes_and_totals.py | 9 +-- .../public/js/controllers/taxes_and_totals.js | 60 +++---------------- 3 files changed, 11 insertions(+), 95 deletions(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 7b659341b97..69a0e05ea60 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -323,12 +323,7 @@ class calculate_taxes_and_totals: if not self.discount_amount_applied and item.qty and (total_tax_slope or total_tax_intercept): amount = flt(item.amount) - total_tax_intercept -<<<<<<< HEAD - item.net_amount = flt(amount / (1 + cumulated_tax_fraction), item.precision("net_amount")) -======= - item._unrounded_net_amount = amount / (1 + total_tax_slope) - item.net_amount = flt(item._unrounded_net_amount, item.precision("net_amount")) ->>>>>>> 986cea2331 (feat: taxable-base resolver hook for custom charge types (#56175)) + item.net_amount = flt(amount / (1 + total_tax_slope), item.precision("net_amount")) item.net_rate = flt(item.net_amount / item.qty, item.precision("net_rate")) item.discount_percentage = flt( item.discount_percentage, item.precision("discount_percentage") @@ -350,12 +345,6 @@ class calculate_taxes_and_totals: if cint(tax.included_in_print_rate): tax_rate = self._get_tax_rate(tax, item_tax_map) -<<<<<<< HEAD -======= - if tax_rate == NOT_APPLICABLE_TAX: - return tax_slope, tax_intercept - ->>>>>>> 986cea2331 (feat: taxable-base resolver hook for custom charge types (#56175)) if tax.charge_type == "On Net Total": tax_slope = tax_rate / 100.0 @@ -546,21 +535,7 @@ class calculate_taxes_and_totals: ) elif tax.charge_type == "On Net Total": -<<<<<<< HEAD current_tax_amount = (tax_rate / 100.0) * item.net_amount -======= - if tax.account_head in item_tax_map: - current_net_amount = item.net_amount - # Use unrounded net for inclusive taxes to avoid double rounding - if ( - cint(tax.included_in_print_rate) - and not self.discount_amount_applied - and item._unrounded_net_amount is not None - ): - current_tax_amount = (tax_rate / 100.0) * item._unrounded_net_amount - else: - current_tax_amount = (tax_rate / 100.0) * item.net_amount ->>>>>>> 986cea2331 (feat: taxable-base resolver hook for custom charge types (#56175)) elif tax.charge_type == "On Previous Row Amount": current_tax_amount = (tax_rate / 100.0) * self.doc.get("taxes")[ cint(tax.row_id) - 1 @@ -573,18 +548,13 @@ class calculate_taxes_and_totals: current_tax_amount = tax_rate * item.qty else: # Custom charge_type: rate applies to the resolver-provided base. - base = self.get_item_taxable_base(item, tax) - current_net_amount = base - current_tax_amount = (tax_rate / 100.0) * base + current_tax_amount = (tax_rate / 100.0) * self.get_item_taxable_base(item, tax) if not (self.doc.get("is_consolidated") or tax.get("dont_recompute_tax")): self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount) return current_tax_amount -<<<<<<< HEAD - def set_item_wise_tax(self, item, tax, tax_rate, current_tax_amount): -======= def get_item_taxable_base(self, item, tax): """Per-item base a custom charge_type's rate is applied to. @@ -614,8 +584,7 @@ class calculate_taxes_and_totals: # fallback return flt(item.net_amount) - def set_item_wise_tax(self, item, tax, tax_rate, current_tax_amount, current_net_amount): ->>>>>>> 986cea2331 (feat: taxable-base resolver hook for custom charge types (#56175)) + def set_item_wise_tax(self, item, tax, tax_rate, current_tax_amount): # store tax breakup for each item key = item.item_code or item.item_name item_wise_tax_amount = current_tax_amount * self.doc.conversion_rate diff --git a/erpnext/controllers/tests/test_taxes_and_totals.py b/erpnext/controllers/tests/test_taxes_and_totals.py index a17263863e1..d9fcbda701c 100644 --- a/erpnext/controllers/tests/test_taxes_and_totals.py +++ b/erpnext/controllers/tests/test_taxes_and_totals.py @@ -2,19 +2,13 @@ from unittest import mock from unittest.mock import patch import frappe -<<<<<<< HEAD from frappe.tests.utils import FrappeTestCase -======= from frappe.utils import flt ->>>>>>> 986cea2331 (feat: taxable-base resolver hook for custom charge types (#56175)) from erpnext.controllers.taxes_and_totals import calculate_taxes_and_totals from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order -<<<<<<< HEAD -class TestTaxesAndTotals(FrappeTestCase): -======= def resolve_on_gross(calc, item, tax): # base = gross printed line amount return flt(item.amount) @@ -25,8 +19,7 @@ def resolve_on_mrp(calc, item, tax): return flt(item.price_list_rate) * flt(item.qty) -class TestTaxesAndTotals(ERPNextTestSuite): ->>>>>>> 986cea2331 (feat: taxable-base resolver hook for custom charge types (#56175)) +class TestTaxesAndTotals(FrappeTestCase): def test_regional_round_off_accounts(self): """ Regional overrides cannot extend the list in-place — the return diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index e6aad4684a5..5fb9a6b6080 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -1,16 +1,11 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -<<<<<<< HEAD -======= -const NOT_APPLICABLE_TAX = "N/A"; - // Per-charge_type base resolvers, mirror of the `erpnext_taxable_base_resolvers` // server hook. A localization registers `fn(calc, item, tax)` returning the per-item // base, so the client preview matches the server for custom charge types. erpnext.taxable_base_resolvers = erpnext.taxable_base_resolvers || {}; ->>>>>>> 986cea2331 (feat: taxable-base resolver hook for custom charge types (#56175)) erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { setup() { this.fetch_round_off_accounts(); @@ -262,22 +257,13 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { $.each(this.frm.doc.items || [], function(n, item) { var item_tax_map = me._load_item_tax_rate(item.item_tax_rate); -<<<<<<< HEAD - var cumulated_tax_fraction = 0.0; - var total_inclusive_tax_amount_per_qty = 0; - $.each(me.frm.doc["taxes"] || [], function(i, tax) { - var current_tax_fraction = me.get_current_tax_fraction(tax, item_tax_map); - tax.tax_fraction_for_current_item = current_tax_fraction[0]; - var inclusive_tax_amount_per_qty = current_tax_fraction[1]; -======= var total_tax_slope = 0.0; var total_tax_intercept = 0; - $.each(me.frm.doc["taxes"] || [], function (i, tax) { + $.each(me.frm.doc["taxes"] || [], function(i, tax) { var tax_contribution = me.get_current_tax_fraction(tax, item_tax_map, item); tax.tax_fraction_for_current_item = tax_contribution[0]; var tax_intercept_per_qty = tax_contribution[1]; tax.inclusive_amount_per_qty = tax_intercept_per_qty; ->>>>>>> 986cea2331 (feat: taxable-base resolver hook for custom charge types (#56175)) if(i==0) { tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item; @@ -285,30 +271,19 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { } else { var prev = me.frm.doc["taxes"][i - 1]; tax.grand_total_fraction_for_current_item = -<<<<<<< HEAD - me.frm.doc["taxes"][i-1].grand_total_fraction_for_current_item + + prev.grand_total_fraction_for_current_item + tax.tax_fraction_for_current_item; -======= - prev.grand_total_fraction_for_current_item + tax.tax_fraction_for_current_item; tax.grand_total_amount_per_qty = flt(prev.grand_total_amount_per_qty) + tax_intercept_per_qty; ->>>>>>> 986cea2331 (feat: taxable-base resolver hook for custom charge types (#56175)) } total_tax_slope += tax.tax_fraction_for_current_item; total_tax_intercept += tax_intercept_per_qty * flt(item.qty); }); -<<<<<<< HEAD - if(!me.discount_amount_applied && item.qty && (total_inclusive_tax_amount_per_qty || cumulated_tax_fraction)) { - var amount = flt(item.amount) - total_inclusive_tax_amount_per_qty; - item.net_amount = flt(amount / (1 + cumulated_tax_fraction), precision("net_amount", item)); -======= - if (!me.discount_amount_applied && item.qty && (total_tax_intercept || total_tax_slope)) { + if(!me.discount_amount_applied && item.qty && (total_tax_intercept || total_tax_slope)) { var amount = flt(item.amount) - total_tax_intercept; - item._unrounded_net_amount = amount / (1 + total_tax_slope); - item.net_amount = flt(item._unrounded_net_amount, precision("net_amount", item)); ->>>>>>> 986cea2331 (feat: taxable-base resolver hook for custom charge types (#56175)) + item.net_amount = flt(amount / (1 + total_tax_slope), precision("net_amount", item)); item.net_rate = item.qty ? flt(item.net_amount / item.qty, precision("net_rate", item)) : 0; me.set_in_company_currency(item, ["net_rate", "net_amount"]); @@ -325,33 +300,18 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { if(cint(tax.included_in_print_rate)) { var tax_rate = this._get_tax_rate(tax, item_tax_map); -<<<<<<< HEAD if(tax.charge_type == "On Net Total") { - current_tax_fraction = (tax_rate / 100.0); + tax_slope = (tax_rate / 100.0); } else if(tax.charge_type == "On Previous Row Amount") { - current_tax_fraction = (tax_rate / 100.0) * - this.frm.doc["taxes"][cint(tax.row_id) - 1].tax_fraction_for_current_item; - - } else if(tax.charge_type == "On Previous Row Total") { - current_tax_fraction = (tax_rate / 100.0) * - this.frm.doc["taxes"][cint(tax.row_id) - 1].grand_total_fraction_for_current_item; -======= - if (tax_rate === NOT_APPLICABLE_TAX) { - return [tax_slope, tax_intercept]; - } - - if (tax.charge_type == "On Net Total") { - tax_slope = tax_rate / 100.0; - } else if (tax.charge_type == "On Previous Row Amount") { const row = this.frm.doc["taxes"][cint(tax.row_id) - 1]; tax_slope = (tax_rate / 100.0) * row.tax_fraction_for_current_item; tax_intercept = (tax_rate / 100.0) * flt(row.inclusive_amount_per_qty); - } else if (tax.charge_type == "On Previous Row Total") { + + } else if(tax.charge_type == "On Previous Row Total") { const row = this.frm.doc["taxes"][cint(tax.row_id) - 1]; tax_slope = (tax_rate / 100.0) * row.grand_total_fraction_for_current_item; tax_intercept = (tax_rate / 100.0) * flt(row.grand_total_amount_per_qty); ->>>>>>> 986cea2331 (feat: taxable-base resolver hook for custom charge types (#56175)) } else if (tax.charge_type == "On Item Quantity") { tax_intercept = flt(tax_rate); } else { @@ -363,15 +323,9 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { } } -<<<<<<< HEAD if(tax.add_deduct_tax && tax.add_deduct_tax == "Deduct") { - current_tax_fraction *= -1; - inclusive_tax_amount_per_qty *= -1; -======= - if (tax.add_deduct_tax && tax.add_deduct_tax == "Deduct") { tax_slope *= -1; tax_intercept *= -1; ->>>>>>> 986cea2331 (feat: taxable-base resolver hook for custom charge types (#56175)) } return [tax_slope, tax_intercept]; } From 8dc9919691ae836bca4437f4c1ffa8cc401ac60f Mon Sep 17 00:00:00 2001 From: nishkagosalia Date: Fri, 14 Aug 2026 15:52:30 +0530 Subject: [PATCH 20/59] fix: validation for task end date check (cherry picked from commit 7c6da80f9883ee2f6291631c3e1ce8e4086df4d1) --- erpnext/projects/doctype/task/task.py | 42 ++++++++++++++++++--------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 5028c60f1ad..b5e2fbf31df 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -116,19 +116,35 @@ class Task(NestedSet): if not self.project or frappe.flags.in_test: return - if project_end_date := frappe.db.get_value("Project", self.project, "expected_end_date"): - project_end_date = getdate(project_end_date) - for fieldname in ("exp_start_date", "exp_end_date", "act_start_date", "act_end_date"): - task_date = self.get(fieldname) - if task_date and date_diff(project_end_date, getdate(task_date)) < 0: - frappe.throw( - _("{0}'s {1} cannot be after {2}'s Expected End Date.").format( - frappe.bold(frappe.get_desk_link("Task", self.name)), - _(self.meta.get_label(fieldname)), - frappe.bold(frappe.get_desk_link("Project", self.project)), - ), - frappe.exceptions.InvalidDates, - ) + project_start_date, project_end_date = frappe.db.get_value( + "Project", self.project, ["expected_start_date", "expected_end_date"] + ) + + for fieldname in ("exp_start_date", "exp_end_date", "act_start_date", "act_end_date"): + task_date = self.get(fieldname) + if not task_date: + continue + task_date = getdate(task_date) + + if project_end_date and date_diff(getdate(project_end_date), task_date) < 0: + frappe.throw( + _("{0}'s {1} cannot be after {2}'s Expected End Date.").format( + get_link_to_form("Task", self.name), + _(self.meta.get_label(fieldname)), + get_link_to_form("Project", self.project), + ), + frappe.exceptions.InvalidDates, + ) + + if project_start_date and date_diff(task_date, getdate(project_start_date)) < 0: + frappe.throw( + _("{0}'s {1} cannot be before {2}'s Expected Start Date.").format( + get_link_to_form("Task", self.name), + _(self.meta.get_label(fieldname)), + get_link_to_form("Project", self.project), + ), + frappe.exceptions.InvalidDates, + ) def validate_status(self): if self.is_template and self.status != "Template": From e704e589cab39b315abcefcfbe66cfb57e945aff Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 16:27:37 +0530 Subject: [PATCH 21/59] feat(accounts): opt-in 'Consider Accounting Dimension' filter on General Ledger Report (backport #58156) (#58157) Co-authored-by: Diptanil Saha --- .../accounts_settings/accounts_settings.json | 17 ++++++++++++----- .../accounts_settings/accounts_settings.py | 1 + .../report/general_ledger/general_ledger.js | 2 +- erpnext/startup/boot.py | 3 +++ 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 62b6d5c0b6a..595cc029e39 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -89,13 +89,14 @@ "enable_fuzzy_matching", "reports_tab", "remarks_section", - "general_ledger_remarks_length", + "disable_include_dimensions", "column_break_lvjk", - "receivable_payable_remarks_length", + "general_ledger_remarks_length", "accounts_receivable_payable_tuning_section", "receivable_payable_fetch_method", "default_ageing_range", "column_break_ntmi", + "receivable_payable_remarks_length", "legacy_section", "ignore_is_opening_check_for_reporting", "payment_request_settings", @@ -483,7 +484,7 @@ { "fieldname": "remarks_section", "fieldtype": "Section Break", - "label": "Remarks Column Length" + "label": "General Ledger Report" }, { "default": "0", @@ -566,7 +567,7 @@ { "fieldname": "accounts_receivable_payable_tuning_section", "fieldtype": "Section Break", - "label": "Accounts Receivable / Payable Tuning" + "label": "Accounts Receivable / Payable Report" }, { "fieldname": "legacy_section", @@ -665,6 +666,12 @@ "fieldname": "default_ageing_range", "fieldtype": "Data", "label": "Default Ageing Range" + }, + { + "default": "0", + "fieldname": "disable_include_dimensions", + "fieldtype": "Check", + "label": "Disable \"Consider Accounting Dimension\" Filter" } ], "icon": "icon-cog", @@ -672,7 +679,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2026-06-24 12:59:41.868865", + "modified": "2026-08-14 13:12:47.895908", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py index 83ece261895..27ed0290827 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py @@ -44,6 +44,7 @@ class AccountsSettings(Document): default_ageing_range: DF.Data | None delete_linked_ledger_entries: DF.Check determine_address_tax_category_from: DF.Literal["Billing Address", "Shipping Address"] + disable_include_dimensions: DF.Check enable_common_party_accounting: DF.Check enable_fuzzy_matching: DF.Check enable_immutable_ledger: DF.Check diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index c8470bc4c3f..ec24ec900a1 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -174,7 +174,7 @@ frappe.query_reports["General Ledger"] = { fieldname: "include_dimensions", label: __("Consider Accounting Dimensions"), fieldtype: "Check", - default: 1, + default: frappe.boot.sysdefaults.disable_include_dimensions ? 0 : 1, }, { fieldname: "disable_opening_balance_calculation", diff --git a/erpnext/startup/boot.py b/erpnext/startup/boot.py index ff1141fb07e..ffe81ab3915 100644 --- a/erpnext/startup/boot.py +++ b/erpnext/startup/boot.py @@ -25,6 +25,9 @@ def boot_session(bootinfo): bootinfo.sysdefaults.over_billing_allowance = frappe.db.get_single_value( "Accounts Settings", "over_billing_allowance" ) + bootinfo.sysdefaults.disable_include_dimensions = cint( + frappe.get_single_value("Accounts Settings", "disable_include_dimensions") + ) bootinfo.sysdefaults.quotation_valid_till = cint( frappe.db.get_single_value("CRM Settings", "default_valid_till") From 754e7052ca60afafe0b0ef370e42de033d340d73 Mon Sep 17 00:00:00 2001 From: pandiyan Date: Fri, 14 Aug 2026 16:56:06 +0530 Subject: [PATCH 22/59] test(accounts): cover reversal of a reverse journal entry also assert that a user without read access on the entry gets a permission error instead of the reversal relationship. --- .../journal_entry/test_journal_entry.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py index 62e74d02033..61ab04d2a8b 100644 --- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py @@ -249,6 +249,27 @@ class TestJournalEntry(unittest.TestCase): self.check_gl_entries() + def test_disallow_reversal_of_a_reversal_journal_entry(self): + from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry + + jv = make_journal_entry("_Test Bank - _TC", "Sales - _TC", 100, submit=True) + + rjv = make_reverse_journal_entry(jv.name) + rjv.posting_date = nowdate() + rjv.submit() + + self.assertRaisesRegex( + frappe.ValidationError, + "is already a Reverse Journal Entry", + make_reverse_journal_entry, + rjv.name, + ) + + # the guard must not disclose the reversal to a user who cannot read the entry + frappe.set_user("Guest") + self.addCleanup(frappe.set_user, "Administrator") + self.assertRaises(frappe.PermissionError, make_reverse_journal_entry, rjv.name) + def test_disallow_change_in_account_currency_for_a_party(self): # create jv in USD jv = make_journal_entry("_Test Bank USD - _TC", "_Test Receivable USD - _TC", 100, save=False) From 15041a62dd6cf45eb87d0b71c8e9db85f9331838 Mon Sep 17 00:00:00 2001 From: pandiyan Date: Fri, 14 Aug 2026 16:56:30 +0530 Subject: [PATCH 23/59] fix(accounts): disallow reversing a reverse journal entry check read permission on the source entry before the guard runs, so the reversal relationship is not disclosed to a user who cannot read it. --- .../doctype/journal_entry/journal_entry.js | 2 +- .../doctype/journal_entry/journal_entry.py | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index 47ba392802e..7c396c94eed 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -65,7 +65,7 @@ frappe.ui.form.on("Journal Entry", { ); } - if (frm.doc.docstatus == 1) { + if (frm.doc.docstatus == 1 && !frm.doc.reversal_of) { frm.add_custom_button( __("Reverse Journal Entry"), function () { diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 8acd5cf8587..bccf718f4a6 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -7,6 +7,7 @@ import json import frappe from frappe import _, msgprint, scrub from frappe.core.doctype.submission_queue.submission_queue import queue_submission +from frappe.model.document import Document from frappe.utils import comma_and, cstr, flt, fmt_money, formatdate, get_link_to_form, getdate, nowdate import erpnext @@ -1892,7 +1893,21 @@ def make_inter_company_journal_entry(name, voucher_type, company): @frappe.whitelist() -def make_reverse_journal_entry(source_name, target_doc=None): +def make_reverse_journal_entry(source_name: str, target_doc: str | dict | Document | None = None) -> Document: + # `get_mapped_doc` checks this as well, but the guard below discloses which entry + # reverses which, so read access has to be settled before it runs + if not frappe.has_permission("Journal Entry", doc=source_name): + frappe.throw(_("Not permitted"), frappe.PermissionError) + + reversal_of = frappe.db.get_value("Journal Entry", source_name, "reversal_of") + if reversal_of: + frappe.throw( + _("{0} is already a Reverse Journal Entry of {1}. Cancel it instead of reversing it.").format( + get_link_to_form("Journal Entry", source_name), + get_link_to_form("Journal Entry", reversal_of), + ) + ) + from frappe.model.mapper import get_mapped_doc def post_process(source, target): From 6d06b434343630c8d249cb3fec392203f01eb083 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 13:57:27 +0000 Subject: [PATCH 24/59] fix(stock): honour pick serial / batch based on in the batch selector (backport #58176) (#58181) Co-authored-by: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com> --- .../public/js/utils/serial_no_batch_selector.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js index 211059ca603..14011751d3f 100644 --- a/erpnext/public/js/utils/serial_no_batch_selector.js +++ b/erpnext/public/js/utils/serial_no_batch_selector.js @@ -8,6 +8,16 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { ? this.item.rejected_serial_and_batch_bundle : this.item.serial_and_batch_bundle; + this.init(); + } + + async init() { + try { + this.based_on = await erpnext.stock.get_pick_serial_batch_based_on(); + } catch (e) { + this.based_on = "FIFO"; + } + this.make(); this.render_data(); } @@ -379,7 +389,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { { fieldtype: "Select", options: ["FIFO", "LIFO", "Expiry"], - default: "FIFO", + default: this.based_on, fieldname: "based_on", label: __("Fetch Based On"), onchange: () => this.get_auto_data(), @@ -525,7 +535,7 @@ erpnext.SerialBatchPackageSelector = class SerialNoBatchBundleUpdate { } if (!based_on) { - based_on = "FIFO"; + based_on = this.based_on; } let warehouse = this.item.warehouse || this.item.s_warehouse; From 4c9a76ef9fa3befb7b5c72fe5615fbc9749354af Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 16:56:24 +0000 Subject: [PATCH 25/59] feat: Belgian Charts of Accounts (commercial + non-profit, FR + NL) (backport #54679) (#58185) Co-authored-by: Antoine Maas Co-authored-by: Claude Co-authored-by: Diptanil Saha --- .../unverified/be_l10nbe_chart_template.json | 1539 ---------------- ...liseerd_rekeningstelsel_ondernemingen.json | 1597 +++++++++++++++++ ...eningstelsel_verenigingen_stichtingen.json | 1478 +++++++++++++++ ...mum_normalise_associations_fondations.json | 1478 +++++++++++++++ ...mptable_minimum_normalise_entreprises.json | 1597 +++++++++++++++++ 5 files changed, 6150 insertions(+), 1539 deletions(-) delete mode 100644 erpnext/accounts/doctype/account/chart_of_accounts/unverified/be_l10nbe_chart_template.json create mode 100644 erpnext/accounts/doctype/account/chart_of_accounts/verified/be_minimum_genormaliseerd_rekeningstelsel_ondernemingen.json create mode 100644 erpnext/accounts/doctype/account/chart_of_accounts/verified/be_minimum_genormaliseerd_rekeningstelsel_verenigingen_stichtingen.json create mode 100644 erpnext/accounts/doctype/account/chart_of_accounts/verified/be_plan_comptable_minimum_normalise_associations_fondations.json create mode 100644 erpnext/accounts/doctype/account/chart_of_accounts/verified/be_plan_comptable_minimum_normalise_entreprises.json diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/unverified/be_l10nbe_chart_template.json b/erpnext/accounts/doctype/account/chart_of_accounts/unverified/be_l10nbe_chart_template.json deleted file mode 100644 index 7fc58ce410b..00000000000 --- a/erpnext/accounts/doctype/account/chart_of_accounts/unverified/be_l10nbe_chart_template.json +++ /dev/null @@ -1,1539 +0,0 @@ -{ - "country_code": "be", - "name": "Belgian - PCMN", - "tree": { - "CLASSE 1": { - "BENEFICE (PERTE) REPORTE(E)": { - "B\u00e9n\u00e9fice report\u00e9": {}, - "Perte report\u00e9e": {} - }, - "CAPITAL": { - "Capital non appel\u00e9": {}, - "Capital souscrit ou capital personnel": { - "Capital amorti": {}, - "Capital non amorti": {} - }, - "Compte de l'exploitant": { - "Imp\u00f4ts personnels": {}, - "Op\u00e9rations courantes": {}, - "R\u00e9mun\u00e9rations et autres avantages": {} - } - }, - "COMPTES DE LIAISON DES ETABLISSEMENTS ET SUCCURSALES": {}, - "DETTES A PLUS D'UN AN": { - "Acomptes re\u00e7us sur commandes": {}, - "Autres emprunts": {}, - "Cautionnements re\u00e7us en num\u00e9raires": {}, - "Dettes commerciales": { - "Effets \u00e0 payer": { - "Entreprises apparent\u00e9es": { - "Entreprises avec lesquelles il existe un lien de participation": {}, - "Entreprises li\u00e9es": {} - }, - "Fournisseurs ordinaires": { - "Fournisseurs C.E.E.": {}, - "Fournisseurs belges": {}, - "Fournisseurs importation": {} - } - }, - "Fournisseurs : dettes en compte": { - "Entreprises apparent\u00e9es": { - "Entreprises avec lesquelles il existe un lien de participation": {}, - "Entreprises li\u00e9es": {} - }, - "Fournisseurs ordinaires": { - "Fournisseurs C.E.E.": {}, - "Fournisseurs belges": {}, - "Fournisseurs importation": {} - } - } - }, - "Dettes de location-financement et assimil\u00e9s": { - "Dettes de location-financement de biens immobiliers": {}, - "Dettes de location-financement de biens mobiliers": {}, - "Dettes sur droits r\u00e9els sur immeubles": {} - }, - "Dettes diverses": { - "Administrateurs, g\u00e9rants, associ\u00e9s": {}, - "Autres dettes diverses": {}, - "Autres entreprises avec lesquelles il existe un lien de participation": {}, - "Dettes envers les coparticipants des associations momentan\u00e9es et en participation": {}, - "Entreprises li\u00e9es": {}, - "Rentes viag\u00e8res capitalis\u00e9es": {} - }, - "Emprunts obligataires non subordonn\u00e9s": { - "Convertibles": {}, - "Non convertibles": {} - }, - "Emprunts subordonn\u00e9s": { - "Convertibles": {}, - "Non convertibles": {} - }, - "Etablissements de cr\u00e9dit": { - "Cr\u00e9dits d'acceptation": { - "Banque A": {}, - "Banque B": {} - }, - "Dettes en compte": { - "Banque A": {}, - "Banque B": {} - }, - "Promesses": { - "Banque A": {}, - "Banque B": {} - } - } - }, - "PLUS-VALUES DE REEVALUATION": { - "Plus-values de r\u00e9\u00e9valuation sur immobilisations corporelles": { - "Plus-values de r\u00e9\u00e9valuation": {}, - "Reprises de r\u00e9ductions de valeur": {} - }, - "Plus-values de r\u00e9\u00e9valuation sur immobilisations financi\u00e8res": { - "Plus-values de r\u00e9\u00e9valuation": {}, - "Reprises de r\u00e9ductions de valeur": {} - }, - "Plus-values de r\u00e9\u00e9valuation sur immobilisations incorporelles": { - "Plus-values de r\u00e9\u00e9valuation": {}, - "Reprises de r\u00e9ductions de valeur": {} - }, - "Plus-values de r\u00e9\u00e9valuation sur stocks": {}, - "Reprises de r\u00e9ductions de valeur sur placements de tr\u00e9sorerie": {} - }, - "PRIMES D'EMISSION": {}, - "PROVISIONS POUR RISQUES ET CHARGES": { - "Provisions pour autres risques et charges": {}, - "Provisions pour charges fiscales": {}, - "Provisions pour engagements relatifs \u00e0 l'acquisition ou \u00e0 la cession d'immobilisations": {}, - "Provisions pour ex\u00e9cution de commandes pass\u00e9es ou re\u00e7ues": {}, - "Provisions pour garanties techniques attach\u00e9es aux ventes et prestations d\u00e9j\u00e0 effectu\u00e9es par l'entreprise": {}, - "Provisions pour grosses r\u00e9parations et gros entretiens": {}, - "Provisions pour pensions et obligations similaires": {}, - "Provisions pour positions et march\u00e9s \u00e0 terme en devises ou positions et march\u00e9s \u00e0 terme en marchandises": {}, - "Provisions pour s\u00fbret\u00e9s personnelles ou r\u00e9elles constitu\u00e9es \u00e0 l'appui de dettes et d'engagements de tiers": {} - }, - "RESERVES": { - "R\u00e9serve l\u00e9gale": {}, - "R\u00e9serves disponibles": { - "R\u00e9serve pour installations en faveur du personnel 1333 R\u00e9serves libres": {}, - "R\u00e9serve pour renouvellement des immobilisations": {}, - "R\u00e9serve pour r\u00e9gularisation de dividendes": {} - }, - "R\u00e9serves immunis\u00e9es": {}, - "R\u00e9serves indisponibles": { - "Autres r\u00e9serves indisponibles": {}, - "R\u00e9serve pour actions propres": {} - } - }, - "SUBSIDES EN CAPITAL": { - "Montants obtenus": {}, - "Montants transf\u00e9r\u00e9s aux r\u00e9sultats": {} - }, - "root_type": "" - }, - "CLASSE 2. FRAIS D'ETABLISSEMENT. ACTIFS IMMOBILISES ET CREANCES A PLUS D'UN AN": { - "AUTRES IMMOBILISATIONS CORPORELLES": { - "Amortissements sur autres immobilisations corporelles": { - "Amortissements sur emballages r\u00e9cup\u00e9rables": {}, - "Amortissements sur frais d'am\u00e9nagement des locaux pris en location": {}, - "Amortissements sur maison d'habitation": {}, - "Amortissements sur mat\u00e9riel d'emballage": {}, - "Amortissements sur r\u00e9serve immobili\u00e8re": {} - }, - "Emballages r\u00e9cup\u00e9rables": {}, - "Frais d'am\u00e9nagements de locaux pris en location": {}, - "Maison d'habitation": {}, - "Mat\u00e9riel d'emballage": {}, - "Plus-values act\u00e9es sur autres immobilisations corporelles": {}, - "R\u00e9serve immobili\u00e8re": {} - }, - "CREANCES A PLUS D'UN AN": { - "Autres cr\u00e9ances": { - "Cr\u00e9ances douteuses": {}, - "Cr\u00e9ances en compte": { - "Cr\u00e9ances autres d\u00e9biteurs": {}, - "Cr\u00e9ances entreprises avec lesquelles il existe un lien de participation": {}, - "Cr\u00e9ances entreprises li\u00e9es": {} - }, - "Cr\u00e9ances r\u00e9sultant de la cession d'immobilisations donn\u00e9es en leasing": {}, - "Effets \u00e0 recevoir": { - "Sur autres d\u00e9biteurs": {}, - "Sur entreprises avec lesquelles il existe un lien de participation": {}, - "Sur entreprises li\u00e9es": {} - }, - "R\u00e9ductions de valeur act\u00e9es": {} - }, - "Cr\u00e9ances commerciales": { - "Acomptes vers\u00e9s": {}, - "Clients": { - "Cr\u00e9ances en compte sur entreprises li\u00e9es": {}, - "Cr\u00e9ances sur les coparticipants": {}, - "Sur clients Belgique": {}, - "Sur clients C.E.E.": {}, - "Sur clients exportation hors C.E.E.": {}, - "Sur entreprises avec lesquelles il existe un lien de participation": {} - }, - "Cr\u00e9ances douteuses": {}, - "Effets \u00e0 recevoir": { - "Sur clients Belgique": {}, - "Sur clients C.E.E.": {}, - "Sur clients exportation hors C.E.E.": {}, - "Sur entreprises avec lesquelles il existe un lien de participation": {}, - "Sur entreprises li\u00e9es": {} - }, - "Retenues sur garanties": {}, - "R\u00e9ductions de valeur act\u00e9es": {} - } - }, - "FRAIS D'ETABLISSEMENT": { - "Autres frais d'\u00e9tablissement": { - "Amortissements sur autres frais d'\u00e9tablissement": {}, - "Autres frais d'\u00e9tablissement": {} - }, - "Frais d'\u00e9mission d'emprunts et primes de remboursement": { - "Agios sur emprunts et frais d'\u00e9mission d'emprunts": {}, - "Amortissements sur agios sur emprunts et frais d'\u00e9mission d'emprunts": {} - }, - "Frais de constitution et d'augmentation de capital": { - "Amortissements sur frais de constitution et d'augmentation de capital": {}, - "Frais de constitution et d'augmentation de capital": {} - }, - "Frais de restructuration": { - "Amortissements sur frais de restructuration": {}, - "Co\u00fbt des frais de restructuration": {} - }, - "Int\u00e9r\u00eats intercalaires": { - "Amortissements sur int\u00e9r\u00eats intercalaires": {}, - "Int\u00e9r\u00eats intercalaires": {} - } - }, - "IMMOBILISATION DETENUES EN LOCATION-FINANCEMENT ET DROITS SIMILAIRES": { - "Installations, machines et outillage": { - "Amortissements sur installations, machines et outillage pris en leasing": {}, - "Installations": {}, - "Machines": {}, - "Outillage": {}, - "Plus-values act\u00e9es sur installations, machines et outillage pris en leasing": {} - }, - "Mobilier et mat\u00e9riel roulant": { - "Amortissements sur mobilier et mat\u00e9riel roulant en leasing": {}, - "Mat\u00e9riel roulant": {}, - "Mobilier": {}, - "Plus-values act\u00e9es sur mobilier et mat\u00e9riel roulant en leasing": {} - }, - "Terrains et constructions": { - "Amortissements et r\u00e9ductions de valeur sur terrains et constructions en leasing": {}, - "Constructions": {}, - "Plus-values sur emphyt\u00e9ose, leasing et droits similaires : terrains et constructions": {}, - "Terrains": {} - } - }, - "IMMOBILISATIONS CORPORELLES EN COURS ET ACOMPTES VERSES": { - "Avances et acomptes vers\u00e9s sur immobilisations en cours": {}, - "Immobilisations en cours": { - "Autres immobilisations corporelles": {}, - "Constructions": {}, - "Installations, machines et outillage": {}, - "Mobilier et mat\u00e9riel roulant": {} - } - }, - "IMMOBILISATIONS FINANCIERES": { - "Autres actions et parts": { - "Montants non appel\u00e9s": {}, - "Plus-values act\u00e9es": {}, - "R\u00e9ductions de valeur act\u00e9es": {}, - "Valeur d'acquisition": {} - }, - "Autres cr\u00e9ances": { - "Cr\u00e9ances douteuses": {}, - "Cr\u00e9ances en compte": {}, - "Effets \u00e0 recevoir": {}, - "R\u00e9ductions de valeur act\u00e9es": {}, - "Titres \u00e0 revenu fixe": {} - }, - "Cautionnements vers\u00e9s en num\u00e9raires": { - "Autres cautionnements vers\u00e9s en num\u00e9raires": {}, - "Eau": {}, - "Electricit\u00e9": {}, - "Gaz": {}, - "T\u00e9l\u00e9phone, t\u00e9lefax, t\u00e9lex": {} - }, - "Cr\u00e9ances sur des entreprises avec lesquelles il existe un lien de participation": { - "Cr\u00e9ances douteuses": {}, - "Cr\u00e9ances en compte": {}, - "Effets \u00e0 recevoir": {}, - "R\u00e9ductions de valeurs act\u00e9es": {}, - "Titres \u00e0 revenu fixe": {} - }, - "Cr\u00e9ances sur des entreprises li\u00e9es": { - "Cr\u00e9ances douteuses": {}, - "Cr\u00e9ances en compte": {}, - "Effets \u00e0 recevoir": {}, - "R\u00e9ductions de valeurs act\u00e9es": {}, - "Titres \u00e0 revenu fixes": {} - }, - "Participations dans des entreprises avec lesquelles il existe un lien de participation": { - "Montants non appel\u00e9s": {}, - "Plus-values act\u00e9es": {}, - "R\u00e9ductions de valeurs act\u00e9es": {}, - "Valeur d'acquisition": {} - }, - "Participations dans des entreprises li\u00e9es": { - "Montants non appel\u00e9s": {}, - "Plus-values act\u00e9es": {}, - "R\u00e9ductions de valeurs act\u00e9es": {}, - "Valeur d'acquisition": {} - } - }, - "IMMOBILISATIONS INCORPORELLES": { - "Acomptes vers\u00e9s": {}, - "Concessions, brevets, licences, savoir-faire, marques et droits similaires": { - "Amortissements sur concessions, brevets, etc...": {}, - "Concessions, brevets, licences, savoir-faire, marques, etc...": {}, - "Plus-values act\u00e9es sur concessions, brevets, etc...": {} - }, - "Frais de recherche et de d\u00e9veloppement": { - "Amortissements sur frais de recherche et de mise au point": {}, - "Frais de recherche et de mise au point": {}, - "Plus-values act\u00e9es sur frais de recherche et de mise au point": {} - }, - "Goodwill": { - "Amortissements sur goodwill": {}, - "Co\u00fbt d'acquisition": {}, - "Plus-values act\u00e9es": {} - } - }, - "INSTALLATIONS, MACHINES ET OUTILLAGE": { - "Amortissements": { - "Sur installations": {}, - "Sur machines": {}, - "Sur outillage": {} - }, - "Installations": { - "Installation d'eau": {}, - "Installation d'\u00e9lectricit\u00e9": {}, - "Installation de chargement": {}, - "Installation de chauffage": {}, - "Installation de conditionnement d'air": {}, - "Installation de gaz": {}, - "Installation de vapeur": {} - }, - "Machines": { - "Division A": {}, - "Division B": {} - }, - "Outillage": { - "Division A": {}, - "Division B": {} - }, - "Plus-values act\u00e9es": { - "Sur installations": {}, - "Sur machines": {}, - "Sur outillage": {} - } - }, - "MOBILIER ET MATERIEL ROULANT": { - "Mat\u00e9riel roulant": { - "Amortissements sur mat\u00e9riel roulant": { - "Amortissements sur mat\u00e9riel automobile": {}, - "Idem sur mat\u00e9riel a\u00e9rien": {}, - "Idem sur mat\u00e9riel ferroviaire": {}, - "Idem sur mat\u00e9riel fluvial": {}, - "Idem sur mat\u00e9riel naval": {} - }, - "Mat\u00e9riel automobile": { - "Camions": {}, - "Voitures": {} - }, - "Mat\u00e9riel a\u00e9rien": {}, - "Mat\u00e9riel ferroviaire": {}, - "Mat\u00e9riel fluvial": {}, - "Mat\u00e9riel naval": {}, - "Plus-values sur mat\u00e9riel roulant": { - "Idem sur mat\u00e9riel a\u00e9rien": {}, - "Idem sur mat\u00e9riel ferroviaire": {}, - "Idem sur mat\u00e9riel fluvial": {}, - "Idem sur mat\u00e9riel naval": {}, - "Plus-values sur mat\u00e9riel automobile": {} - } - }, - "Mobilier": { - "Amortissements": { - "Amortissements sur mat\u00e9riel de bureau et service social": {}, - "Amortissements sur mobilier": {} - }, - "Mat\u00e9riel de bureau et de service social": { - "Des autres b\u00e2timents d'exploitation": {}, - "Des b\u00e2timents administratifs et commerciaux": {}, - "Des b\u00e2timents industriels": {}, - "Des oeuvres sociales": {} - }, - "Mobilier": { - "Mobilier des autres b\u00e2timents d'exploitation": {}, - "Mobilier des b\u00e2timents administratifs et commerciaux": {}, - "Mobilier des b\u00e2timents industriels": {}, - "Mobilier oeuvres sociales": {} - }, - "Plus-values act\u00e9es": { - "Plus-values act\u00e9es sur mat\u00e9riel de bureau et service social": {}, - "Plus-values act\u00e9es sur mobilier": {} - } - } - }, - "TERRAINS ET CONSTRUCTIONS": { - "Autres droits r\u00e9els sur des immeubles": { - "Amortissements": {}, - "Plus-values act\u00e9es": {}, - "Valeur d'acquisition": {} - }, - "Constructions": { - "Amortissements sur constructions": { - "Sur autres b\u00e2timents d'exploitation": {}, - "Sur b\u00e2timents administratifs et commerciaux": {}, - "Sur b\u00e2timents industriels": {}, - "Sur constructions sur sol d'autrui": {}, - "Sur frais d'acquisition sur constructions": {}, - "Sur voies de transport et ouvrages d'art": {} - }, - "Autres b\u00e2timents d'exploitation": {}, - "B\u00e2timents administratifs et commerciaux": {}, - "B\u00e2timents industriels": {}, - "Constructions sur sol d'autrui": {}, - "Frais d'acquisition sur constructions": {}, - "Plus-values act\u00e9es": { - "Sur autres b\u00e2timents d'exploitation": {}, - "Sur b\u00e2timents administratifs et commerciaux": {}, - "Sur b\u00e2timents industriels": {}, - "Sur voies de transport et ouvrages d'art": {} - }, - "Voies de transport et ouvrages d'art": {} - }, - "Terrains": { - "Amortissements et r\u00e9ductions de valeur": { - "Amortissements sur frais d'acquisition": {}, - "R\u00e9ductions de valeur sur terrains": {} - }, - "Frais d'acquisition sur terrains": {}, - "Plus-values act\u00e9es sur terrains": {}, - "Terrains": {} - }, - "Terrains b\u00e2tis": { - "Amortissements sur terrains b\u00e2tis": { - "Sur autres b\u00e2timents d'exploitation": {}, - "Sur b\u00e2timents administratifs et commerciaux": {}, - "Sur b\u00e2timents industriels": {}, - "Sur frais d'acquisition des terrains b\u00e2tis": {}, - "Sur voies de transport et ouvrages d'art": {} - }, - "Plus-values act\u00e9es": { - "Sur autres b\u00e2timents d'exploitation": {}, - "Sur b\u00e2timents administratifs et commerciaux": {}, - "Sur b\u00e2timents industriels": {}, - "Sur voies de transport et ouvrages d'art": {} - }, - "Valeur d'acquisition": { - "Autres b\u00e2timents d'exploitation": {}, - "B\u00e2timents administratifs et commerciaux": {}, - "B\u00e2timents industriels": {}, - "Frais d'acquisition des terrains \u00e0 b\u00e2tir": {}, - "Voies de transport et ouvrages d'art": {} - } - } - }, - "root_type": "" - }, - "CLASSE 3. STOCK ET COMMANDES EN COURS D'EXECUTION": { - "ACOMPTES VERSES SUR ACHATS POUR STOCKS": { - "Acomptes vers\u00e9s": { - "account_type": "Stock" - }, - "R\u00e9ductions de valeur act\u00e9es": { - "account_type": "Stock" - }, - "account_type": "Stock" - }, - "APPROVISIONNEMENTS - MATIERES PREMIERES": { - "R\u00e9ductions de valeur act\u00e9es": { - "account_type": "Stock" - }, - "Valeur d'acquisition": { - "account_type": "Stock" - }, - "account_type": "Stock" - }, - "APPROVISIONNEMENTS ET FOURNITURES": { - "R\u00e9ductions de valeur act\u00e9es": { - "account_type": "Stock" - }, - "Valeur d'acquisition": { - "Emballages commerciaux": { - "Emballages perdus": { - "account_type": "Stock" - }, - "Emballages r\u00e9cup\u00e9rables": { - "account_type": "Stock" - }, - "account_type": "Stock" - }, - "Energie, charbon, coke, Mazout, essence, propane": { - "account_type": "Stock" - }, - "Fournitures de services sociaux": { - "account_type": "Stock" - }, - "Fournitures diverses et petit outillage": { - "account_type": "Stock" - }, - "Imprim\u00e9s et fournitures de bureau": { - "account_type": "Stock" - }, - "Mati\u00e8res d'approvisionnement": { - "account_type": "Stock" - }, - "Produits d'entretien": { - "account_type": "Stock" - }, - "account_type": "Stock" - }, - "account_type": "Stock" - }, - "COMMANDES EN COURS D'EXECUTION": { - "B\u00e9n\u00e9fice pris en compte": { - "account_type": "Stock" - }, - "R\u00e9ductions de valeur act\u00e9es": { - "account_type": "Stock" - }, - "Valeur d'acquisition": { - "account_type": "Stock" - }, - "account_type": "Stock" - }, - "EN COURS DE FABRICATION": { - "R\u00e9ductions de valeur act\u00e9es": { - "account_type": "Stock" - }, - "Valeur d'acquisition": { - "D\u00e9chets": { - "account_type": "Stock" - }, - "Produits en cours de fabrication": { - "account_type": "Stock" - }, - "Produits semi-ouvr\u00e9s": { - "account_type": "Stock" - }, - "Rebuts": { - "account_type": "Stock" - }, - "Travaux en association momentan\u00e9e": { - "account_type": "Stock" - }, - "Travaux en cours": { - "account_type": "Stock" - }, - "account_type": "Stock" - }, - "account_type": "Stock" - }, - "IMMEUBLES DESTINES A LA VENTE": { - "Immeubles construits en vue de leur revente": { - "Immeuble A": { - "account_type": "Stock" - }, - "Immeuble B": { - "account_type": "Stock" - }, - "account_type": "Stock" - }, - "R\u00e9ductions de valeurs act\u00e9es": { - "account_type": "Stock" - }, - "Valeur d'acquisition": { - "Immeuble A": { - "account_type": "Stock" - }, - "Immeuble B": { - "account_type": "Stock" - }, - "account_type": "Stock" - }, - "account_type": "Stock" - }, - "MARCHANDISES": { - "R\u00e9ductions de valeur act\u00e9es": { - "account_type": "Stock" - }, - "Valeur d'acquisition": { - "Groupe A": { - "account_type": "Stock" - }, - "Groupe B": { - "account_type": "Stock" - }, - "account_type": "Stock" - }, - "account_type": "Stock" - }, - "PRODUITS FINIS": { - "R\u00e9ductions de valeur act\u00e9es": { - "account_type": "Stock" - }, - "Valeur d'acquisition": { - "Produits finis": { - "account_type": "Stock" - }, - "account_type": "Stock" - }, - "account_type": "Stock" - }, - "account_type": "Stock", - "root_type": "" - }, - "CLASSE 4. CREANCES ET DETTES A UN AN AU PLUS": { - "ACOMPTES RECUS SUR COMMANDES": { - "account_type": "Payable" - }, - "AUTRES CREANCES": { - "Capital appel\u00e9, non vers\u00e9": { - "Actionnaires d\u00e9faillants": { - "account_type": "Receivable" - }, - "Appels de fonds": { - "account_type": "Receivable" - } - }, - "Cautionnements vers\u00e9s en num\u00e9raires": { - "account_type": "Receivable" - }, - "Cr\u00e9ances diverses": { - "Associ\u00e9s": { - "account_type": "Receivable" - }, - "Avances et pr\u00eats au personnel": { - "account_type": "Receivable" - }, - "Compte courant des administrateurs et g\u00e9rants": { - "account_type": "Receivable" - }, - "Compte courant des associ\u00e9s en S.P.R.L.": { - "account_type": "Receivable" - }, - "Cr\u00e9ances sur soci\u00e9t\u00e9s apparent\u00e9es": { - "account_type": "Receivable" - }, - "Emballages et mat\u00e9riel \u00e0 rendre": { - "account_type": "Receivable" - }, - "Etat et \u00e9tablissements publics": { - "Autres cr\u00e9ances": { - "account_type": "Receivable" - }, - "Subsides \u00e0 recevoir": { - "account_type": "Receivable" - } - }, - "Rabais, ristournes, remises \u00e0 obtenir et autres avoirs non encore re\u00e7us": { - "account_type": "Receivable" - } - }, - "Cr\u00e9ances douteuses": { - "account_type": "Receivable" - }, - "Imp\u00f4ts et versements fiscaux \u00e0 r\u00e9cup\u00e9rer": { - "Imp\u00f4ts \u00e9trangers": { - "account_type": "Receivable" - }, - "\u00e0 4124 Imp\u00f4ts belges sur le r\u00e9sultat": { - "account_type": "Receivable" - }, - "\u00e0 4127 Autres imp\u00f4ts belges": { - "account_type": "Receivable" - } - }, - "Produits \u00e0 recevoir": { - "account_type": "Receivable" - }, - "R\u00e9ductions de valeur act\u00e9es": { - "account_type": "Receivable" - }, - "T.V.A. \u00e0 r\u00e9cup\u00e9rer": { - "Compte courant administration T.V.A.": { - "account_type": "Receivable" - }, - "T.V.A D\u00e9ductible": { - "account_type": "Receivable" - }, - "Taxe d'\u00e9galisation due": { - "account_type": "Receivable" - } - } - }, - "COMPTES DE REGULARISATION ET COMPTES D'ATTENTE": { - "Charges \u00e0 imputer": { - "account_type": "Payable" - }, - "Charges \u00e0 reporter": { - "account_type": "Payable" - }, - "Comptes d'attente": { - "Compte d'attente": { - "account_type": "Payable" - }, - "Compte de r\u00e9partition p\u00e9riodique des charges": { - "account_type": "Payable" - }, - "Transferts d'exercice": { - "account_type": "Payable" - } - }, - "Produits acquis": { - "Produits d'exploitation": { - "Autres produits d'exploitation": { - "account_type": "Payable" - }, - "Commissions \u00e0 obtenir": { - "account_type": "Payable" - }, - "Ristournes, rabais \u00e0 obtenir": { - "account_type": "Payable" - } - }, - "Produits financiers": { - "Autres produits financiers": { - "account_type": "Payable" - }, - "Int\u00e9r\u00eats courus et non \u00e9chus sur pr\u00eats et d\u00e9bits": { - "account_type": "Payable" - } - } - }, - "Produits \u00e0 reporter": { - "Produits d'exploitation \u00e0 reporter": { - "account_type": "Payable" - }, - "Produits financiers \u00e0 reporter": { - "account_type": "Payable" - } - } - }, - "CREANCES COMMERCIALES": { - "Acomptes vers\u00e9s": { - "account_type": "Receivable" - }, - "Clients": { - "Clients": { - "account_type": "Receivable" - }, - "Cr\u00e9ances r\u00e9sultant de livraisons de biens": { - "account_type": "Receivable" - }, - "Rabais, remises, ristournes \u00e0 accorder et autres notes de cr\u00e9dit \u00e0 \u00e9tablir": { - "account_type": "Receivable" - } - }, - "Clients : retenues sur garanties": { - "account_type": "Receivable" - }, - "Clients, cr\u00e9ances courantes, entreprises apparent\u00e9es, administrateurs et g\u00e9rants": { - "Administrateurs et g\u00e9rants d'entreprise": { - "account_type": "Receivable" - }, - "Autres entreprises avec lesquelles il existe un lien de participation": { - "account_type": "Receivable" - }, - "Entreprises li\u00e9es": { - "account_type": "Receivable" - } - }, - "Compensation clients": { - "account_type": "Receivable" - }, - "Cr\u00e9ances douteuses": { - "account_type": "Receivable" - }, - "Effets \u00e0 recevoir": { - "Effets \u00e0 l'encaissement": { - "account_type": "Receivable" - }, - "Effets \u00e0 l'escompte": { - "account_type": "Receivable" - }, - "Effets \u00e0 recevoir": { - "account_type": "Receivable" - } - }, - "Effets \u00e0 recevoir sur entreprises apparent\u00e9es et administrateurs et g\u00e9rants": { - "Administrateurs et g\u00e9rants de l'entreprise": { - "account_type": "Receivable" - }, - "Autres entreprises avec lesquelles il existe un lien de participation": { - "account_type": "Receivable" - }, - "Entreprises li\u00e9es": { - "account_type": "Receivable" - } - }, - "Produits \u00e0 recevoir": { - "account_type": "Receivable" - }, - "R\u00e9ductions de valeur act\u00e9es": { - "account_type": "Receivable" - } - }, - "DETTES A PLUS D'UN AN ECHEANT DANS L'ANNEE": { - "Autres emprunts": { - "account_type": "Payable" - }, - "Cautionnements re\u00e7us en num\u00e9raires": { - "account_type": "Payable" - }, - "Dettes commerciales": { - "Effets \u00e0 payer": { - "account_type": "Payable" - }, - "Fournisseurs": { - "account_type": "Payable" - } - }, - "Dettes de location-financement et assimil\u00e9es": { - "Financement de biens immobiliers": { - "account_type": "Payable" - }, - "Financement de biens mobiliers": { - "account_type": "Payable" - } - }, - "Dettes diverses": { - "Administrateurs, g\u00e9rants, associ\u00e9s": { - "account_type": "Payable" - }, - "Autres dettes": { - "account_type": "Payable" - }, - "Entreprises avec lesquelles il existe un lien de participation": { - "account_type": "Payable" - }, - "Entreprises li\u00e9es": { - "account_type": "Payable" - } - }, - "Emprunts obligataires non subordonn\u00e9s": { - "Convertibles": { - "account_type": "Payable" - }, - "Non convertibles": { - "account_type": "Payable" - } - }, - "Emprunts subordonn\u00e9s": { - "Convertibles": { - "account_type": "Payable" - }, - "Non convertibles": { - "account_type": "Payable" - } - }, - "Etablissements de cr\u00e9dit": { - "Cr\u00e9dits d'acceptation": { - "account_type": "Payable" - }, - "Dettes en compte": { - "account_type": "Payable" - }, - "Promesses": { - "account_type": "Payable" - } - } - }, - "DETTES COMMERCIALES": { - "Acomptes re\u00e7us": { - "account_type": "Payable" - }, - "Compensations fournisseurs": { - "account_type": "Payable" - }, - "Effets \u00e0 payer": { - "Entreprises apparent\u00e9es": { - "Entreprises avec lesquelles il existe un lien de participation": { - "account_type": "Payable" - }, - "Entreprises li\u00e9es": { - "account_type": "Payable" - } - }, - "Fournisseurs ordinaires": { - "Fournisseurs CEE": { - "account_type": "Payable" - }, - "Fournisseurs belges": { - "account_type": "Payable" - }, - "Fournisseurs importation": { - "account_type": "Payable" - } - } - }, - "Factures \u00e0 recevoir": { - "account_type": "Payable" - }, - "Fournisseurs": { - "Dettes envers les coparticipants": { - "account_type": "Payable" - }, - "Entreprises apparent\u00e9es": { - "Entreprises avec lesquelles il existe un lien de participation": { - "account_type": "Payable" - }, - "Entreprises li\u00e9es": { - "account_type": "Payable" - } - }, - "Fournisseurs - retenues de garanties": { - "account_type": "Payable" - }, - "Fournisseurs ordinaires": { - "Fournisseurs CEE": { - "account_type": "Payable" - }, - "Fournisseurs belges": { - "account_type": "Payable" - }, - "Fournisseurs importation": { - "account_type": "Payable" - } - } - } - }, - "DETTES DECOULANT DE L'AFFECTATION DES RESULTATS": { - "Autres allocataires": { - "account_type": "Payable" - }, - "Dividendes de l'exercice": { - "account_type": "Payable" - }, - "Dividendes et tanti\u00e8mes d'exercices ant\u00e9rieurs": { - "account_type": "Payable" - }, - "Tanti\u00e8mes de l'exercice": { - "account_type": "Payable" - } - }, - "DETTES DIVERSES": { - "Acomptes re\u00e7us d'autres tiers \u00e0 moins d'un an": { - "account_type": "Payable" - }, - "Actionnaires - capital \u00e0 rembourser": { - "account_type": "Payable" - }, - "Autres dettes diverses": { - "account_type": "Payable" - }, - "Cautionnements re\u00e7us en num\u00e9raires": { - "account_type": "Payable" - }, - "Emballages et mat\u00e9riel consign\u00e9s": { - "account_type": "Payable" - }, - "Obligations et coupons \u00e9chus": { - "account_type": "Payable" - }, - "Participation du personnel \u00e0 payer": { - "account_type": "Payable" - } - }, - "DETTES FINANCIERES": { - "Autres emprunts": { - "account_type": "Payable" - }, - "Etablissements de cr\u00e9dit. Cr\u00e9dits d'acceptation": { - "account_type": "Payable" - }, - "Etablissements de cr\u00e9dit. Dettes en compte courant": { - "account_type": "Payable" - }, - "Etablissements de cr\u00e9dit. Emprunts en compte \u00e0 terme fixe": { - "account_type": "Payable" - }, - "Etablissements de cr\u00e9dit. Promesses": { - "account_type": "Payable" - } - }, - "DETTES FISCALES, SALARIALES ET SOCIALES": { - "Autres dettes sociales": { - "Assurances relatives au personnel": { - "Assurance groupe ": { - "account_type": "Payable" - }, - "Assurance loi": { - "account_type": "Payable" - }, - "Assurance salaire garanti ": { - "account_type": "Payable" - }, - "Assurances individuelles": { - "account_type": "Payable" - } - }, - "Caisse d'assurances sociales pour travailleurs ind\u00e9pendants": { - "account_type": "Payable" - }, - "Dettes et provisions sociales diverses": { - "account_type": "Payable" - }, - "D\u00e9parts de personnel": { - "account_type": "Payable" - }, - "Oppositions sur r\u00e9mun\u00e9rations": { - "account_type": "Payable" - }, - "Provision pour gratifications de fin d'ann\u00e9e": { - "account_type": "Payable" - } - }, - "Dettes fiscales estim\u00e9es": { - "Imp\u00f4ts \u00e0 l'\u00e9tranger": { - "account_type": "Payable" - }, - "\u00e0 4504 Imp\u00f4ts sur le r\u00e9sultat": { - "account_type": "Payable" - }, - "\u00e0 4507 Autres imp\u00f4ts en Belgique": { - "account_type": "Payable" - } - }, - "Imp\u00f4ts et taxes \u00e0 payer": { - "Autres imp\u00f4ts et taxes en Belgique": { - "Autres imp\u00f4ts et taxes \u00e0 payer": { - "account_type": "Payable" - }, - "Imp\u00f4ts communaux \u00e0 payer": { - "account_type": "Payable" - }, - "Imp\u00f4ts provinciaux \u00e0 payer": { - "account_type": "Payable" - }, - "Pr\u00e9compte immobilier": { - "account_type": "Payable" - } - }, - "Autres imp\u00f4ts sur le r\u00e9sultat": { - "account_type": "Payable" - }, - "Imp\u00f4ts et taxes \u00e0 l'\u00e9tranger": { - "account_type": "Payable" - } - }, - "Office National de la S\u00e9curit\u00e9 Sociale": { - "1er trimestre": { - "account_type": "Payable" - }, - "2\u00e8me trimestre": { - "account_type": "Payable" - }, - "3\u00e8me trimestre": { - "account_type": "Payable" - }, - "4\u00e8me trimestre": { - "account_type": "Payable" - }, - "Arri\u00e9r\u00e9s": { - "account_type": "Payable" - } - }, - "Pr\u00e9comptes retenus": { - "Autres pr\u00e9comptes retenus": { - "account_type": "Payable" - }, - "Pr\u00e9compte mobilier retenu sur dividendes attribu\u00e9s": { - "account_type": "Payable" - }, - "Pr\u00e9compte mobilier retenu sur int\u00e9r\u00eats pay\u00e9s": { - "account_type": "Payable" - }, - "Pr\u00e9compte professionnel retenu sur r\u00e9mun\u00e9rations": { - "account_type": "Payable" - }, - "Pr\u00e9compte professionnel retenu sur tanti\u00e8mes": { - "account_type": "Payable" - } - }, - "P\u00e9cules de vacances": { - "Direction": { - "account_type": "Payable" - }, - "Employ\u00e9s": { - "account_type": "Payable" - }, - "Ouvriers": { - "account_type": "Payable" - } - }, - "R\u00e9mun\u00e9rations": { - "Administrateurs, g\u00e9rants et commissaires": { - "account_type": "Payable" - }, - "Direction": { - "account_type": "Payable" - }, - "Employ\u00e9s": { - "account_type": "Payable" - }, - "Ouvriers": { - "account_type": "Payable" - } - }, - "T.V.A. \u00e0 payer": { - "Compte courant administration T.V.A.": { - "account_type": "Payable" - }, - "T.V.A. \u00e0 payer": { - "account_type": "Payable" - }, - "T.V.A. \u00e0 payer - Cocontractant": { - "account_type": "Receivable" - }, - "T.V.A. \u00e0 payer - Import": { - "account_type": "Receivable" - }, - "T.V.A. \u00e0 payer - Intra-communautaire": { - "account_type": "Receivable" - }, - "Taxe d'\u00e9galisation due": { - "account_type": "Payable" - } - } - }, - "root_type": "" - }, - "CLASSE 5. PLACEMENTS DE TRESORERIE ET DE VALEURS DISPONIBLES": { - "ACTIONS ET PARTS": { - "Montants non appel\u00e9s": { - "account_type": "Cash" - }, - "R\u00e9ductions de valeur act\u00e9es": { - "account_type": "Cash" - }, - "Valeur d'acquisition": { - "account_type": "Cash" - } - }, - "ACTIONS PROPRES": { - "account_type": "Cash" - }, - "CAISSES": { - "Caisses - esp\u00e8ces": { - "Caisse principale": { - "account_type": "Cash" - } - }, - "Caisses - timbres": { - "account_type": "Cash" - } - }, - "DEPOTS A TERME": { - "D'un mois au plus": { - "account_type": "Cash" - }, - "De plus d'un an": { - "account_type": "Cash" - }, - "De plus d'un mois et \u00e0 un an au plus": { - "account_type": "Cash" - }, - "R\u00e9ductions de valeur act\u00e9es": { - "account_type": "Cash" - } - }, - "ETABLISSEMENTS DE CREDIT.": { - "Comptes ouverts aupr\u00e8s des divers \u00e9tablissements": {} - }, - "OFFICE DES CHEQUES POSTAUX": { - "Ch\u00e8ques \u00e9mis": { - "account_type": "Cash" - }, - "Compte courant": { - "account_type": "Cash" - } - }, - "TITRES A REVENUS FIXES": { - "R\u00e9ductions de valeur act\u00e9es": { - "account_type": "Cash" - }, - "Valeur d'acquisition": { - "account_type": "Cash" - } - }, - "VALEURS ECHUES A L'ENCAISSEMENT": { - "Ch\u00e8ques \u00e0 encaisser": { - "account_type": "Cash" - }, - "Coupons \u00e0 encaisser": { - "account_type": "Cash" - } - }, - "VIREMENTS INTERNES": { - "account_type": "Cash" - }, - "root_type": "" - }, - "CLASSE 6. - CHARGES": { - "AFFECTATION DES RESULTATS": { - "Administrateurs ou g\u00e9rants": {}, - "Autres allocataires": {}, - "B\u00e9n\u00e9fice \u00e0 reporter": {}, - "Dotation aux autres r\u00e9serves": {}, - "Dotation \u00e0 la r\u00e9serve l\u00e9gale": {}, - "Perte report\u00e9e de l'exercice pr\u00e9c\u00e9dent": {}, - "R\u00e9mun\u00e9ration du capital": {} - }, - "AMORTISSEMENTS, REDUCTIONS DE VALEUR ET PROVISIONS POUR RISQUES ET CHARGES": { - "Dotations aux amortissements et aux r\u00e9ductions de valeur sur immobilisations": { - "Dotations aux amortissements sur frais d'\u00e9tablissement": {}, - "Dotations aux amortissements sur immobilisations corporelles": {}, - "Dotations aux amortissements sur immobilisations incorporelles": {}, - "Dotations aux r\u00e9ductions de valeur sur immobilisations corporelles": {}, - "Dotations aux r\u00e9ductions de valeur sur immobilisations incorporelles": {} - }, - "Provisions pour autres risques et charges": { - "Dotations ": {}, - "Utilisations et reprises": {} - }, - "Provisions pour grosses r\u00e9parations et gros entretiens": { - "Dotations": {}, - "Utilisations et reprises": {} - }, - "Provisions pour pensions et obligations similaires": { - "Dotations": {}, - "Utilisations et reprises": {} - }, - "R\u00e9ductions de valeur sur commandes en cours d'ex\u00e9cution": { - "Dotations": {}, - "Reprises": {} - }, - "R\u00e9ductions de valeur sur cr\u00e9ances commerciales \u00e0 plus d'un an": { - "Dotations": {}, - "Reprises": {} - }, - "R\u00e9ductions de valeur sur cr\u00e9ances commerciales \u00e0 un an au plus": { - "Dotations": {}, - "Reprises": {} - }, - "R\u00e9ductions de valeur sur stocks": { - "Dotations": {}, - "Reprises": {} - } - }, - "APPROVISIONNEMENTS ET MARCHANDISES": { - "Achats d'immeubles destin\u00e9s \u00e0 la revente": {}, - "Achats de fournitures": {}, - "Achats de marchandises": {}, - "Achats de mati\u00e8res premi\u00e8res": {}, - "Achats de services, travaux et \u00e9tudes": {}, - "Remises, ristournes et rabais obtenus sur achats": {}, - "Sous-traitances g\u00e9n\u00e9rales": {}, - "Variations de stocks": { - "D'immeubles destin\u00e9s \u00e0 la vente": {}, - "De fournitures": {}, - "De marchandises": {}, - "De mati\u00e8res premi\u00e8res": {} - } - }, - "AUTRES CHARGES D'EXPLOITATION": { - "Charges d'exploitation port\u00e9es \u00e0 l'actif au titre de restructuration": {}, - "Charges fiscales d'exploitation": { - "Imp\u00f4ts provinciaux et communaux": { - "Taxe sur la force motrice": {}, - "Taxe sur le personnel occup\u00e9": {} - }, - "Taxes diverses": {}, - "Taxes et imp\u00f4ts directs": { - "Taxes sur autos et camions": {} - }, - "Taxes et imp\u00f4ts indirects": { - "Droits d'enregistrement": {}, - "T.V.A. non d\u00e9ductible": {}, - "Timbres fiscaux pris en charge par la firme": {} - } - }, - "Moins-values sur r\u00e9alisations courantes d'immobilisations corporelles": {}, - "Moins-values sur r\u00e9alisations de cr\u00e9ances commerciales": {}, - "\u00e0 648 Charges d'exploitations diverses": {} - }, - "CHARGES EXCEPTIONNELLES": { - "Amortissements et r\u00e9ductions de valeur exceptionnels": { - "Sur frais d'\u00e9tablissement": {}, - "Sur immobilisations corporelles": {}, - "Sur immobilisations incorporelles": {} - }, - "Autres charges exceptionnelles": {}, - "Charges exceptionnelles transf\u00e9r\u00e9es \u00e0 l'actif en frais de restructuration": {}, - "Diff\u00e9rence de charge": {}, - "Moins-values sur r\u00e9alisation d'actifs immobilis\u00e9s": { - "Sur immeubles acquis ou construits en vue de la revente": {}, - "Sur immobilisations corporelles": {}, - "Sur immobilisations d\u00e9tenues en location-financement et droits similaires": {}, - "Sur immobilisations financi\u00e8res": {}, - "Sur immobilisations incorporelles": {} - }, - "Provisions pour risques et charges exceptionnels": {}, - "P\u00e9nalit\u00e9s et amendes diverses": {}, - "R\u00e9ductions de valeur sur immobilisations financi\u00e8res": {} - }, - "CHARGES FINANCIERES": { - "Charges d'escompte de cr\u00e9ances": {}, - "Charges des dettes": { - "Amortissements des agios et frais d'\u00e9mission d'emprunts": {}, - "Autres charges de dettes": {}, - "Int\u00e9r\u00eats intercalaires port\u00e9s \u00e0 l'actif": {}, - "Int\u00e9r\u00eats, commissions et frais aff\u00e9rents aux dettes": {} - }, - "Commissions sur ouvertures de cr\u00e9dit, cautions, avals": {}, - "Diff\u00e9rences de change": {}, - "Ecarts de conversion des devises": {}, - "Frais de banques, de ch\u00e8ques postaux": {}, - "Frais de vente des titres": {}, - "Moins-values sur r\u00e9alisation d'actifs circulants": {}, - "R\u00e9ductions de valeur sur actifs circulants": { - "Dotations ": {}, - "Reprises": {} - } - }, - "IMPOTS SUR LE RESULTAT": { - "Imp\u00f4ts belges sur le r\u00e9sultat d'exercices ant\u00e9rieurs": { - "Provisions fiscales constitu\u00e9es": {}, - "Suppl\u00e9ments d'imp\u00f4ts dus ou vers\u00e9s": {}, - "Suppl\u00e9ments d'imp\u00f4ts estim\u00e9s": {} - }, - "Imp\u00f4ts belges sur le r\u00e9sultat de l'exercice": { - "Charges fiscales estim\u00e9es": {}, - "Exc\u00e9dent de versements d'imp\u00f4ts et pr\u00e9comptes port\u00e9 \u00e0 l'actif": {}, - "Imp\u00f4ts et pr\u00e9comptes dus ou vers\u00e9s": {} - }, - "Imp\u00f4ts \u00e9trangers sur le r\u00e9sultat d'exercices ant\u00e9rieurs": {}, - "Imp\u00f4ts \u00e9trangers sur le r\u00e9sultat de l'exercice": {} - }, - "REMUNERATIONS, CHARGES SOCIALES ET PENSIONS": { - "Autres frais de personnel": { - "Assurances du personnel": { - "Assurance salaire garanti": {}, - "Assurances individuelles": {}, - "Assurances loi, responsabilit\u00e9 civile, chemin du travail": {} - }, - "Charges sociales des administrateurs, g\u00e9rants et commissaires": { - "Allocations familiales compl\u00e9mentaires pour non salari\u00e9s": {}, - "Divers": {}, - "Lois sociales pour ind\u00e9pendants": {} - }, - "Charges sociales diverses": { - "Allocations familiales compl\u00e9mentaires": {}, - "Jours f\u00e9ri\u00e9s pay\u00e9s": {}, - "Salaire hebdomadaire garanti": {} - } - }, - "Cotisations patronales d'assurances sociales": { - "Sur appointements et commissions": {}, - "Sur salaires": {} - }, - "Pensions de retraite et de survie": { - "Administrateurs et g\u00e9rants": {}, - "Personnel": {} - }, - "Primes patronales pour assurances extral\u00e9gales": {}, - "Provision pour p\u00e9cule de vacances": { - "Dotations": {}, - "Utilisations et reprises": {} - }, - "R\u00e9mun\u00e9rations et avantages sociaux directs": { - "Administrateurs ou g\u00e9rants": {}, - "Autres membres du personnel": {}, - "Employ\u00e9s": {}, - "Ouvriers": {}, - "Personnel de direction": {} - } - }, - "SERVICES ET BIENS DIVERS": { - "Annonces, publicit\u00e9, propagande et documentation": { - "Annonces et insertions": {}, - "Cadeaux \u00e0 la client\u00e8le": {}, - "Catalogues et imprim\u00e9s": {}, - "Documentation": {}, - "Echantillons": {}, - "Foires et expositions": {}, - "Missions et r\u00e9ceptions": {}, - "Primes": {} - }, - "Entretien et r\u00e9paration": {}, - "Fournitures faites \u00e0 l'entreprise": { - "Eau, gaz, \u00e9lectricit\u00e9, vapeur": { - "Eau": {}, - "Electricit\u00e9": {}, - "Gaz": {}, - "Vapeur": {} - }, - "Imprim\u00e9s et fournitures de bureau": {}, - "Livres, biblioth\u00e8que": {}, - "T\u00e9l\u00e9phone, t\u00e9l\u00e9grammes, t\u00e9lex, t\u00e9l\u00e9fax, frais postaux": { - "Frais postaux": {}, - "T\u00e9lex et t\u00e9l\u00e9fax": {}, - "T\u00e9l\u00e9grammes": {}, - "T\u00e9l\u00e9phone": {} - } - }, - "Loyers et charges locatives": { - "Charges locatives": {}, - "Loyers divers": {} - }, - "Personnel int\u00e9rimaire et personnes mises \u00e0 la disposition de l'entreprise": {}, - "R\u00e9mun\u00e9rations, primes pour assurances extral\u00e9gales": {}, - "R\u00e9tributions de tiers": { - "Assurances non relatives au personnel": { - "Assurance autos": {}, - "Assurance cr\u00e9dit": {}, - "Assurance incendie": {}, - "Assurance vol": {}, - "Assurances frais g\u00e9n\u00e9raux": {} - }, - "Divers": { - "Commissions aux tiers": {}, - "Cotisations aux groupements professionnels": {}, - "Dons, lib\u00e9ralit\u00e9s, ...": {}, - "Frais de contentieux": {}, - "Honoraires d'avocats, d'experts, etc ...": {}, - "Publications l\u00e9gales": {} - }, - "Personnel int\u00e9rimaire": {}, - "Redevances et royalties": { - "Autres redevances": {}, - "Redevances pour brevets, licences, marques, accessoires": {} - }, - "Transports et d\u00e9placements": { - "Transports de personnel": {}, - "Voyages, d\u00e9placements, repr\u00e9sentations": {} - } - }, - "Sous-traitants": { - "Quote-part b\u00e9n\u00e9ficiaire des coparticipants": {}, - "Sous-traitants d'associations momentan\u00e9es": {}, - "Sous-traitants pour activit\u00e9s propres": {} - } - }, - "TRANSFERTS AUX RESERVES IMMUNISEES": {}, - "root_type": "" - }, - "CLASSE 7. - PRODUITS": { - "AFFECTATION AUX RESULTATS": { - "B\u00e9n\u00e9fice report\u00e9 de l'exercice pr\u00e9c\u00e9dent": {}, - "Intervention d'associ\u00e9s": {}, - "Perte \u00e0 reporter": {}, - "Pr\u00e9l\u00e8vement sur le capital et les primes d'\u00e9mission": {}, - "Pr\u00e9l\u00e8vement sur les r\u00e9serves": {} - }, - "AUTRES PRODUITS D'EXPLOITATION": { - "Commissions et courtages": {}, - "Locations diverses \u00e0 caract\u00e8re professionnel": {}, - "Plus-values sur r\u00e9alisations courantes d'immobilisations corporelles": {}, - "Plus-values sur r\u00e9alisations de cr\u00e9ances commerciales": {}, - "Prestations de services": {}, - "Produits de services exploit\u00e9s dans l'int\u00e9r\u00eat du personnel": {}, - "Produits divers": { - "Bonis sur reprises d'emballages consign\u00e9s": {}, - "Bonis sur travaux en associations momentan\u00e9es": {} - }, - "Redevances pour brevets et licences": {}, - "Revenus des immeubles affect\u00e9s aux activit\u00e9s non professionnelles": {}, - "Subsides d'exploitation et montants compensatoires": {} - }, - "CHIFFRE D'AFFAIRES": { - "Facturations des travaux en cours": {}, - "Prestations de services": { - "Prestations de services dans les pays membres de la C.E.E.": {}, - "Prestations de services en Belgique": {}, - "Prestations de services en vue de l'exportation": {} - }, - "P\u00e9nalit\u00e9s et d\u00e9dits obtenus par l'entreprise": {}, - "Remises, ristournes et rabais accord\u00e9s": { - "Mali sur travaux factur\u00e9s aux associations momentan\u00e9es": {}, - "Sur prestations de services": {}, - "Sur ventes de d\u00e9chets et rebuts": {}, - "Sur ventes de marchandises": {}, - "Sur ventes de produits finis": {} - }, - "Ventes d'emballages r\u00e9cup\u00e9rables": {}, - "Ventes de d\u00e9chets et rebuts": { - "Ventes dans les pays membres de la C.E.E.": {}, - "Ventes en Belgique": {}, - "Ventes \u00e0 l'exportation": {} - }, - "Ventes de marchandises": { - "Ventes dans les pays membres de la C.E.E.": {}, - "Ventes en Belgique": {}, - "Ventes \u00e0 l'exportation": {} - }, - "Ventes de produits finis": { - "Ventes dans les pays membres de la C.E.E.": {}, - "Ventes en Belgique": {}, - "Ventes \u00e0 l'exportation": {} - } - }, - "PRODUCTION IMMOBILISEE": { - "En frais d'\u00e9tablissement": {}, - "En immobilisations corporelles": {}, - "En immobilisations en cours": {}, - "En immobilisations incorporelles": {} - }, - "PRODUITS EXCEPTIONNELS": { - "Autres produits exceptionnels": {}, - "Plus-values sur r\u00e9alisation d'actifs immobilis\u00e9s": { - "Sur immobilisations corporelles": {}, - "Sur immobilisations financi\u00e8res": {}, - "Sur immobilisations incorporelles": {} - }, - "Reprises d'amortissements et de r\u00e9ductions de valeur": { - "Sur immobilisations corporelles": {}, - "Sur immobilisations incorporelles": {} - }, - "Reprises de provisions pour risques et charges exceptionnelles": {}, - "Reprises de r\u00e9ductions de valeur sur immobilisations financi\u00e8res": {} - }, - "PRODUITS FINANCIERS": { - "Diff\u00e9rences de change": {}, - "Ecarts de conversion des devises": {}, - "Escomptes obtenus": {}, - "Plus-values sur r\u00e9alisations d'actifs circulants": {}, - "Produits des actifs circulants": {}, - "Produits des autres cr\u00e9ances": {}, - "Produits des immobilisations financi\u00e8res": { - "Revenus des actions": {}, - "Revenus des cr\u00e9ances \u00e0 plus d'un an": {}, - "Revenus des obligations": {} - }, - "Subsides en capital et en int\u00e9r\u00eats": {} - }, - "REGULARISATIONS D'IMPOTS ET REPRISES DE PROVISIONS FISCALES": { - "Imp\u00f4ts belges sur le r\u00e9sultat": { - "Reprises de provisions fiscales": {}, - "R\u00e9gularisations d'imp\u00f4ts dus ou vers\u00e9s": {}, - "R\u00e9gularisations d'imp\u00f4ts estim\u00e9s": {} - }, - "Imp\u00f4ts \u00e9trangers sur le r\u00e9sultat": {} - }, - "VARIATION DES STOCKS ET DES COMMANDES EN COURS D'EXECUTION": { - "Des commandes en cours d'ex\u00e9cution": { - "B\u00e9n\u00e9fices port\u00e9s en compte sur commandes en cours": { - "Sur commandes en cours d'ex\u00e9cution": {}, - "Sur travaux en cours des associations momentan\u00e9es": {} - }, - "Commandes en cours - Co\u00fbt de revient": { - "Co\u00fbt des commandes en cours d'ex\u00e9cution": {}, - "Co\u00fbt des travaux en cours des associations momentan\u00e9es": {} - } - }, - "Des en cours de fabrication": {}, - "Des immeubles construits destin\u00e9s \u00e0 la vente": {}, - "Des produits finis": {} - }, - "root_type": "" - } - } -} diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/be_minimum_genormaliseerd_rekeningstelsel_ondernemingen.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/be_minimum_genormaliseerd_rekeningstelsel_ondernemingen.json new file mode 100644 index 00000000000..21a48c71db7 --- /dev/null +++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/be_minimum_genormaliseerd_rekeningstelsel_ondernemingen.json @@ -0,0 +1,1597 @@ +{ + "country_code": "be", + "name": "België - Minimum genormaliseerd algemeen rekeningstelsel voor ondernemingen", + "tree": { + "KLASSE 1 : EIGEN VERMOGEN": { + "root_type": "Equity", + "Kapitaal": { + "Geplaatst kapitaal": { + "account_number": "100", + "account_type": "Equity" + }, + "Niet opgevraagd kapitaal (-)": { + "account_number": "101", + "account_type": "Equity" + }, + "account_number": "10", + "account_type": "Equity" + }, + "Inbreng buiten kapitaal": { + "Beschikbare inbreng buiten kapitaal": { + "Uitgiftepremie": { + "account_number": "1100", + "account_type": "Equity" + }, + "Andere": { + "account_number": "1109", + "account_type": "Equity" + }, + "account_number": "110", + "account_type": "Equity" + }, + "Onbeschikbare inbreng buiten kapitaal": { + "Uitgiftepremie": { + "account_number": "1110", + "account_type": "Equity" + }, + "Andere": { + "account_number": "1119", + "account_type": "Equity" + }, + "account_number": "111", + "account_type": "Equity" + }, + "account_number": "11", + "account_type": "Equity" + }, + "Herwaarderingsmeerwaarden": { + "Herwaarderingsmeerwaarden op immateriële vaste activa": { + "account_number": "120", + "account_type": "Equity" + }, + "Herwaarderingsmeerwaarden op materiële vaste activa": { + "account_number": "121", + "account_type": "Equity" + }, + "Herwaarderingsmeerwaarden op financiële vaste activa": { + "account_number": "122", + "account_type": "Equity" + }, + "Herwaarderingsmeerwaarden op voorraden": { + "account_number": "123", + "account_type": "Equity" + }, + "Terugneming van waardeverminderingen op geldbeleggingen": { + "account_number": "124", + "account_type": "Equity" + }, + "account_number": "12", + "account_type": "Equity" + }, + "Reserves": { + "Wettelijke reserves": { + "account_number": "130", + "account_type": "Equity" + }, + "Andere onbeschikbare reserves": { + "Statutair onbeschikbare reserves": { + "account_number": "1311", + "account_type": "Equity" + }, + "Reserve voor eigen aandelen": { + "account_number": "1312", + "account_type": "Equity" + }, + "Financiële steunverlening": { + "account_number": "1313", + "account_type": "Equity" + }, + "Overige": { + "account_number": "1319", + "account_type": "Equity" + }, + "account_number": "131", + "account_type": "Equity" + }, + "Belastingvrije reserves": { + "account_number": "132", + "account_type": "Equity" + }, + "Beschikbare reserves": { + "account_number": "133", + "account_type": "Equity" + }, + "account_number": "13", + "account_type": "Equity" + }, + "Overgedragen winst of Overgedragen verlies (-)": { + "account_number": "14", + "account_type": "Equity" + }, + "Kapitaalsubsidies": { + "account_number": "15", + "account_type": "Equity" + } + }, + "KLASSE 1 : VOORZIENINGEN, UITGESTELDE BELASTINGEN EN SCHULDEN OP MEER DAN ÉÉN JAAR": { + "root_type": "Liability", + "Voorzieningen en uitgestelde belastingen": { + "Voorzieningen voor pensioenen en soortgelijke verplichtingen": { + "account_number": "160", + "account_type": "Liability" + }, + "Voorzieningen voor belastingen": { + "account_number": "161", + "account_type": "Liability" + }, + "Voorzieningen voor grote herstellingswerken en grote onderhoudswerken": { + "account_number": "162", + "account_type": "Liability" + }, + "Voorzieningen voor milieuverplichtingen": { + "account_number": "163", + "account_type": "Liability" + }, + "Uitgestelde belastingen": { + "Uitgestelde belastingen op kapitaalsubsidies": { + "account_number": "1680", + "account_type": "Liability" + }, + "Uitgestelde belastingen op gerealiseerde meerwaarden op immateriële vaste activa": { + "account_number": "1681", + "account_type": "Liability" + }, + "Uitgestelde belastingen op gerealiseerde meerwaarden op materiële vaste activa": { + "account_number": "1682", + "account_type": "Liability" + }, + "Uitgestelde belastingen op gerealiseerde meerwaarden op effecten die zijn uitgegeven door de Belgische openbare sector": { + "account_number": "1687", + "account_type": "Liability" + }, + "Buitenlandse uitgestelde belastingen": { + "account_number": "1688", + "account_type": "Liability" + }, + "account_number": "168", + "account_type": "Liability" + }, + "account_number": "16", + "account_type": "Liability" + }, + "Schulden op meer dan één jaar": { + "Achtergestelde leningen": { + "Converteerbaar": { + "account_number": "1700", + "account_type": "Liability" + }, + "Niet converteerbaar": { + "account_number": "1701", + "account_type": "Liability" + }, + "account_number": "170", + "account_type": "Liability" + }, + "Niet-achtergestelde obligatieleningen": { + "Converteerbaar": { + "account_number": "1710", + "account_type": "Liability" + }, + "Niet converteerbaar": { + "account_number": "1711", + "account_type": "Liability" + }, + "account_number": "171", + "account_type": "Liability" + }, + "Leasingschulden en soortgelijke schulden": { + "account_number": "172", + "account_type": "Liability" + }, + "Kredietinstellingen": { + "Schulden op rekening": { + "account_number": "1730", + "account_type": "Liability" + }, + "Promessen": { + "account_number": "1731", + "account_type": "Liability" + }, + "Acceptkredieten": { + "account_number": "1732", + "account_type": "Liability" + }, + "account_number": "173", + "account_type": "Liability" + }, + "Overige leningen": { + "account_number": "174", + "account_type": "Liability" + }, + "Handelsschulden": { + "Leveranciers": { + "account_number": "1750", + "account_type": "Liability" + }, + "Te betalen wissels": { + "account_number": "1751", + "account_type": "Liability" + }, + "account_number": "175", + "account_type": "Liability" + }, + "Vooruitbetalingen op bestellingen": { + "account_number": "176", + "account_type": "Liability" + }, + "Borgtochten ontvangen in contanten": { + "account_number": "178", + "account_type": "Liability" + }, + "Overige schulden": { + "account_number": "179", + "account_type": "Liability" + }, + "account_number": "17", + "account_type": "Liability" + }, + "Voorschot aan de vennoten op de verdeling van het netto-actief (-)": { + "account_number": "19", + "account_type": "Liability" + } + }, + "KLASSE 2 : OPRICHTINGSKOSTEN, VASTE ACTIVA EN VORDERINGEN OP MEER DAN ÉÉN JAAR": { + "root_type": "Asset", + "Oprichtingskosten": { + "Kosten van oprichting, kapitaalverhoging of verhoging van de inbreng": { + "account_number": "200", + "account_type": "Fixed Asset" + }, + "Kosten bij uitgifte van leningen": { + "account_number": "201", + "account_type": "Fixed Asset" + }, + "Overige oprichtingskosten": { + "account_number": "202", + "account_type": "Fixed Asset" + }, + "Herstructureringskosten": { + "account_number": "204", + "account_type": "Fixed Asset" + }, + "account_number": "20", + "account_type": "Fixed Asset" + }, + "Immateriële vaste activa": { + "Kosten van onderzoek en ontwikkeling": { + "account_number": "210", + "account_type": "Fixed Asset" + }, + "Concessies, octrooien, licenties, know-how, merken en soortgelijke rechten": { + "account_number": "211", + "account_type": "Fixed Asset" + }, + "Goodwill": { + "account_number": "212", + "account_type": "Fixed Asset" + }, + "Vooruitbetalingen": { + "account_number": "213", + "account_type": "Fixed Asset" + }, + "Geboekte afschrijvingen en waardeverminderingen (-)": { + "account_number": "219", + "account_type": "Accumulated Depreciation" + }, + "account_number": "21", + "account_type": "Fixed Asset" + }, + "Terreinen en gebouwen": { + "Terreinen": { + "account_number": "220", + "account_type": "Fixed Asset" + }, + "Gebouwen": { + "account_number": "221", + "account_type": "Fixed Asset" + }, + "Bebouwde terreinen": { + "account_number": "222", + "account_type": "Fixed Asset" + }, + "Overige zakelijke rechten op onroerende goederen": { + "account_number": "223", + "account_type": "Fixed Asset" + }, + "Geboekte afschrijvingen en waardeverminderingen (-)": { + "account_number": "229", + "account_type": "Accumulated Depreciation" + }, + "account_number": "22", + "account_type": "Fixed Asset" + }, + "Installaties, machines en uitrusting": { + "account_number": "23", + "account_type": "Fixed Asset" + }, + "Meubilair en rollend materieel": { + "account_number": "24", + "account_type": "Fixed Asset" + }, + "Vaste activa in leasing of op grond van een soortgelijk recht": { + "Terreinen en gebouwen": { + "account_number": "250", + "account_type": "Fixed Asset" + }, + "Installaties, machines en uitrusting": { + "account_number": "251", + "account_type": "Fixed Asset" + }, + "Meubilair en rollend materieel": { + "account_number": "252", + "account_type": "Fixed Asset" + }, + "Geboekte afschrijvingen en waardeverminderingen (-)": { + "account_number": "259", + "account_type": "Accumulated Depreciation" + }, + "account_number": "25", + "account_type": "Fixed Asset" + }, + "Overige materiële vaste activa": { + "account_number": "26", + "account_type": "Fixed Asset" + }, + "Vaste activa in aanbouw en vooruitbetalingen": { + "account_number": "27", + "account_type": "Fixed Asset" + }, + "Financiële vaste activa": { + "Deelnemingen in verbonden ondernemingen": { + "Aanschaffingswaarde": { + "account_number": "2800", + "account_type": "Fixed Asset" + }, + "Nog te storten bedragen (-)": { + "account_number": "2801", + "account_type": "Fixed Asset" + }, + "Geboekte meerwaarden": { + "account_number": "2808", + "account_type": "Fixed Asset" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "2809", + "account_type": "Fixed Asset" + }, + "account_number": "280", + "account_type": "Fixed Asset" + }, + "Vorderingen op verbonden ondernemingen": { + "Vorderingen op rekening": { + "account_number": "2810", + "account_type": "Fixed Asset" + }, + "Te innen wissels": { + "account_number": "2811", + "account_type": "Fixed Asset" + }, + "Vastrentende effecten": { + "account_number": "2812", + "account_type": "Fixed Asset" + }, + "Dubieuze debiteuren": { + "account_number": "2817", + "account_type": "Fixed Asset" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "2819", + "account_type": "Fixed Asset" + }, + "account_number": "281", + "account_type": "Fixed Asset" + }, + "Deelnemingen in ondernemingen waarmee een deelnemingsverhouding bestaat": { + "Aanschaffingswaarde": { + "account_number": "2820", + "account_type": "Fixed Asset" + }, + "Nog te storten bedragen (-)": { + "account_number": "2821", + "account_type": "Fixed Asset" + }, + "Geboekte meerwaarden": { + "account_number": "2828", + "account_type": "Fixed Asset" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "2829", + "account_type": "Fixed Asset" + }, + "account_number": "282", + "account_type": "Fixed Asset" + }, + "Vorderingen op ondernemingen waarmee een deelnemingsverhouding bestaat": { + "Vorderingen op rekening": { + "account_number": "2830", + "account_type": "Fixed Asset" + }, + "Te innen wissels": { + "account_number": "2831", + "account_type": "Fixed Asset" + }, + "Vastrentende effecten": { + "account_number": "2832", + "account_type": "Fixed Asset" + }, + "Dubieuze debiteuren": { + "account_number": "2837", + "account_type": "Fixed Asset" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "2839", + "account_type": "Fixed Asset" + }, + "account_number": "283", + "account_type": "Fixed Asset" + }, + "Andere aandelen": { + "Aanschaffingswaarde": { + "account_number": "2840", + "account_type": "Fixed Asset" + }, + "Nog te storten bedragen (-)": { + "account_number": "2841", + "account_type": "Fixed Asset" + }, + "Geboekte meerwaarden": { + "account_number": "2848", + "account_type": "Fixed Asset" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "2849", + "account_type": "Fixed Asset" + }, + "account_number": "284", + "account_type": "Fixed Asset" + }, + "Overige vorderingen": { + "Vorderingen op rekening": { + "account_number": "2850", + "account_type": "Fixed Asset" + }, + "Te innen wissels": { + "account_number": "2851", + "account_type": "Fixed Asset" + }, + "Vastrentende effecten": { + "account_number": "2852", + "account_type": "Fixed Asset" + }, + "Dubieuze debiteuren": { + "account_number": "2857", + "account_type": "Fixed Asset" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "2859", + "account_type": "Fixed Asset" + }, + "account_number": "285", + "account_type": "Fixed Asset" + }, + "Borgtochten betaald in contanten": { + "account_number": "288", + "account_type": "Fixed Asset" + }, + "account_number": "28", + "account_type": "Fixed Asset" + }, + "Vorderingen op meer dan één jaar": { + "Handelsvorderingen": { + "Handelsdebiteuren": { + "account_number": "2900" + }, + "Te innen wissels": { + "account_number": "2901" + }, + "Vooruitbetalingen": { + "account_number": "2906" + }, + "Dubieuze debiteuren": { + "account_number": "2907" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "2909" + }, + "account_number": "290" + }, + "Overige vorderingen": { + "Vorderingen op rekening": { + "account_number": "2910" + }, + "Te innen wissels": { + "account_number": "2911" + }, + "Dubieuze debiteuren": { + "account_number": "2917" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "2919" + }, + "account_number": "291" + }, + "account_number": "29" + } + }, + "KLASSE 3 : VOORRADEN EN BESTELLINGEN IN UITVOERING": { + "root_type": "Asset", + "Grondstoffen": { + "Aanschaffingswaarde": { + "account_number": "300" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "309" + }, + "account_number": "30" + }, + "Hulpstoffen": { + "Aanschaffingswaarde": { + "account_number": "310" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "319" + }, + "account_number": "31" + }, + "Goederen in bewerking": { + "Aanschaffingswaarde": { + "account_number": "320" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "329" + }, + "account_number": "32" + }, + "Gereed product": { + "Aanschaffingswaarde": { + "account_number": "330" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "339" + }, + "account_number": "33" + }, + "Handelsgoederen": { + "Aanschaffingswaarde": { + "account_number": "340", + "account_type": "Stock" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "349" + }, + "account_number": "34", + "account_type": "Stock" + }, + "Onroerende goederen bestemd voor verkoop": { + "Aanschaffingswaarde": { + "account_number": "350" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "359" + }, + "account_number": "35" + }, + "Vooruitbetalingen op voorraadinkopen": { + "Vooruitbetalingen": { + "account_number": "360" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "369" + }, + "account_number": "36" + }, + "Bestellingen in uitvoering": { + "Aanschaffingswaarde": { + "account_number": "370" + }, + "Toegerekende winst": { + "account_number": "371" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "379" + }, + "account_number": "37" + }, + "Geleverde voorraad, niet gefactureerd": { + "account_type": "Stock Delivered But Not Billed" + } + }, + "KLASSE 4 : VORDERINGEN OP TEN HOOGSTE ÉÉN JAAR": { + "root_type": "Asset", + "Handelsvorderingen": { + "Handelsdebiteuren": { + "account_number": "400", + "account_type": "Receivable" + }, + "Te innen wissels": { + "account_number": "401", + "account_type": "Receivable" + }, + "Te innen opbrengsten": { + "account_number": "404", + "account_type": "Receivable" + }, + "Vooruitbetalingen": { + "account_number": "406" + }, + "Dubieuze debiteuren": { + "account_number": "407", + "account_type": "Receivable" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "409" + }, + "account_number": "40", + "account_type": "Receivable" + }, + "Overige vorderingen": { + "Opgevraagd, niet gestort kapitaal of inbreng": { + "account_number": "410" + }, + "Terug te vorderen btw": { + "account_number": "411", + "account_type": "Tax" + }, + "Terug te vorderen belastingen en voorheffingen": { + "Buitenlandse belastingen": { + "account_number": "4128" + }, + "account_number": "412" + }, + "Te innen opbrengsten": { + "account_number": "414" + }, + "Diverse vorderingen": { + "account_number": "416" + }, + "Dubieuze debiteuren": { + "account_number": "417" + }, + "Borgtochten betaald in contanten": { + "account_number": "418" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "419" + }, + "account_number": "41" + } + }, + "KLASSE 4 : SCHULDEN OP TEN HOOGSTE ÉÉN JAAR": { + "root_type": "Liability", + "Schulden op meer dan één jaar die binnen het jaar vervallen (16) (zelfde onderverdeling als 17)": { + "account_number": "42" + }, + "Financiële schulden": { + "Kredietinstellingen - Leningen op rekening met vaste termijn": { + "account_number": "430" + }, + "Kredietinstellingen - Promessen": { + "account_number": "431" + }, + "Kredietinstellingen - Acceptkredieten": { + "account_number": "432" + }, + "Kredietinstellingen - Schulden in rekening-courant": { + "account_number": "433" + }, + "Overige leningen": { + "account_number": "439" + }, + "account_number": "43" + }, + "Handelsschulden": { + "Leveranciers": { + "account_number": "440", + "account_type": "Payable" + }, + "Te betalen wissels": { + "account_number": "441", + "account_type": "Payable" + }, + "Te ontvangen facturen": { + "account_number": "444", + "account_type": "Stock Received But Not Billed" + }, + "account_number": "44", + "account_type": "Payable" + }, + "Schulden met betrekking tot belastingen, bezoldigingen en sociale lasten": { + "Geraamd bedrag der belastingschulden": { + "Buitenlandse belastingen en taksen": { + "account_number": "4508" + }, + "account_number": "450" + }, + "Te betalen btw": { + "account_number": "451", + "account_type": "Tax" + }, + "Te betalen belastingen en taksen": { + "Buitenlandse belastingen en taksen": { + "account_number": "4528" + }, + "account_number": "452" + }, + "Ingehouden voorheffingen": { + "account_number": "453" + }, + "Rijksdienst voor Sociale Zekerheid": { + "account_number": "454" + }, + "Bezoldigingen": { + "account_number": "455" + }, + "Vakantiegeld": { + "account_number": "456" + }, + "Andere sociale schulden": { + "account_number": "459" + }, + "account_number": "45" + }, + "Vooruitbetalingen op bestellingen": { + "account_number": "46" + }, + "Schulden uit de bestemming van het resultaat": { + "Dividenden en tantièmes over vorige boekjaren": { + "account_number": "470" + }, + "Dividenden over het boekjaar": { + "account_number": "471" + }, + "Tantièmes over het boekjaar": { + "account_number": "472" + }, + "Andere rechthebbenden": { + "account_number": "473" + }, + "account_number": "47" + }, + "Diverse schulden": { + "Vervallen obligaties en coupons": { + "account_number": "480" + }, + "Borgtochten ontvangen in contanten": { + "account_number": "488" + }, + "Andere diverse schulden": { + "account_number": "489" + }, + "account_number": "48" + }, + "Overlopende rekeningen": { + "Over te dragen kosten": { + "account_number": "490" + }, + "Verkregen opbrengsten": { + "account_number": "491" + }, + "Toe te rekenen kosten": { + "account_number": "492" + }, + "Over te dragen opbrengsten": { + "account_number": "493" + }, + "Wachtrekeningen": { + "account_number": "499" + }, + "account_number": "49" + } + }, + "KLASSE 5 : GELDBELEGGINGEN EN LIQUIDE MIDDELEN": { + "root_type": "Asset", + "Eigen aandelen": { + "account_number": "50" + }, + "Aandelen en geldbeleggingen andere dan vastrentende beleggingen": { + "Aanschaffingswaarde": { + "Aandelen": { + "account_number": "5100" + }, + "Geldbeleggingen andere dan vastrentende beleggingen": { + "account_number": "5101" + }, + "account_number": "510" + }, + "Niet-opgevraagde bedragen (-)": { + "Aandelen": { + "account_number": "5110" + }, + "account_number": "511" + }, + "Geboekte waardeverminderingen (-)": { + "Aandelen": { + "account_number": "5190" + }, + "Geldbeleggingen andere dan vastrentende beleggingen": { + "account_number": "5191" + }, + "account_number": "519" + }, + "account_number": "51" + }, + "Vastrentende effecten": { + "Aanschaffingswaarde": { + "account_number": "520" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "529" + }, + "account_number": "52" + }, + "Termijndeposito's": { + "Op meer dan één jaar": { + "account_number": "530" + }, + "Op meer dan één maand en op ten hoogste één jaar": { + "account_number": "531" + }, + "Op ten hoogste één maand": { + "account_number": "532" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "539" + }, + "account_number": "53" + }, + "Te incasseren vervallen waarden": { + "account_number": "54" + }, + "Kredietinstellingen": { + "Bank": { + "account_number": "5500", + "account_type": "Bank" + }, + "account_number": "55", + "account_type": "Bank" + }, + "Kassen": { + "Kassen-zegels": { + "account_number": "578", + "account_type": "Cash" + }, + "account_number": "57", + "account_type": "Cash" + }, + "Interne overboekingen": { + "account_number": "58", + "account_type": "Temporary" + } + }, + "KLASSE 6 : KOSTEN": { + "root_type": "Expense", + "Handelsgoederen, grond- en hulpstoffen": { + "Aankopen van grondstoffen": { + "account_number": "600", + "account_type": "Expense Account" + }, + "Aankopen van hulpstoffen": { + "account_number": "601", + "account_type": "Expense Account" + }, + "Aankopen van diensten, werk en studies": { + "account_number": "602", + "account_type": "Expense Account" + }, + "Algemene onderaannemingen": { + "account_number": "603", + "account_type": "Expense Account" + }, + "Aankopen van handelsgoederen": { + "account_number": "604", + "account_type": "Cost of Goods Sold" + }, + "Aankopen van onroerende goederen bestemd voor verkoop": { + "account_number": "605", + "account_type": "Expense Account" + }, + "Ontvangen kortingen, ristorno's en rabatten (-)": { + "account_number": "608", + "account_type": "Expense Account" + }, + "Voorraadwijzigingen": { + "van grondstoffen": { + "account_number": "6090", + "account_type": "Stock Adjustment" + }, + "van hulpstoffen": { + "account_number": "6091", + "account_type": "Stock Adjustment" + }, + "van handelsgoederen": { + "account_number": "6094", + "account_type": "Stock Adjustment" + }, + "van gekochte onroerende goederen bestemd voor verkoop": { + "account_number": "6095", + "account_type": "Stock Adjustment" + }, + "account_number": "609", + "account_type": "Stock Adjustment" + }, + "account_number": "60", + "account_type": "Expense Account" + }, + "Diensten en diverse goederen": { + "Uitzendkrachten en personen ter beschikking gesteld van de onderneming": { + "account_number": "617", + "account_type": "Expense Account" + }, + "Bezoldigingen en pensioenen van bestuurders, zaakvoerders en werkende vennoten, buiten arbeidsovereenkomst": { + "account_number": "618", + "account_type": "Expense Account" + }, + "Aankoopkosten begrepen in de waarde van de voorraden": { + "account_type": "Expenses Included In Valuation" + }, + "account_number": "61", + "account_type": "Expense Account" + }, + "Bezoldigingen, sociale lasten en pensioenen": { + "Bezoldigingen en rechtstreekse sociale voordelen": { + "Bestuurders of zaakvoerders": { + "account_number": "6200", + "account_type": "Expense Account" + }, + "Directiepersoneel": { + "account_number": "6201", + "account_type": "Expense Account" + }, + "Bedienden": { + "account_number": "6202", + "account_type": "Expense Account" + }, + "Arbeiders": { + "account_number": "6203", + "account_type": "Expense Account" + }, + "Andere personeelsleden": { + "account_number": "6204", + "account_type": "Expense Account" + }, + "account_number": "620", + "account_type": "Expense Account" + }, + "Werkgeversbijdragen voor sociale verzekeringen": { + "account_number": "621", + "account_type": "Expense Account" + }, + "Werkgeverspremies voor bovenwettelijke verzekeringen": { + "account_number": "622", + "account_type": "Expense Account" + }, + "Andere personeelskosten": { + "account_number": "623", + "account_type": "Expense Account" + }, + "Ouderdoms- en overlevingspensioenen": { + "Bestuurders of zaakvoerders": { + "account_number": "6240", + "account_type": "Expense Account" + }, + "Personeel": { + "account_number": "6241", + "account_type": "Expense Account" + }, + "account_number": "624", + "account_type": "Expense Account" + }, + "account_number": "62", + "account_type": "Expense Account" + }, + "Afschrijvingen, waardeverminderingen en voorzieningen voor risico's": { + "Afschrijvingen en waardeverminderingen op vaste activa-toevoeging": { + "Afschrijvingen op oprichtingskosten": { + "account_number": "6300", + "account_type": "Depreciation" + }, + "Afschrijvingen op immateriële vaste activa": { + "account_number": "6301", + "account_type": "Depreciation" + }, + "Afschrijvingen op materiële vaste activa": { + "account_number": "6302", + "account_type": "Depreciation" + }, + "Waardeverminderingen op immateriële vaste activa": { + "account_number": "6308", + "account_type": "Depreciation" + }, + "Waardeverminderingen op materiële vaste activa": { + "account_number": "6309", + "account_type": "Depreciation" + }, + "account_number": "630", + "account_type": "Depreciation" + }, + "Waardeverminderingen op voorraden": { + "Toevoeging": { + "account_number": "6310", + "account_type": "Depreciation" + }, + "Terugneming (-)": { + "account_number": "6311", + "account_type": "Depreciation" + }, + "account_number": "631", + "account_type": "Depreciation" + }, + "Waardeverminderingen op bestellingen in uitvoering": { + "Toevoeging": { + "account_number": "6320", + "account_type": "Depreciation" + }, + "Terugneming (-)": { + "account_number": "6321", + "account_type": "Depreciation" + }, + "account_number": "632", + "account_type": "Depreciation" + }, + "Waardeverminderingen op handelsvorderingen op meer dan één jaar": { + "Toevoeging": { + "account_number": "6330", + "account_type": "Depreciation" + }, + "Terugneming (-)": { + "account_number": "6331", + "account_type": "Depreciation" + }, + "account_number": "633", + "account_type": "Depreciation" + }, + "Waardeverminderingen op handelsvorderingen op ten hoogste één jaar": { + "Toevoeging": { + "account_number": "6340", + "account_type": "Depreciation" + }, + "Terugneming (-)": { + "account_number": "6341", + "account_type": "Depreciation" + }, + "account_number": "634", + "account_type": "Depreciation" + }, + "Voorzieningen voor pensioenen en soortgelijke verplichtingen": { + "Toevoeging": { + "account_number": "6350", + "account_type": "Depreciation" + }, + "Besteding en terugneming (-)": { + "account_number": "6351", + "account_type": "Depreciation" + }, + "account_number": "635", + "account_type": "Depreciation" + }, + "Voorzieningen voor grote herstellingswerken en grote onderhoudswerken": { + "Toevoeging": { + "account_number": "6360", + "account_type": "Depreciation" + }, + "Besteding en terugneming (-)": { + "account_number": "6361", + "account_type": "Depreciation" + }, + "account_number": "636", + "account_type": "Depreciation" + }, + "Voorzieningen voor milieuverplichtingen": { + "Toevoeging": { + "account_number": "6370", + "account_type": "Depreciation" + }, + "Besteding en terugneming (-)": { + "account_number": "6371", + "account_type": "Depreciation" + }, + "account_number": "637", + "account_type": "Depreciation" + }, + "Voorzieningen voor andere risico's en kosten": { + "Toevoeging": { + "account_number": "6380", + "account_type": "Expense Account" + }, + "Besteding en terugneming (-)": { + "account_number": "6381", + "account_type": "Expense Account" + }, + "account_number": "638", + "account_type": "Expense Account" + }, + "account_number": "63", + "account_type": "Expense Account" + }, + "Andere bedrijfskosten": { + "Bedrijfsbelastingen": { + "account_number": "640", + "account_type": "Expense Account" + }, + "Minderwaarden op de courante realisatie van vaste activa": { + "account_number": "641", + "account_type": "Expense Account" + }, + "Minderwaarden op de realisatie van handelsvorderingen": { + "account_number": "642", + "account_type": "Expense Account" + }, + "Diverse bedrijfskosten (643 tot 648)": { + "account_number": "643", + "account_type": "Expense Account" + }, + "Als herstructureringskosten geactiveerde bedrijfskosten (-)": { + "account_number": "649", + "account_type": "Expense Account" + }, + "account_number": "64", + "account_type": "Expense Account" + }, + "Financiële kosten": { + "Kosten van schulden": { + "Rente, commissies en kosten verbonden aan schulden": { + "account_number": "6500", + "account_type": "Expense Account" + }, + "Afschrijving van kosten bij uitgifte van leningen": { + "account_number": "6501", + "account_type": "Expense Account" + }, + "Geactiveerde intercalaire interesten (-)": { + "account_number": "6502", + "account_type": "Expense Account" + }, + "account_number": "650", + "account_type": "Expense Account" + }, + "Waardeverminderingen op vlottende activa": { + "Toevoeging": { + "account_number": "6510", + "account_type": "Expense Account" + }, + "Terugneming (-)": { + "account_number": "6511", + "account_type": "Expense Account" + }, + "account_number": "651", + "account_type": "Expense Account" + }, + "Minderwaarden op de realisatie van vlottende activa": { + "account_number": "652", + "account_type": "Expense Account" + }, + "Discontokosten op vorderingen": { + "account_number": "653", + "account_type": "Expense Account" + }, + "Wisselresultaten": { + "account_number": "654", + "account_type": "Expense Account" + }, + "Resultaten uit de omrekening van vreemde valuta": { + "account_number": "655", + "account_type": "Expense Account" + }, + "Voorzieningen met financieel karakter": { + "Toevoegingen": { + "account_number": "6560", + "account_type": "Expense Account" + }, + "Bestedingen en terugnemingen (-)": { + "account_number": "6561", + "account_type": "Expense Account" + }, + "account_number": "656", + "account_type": "Expense Account" + }, + "Diverse financiële kosten (657 tot 658)": { + "account_number": "657", + "account_type": "Expense Account" + }, + "Als herstructureringskosten geactiveerde financiële kosten (-)": { + "account_number": "659", + "account_type": "Expense Account" + }, + "account_number": "65", + "account_type": "Expense Account" + }, + "Niet-recurrente bedrijfs- of financiële kosten": { + "Niet-recurrente afschrijvingen en waardeverminderingen (toevoeging)": { + "op oprichtingskosten": { + "account_number": "6600", + "account_type": "Expense Account" + }, + "op immateriële vaste activa": { + "account_number": "6601", + "account_type": "Expense Account" + }, + "op materiële vaste activa": { + "account_number": "6602", + "account_type": "Expense Account" + }, + "account_number": "660", + "account_type": "Expense Account" + }, + "Waardeverminderingen op financiële vaste activa (toevoeging)": { + "account_number": "661", + "account_type": "Expense Account" + }, + "Voorzieningen voor niet-recurrente risico's en kosten": { + "Voorzieningen voor niet-recurrente bedrijfsrisico's en -kosten": { + "Toevoeging": { + "account_number": "66200", + "account_type": "Expense Account" + }, + "Besteding (-)": { + "account_number": "66201", + "account_type": "Expense Account" + }, + "account_number": "6620", + "account_type": "Expense Account" + }, + "Voorzieningen voor niet-recurrente financiële risico's en -kosten": { + "Toevoeging": { + "account_number": "66210", + "account_type": "Expense Account" + }, + "Besteding (-)": { + "account_number": "66211", + "account_type": "Expense Account" + }, + "account_number": "6621", + "account_type": "Expense Account" + }, + "account_number": "662", + "account_type": "Expense Account" + }, + "Minderwaarden op de realisatie van vaste activa": { + "Minderwaarden op de realisatie van immateriële en materiële vaste activa": { + "account_number": "6630", + "account_type": "Expense Account" + }, + "Minderwaarden op de realisatie van financiële vaste activa": { + "account_number": "6631", + "account_type": "Expense Account" + }, + "account_number": "663", + "account_type": "Expense Account" + }, + "Andere niet-recurrente bedrijfskosten (664 tot 667)": { + "account_number": "664", + "account_type": "Expense Account" + }, + "Andere niet-recurrente financiële kosten": { + "account_number": "668", + "account_type": "Expense Account" + }, + "Als herstructureringskosten geactiveerde niet-recurrente bedrijfskosten (-)": { + "account_number": "6690", + "account_type": "Expense Account" + }, + "Als herstructureringskosten geactiveerde niet-recurrente financiële kosten (-)": { + "account_number": "6691", + "account_type": "Expense Account" + }, + "account_number": "66", + "account_type": "Expense Account" + }, + "Belastingen op het resultaat": { + "Belgische belastingen op het resultaat van het boekjaar": { + "Verschuldigde of gestorte belastingen en voorheffingen": { + "account_number": "6700", + "account_type": "Expense Account" + }, + "Geactiveerde overschotten van betaalde belastingen en voorheffingen (-)": { + "account_number": "6701", + "account_type": "Expense Account" + }, + "Geraamde belastingen": { + "account_number": "6702", + "account_type": "Expense Account" + }, + "account_number": "670", + "account_type": "Expense Account" + }, + "Belgische belastingen op het resultaat van vorige boekjaren": { + "Verschuldigde of gestorte belastingsupplementen": { + "account_number": "6710", + "account_type": "Expense Account" + }, + "Geraamde belastingsupplementen": { + "account_number": "6711", + "account_type": "Expense Account" + }, + "Gevormde fiscale voorzieningen": { + "account_number": "6712", + "account_type": "Expense Account" + }, + "account_number": "671", + "account_type": "Expense Account" + }, + "Buitenlandse belastingen op het resultaat van het boekjaar": { + "account_number": "672", + "account_type": "Expense Account" + }, + "Buitenlandse belastingen op het resultaat van vorige boekjaren": { + "account_number": "673", + "account_type": "Expense Account" + }, + "account_number": "67", + "account_type": "Expense Account" + }, + "Overboeking naar de uitgestelde belastingen en naar de belastingvrije reserves": { + "Overboeking naar de uitgestelde belastingen": { + "account_number": "680", + "account_type": "Expense Account" + }, + "Overboeking naar de belastingvrije reserves": { + "account_number": "689", + "account_type": "Expense Account" + }, + "account_number": "68", + "account_type": "Expense Account" + }, + "Resultatenverwerking": { + "Overgedragen verlies van het vorige boekjaar": { + "account_number": "690", + "account_type": "Expense Account" + }, + "Toevoeging aan de inbreng": { + "account_number": "691", + "account_type": "Expense Account" + }, + "Toevoeging aan de reserves": { + "Toevoeging aan de wettelijke reserve": { + "account_number": "6920", + "account_type": "Expense Account" + }, + "Toevoeging aan de overige reserves": { + "account_number": "6921", + "account_type": "Expense Account" + }, + "account_number": "692", + "account_type": "Expense Account" + }, + "Over te dragen winst": { + "account_number": "693", + "account_type": "Expense Account" + }, + "Vergoeding van de inbreng": { + "account_number": "694", + "account_type": "Expense Account" + }, + "Bestuurders of zaakvoerders": { + "account_number": "695", + "account_type": "Expense Account" + }, + "Werknemers": { + "account_number": "696", + "account_type": "Expense Account" + }, + "Andere rechthebbenden": { + "account_number": "697", + "account_type": "Expense Account" + }, + "account_number": "69", + "account_type": "Expense Account" + } + }, + "KLASSE 7 : OPBRENGSTEN": { + "root_type": "Income", + "Omzet": { + "Toegekende kortingen, ristorno's en rabatten (-)": { + "account_number": "708", + "account_type": "Income Account" + }, + "account_number": "70", + "account_type": "Income Account" + }, + "Wijzigingen in de voorraden en in de bestellingen in uitvoering": { + "In de voorraad goederen in bewerking": { + "account_number": "712", + "account_type": "Income Account" + }, + "In de voorraad gereed product": { + "account_number": "713", + "account_type": "Income Account" + }, + "In de voorraad onroerende goederen bestemd voor verkoop": { + "account_number": "715", + "account_type": "Income Account" + }, + "In de bestellingen in uitvoering": { + "Aanschaffingswaarde": { + "account_number": "7170", + "account_type": "Income Account" + }, + "Toegerekende winst": { + "account_number": "7171", + "account_type": "Income Account" + }, + "account_number": "717", + "account_type": "Income Account" + }, + "account_number": "71", + "account_type": "Income Account" + }, + "Geproduceerde vaste activa": { + "account_number": "72", + "account_type": "Income Account" + }, + "Andere bedrijfsopbrengsten": { + "Bedrijfssubsidies en compenserende bedragen": { + "account_number": "740", + "account_type": "Income Account" + }, + "Meerwaarden op de courante realisatie van materiële vaste activa": { + "account_number": "741", + "account_type": "Income Account" + }, + "Meerwaarden op de realisatie van handelsvorderingen": { + "account_number": "742", + "account_type": "Income Account" + }, + "Diverse bedrijfsopbrengsten (743 tot 749)": { + "account_number": "743", + "account_type": "Income Account" + }, + "account_number": "74", + "account_type": "Income Account" + }, + "Financiële opbrengsten": { + "Opbrengsten uit financiële vaste activa": { + "account_number": "750", + "account_type": "Income Account" + }, + "Opbrengsten uit vlottende activa": { + "account_number": "751", + "account_type": "Income Account" + }, + "Meerwaarden op de realisatie van vlottende activa": { + "account_number": "752", + "account_type": "Income Account" + }, + "Kapitaal- en interestsubsidies": { + "account_number": "753", + "account_type": "Income Account" + }, + "Wisselresultaten": { + "account_number": "754", + "account_type": "Income Account" + }, + "Resultaten uit de omrekening van vreemde valuta": { + "account_number": "755", + "account_type": "Income Account" + }, + "Diverse financiële opbrengsten (756 tot 759)": { + "account_number": "756", + "account_type": "Income Account" + }, + "account_number": "75", + "account_type": "Income Account" + }, + "Niet-recurrente bedrijfs- of financiële opbrengsten": { + "Terugneming van afschrijvingen en waardeverminderingen": { + "op immateriële vaste activa": { + "account_number": "7600", + "account_type": "Income Account" + }, + "op materiële vaste activa": { + "account_number": "7601", + "account_type": "Income Account" + }, + "account_number": "760", + "account_type": "Income Account" + }, + "Terugneming van waardeverminderingen op financiële vaste activa": { + "account_number": "761", + "account_type": "Income Account" + }, + "Terugneming van voorzieningen voor niet-recurrente risico's en kosten": { + "Terugneming van voorzieningen voor niet-recurrente bedrijfsrisico's en -kosten": { + "account_number": "7620", + "account_type": "Income Account" + }, + "Terugneming van voorzieningen voor niet-recurrente financiële risico's en -kosten": { + "account_number": "7621", + "account_type": "Income Account" + }, + "account_number": "762", + "account_type": "Income Account" + }, + "Meerwaarden op de realisatie van vaste activa": { + "Meerwaarde op de realisatie van immateriële en materiële vaste activa": { + "account_number": "7630", + "account_type": "Income Account" + }, + "Meerwaarde op de realisatie van financiële vaste activa": { + "account_number": "7631", + "account_type": "Income Account" + }, + "account_number": "763", + "account_type": "Income Account" + }, + "Andere niet-recurrente bedrijfsopbrengsten (764 tot 768)": { + "account_number": "764", + "account_type": "Income Account" + }, + "Andere niet-recurrente financiële opbrengsten": { + "account_number": "769", + "account_type": "Income Account" + }, + "account_number": "76", + "account_type": "Income Account" + }, + "Regularisering van belastingen en terugneming van fiscale voorzieningen": { + "Belgische belastingen op het resultaat": { + "Regularisering van verschuldigde of betaalde belastingen": { + "account_number": "7710", + "account_type": "Income Account" + }, + "Regularisering van geraamde belastingen": { + "account_number": "7711", + "account_type": "Income Account" + }, + "Terugneming van fiscale voorzieningen": { + "account_number": "7712", + "account_type": "Income Account" + }, + "account_number": "771", + "account_type": "Income Account" + }, + "Buitenlandse belastingen op het resultaat": { + "account_number": "773", + "account_type": "Income Account" + }, + "account_number": "77", + "account_type": "Income Account" + }, + "Onttrekkingen aan de belastingvrije reserves en uitgestelde belastingen": { + "Onttrekkingen aan de uitgestelde belastingen": { + "account_number": "780", + "account_type": "Income Account" + }, + "Onttrekkingen aan de belastingvrije reserves": { + "account_number": "789", + "account_type": "Income Account" + }, + "account_number": "78", + "account_type": "Income Account" + }, + "Resultaatverwerking": { + "Overgedragen winst van het vorige boekjaar": { + "account_number": "790", + "account_type": "Income Account" + }, + "Onttrekking aan de inbreng": { + "account_number": "791", + "account_type": "Income Account" + }, + "Onttrekking aan de reserves": { + "account_number": "792", + "account_type": "Income Account" + }, + "Over te dragen verlies": { + "account_number": "793", + "account_type": "Income Account" + }, + "Tussenkomst van vennoten (of van de eigenaar) in het verlies": { + "account_number": "794", + "account_type": "Income Account" + }, + "account_number": "79", + "account_type": "Income Account" + } + } + } +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/be_minimum_genormaliseerd_rekeningstelsel_verenigingen_stichtingen.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/be_minimum_genormaliseerd_rekeningstelsel_verenigingen_stichtingen.json new file mode 100644 index 00000000000..d94c3552e40 --- /dev/null +++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/be_minimum_genormaliseerd_rekeningstelsel_verenigingen_stichtingen.json @@ -0,0 +1,1478 @@ +{ + "country_code": "be", + "name": "België - Minimum genormaliseerd algemeen rekeningstelsel voor verenigingen en stichtingen", + "tree": { + "KLASSE 1 : VERENIGINGSFONDS EN STICHTINGSFONDS": { + "root_type": "Equity", + "Fondsen van de vereniging of stichting": { + "account_number": "10", + "account_type": "Equity" + }, + "Herwaarderingsmeerwaarden": { + "Herwaarderingsmeerwaarden op immateriële vaste activa": { + "account_number": "120", + "account_type": "Equity" + }, + "Herwaarderingsmeerwaarden op materiële vaste activa": { + "account_number": "121", + "account_type": "Equity" + }, + "Herwaarderingsmeerwaarden op financiële vaste activa": { + "account_number": "122", + "account_type": "Equity" + }, + "Terugneming van waardeverminderingen op geldbeleggingen": { + "account_number": "124", + "account_type": "Equity" + }, + "account_number": "12", + "account_type": "Equity" + }, + "Bestemde fondsen en andere reserves": { + "Fondsen bestemd voor investeringen": { + "account_number": "130", + "account_type": "Equity" + }, + "Fondsen bestemd voor sociaal passief": { + "account_number": "131", + "account_type": "Equity" + }, + "Belastingvrije reserves": { + "account_number": "132", + "account_type": "Equity" + }, + "Andere bestemde fondsen en andere reserves": { + "account_number": "139", + "account_type": "Equity" + }, + "account_number": "13", + "account_type": "Equity" + }, + "Overgedragen resultaat (+)(-)": { + "account_number": "14", + "account_type": "Equity" + }, + "Kapitaalsubsidies": { + "account_number": "15", + "account_type": "Equity" + } + }, + "KLASSE 1 : VOORZIENINGEN, UITGESTELDE BELASTINGEN EN SCHULDEN OP MEER DAN ÉÉN JAAR": { + "root_type": "Liability", + "Voorzieningen en uitgestelde belastingen": { + "Voorzieningen voor pensioenen en soortgelijke verplichtingen": { + "account_number": "160", + "account_type": "Liability" + }, + "Voorzieningen voor belastingen": { + "account_number": "161", + "account_type": "Liability" + }, + "Voorzieningen voor grote herstellingswerken en grote onderhoudswerken": { + "account_number": "162", + "account_type": "Liability" + }, + "Voorzieningen voor milieuverplichtingen": { + "account_number": "163", + "account_type": "Liability" + }, + "Voorzieningen voor terug te betalen subsidies, legaten en schenkingen met terugnemingsrecht": { + "account_number": "167", + "account_type": "Liability" + }, + "Uitgestelde belastingen": { + "account_number": "168", + "account_type": "Liability" + }, + "account_number": "16", + "account_type": "Liability" + }, + "Schulden op meer dan één jaar": { + "Achtergestelde leningen": { + "account_number": "170", + "account_type": "Liability" + }, + "Niet-achtergestelde obligatieleningen": { + "account_number": "171", + "account_type": "Liability" + }, + "Leasingschulden en soortgelijke schulden": { + "account_number": "172", + "account_type": "Liability" + }, + "Kredietinstellingen": { + "Schulden op rekening": { + "account_number": "1730", + "account_type": "Liability" + }, + "Promessen": { + "account_number": "1731", + "account_type": "Liability" + }, + "Acceptkredieten": { + "account_number": "1732", + "account_type": "Liability" + }, + "account_number": "173", + "account_type": "Liability" + }, + "Overige leningen": { + "account_number": "174", + "account_type": "Liability" + }, + "Handelsschulden": { + "Leveranciers": { + "account_number": "1750", + "account_type": "Liability" + }, + "Te betalen wissels": { + "account_number": "1751", + "account_type": "Liability" + }, + "account_number": "175", + "account_type": "Liability" + }, + "Vooruitbetalingen op bestellingen": { + "account_number": "176", + "account_type": "Liability" + }, + "Borgtochten in contanten": { + "account_number": "178", + "account_type": "Liability" + }, + "Overige schulden": { + "Rentedragend": { + "account_number": "1790", + "account_type": "Liability" + }, + "Niet-rentedragend of gekoppeld aan een abnormaal lage rente": { + "account_number": "1791", + "account_type": "Liability" + }, + "account_number": "179", + "account_type": "Liability" + }, + "account_number": "17", + "account_type": "Liability" + } + }, + "KLASSE 2 : OPRICHTINGSKOSTEN, VASTE ACTIVA EN VORDERINGEN OP MEER DAN ÉÉN JAAR": { + "root_type": "Asset", + "Oprichtingskosten": { + "Kosten van oprichting": { + "account_number": "200", + "account_type": "Fixed Asset" + }, + "Kosten bij uitgifte van leningen": { + "account_number": "201", + "account_type": "Fixed Asset" + }, + "Overige oprichtingskosten": { + "account_number": "202", + "account_type": "Fixed Asset" + }, + "Herstructureringskosten": { + "account_number": "204", + "account_type": "Fixed Asset" + }, + "account_number": "20", + "account_type": "Fixed Asset" + }, + "Immateriële vaste activa": { + "Kosten van onderzoek en ontwikkeling": { + "account_number": "210", + "account_type": "Fixed Asset" + }, + "Concessies, octrooien, licenties, knowhow, merken en soortgelijke rechten": { + "account_number": "211", + "account_type": "Fixed Asset" + }, + "Goodwill": { + "account_number": "212", + "account_type": "Fixed Asset" + }, + "Vooruitbetalingen": { + "account_number": "213", + "account_type": "Fixed Asset" + }, + "Geboekte afschrijvingen en waardeverminderingen (-)": { + "account_number": "219", + "account_type": "Accumulated Depreciation" + }, + "account_number": "21", + "account_type": "Fixed Asset" + }, + "Terreinen en gebouwen": { + "Terreinen": { + "account_number": "220", + "account_type": "Fixed Asset" + }, + "Gebouwen": { + "account_number": "221", + "account_type": "Fixed Asset" + }, + "Bebouwde terreinen": { + "account_number": "222", + "account_type": "Fixed Asset" + }, + "Overige zakelijke rechten op onroerende goederen": { + "account_number": "223", + "account_type": "Fixed Asset" + }, + "Geboekte afschrijvingen en waardeverminderingen (-)": { + "account_number": "229", + "account_type": "Accumulated Depreciation" + }, + "account_number": "22", + "account_type": "Fixed Asset" + }, + "Installaties, machines en uitrusting": { + "account_number": "23", + "account_type": "Fixed Asset" + }, + "Meubilair en rollend materieel": { + "account_number": "24", + "account_type": "Fixed Asset" + }, + "Vaste activa in leasing of op grond van soortgelijke rechten": { + "Terreinen en gebouwen": { + "account_number": "250", + "account_type": "Fixed Asset" + }, + "Installaties, machines en uitrusting": { + "account_number": "251", + "account_type": "Fixed Asset" + }, + "Meubilair en rollend materieel": { + "account_number": "252", + "account_type": "Fixed Asset" + }, + "Geboekte afschrijvingen en waardeverminderingen (-)": { + "account_number": "259", + "account_type": "Accumulated Depreciation" + }, + "account_number": "25", + "account_type": "Fixed Asset" + }, + "Overige materiële vaste activa": { + "account_number": "26", + "account_type": "Fixed Asset" + }, + "Materiële activa in aanbouw en vooruitbetalingen": { + "account_number": "27", + "account_type": "Fixed Asset" + }, + "Financiële vaste activa": { + "Deelnemingen in verbonden vennootschappen": { + "Aanschaffingswaarde": { + "account_number": "2800", + "account_type": "Fixed Asset" + }, + "Nog te storten bedragen (-)": { + "account_number": "2801", + "account_type": "Fixed Asset" + }, + "Geboekte meerwaarden": { + "account_number": "2808", + "account_type": "Fixed Asset" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "2809", + "account_type": "Fixed Asset" + }, + "account_number": "280", + "account_type": "Fixed Asset" + }, + "Vorderingen op verbonden entiteiten": { + "Vorderingen op rekening": { + "account_number": "2810", + "account_type": "Fixed Asset" + }, + "Te innen wissels": { + "account_number": "2811", + "account_type": "Fixed Asset" + }, + "Vastrentende effecten": { + "account_number": "2812", + "account_type": "Fixed Asset" + }, + "Dubieuze debiteuren": { + "account_number": "2817", + "account_type": "Fixed Asset" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "2819", + "account_type": "Fixed Asset" + }, + "account_number": "281", + "account_type": "Fixed Asset" + }, + "Deelnemingen in vennootschappen waarmee een deelnemingsverhouding bestaat": { + "Aanschaffingswaarde": { + "account_number": "2820", + "account_type": "Fixed Asset" + }, + "Nog te storten bedragen (-)": { + "account_number": "2821", + "account_type": "Fixed Asset" + }, + "Geboekte meerwaarden": { + "account_number": "2828", + "account_type": "Fixed Asset" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "2829", + "account_type": "Fixed Asset" + }, + "account_number": "282", + "account_type": "Fixed Asset" + }, + "Vorderingen op vennootschappen waarmee een deelnemingsverhouding bestaat": { + "Vorderingen op rekening": { + "account_number": "2830", + "account_type": "Fixed Asset" + }, + "Te innen wissels": { + "account_number": "2831", + "account_type": "Fixed Asset" + }, + "Vastrentende effecten": { + "account_number": "2832", + "account_type": "Fixed Asset" + }, + "Dubieuze debiteuren": { + "account_number": "2837", + "account_type": "Fixed Asset" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "2839", + "account_type": "Fixed Asset" + }, + "account_number": "283", + "account_type": "Fixed Asset" + }, + "Andere aandelen": { + "Aanschaffingswaarde": { + "account_number": "2840", + "account_type": "Fixed Asset" + }, + "Nog te storten bedragen (-)": { + "account_number": "2841", + "account_type": "Fixed Asset" + }, + "Geboekte meerwaarden": { + "account_number": "2848", + "account_type": "Fixed Asset" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "2849", + "account_type": "Fixed Asset" + }, + "account_number": "284", + "account_type": "Fixed Asset" + }, + "Overige vorderingen": { + "Vorderingen op rekening": { + "account_number": "2850", + "account_type": "Fixed Asset" + }, + "Te innen wissels": { + "account_number": "2851", + "account_type": "Fixed Asset" + }, + "Vastrentende effecten": { + "account_number": "2852", + "account_type": "Fixed Asset" + }, + "Dubieuze debiteuren": { + "account_number": "2857", + "account_type": "Fixed Asset" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "2859", + "account_type": "Fixed Asset" + }, + "account_number": "285", + "account_type": "Fixed Asset" + }, + "Borgtochten betaald in contanten": { + "account_number": "288", + "account_type": "Fixed Asset" + }, + "account_number": "28", + "account_type": "Fixed Asset" + }, + "Vorderingen op meer dan 1 jaar": { + "Handelsvorderingen": { + "Handelsdebiteuren": { + "account_number": "2900" + }, + "Te innen wissels": { + "account_number": "2901" + }, + "Vooruitbetalingen": { + "account_number": "2906" + }, + "Dubieuze debiteuren": { + "account_number": "2907" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "2909" + }, + "account_number": "290" + }, + "Overige vorderingen": { + "Vorderingen op rekening": { + "account_number": "2910" + }, + "Te innen wissels": { + "account_number": "2911" + }, + "Te ontvangen subsidies": { + "account_number": "2912" + }, + "Niet-rentedragende vorderingen of gekoppeld aan een abnormaal lage rente": { + "account_number": "2915" + }, + "Dubieuze debiteuren": { + "account_number": "2916" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "2919" + }, + "account_number": "291" + }, + "account_number": "29" + } + }, + "KLASSE 3 : VOORRADEN EN BESTELLINGEN IN UITVOERING": { + "root_type": "Asset", + "Grondstoffen": { + "Aanschaffingswaarde": { + "account_number": "300" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "309" + }, + "account_number": "30" + }, + "Hulpstoffen": { + "Aanschaffingswaarde": { + "account_number": "310" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "319" + }, + "account_number": "31" + }, + "Goederen in bewerking": { + "Aanschaffingswaarde": { + "account_number": "320" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "329" + }, + "account_number": "32" + }, + "Gereed product": { + "Aanschaffingswaarde": { + "account_number": "330" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "339" + }, + "account_number": "33" + }, + "Handelsgoederen": { + "Aanschaffingswaarde": { + "account_number": "340", + "account_type": "Stock" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "349" + }, + "account_number": "34", + "account_type": "Stock" + }, + "Onroerende goederen bestemd voor verkoop": { + "Aanschaffingswaarde": { + "account_number": "350" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "359" + }, + "account_number": "35" + }, + "Vooruitbetalingen op voorraadinkopen": { + "Vooruitbetalingen": { + "account_number": "360" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "369" + }, + "account_number": "36" + }, + "Bestellingen in uitvoering": { + "Aanschaffingswaarde": { + "account_number": "370" + }, + "Toegerekende winst": { + "account_number": "371" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "379" + }, + "account_number": "37" + }, + "Geleverde voorraad, niet gefactureerd": { + "account_type": "Stock Delivered But Not Billed" + } + }, + "KLASSE 4 : VORDERINGEN OP TEN HOOGSTE ÉÉN JAAR": { + "root_type": "Asset", + "Handelsvorderingen": { + "Handelsdebiteuren": { + "account_number": "400", + "account_type": "Receivable" + }, + "Te innen wissels": { + "account_number": "401", + "account_type": "Receivable" + }, + "Te innen opbrengsten": { + "account_number": "404", + "account_type": "Receivable" + }, + "Vooruitbetalingen": { + "account_number": "406" + }, + "Dubieuze debiteuren": { + "account_number": "407", + "account_type": "Receivable" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "409" + }, + "account_number": "40", + "account_type": "Receivable" + }, + "Overige vorderingen": { + "Terug te vorderen btw": { + "account_number": "411", + "account_type": "Tax" + }, + "Terug te vorderen belastingen en voorheffingen": { + "Andere Belgische belastingen": { + "account_number": "4125" + }, + "Buitenlandse belastingen": { + "account_number": "4128" + }, + "account_number": "412" + }, + "Te ontvangen subsidies": { + "account_number": "413" + }, + "Te innen opbrengsten": { + "account_number": "414" + }, + "Niet-rentedragende vorderingen of gekoppeld aan een abnormaal lage rente": { + "account_number": "415" + }, + "Diverse vorderingen": { + "account_number": "416" + }, + "Dubieuze debiteuren": { + "account_number": "417" + }, + "Borgtochten betaald in contanten": { + "account_number": "418" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "419" + }, + "account_number": "41" + } + }, + "KLASSE 4 : SCHULDEN OP TEN HOOGSTE ÉÉN JAAR": { + "root_type": "Liability", + "Schulden op meer dan één jaar die binnen het jaar vervallen": { + "account_number": "42" + }, + "Financiële schulden": { + "Kredietinstellingen - Leningen op rekening met vaste termijn": { + "account_number": "430" + }, + "Kredietinstellingen - Promessen": { + "account_number": "431" + }, + "Kredietinstellingen - Acceptkredieten": { + "account_number": "432" + }, + "Kredietinstellingen - Schulden op rekening-courant": { + "account_number": "433" + }, + "Overige leningen": { + "account_number": "439" + }, + "account_number": "43" + }, + "Handelsschulden": { + "Leveranciers": { + "account_number": "440", + "account_type": "Payable" + }, + "Te betalen wissels": { + "account_number": "441", + "account_type": "Payable" + }, + "Te ontvangen facturen": { + "account_number": "444", + "account_type": "Stock Received But Not Billed" + }, + "account_number": "44", + "account_type": "Payable" + }, + "Schulden met betrekking tot belastingen, bezoldigingen en sociale lasten": { + "Geraamd bedrag der belastingschulden": { + "Andere Belgische belastingen": { + "account_number": "4505" + }, + "Buitenlandse belastingen": { + "account_number": "4508" + }, + "account_number": "450" + }, + "Te betalen btw": { + "account_number": "451", + "account_type": "Tax" + }, + "Te betalen belastingen en taksen": { + "Andere Belgische belastingen": { + "account_number": "4525" + }, + "Buitenlandse belastingen": { + "account_number": "4528" + }, + "account_number": "452" + }, + "Ingehouden voorheffingen": { + "account_number": "453" + }, + "Rijksdienst voor Sociale Zekerheid": { + "account_number": "454" + }, + "Bezoldigingen": { + "account_number": "455" + }, + "Vakantiegeld": { + "account_number": "456" + }, + "Andere sociale schulden": { + "account_number": "459" + }, + "account_number": "45" + }, + "Overlopende rekeningen": { + "Over te dragen kosten": { + "account_number": "490" + }, + "Verkregen opbrengsten": { + "account_number": "491" + }, + "Toe te rekenen kosten": { + "account_number": "492" + }, + "Over te dragen opbrengsten": { + "account_number": "493" + }, + "Wachtrekeningen": { + "account_number": "499" + }, + "account_number": "49" + }, + "Vervallen obligaties en coupons": { + "account_number": "480" + }, + "Terug te betalen subsidies": { + "account_number": "483" + }, + "Borgtochten ontvangen in contanten": { + "account_number": "488" + }, + "Andere diverse schulden": { + "Rentedragend": { + "account_number": "4890" + }, + "Niet-rentedragend of gekoppeld aan een abnormaal lage rente": { + "account_number": "4891" + }, + "account_number": "489" + } + }, + "KLASSE 5 : GELDBELEGGINGEN EN LIQUIDE MIDDELEN": { + "root_type": "Asset", + "Geldbeleggingen andere dan aandelen, vastrentende effecten en termijndeposito's": { + "Aanschaffingswaarde": { + "account_number": "500" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "509" + }, + "account_number": "50" + }, + "Aandelen": { + "Aanschaffingswaarde": { + "account_number": "510" + }, + "Nog te storten bedragen (-)": { + "account_number": "511" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "519" + }, + "account_number": "51" + }, + "Vastrentende effecten": { + "Aanschaffingswaarde": { + "account_number": "520" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "529" + }, + "account_number": "52" + }, + "Termijndeposito's": { + "Op meer dan één jaar": { + "account_number": "530" + }, + "Op meer dan één maand en op ten hoogste één jaar": { + "account_number": "531" + }, + "Op ten hoogste één maand": { + "account_number": "532" + }, + "Geboekte waardeverminderingen (-)": { + "account_number": "539" + }, + "account_number": "53" + }, + "Te incasseren vervallen waarden": { + "account_number": "54" + }, + "Kredietinstellingen": { + "Bank": { + "account_number": "5500", + "account_type": "Bank" + }, + "account_number": "55", + "account_type": "Bank" + }, + "Kassen": { + "Kassen-contanten": { + "account_number": "570", + "account_type": "Cash" + }, + "Kassen-zegels": { + "account_number": "578", + "account_type": "Cash" + }, + "account_number": "57", + "account_type": "Cash" + }, + "Interne overboekingen": { + "account_number": "58", + "account_type": "Temporary" + } + }, + "KLASSE 6 : KOSTEN": { + "root_type": "Expense", + "Handelsgoederen, grond- en hulpstoffen": { + "Aankopen van grondstoffen": { + "account_number": "600", + "account_type": "Expense Account" + }, + "Aankopen van hulpstoffen": { + "account_number": "601", + "account_type": "Expense Account" + }, + "Aankopen van diensten, werk en studies": { + "account_number": "602", + "account_type": "Expense Account" + }, + "Algemene onderaannemingen": { + "account_number": "603", + "account_type": "Expense Account" + }, + "Aankopen van handelsgoederen": { + "account_number": "604", + "account_type": "Cost of Goods Sold" + }, + "Aankopen van onroerende goederen bestemd voor verkoop": { + "account_number": "605", + "account_type": "Expense Account" + }, + "Ontvangen kortingen, ristorno's en rabatten (-)": { + "account_number": "608", + "account_type": "Expense Account" + }, + "Voorraadwijzigingen": { + "van grondstoffen": { + "account_number": "6090", + "account_type": "Stock Adjustment" + }, + "van hulpstoffen": { + "account_number": "6091", + "account_type": "Stock Adjustment" + }, + "van handelsgoederen": { + "account_number": "6094", + "account_type": "Stock Adjustment" + }, + "van gekochte onroerende goederen bestemd voor verkoop": { + "account_number": "6095", + "account_type": "Stock Adjustment" + }, + "account_number": "609", + "account_type": "Stock Adjustment" + }, + "account_number": "60", + "account_type": "Expense Account" + }, + "Diensten en diverse goederen": { + "Uitzendpersoneel en personen die ter beschikking worden gesteld van de vereniging of stichting": { + "account_number": "617", + "account_type": "Expense Account" + }, + "Bezoldigingen en pensioenen van bestuurders, buiten arbeidsovereenkomst": { + "account_number": "618", + "account_type": "Expense Account" + }, + "Aankoopkosten begrepen in de waarde van de voorraden": { + "account_type": "Expenses Included In Valuation" + }, + "account_number": "61", + "account_type": "Expense Account" + }, + "Bezoldigingen, sociale lasten en pensioenen": { + "Bezoldigingen en rechtstreekse sociale voordelen": { + "Bestuurders of zaakvoerders": { + "account_number": "6200", + "account_type": "Expense Account" + }, + "Directiepersoneel": { + "account_number": "6201", + "account_type": "Expense Account" + }, + "Bedienden": { + "account_number": "6202", + "account_type": "Expense Account" + }, + "Arbeiders": { + "account_number": "6203", + "account_type": "Expense Account" + }, + "Andere personeelsleden": { + "account_number": "6204", + "account_type": "Expense Account" + }, + "account_number": "620", + "account_type": "Expense Account" + }, + "Werkgeversbijdragen voor sociale verzekeringen": { + "account_number": "621", + "account_type": "Expense Account" + }, + "Werkgeverspremies voor buitenwettelijke verzekeringen": { + "account_number": "622", + "account_type": "Expense Account" + }, + "Andere personeelskosten": { + "account_number": "623", + "account_type": "Expense Account" + }, + "Ouderdoms- en overlevingspensioenen": { + "Bestuurders of zaakvoerders": { + "account_number": "6240", + "account_type": "Expense Account" + }, + "Personeel": { + "account_number": "6241", + "account_type": "Expense Account" + }, + "account_number": "624", + "account_type": "Expense Account" + }, + "account_number": "62", + "account_type": "Expense Account" + }, + "Afschrijvingen, waardeverminderingen en voorzieningen voor risico's en kosten": { + "Afschrijvingen en waardeverminderingen op vaste activa-toevoeging": { + "Afschrijvingen op oprichtingskosten": { + "account_number": "6300", + "account_type": "Depreciation" + }, + "Afschrijvingen op immateriële vaste activa": { + "account_number": "6301", + "account_type": "Depreciation" + }, + "Afschrijvingen op materiële vaste activa": { + "account_number": "6302", + "account_type": "Depreciation" + }, + "Waardeverminderingen op immateriële vaste activa": { + "account_number": "6308", + "account_type": "Depreciation" + }, + "Waardeverminderingen op materiële vaste activa": { + "account_number": "6309", + "account_type": "Depreciation" + }, + "account_number": "630", + "account_type": "Depreciation" + }, + "Waardeverminderingen op voorraden": { + "Toevoeging": { + "account_number": "6310", + "account_type": "Depreciation" + }, + "Terugneming (-)": { + "account_number": "6311", + "account_type": "Depreciation" + }, + "account_number": "631", + "account_type": "Depreciation" + }, + "Waardeverminderingen op bestellingen in uitvoering": { + "Toevoeging": { + "account_number": "6320", + "account_type": "Depreciation" + }, + "Terugneming (-)": { + "account_number": "6321", + "account_type": "Depreciation" + }, + "account_number": "632", + "account_type": "Depreciation" + }, + "Waardeverminderingen op handelsvorderingen op meer dan één jaar": { + "Toevoeging": { + "account_number": "6330", + "account_type": "Depreciation" + }, + "Terugneming (-)": { + "account_number": "6331", + "account_type": "Depreciation" + }, + "account_number": "633", + "account_type": "Depreciation" + }, + "Waardeverminderingen op handelsvorderingen op ten hoogste één jaar": { + "Toevoeging": { + "account_number": "6340", + "account_type": "Depreciation" + }, + "Terugneming (-)": { + "account_number": "6341", + "account_type": "Depreciation" + }, + "account_number": "634", + "account_type": "Depreciation" + }, + "Voorzieningen voor pensioenen en soortgelijke verplichtingen": { + "Toevoeging": { + "account_number": "6350", + "account_type": "Depreciation" + }, + "Besteding en terugneming (-)": { + "account_number": "6351", + "account_type": "Depreciation" + }, + "account_number": "635", + "account_type": "Depreciation" + }, + "Voorzieningen voor grote herstellingswerken en grote onderhoudswerken": { + "Toevoeging": { + "account_number": "6360", + "account_type": "Depreciation" + }, + "Besteding en terugneming (-)": { + "account_number": "6361", + "account_type": "Depreciation" + }, + "account_number": "636", + "account_type": "Depreciation" + }, + "Voorzieningen voor milieuverplichtingen": { + "Toevoeging": { + "account_number": "6370", + "account_type": "Depreciation" + }, + "Besteding en terugneming (-)": { + "account_number": "6371", + "account_type": "Depreciation" + }, + "account_number": "637", + "account_type": "Depreciation" + }, + "Voorzieningen voor terug te betalen subsidies en legaten en voor schenkingen met terugnemingsrecht": { + "Toevoeging": { + "account_number": "6380", + "account_type": "Expense Account" + }, + "Besteding en terugneming (-)": { + "account_number": "6381", + "account_type": "Expense Account" + }, + "account_number": "638", + "account_type": "Expense Account" + }, + "Voorzieningen voor andere risico's en kosten": { + "Toevoeging": { + "account_number": "6390", + "account_type": "Expense Account" + }, + "Besteding en terugneming (-)": { + "account_number": "6391", + "account_type": "Expense Account" + }, + "account_number": "639", + "account_type": "Expense Account" + }, + "account_number": "63", + "account_type": "Expense Account" + }, + "Andere bedrijfskosten": { + "Bedrijfsbelastingen": { + "account_number": "640", + "account_type": "Expense Account" + }, + "Minderwaarden op de courante realisatie van materiële vaste activa": { + "account_number": "641", + "account_type": "Expense Account" + }, + "Minderwaarden op de realisatie van handelsvorderingen": { + "account_number": "642", + "account_type": "Expense Account" + }, + "Schenkingen": { + "account_number": "643", + "account_type": "Expense Account" + }, + "Diverse bedrijfskosten (644 tot 648)": { + "account_number": "644", + "account_type": "Expense Account" + }, + "Als herstructureringskosten geactiveerde bedrijfskosten (-)": { + "account_number": "649", + "account_type": "Expense Account" + }, + "account_number": "64", + "account_type": "Expense Account" + }, + "Financiële kosten": { + "Kosten van schulden": { + "Rente, commissies en kosten verbonden aan schulden": { + "account_number": "6500", + "account_type": "Expense Account" + }, + "Afschrijving van kosten bij uitgifte van leningen en van disagio": { + "account_number": "6501", + "account_type": "Expense Account" + }, + "Geactiveerde intercalaire interesten (-)": { + "account_number": "6502", + "account_type": "Expense Account" + }, + "account_number": "650", + "account_type": "Expense Account" + }, + "Waardeverminderingen op vlottende activa": { + "Toevoeging": { + "account_number": "6510", + "account_type": "Expense Account" + }, + "Terugneming (-)": { + "account_number": "6511", + "account_type": "Expense Account" + }, + "account_number": "651", + "account_type": "Expense Account" + }, + "Minderwaarden op verwezenlijking van vlottende activa": { + "account_number": "652", + "account_type": "Expense Account" + }, + "Discontokosten op vorderingen": { + "account_number": "653", + "account_type": "Expense Account" + }, + "Wisselresultaten": { + "account_number": "654", + "account_type": "Expense Account" + }, + "Resultaten uit de omrekening van vreemde valuta": { + "account_number": "655", + "account_type": "Expense Account" + }, + "Voorzieningen van financiële aard": { + "Toevoeging": { + "account_number": "6560", + "account_type": "Expense Account" + }, + "Besteding en terugneming (-)": { + "account_number": "6561", + "account_type": "Expense Account" + }, + "account_number": "656", + "account_type": "Expense Account" + }, + "Diverse financiële kosten (657 tot 658)": { + "account_number": "657", + "account_type": "Expense Account" + }, + "Als herstructureringskosten geactiveerde financiële kosten (-)": { + "account_number": "659", + "account_type": "Expense Account" + }, + "account_number": "65", + "account_type": "Expense Account" + }, + "Niet-recurrente bedrijfs- of financiële kosten": { + "Niet-recurrente afschrijvingen en waardeverminderingen (toevoeging)": { + "op oprichtingskosten": { + "account_number": "6600", + "account_type": "Expense Account" + }, + "op immateriële vaste activa": { + "account_number": "6601", + "account_type": "Expense Account" + }, + "op materiële vaste activa": { + "account_number": "6602", + "account_type": "Expense Account" + }, + "account_number": "660", + "account_type": "Expense Account" + }, + "Waardeverminderingen op vaste financiële activa (toevoeging)": { + "account_number": "661", + "account_type": "Expense Account" + }, + "Voorzieningen voor niet-recurrente risico's en kosten": { + "Voorzieningen voor niet-recurrente bedrijfsrisico's en -kosten": { + "Toevoeging": { + "account_number": "66200", + "account_type": "Expense Account" + }, + "Bestedingen (-)": { + "account_number": "66201", + "account_type": "Expense Account" + }, + "account_number": "6620", + "account_type": "Expense Account" + }, + "Voorzieningen voor niet-recurrente financiële risico's en -kosten": { + "Toevoeging": { + "account_number": "66210", + "account_type": "Expense Account" + }, + "Besteding (-)": { + "account_number": "66211", + "account_type": "Expense Account" + }, + "account_number": "6621", + "account_type": "Expense Account" + }, + "account_number": "662", + "account_type": "Expense Account" + }, + "Minderwaarden op de realisatie van vaste activa": { + "Minderwaarden op de realisatie van immateriële en materiële vaste activa": { + "account_number": "6630", + "account_type": "Expense Account" + }, + "Minderwaarden op de realisatie van financiële vaste activa": { + "account_number": "6631", + "account_type": "Expense Account" + }, + "account_number": "663", + "account_type": "Expense Account" + }, + "Andere niet-recurrente bedrijfskosten (664 tot 667)": { + "account_number": "664", + "account_type": "Expense Account" + }, + "Andere niet-recurrente financiële kosten": { + "account_number": "668", + "account_type": "Expense Account" + }, + "Als herstructureringskosten geactiveerde niet-recurrente bedrijfskosten (-)": { + "account_number": "6690", + "account_type": "Expense Account" + }, + "Als herstructureringskosten geactiveerde niet-recurrente financiële kosten (-)": { + "account_number": "6691", + "account_type": "Expense Account" + }, + "account_number": "66", + "account_type": "Expense Account" + }, + "Belastingen": { + "Belgische belastingen op het resultaat van het boekjaar": { + "Verschuldigde of gestorte belastingen en voorheffingen": { + "account_number": "6700", + "account_type": "Expense Account" + }, + "Geactiveerde overschotten van betaalde belastingen en voorheffingen (-)": { + "account_number": "6701", + "account_type": "Expense Account" + }, + "Geraamde belastingen": { + "account_number": "6702", + "account_type": "Expense Account" + }, + "account_number": "670", + "account_type": "Expense Account" + }, + "Buitenlandse belastingen op het resultaat van vorige boekjaren": { + "account_number": "673", + "account_type": "Expense Account" + }, + "Verschuldigde of gestorte belastingsupplementen": { + "account_number": "6710", + "account_type": "Expense Account" + }, + "Geraamde belastingsupplementen": { + "account_number": "6711", + "account_type": "Expense Account" + }, + "Gevormde fiscale voorzieningen": { + "account_number": "6712", + "account_type": "Expense Account" + }, + "account_number": "67", + "account_type": "Expense Account" + }, + "Overboeking naar de uitgestelde belastingen en naar de belastingvrije reserves": { + "Overboeking naar de uitgestelde belastingen": { + "account_number": "680", + "account_type": "Expense Account" + }, + "Overboeking naar de belastingvrije reserves": { + "account_number": "689", + "account_type": "Expense Account" + }, + "account_number": "68", + "account_type": "Expense Account" + }, + "Resultaatverwerking": { + "Overgedragen negatief resultaat van het vorig boekjaar": { + "account_number": "690", + "account_type": "Expense Account" + }, + "Overboeking naar de bestemde fondsen en andere reserves": { + "account_number": "691", + "account_type": "Expense Account" + }, + "Over te dragen positief resultaat": { + "account_number": "692", + "account_type": "Expense Account" + }, + "account_number": "69", + "account_type": "Expense Account" + } + }, + "KLASSE 7 : OPBRENGSTEN": { + "root_type": "Income", + "Omzet": { + "Toegekende kortingen, ristorno's en rabatten (-)": { + "account_number": "708", + "account_type": "Income Account" + }, + "account_number": "70", + "account_type": "Income Account" + }, + "Wijziging in de voorraad en bestellingen in uitvoering": { + "In de voorraad goederen in bewerking": { + "account_number": "712", + "account_type": "Income Account" + }, + "In de voorraad gereed product": { + "account_number": "713", + "account_type": "Income Account" + }, + "In de voorraad onroerende goederen bestemd voor verkoop": { + "account_number": "715", + "account_type": "Income Account" + }, + "In de bestellingen in uitvoering": { + "Aanschaffingswaarde": { + "account_number": "7170", + "account_type": "Income Account" + }, + "Toegerekende winst": { + "account_number": "7171", + "account_type": "Income Account" + }, + "account_number": "717", + "account_type": "Income Account" + }, + "account_number": "71", + "account_type": "Income Account" + }, + "Geproduceerde vaste activa": { + "account_number": "72", + "account_type": "Income Account" + }, + "Lidgeld, schenkingen, legaten en subsidies": { + "Lidgelden": { + "account_number": "730", + "account_type": "Income Account" + }, + "Schenkingen": { + "account_number": "731", + "account_type": "Income Account" + }, + "Legaten": { + "account_number": "732", + "account_type": "Income Account" + }, + "Subsidies": { + "account_number": "733", + "account_type": "Income Account" + }, + "account_number": "73", + "account_type": "Income Account" + }, + "Overige bedrijfsopbrengsten": { + "Meerwaarden op de courante realisatie van materiële vaste activa": { + "account_number": "741", + "account_type": "Income Account" + }, + "Meerwaarden op de realisatie van handelsvorderingen": { + "account_number": "742", + "account_type": "Income Account" + }, + "Diverse bedrijfsopbrengsten (743 tot 749)": { + "account_number": "743", + "account_type": "Income Account" + }, + "account_number": "74", + "account_type": "Income Account" + }, + "Financiële opbrengsten": { + "Opbrengsten uit financiële vaste activa": { + "account_number": "750", + "account_type": "Income Account" + }, + "Opbrengsten uit vlottende activa": { + "account_number": "751", + "account_type": "Income Account" + }, + "Meerwaarden op de realisatie van vlottende activa": { + "account_number": "752", + "account_type": "Income Account" + }, + "Wisselresultaten": { + "account_number": "754", + "account_type": "Income Account" + }, + "Resultaten uit de omrekening van vreemde valuta": { + "account_number": "755", + "account_type": "Income Account" + }, + "Diverse financiële opbrengsten (756 tot 759)": { + "account_number": "756", + "account_type": "Income Account" + }, + "account_number": "75", + "account_type": "Income Account" + }, + "Niet-recurrente bedrijfs- of financiële opbrengsten": { + "Terugneming van afschrijvingen en waardeverminderingen": { + "op immateriële vaste activa": { + "account_number": "7600", + "account_type": "Income Account" + }, + "op materiële vaste activa": { + "account_number": "7601", + "account_type": "Income Account" + }, + "account_number": "760", + "account_type": "Income Account" + }, + "Terugneming van waardeverminderingen op financiële vaste activa": { + "account_number": "761", + "account_type": "Income Account" + }, + "Terugneming van voorzieningen voor niet-recurrente risico's en kosten": { + "Terugneming van voorzieningen voor niet-recurrente bedrijfsrisico's en -kosten": { + "account_number": "7620", + "account_type": "Income Account" + }, + "Terugneming van voorzieningen voor niet-recurrente financiële risico's en -kosten": { + "account_number": "7621", + "account_type": "Income Account" + }, + "account_number": "762", + "account_type": "Income Account" + }, + "Meerwaarden op de realisatie van vaste activa": { + "Meerwaarde op de realisatie van immateriële en materiële vaste activa": { + "account_number": "7630", + "account_type": "Income Account" + }, + "Meerwaarde op de realisatie van financiële vaste activa": { + "account_number": "7631", + "account_type": "Income Account" + }, + "account_number": "763", + "account_type": "Income Account" + }, + "Andere niet-recurrente bedrijfsopbrengsten (764 tot 768)": { + "account_number": "764", + "account_type": "Income Account" + }, + "Andere niet-recurrente financiële opbrengsten": { + "account_number": "769", + "account_type": "Income Account" + }, + "account_number": "76", + "account_type": "Income Account" + }, + "Regularisering van belastingen": { + "account_number": "77", + "account_type": "Income Account" + }, + "Onttrekking aan de belastingvrije reserves en uitgestelde belastingen": { + "Onttrekking aan de uitgestelde belastingen": { + "account_number": "780", + "account_type": "Income Account" + }, + "Onttrekking aan de belastingvrije reserves": { + "account_number": "789", + "account_type": "Income Account" + }, + "account_number": "78", + "account_type": "Income Account" + }, + "Resultaatverwerking": { + "Overgedragen positief resultaat van het vorige boekjaar": { + "account_number": "790", + "account_type": "Income Account" + }, + "Andere reserves": { + "account_number": "791", + "account_type": "Income Account" + }, + "Over te dragen negatief resultaat": { + "account_number": "792", + "account_type": "Income Account" + }, + "account_number": "79", + "account_type": "Income Account" + } + } + } +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/be_plan_comptable_minimum_normalise_associations_fondations.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/be_plan_comptable_minimum_normalise_associations_fondations.json new file mode 100644 index 00000000000..5191aa32de9 --- /dev/null +++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/be_plan_comptable_minimum_normalise_associations_fondations.json @@ -0,0 +1,1478 @@ +{ + "country_code": "be", + "name": "Belgique - Plan comptable minimum normalisé (PCMN) des associations et fondations", + "tree": { + "CLASSE 1 : FONDS ASSOCIATIFS ET DE LA FONDATION": { + "root_type": "Equity", + "Fonds de l'association ou de la fondation": { + "account_number": "10", + "account_type": "Equity" + }, + "Plus-values de réévaluation": { + "Plus-values de réévaluation sur immobilisations incorporelles": { + "account_number": "120", + "account_type": "Equity" + }, + "Plus-values de réévaluation sur immobilisations corporelles": { + "account_number": "121", + "account_type": "Equity" + }, + "Plus-values de réévaluation sur immobilisations financières": { + "account_number": "122", + "account_type": "Equity" + }, + "Reprises de réductions de valeur sur placements de trésorerie": { + "account_number": "124", + "account_type": "Equity" + }, + "account_number": "12", + "account_type": "Equity" + }, + "Fonds affectés et autres réserves": { + "Fonds affectés pour investissements": { + "account_number": "130", + "account_type": "Equity" + }, + "Fonds affectés pour passif social": { + "account_number": "131", + "account_type": "Equity" + }, + "Réserves immunisées": { + "account_number": "132", + "account_type": "Equity" + }, + "Autres fonds affectés et autres réserves": { + "account_number": "139", + "account_type": "Equity" + }, + "account_number": "13", + "account_type": "Equity" + }, + "Résultats reportés (+)(-)": { + "account_number": "14", + "account_type": "Equity" + }, + "Subsides en capital": { + "account_number": "15", + "account_type": "Equity" + } + }, + "CLASSE 1 : PROVISIONS ET DETTES À PLUS D'UN AN": { + "root_type": "Liability", + "Provisions et impôts différés": { + "Provisions pour pensions et obligations similaires": { + "account_number": "160", + "account_type": "Liability" + }, + "Provisions pour charges fiscales": { + "account_number": "161", + "account_type": "Liability" + }, + "Provisions pour grosses réparations et gros entretien": { + "account_number": "162", + "account_type": "Liability" + }, + "Provisions pour obligations environnementales": { + "account_number": "163", + "account_type": "Liability" + }, + "Provisions pour remboursement de subsides, legs et dons avec droit de reprise": { + "account_number": "167", + "account_type": "Liability" + }, + "Impôts différés": { + "account_number": "168", + "account_type": "Liability" + }, + "account_number": "16", + "account_type": "Liability" + }, + "Dettes à plus d'un an": { + "Emprunts subordonnés": { + "account_number": "170", + "account_type": "Liability" + }, + "Emprunts obligataires non subordonnés": { + "account_number": "171", + "account_type": "Liability" + }, + "Dettes de location-financement et dettes assimilées": { + "account_number": "172", + "account_type": "Liability" + }, + "Établissements de crédit": { + "Dettes en compte": { + "account_number": "1730", + "account_type": "Liability" + }, + "Promesses": { + "account_number": "1731", + "account_type": "Liability" + }, + "Crédits d'acceptation": { + "account_number": "1732", + "account_type": "Liability" + }, + "account_number": "173", + "account_type": "Liability" + }, + "Autres emprunts": { + "account_number": "174", + "account_type": "Liability" + }, + "Dettes commerciales": { + "Fournisseurs": { + "account_number": "1750", + "account_type": "Liability" + }, + "Effets à payer": { + "account_number": "1751", + "account_type": "Liability" + }, + "account_number": "175", + "account_type": "Liability" + }, + "Acomptes sur commandes": { + "account_number": "176", + "account_type": "Liability" + }, + "Cautionnements en numéraire": { + "account_number": "178", + "account_type": "Liability" + }, + "Autres dettes": { + "Productives d'intérêts": { + "account_number": "1790", + "account_type": "Liability" + }, + "Non productives d'intérêts ou assorties d'un intérêt anormalement faible": { + "account_number": "1791", + "account_type": "Liability" + }, + "account_number": "179", + "account_type": "Liability" + }, + "account_number": "17", + "account_type": "Liability" + } + }, + "CLASSE 2 : FRAIS D'ÉTABLISSEMENT, ACTIFS IMMOBILISÉS ET CRÉANCES À PLUS D'UN AN": { + "root_type": "Asset", + "Frais d'établissement": { + "Frais de constitution": { + "account_number": "200", + "account_type": "Fixed Asset" + }, + "Frais d'émission d'emprunts": { + "account_number": "201", + "account_type": "Fixed Asset" + }, + "Autres frais d'établissement": { + "account_number": "202", + "account_type": "Fixed Asset" + }, + "Frais de restructuration": { + "account_number": "204", + "account_type": "Fixed Asset" + }, + "account_number": "20", + "account_type": "Fixed Asset" + }, + "Immobilisations incorporelles": { + "Frais de recherche et de développement": { + "account_number": "210", + "account_type": "Fixed Asset" + }, + "Concessions, brevets, licences, savoir-faire, marques et droits similaires": { + "account_number": "211", + "account_type": "Fixed Asset" + }, + "Goodwill": { + "account_number": "212", + "account_type": "Fixed Asset" + }, + "Acomptes versés": { + "account_number": "213", + "account_type": "Fixed Asset" + }, + "Amortissements et réductions de valeur actées (-)": { + "account_number": "219", + "account_type": "Accumulated Depreciation" + }, + "account_number": "21", + "account_type": "Fixed Asset" + }, + "Terrains et constructions": { + "Terrains": { + "account_number": "220", + "account_type": "Fixed Asset" + }, + "Constructions": { + "account_number": "221", + "account_type": "Fixed Asset" + }, + "Terrains bâtis": { + "account_number": "222", + "account_type": "Fixed Asset" + }, + "Autres droits réels sur des immeubles": { + "account_number": "223", + "account_type": "Fixed Asset" + }, + "Amortissements et réductions de valeur actées (-)": { + "account_number": "229", + "account_type": "Accumulated Depreciation" + }, + "account_number": "22", + "account_type": "Fixed Asset" + }, + "Installations, machines et outillage": { + "account_number": "23", + "account_type": "Fixed Asset" + }, + "Mobilier et matériel roulant": { + "account_number": "24", + "account_type": "Fixed Asset" + }, + "Immobilisations détenues en location-financement et droits similaires": { + "Terrains et constructions": { + "account_number": "250", + "account_type": "Fixed Asset" + }, + "Installations, machines et outillage": { + "account_number": "251", + "account_type": "Fixed Asset" + }, + "Mobilier et matériel roulant": { + "account_number": "252", + "account_type": "Fixed Asset" + }, + "Amortissements et réductions de valeur actées (-)": { + "account_number": "259", + "account_type": "Accumulated Depreciation" + }, + "account_number": "25", + "account_type": "Fixed Asset" + }, + "Autres immobilisations corporelles": { + "account_number": "26", + "account_type": "Fixed Asset" + }, + "Immobilisations corporelles en cours et acomptes versés": { + "account_number": "27", + "account_type": "Fixed Asset" + }, + "Immobilisations financières": { + "Participations dans des sociétés liées": { + "Valeur d'acquisition": { + "account_number": "2800", + "account_type": "Fixed Asset" + }, + "Montants non appelés (-)": { + "account_number": "2801", + "account_type": "Fixed Asset" + }, + "Plus-values actées": { + "account_number": "2808", + "account_type": "Fixed Asset" + }, + "Réductions de valeur actées (-)": { + "account_number": "2809", + "account_type": "Fixed Asset" + }, + "account_number": "280", + "account_type": "Fixed Asset" + }, + "Créances sur des entités liées": { + "Créances en compte": { + "account_number": "2810", + "account_type": "Fixed Asset" + }, + "Effets à recevoir": { + "account_number": "2811", + "account_type": "Fixed Asset" + }, + "Titres à revenu fixe": { + "account_number": "2812", + "account_type": "Fixed Asset" + }, + "Créances douteuses": { + "account_number": "2817", + "account_type": "Fixed Asset" + }, + "Réductions de valeur actées (-)": { + "account_number": "2819", + "account_type": "Fixed Asset" + }, + "account_number": "281", + "account_type": "Fixed Asset" + }, + "Participations dans des sociétés avec lesquelles il existe un lien de participation": { + "Valeur d'acquisition": { + "account_number": "2820", + "account_type": "Fixed Asset" + }, + "Montants non appelés (-)": { + "account_number": "2821", + "account_type": "Fixed Asset" + }, + "Plus-values actées": { + "account_number": "2828", + "account_type": "Fixed Asset" + }, + "Réductions de valeur actées (-)": { + "account_number": "2829", + "account_type": "Fixed Asset" + }, + "account_number": "282", + "account_type": "Fixed Asset" + }, + "Créances sur des sociétés avec lesquelles il existe un lien de participation": { + "Créances en compte": { + "account_number": "2830", + "account_type": "Fixed Asset" + }, + "Effets à recevoir": { + "account_number": "2831", + "account_type": "Fixed Asset" + }, + "Titres à revenu fixe": { + "account_number": "2832", + "account_type": "Fixed Asset" + }, + "Créances douteuses": { + "account_number": "2837", + "account_type": "Fixed Asset" + }, + "Réductions de valeur actées (-)": { + "account_number": "2839", + "account_type": "Fixed Asset" + }, + "account_number": "283", + "account_type": "Fixed Asset" + }, + "Autres actions et parts": { + "Valeur d'acquisition": { + "account_number": "2840", + "account_type": "Fixed Asset" + }, + "Montants non appelés (-)": { + "account_number": "2841", + "account_type": "Fixed Asset" + }, + "Plus-values actées": { + "account_number": "2848", + "account_type": "Fixed Asset" + }, + "Réductions de valeur actées (-)": { + "account_number": "2849", + "account_type": "Fixed Asset" + }, + "account_number": "284", + "account_type": "Fixed Asset" + }, + "Autres créances": { + "Créances en compte": { + "account_number": "2850", + "account_type": "Fixed Asset" + }, + "Effets à recevoir": { + "account_number": "2851", + "account_type": "Fixed Asset" + }, + "Titres à revenu fixe": { + "account_number": "2852", + "account_type": "Fixed Asset" + }, + "Créances douteuses": { + "account_number": "2857", + "account_type": "Fixed Asset" + }, + "Réductions de valeur actées (-)": { + "account_number": "2859", + "account_type": "Fixed Asset" + }, + "account_number": "285", + "account_type": "Fixed Asset" + }, + "Cautionnements versés en numéraire": { + "account_number": "288", + "account_type": "Fixed Asset" + }, + "account_number": "28", + "account_type": "Fixed Asset" + }, + "Créances à plus d'un an": { + "Créances commerciales": { + "Clients": { + "account_number": "2900" + }, + "Effets à recevoir": { + "account_number": "2901" + }, + "Acomptes versés": { + "account_number": "2906" + }, + "Créances douteuses": { + "account_number": "2907" + }, + "Réductions de valeur actées (-)": { + "account_number": "2909" + }, + "account_number": "290" + }, + "Autres créances": { + "Créances en compte": { + "account_number": "2910" + }, + "Effets à recevoir": { + "account_number": "2911" + }, + "Subsides à recevoir": { + "account_number": "2912" + }, + "Créances non productives d'intérêts ou assorties d'un intérêt anormalement faible": { + "account_number": "2915" + }, + "Créances douteuses": { + "account_number": "2916" + }, + "Réductions de valeur actées (-)": { + "account_number": "2919" + }, + "account_number": "291" + }, + "account_number": "29" + } + }, + "CLASSE 3 : STOCKS ET COMMANDES EN COURS D'EXÉCUTION": { + "root_type": "Asset", + "Matières premières": { + "Valeur d'acquisition": { + "account_number": "300" + }, + "Réductions de valeur actées (-)": { + "account_number": "309" + }, + "account_number": "30" + }, + "Fournitures": { + "Valeur d'acquisition": { + "account_number": "310" + }, + "Réductions de valeur actées (-)": { + "account_number": "319" + }, + "account_number": "31" + }, + "En-cours de fabrication": { + "Valeur d'acquisition": { + "account_number": "320" + }, + "Réductions de valeur actées (-)": { + "account_number": "329" + }, + "account_number": "32" + }, + "Produits finis": { + "Valeur d'acquisition": { + "account_number": "330" + }, + "Réductions de valeur actées (-)": { + "account_number": "339" + }, + "account_number": "33" + }, + "Marchandises": { + "Valeur d'acquisition": { + "account_number": "340", + "account_type": "Stock" + }, + "Réductions de valeur actées (-)": { + "account_number": "349" + }, + "account_number": "34", + "account_type": "Stock" + }, + "Immeubles destinés à la vente": { + "Valeur d'acquisition": { + "account_number": "350" + }, + "Réductions de valeur actées (-)": { + "account_number": "359" + }, + "account_number": "35" + }, + "Acomptes versés sur achats pour stocks": { + "Acomptes versés": { + "account_number": "360" + }, + "Réductions de valeur actées (-)": { + "account_number": "369" + }, + "account_number": "36" + }, + "Commandes en cours d'exécution": { + "Valeur d'acquisition": { + "account_number": "370" + }, + "Bénéfice pris en compte": { + "account_number": "371" + }, + "Réductions de valeur actées (-)": { + "account_number": "379" + }, + "account_number": "37" + }, + "Stock livré non facturé": { + "account_type": "Stock Delivered But Not Billed" + } + }, + "CLASSE 4 : CRÉANCES À UN AN AU PLUS": { + "root_type": "Asset", + "Créances commerciales": { + "Clients": { + "account_number": "400", + "account_type": "Receivable" + }, + "Effets à recevoir": { + "account_number": "401", + "account_type": "Receivable" + }, + "Produits à recevoir": { + "account_number": "404", + "account_type": "Receivable" + }, + "Acomptes versés": { + "account_number": "406" + }, + "Créances douteuses": { + "account_number": "407", + "account_type": "Receivable" + }, + "Réductions de valeur actées (-)": { + "account_number": "409" + }, + "account_number": "40", + "account_type": "Receivable" + }, + "Autres créances": { + "TVA à récupérer": { + "account_number": "411", + "account_type": "Tax" + }, + "Impôts et précomptes à récupérer": { + "Autres impôts et taxes belges (4125 à 4127)": { + "account_number": "4125" + }, + "Impôts et taxes étrangers": { + "account_number": "4128" + }, + "account_number": "412" + }, + "Subsides à recevoir": { + "account_number": "413" + }, + "Produits à recevoir": { + "account_number": "414" + }, + "Créances non productives d'intérêts ou assorties d'un intérêt anormalement faible": { + "account_number": "415" + }, + "Créances diverses": { + "account_number": "416" + }, + "Créances douteuses": { + "account_number": "417" + }, + "Cautionnements versés en numéraire": { + "account_number": "418" + }, + "Réductions de valeur actées (-)": { + "account_number": "419" + }, + "account_number": "41" + } + }, + "CLASSE 4 : DETTES À UN AN AU PLUS": { + "root_type": "Liability", + "Dettes à plus d'un an échéant dans l'année": { + "account_number": "42" + }, + "Dettes financières": { + "Établissements de crédit - Emprunts en compte à terme fixe": { + "account_number": "430" + }, + "Établissements de crédit - Promesses": { + "account_number": "431" + }, + "Établissements de crédit - Crédits d'acceptation": { + "account_number": "432" + }, + "Établissements de crédit - Dettes en compte courant": { + "account_number": "433" + }, + "Autres emprunts": { + "account_number": "439" + }, + "account_number": "43" + }, + "Dettes commerciales": { + "Fournisseurs": { + "account_number": "440", + "account_type": "Payable" + }, + "Effets à payer": { + "account_number": "441", + "account_type": "Payable" + }, + "Factures à recevoir": { + "account_number": "444", + "account_type": "Stock Received But Not Billed" + }, + "account_number": "44", + "account_type": "Payable" + }, + "Dettes fiscales, salariales et sociales": { + "Dettes fiscales estimées": { + "Autres impôts et taxes belges (4505 à 4507)": { + "account_number": "4505" + }, + "Impôts et taxes étrangers": { + "account_number": "4508" + }, + "account_number": "450" + }, + "TVA à payer": { + "account_number": "451", + "account_type": "Tax" + }, + "Impôts et taxes à payer": { + "Autres impôts et taxes belges (4525 à 4527)": { + "account_number": "4525" + }, + "Impôts et taxes étrangers": { + "account_number": "4528" + }, + "account_number": "452" + }, + "Précomptes retenus": { + "account_number": "453" + }, + "Office national de la Sécurité sociale": { + "account_number": "454" + }, + "Rémunérations": { + "account_number": "455" + }, + "Pécules de vacances": { + "account_number": "456" + }, + "Autres dettes sociales": { + "account_number": "459" + }, + "account_number": "45" + }, + "Comptes de régularisation et d'attente": { + "Charges à reporter": { + "account_number": "490" + }, + "Produits acquis": { + "account_number": "491" + }, + "Charges à imputer": { + "account_number": "492" + }, + "Produits à reporter": { + "account_number": "493" + }, + "Comptes d'attente": { + "account_number": "499" + }, + "account_number": "49" + }, + "Obligations et coupons échus": { + "account_number": "480" + }, + "Subsides à rembourser": { + "account_number": "483" + }, + "Cautionnements reçus en numéraire": { + "account_number": "488" + }, + "Autres dettes diverses": { + "Productives d'intérêts": { + "account_number": "4890" + }, + "Non productives d'intérêts ou assorties d'un intérêt anormalement faible": { + "account_number": "4891" + }, + "account_number": "489" + } + }, + "CLASSE 5 : PLACEMENTS DE TRÉSORERIE ET VALEURS DISPONIBLES": { + "root_type": "Asset", + "Placements de trésorerie autres que actions et parts, titres à revenu fixe et dépôts à terme": { + "Valeur d'acquisition": { + "account_number": "500" + }, + "Réductions de valeur actées (-)": { + "account_number": "509" + }, + "account_number": "50" + }, + "Actions et parts": { + "Valeur d'acquisition": { + "account_number": "510" + }, + "Montants non appelés (-)": { + "account_number": "511" + }, + "Réductions de valeur actées (-)": { + "account_number": "519" + }, + "account_number": "51" + }, + "Titres à revenu fixe": { + "Valeur d'acquisition": { + "account_number": "520" + }, + "Réductions de valeur actées (-)": { + "account_number": "529" + }, + "account_number": "52" + }, + "Dépôts à terme": { + "De plus d'un an": { + "account_number": "530" + }, + "De plus d'un mois et à un an au plus": { + "account_number": "531" + }, + "D'un mois au plus": { + "account_number": "532" + }, + "Réductions de valeur actées (-)": { + "account_number": "539" + }, + "account_number": "53" + }, + "Valeurs échues à l'encaissement": { + "account_number": "54" + }, + "Établissements de crédit": { + "Banque": { + "account_number": "5500", + "account_type": "Bank" + }, + "account_number": "55", + "account_type": "Bank" + }, + "Caisses": { + "Caisses-espèces (570 à 577)": { + "account_number": "570", + "account_type": "Cash" + }, + "Caisses-timbres": { + "account_number": "578", + "account_type": "Cash" + }, + "account_number": "57", + "account_type": "Cash" + }, + "Virements internes": { + "account_number": "58", + "account_type": "Temporary" + } + }, + "CLASSE 6 : CHARGES": { + "root_type": "Expense", + "Approvisionnements et marchandises": { + "Achats de matières premières": { + "account_number": "600", + "account_type": "Expense Account" + }, + "Achats de fournitures": { + "account_number": "601", + "account_type": "Expense Account" + }, + "Achats de services, travaux et études": { + "account_number": "602", + "account_type": "Expense Account" + }, + "Sous-traitances générales": { + "account_number": "603", + "account_type": "Expense Account" + }, + "Achats de marchandises": { + "account_number": "604", + "account_type": "Cost of Goods Sold" + }, + "Achats d'immeubles destinés à la vente": { + "account_number": "605", + "account_type": "Expense Account" + }, + "Remises, ristournes et rabais obtenus (-)": { + "account_number": "608", + "account_type": "Expense Account" + }, + "Variations des stocks": { + "de matières premières": { + "account_number": "6090", + "account_type": "Stock Adjustment" + }, + "de fournitures": { + "account_number": "6091", + "account_type": "Stock Adjustment" + }, + "de marchandises": { + "account_number": "6094", + "account_type": "Stock Adjustment" + }, + "d'immeubles destinés à la vente": { + "account_number": "6095", + "account_type": "Stock Adjustment" + }, + "account_number": "609", + "account_type": "Stock Adjustment" + }, + "account_number": "60", + "account_type": "Expense Account" + }, + "Services et biens divers": { + "Personnel intérimaire et personnes mises à la disposition de l'association ou de la fondation": { + "account_number": "617", + "account_type": "Expense Account" + }, + "Rémunérations et pensions des administrateurs, hors contrat de travail": { + "account_number": "618", + "account_type": "Expense Account" + }, + "Frais accessoires d'achat inclus dans la valeur des stocks": { + "account_type": "Expenses Included In Valuation" + }, + "account_number": "61", + "account_type": "Expense Account" + }, + "Rémunérations, charges sociales et pensions": { + "Rémunérations et avantages sociaux directs": { + "Administrateurs ou gérants": { + "account_number": "6200", + "account_type": "Expense Account" + }, + "Personnel de direction": { + "account_number": "6201", + "account_type": "Expense Account" + }, + "Employés": { + "account_number": "6202", + "account_type": "Expense Account" + }, + "Ouvriers": { + "account_number": "6203", + "account_type": "Expense Account" + }, + "Autres membres du personnel": { + "account_number": "6204", + "account_type": "Expense Account" + }, + "account_number": "620", + "account_type": "Expense Account" + }, + "Cotisations patronales pour assurances sociales": { + "account_number": "621", + "account_type": "Expense Account" + }, + "Primes patronales pour assurances extra-légales": { + "account_number": "622", + "account_type": "Expense Account" + }, + "Autres frais du personnel": { + "account_number": "623", + "account_type": "Expense Account" + }, + "Pensions de retraite et de survie": { + "Administrateurs ou gérants": { + "account_number": "6240", + "account_type": "Expense Account" + }, + "Personnel": { + "account_number": "6241", + "account_type": "Expense Account" + }, + "account_number": "624", + "account_type": "Expense Account" + }, + "account_number": "62", + "account_type": "Expense Account" + }, + "Amortissements, réductions de valeur et provisions pour risques et charges": { + "Dotations aux amortissements et aux réductions de valeur sur immobilisations": { + "Dotations aux amortissements sur frais d'établissement": { + "account_number": "6300", + "account_type": "Depreciation" + }, + "Dotation aux amortissements sur immobilisations incorporelles": { + "account_number": "6301", + "account_type": "Depreciation" + }, + "Dotation aux amortissements sur immobilisations corporelles": { + "account_number": "6302", + "account_type": "Depreciation" + }, + "Dotation aux réductions de valeur sur immobilisations incorporelles": { + "account_number": "6308", + "account_type": "Depreciation" + }, + "Dotation aux réductions de valeur sur immobilisations corporelles": { + "account_number": "6309", + "account_type": "Depreciation" + }, + "account_number": "630", + "account_type": "Depreciation" + }, + "Réductions de valeur sur stocks": { + "Dotations": { + "account_number": "6310", + "account_type": "Depreciation" + }, + "Reprises (-)": { + "account_number": "6311", + "account_type": "Depreciation" + }, + "account_number": "631", + "account_type": "Depreciation" + }, + "Réductions de valeur sur commandes en cours d'exécution": { + "Dotations": { + "account_number": "6320", + "account_type": "Depreciation" + }, + "Reprises (-)": { + "account_number": "6321", + "account_type": "Depreciation" + }, + "account_number": "632", + "account_type": "Depreciation" + }, + "Réductions de valeur sur créances commerciales à plus d'un an": { + "Dotations": { + "account_number": "6330", + "account_type": "Depreciation" + }, + "Reprises (-)": { + "account_number": "6331", + "account_type": "Depreciation" + }, + "account_number": "633", + "account_type": "Depreciation" + }, + "Réductions de valeur sur créances à un an au plus": { + "Dotations": { + "account_number": "6340", + "account_type": "Depreciation" + }, + "Reprises (-)": { + "account_number": "6341", + "account_type": "Depreciation" + }, + "account_number": "634", + "account_type": "Depreciation" + }, + "Provisions pour pensions et obligations similaires": { + "Dotations": { + "account_number": "6350", + "account_type": "Depreciation" + }, + "Utilisations et reprises (-)": { + "account_number": "6351", + "account_type": "Depreciation" + }, + "account_number": "635", + "account_type": "Depreciation" + }, + "Provisions pour grosses réparations et gros entretien": { + "Dotations": { + "account_number": "6360", + "account_type": "Depreciation" + }, + "Utilisations et reprises (-)": { + "account_number": "6361", + "account_type": "Depreciation" + }, + "account_number": "636", + "account_type": "Depreciation" + }, + "Provisions pour obligations environnementales": { + "Dotations": { + "account_number": "6370", + "account_type": "Depreciation" + }, + "Utilisations et reprises (-)": { + "account_number": "6371", + "account_type": "Depreciation" + }, + "account_number": "637", + "account_type": "Depreciation" + }, + "Provisions pour subsides et legs à rembourser et pour dons avec droit de reprise": { + "Dotations": { + "account_number": "6380", + "account_type": "Expense Account" + }, + "Utilisations et reprises (-)": { + "account_number": "6381", + "account_type": "Expense Account" + }, + "account_number": "638", + "account_type": "Expense Account" + }, + "Provisions pour autres risques et charges": { + "Dotations": { + "account_number": "6390", + "account_type": "Expense Account" + }, + "Utilisations et reprises (-)": { + "account_number": "6391", + "account_type": "Expense Account" + }, + "account_number": "639", + "account_type": "Expense Account" + }, + "account_number": "63", + "account_type": "Expense Account" + }, + "Autres charges d'exploitation": { + "Charges fiscales": { + "account_number": "640", + "account_type": "Expense Account" + }, + "Moins-values sur réalisations courantes d'immobilisations corporelles": { + "account_number": "641", + "account_type": "Expense Account" + }, + "Moins-values sur réalisations de créances commerciales": { + "account_number": "642", + "account_type": "Expense Account" + }, + "Dons": { + "account_number": "643", + "account_type": "Expense Account" + }, + "Charges d'exploitations diverses (644 à 648)": { + "account_number": "644", + "account_type": "Expense Account" + }, + "Charges d'exploitation portées à l'actif au titre de frais de restructuration (-)": { + "account_number": "649", + "account_type": "Expense Account" + }, + "account_number": "64", + "account_type": "Expense Account" + }, + "Charges financières": { + "Charges des dettes": { + "Intérêts, commissions et frais afférents aux dettes": { + "account_number": "6500", + "account_type": "Expense Account" + }, + "Amortissements frais d'émission d'emprunts et des primes de remboursement": { + "account_number": "6501", + "account_type": "Expense Account" + }, + "Intérêts intercalaires portés à l'actif (-)": { + "account_number": "6502", + "account_type": "Expense Account" + }, + "account_number": "650", + "account_type": "Expense Account" + }, + "Réductions de valeur sur actifs circulants": { + "Dotations": { + "account_number": "6510", + "account_type": "Expense Account" + }, + "Reprises (-)": { + "account_number": "6511", + "account_type": "Expense Account" + }, + "account_number": "651", + "account_type": "Expense Account" + }, + "Moins-values sur réalisation d'actifs circulants": { + "account_number": "652", + "account_type": "Expense Account" + }, + "Charges d'escompte de créances": { + "account_number": "653", + "account_type": "Expense Account" + }, + "Différences de change": { + "account_number": "654", + "account_type": "Expense Account" + }, + "Écarts de conversion des devises": { + "account_number": "655", + "account_type": "Expense Account" + }, + "Provisions à caractère financier": { + "Dotations": { + "account_number": "6560", + "account_type": "Expense Account" + }, + "Utilisations et reprises (-)": { + "account_number": "6561", + "account_type": "Expense Account" + }, + "account_number": "656", + "account_type": "Expense Account" + }, + "Charges financières diverses (657 à 658)": { + "account_number": "657", + "account_type": "Expense Account" + }, + "Charges financières portées à l'actif au titre de frais de restructuration (-)": { + "account_number": "659", + "account_type": "Expense Account" + }, + "account_number": "65", + "account_type": "Expense Account" + }, + "Charges d'exploitation ou financières non récurrentes": { + "Amortissements et réductions de valeur non récurrents (dotations)": { + "sur frais d'établissement": { + "account_number": "6600", + "account_type": "Expense Account" + }, + "sur immobilisations incorporelles": { + "account_number": "6601", + "account_type": "Expense Account" + }, + "sur immobilisations corporelles": { + "account_number": "6602", + "account_type": "Expense Account" + }, + "account_number": "660", + "account_type": "Expense Account" + }, + "Réduction de valeur sur immobilisations financières (dotation)": { + "account_number": "661", + "account_type": "Expense Account" + }, + "Provisions pour risques et charges non récurrents": { + "Provisions pour risques et charges d'exploitation non récurrents": { + "Dotations": { + "account_number": "66200", + "account_type": "Expense Account" + }, + "Utilisations (-)": { + "account_number": "66201", + "account_type": "Expense Account" + }, + "account_number": "6620", + "account_type": "Expense Account" + }, + "Provisions pour risques et charges financiers non récurrents": { + "Dotations": { + "account_number": "66210", + "account_type": "Expense Account" + }, + "Utilisations (-)": { + "account_number": "66211", + "account_type": "Expense Account" + }, + "account_number": "6621", + "account_type": "Expense Account" + }, + "account_number": "662", + "account_type": "Expense Account" + }, + "Moins-values sur réalisation d'actifs immobilisés": { + "Moins-values sur réalisation d'immobilisations incorporelles et corporelles": { + "account_number": "6630", + "account_type": "Expense Account" + }, + "Moins-values sur réalisation d'immobilisations financières": { + "account_number": "6631", + "account_type": "Expense Account" + }, + "account_number": "663", + "account_type": "Expense Account" + }, + "Autres charges d'exploitation non récurrentes (664 à 667)": { + "account_number": "664", + "account_type": "Expense Account" + }, + "Autres charges financières non récurrentes": { + "account_number": "668", + "account_type": "Expense Account" + }, + "Charges d'exploitation portées à l'actif au titre de frais de restructuration (-)": { + "account_number": "6690", + "account_type": "Expense Account" + }, + "Charges financières non récurrentes portées à l'actif au titre de frais de restructuration (-)": { + "account_number": "6691", + "account_type": "Expense Account" + }, + "account_number": "66", + "account_type": "Expense Account" + }, + "Impôts": { + "Impôts belges sur le résultat de l'exercice": { + "Impôts et précomptes dus ou versés": { + "account_number": "6700", + "account_type": "Expense Account" + }, + "Excédent de versements d'impôts et de précomptes porté à l'actif (-)": { + "account_number": "6701", + "account_type": "Expense Account" + }, + "Charges fiscales estimées": { + "account_number": "6702", + "account_type": "Expense Account" + }, + "account_number": "670", + "account_type": "Expense Account" + }, + "Impôts belges sur le résultat d'exercices antérieurs": { + "account_number": "673", + "account_type": "Expense Account" + }, + "Suppléments d'impôts dus ou versés": { + "account_number": "6710", + "account_type": "Expense Account" + }, + "Suppléments d'impôts estimés": { + "account_number": "6711", + "account_type": "Expense Account" + }, + "Provisions fiscales constituées": { + "account_number": "6712", + "account_type": "Expense Account" + }, + "account_number": "67", + "account_type": "Expense Account" + }, + "Transferts aux impôts différés et aux réserves immunisées": { + "Transferts aux impôts différés": { + "account_number": "680", + "account_type": "Expense Account" + }, + "Transferts aux réserves immunisées": { + "account_number": "689", + "account_type": "Expense Account" + }, + "account_number": "68", + "account_type": "Expense Account" + }, + "Affectations et prélèvements": { + "Résultat négatif de l'exercice antérieur reporté": { + "account_number": "690", + "account_type": "Expense Account" + }, + "Transfert aux fonds affectés et autres réserves": { + "account_number": "691", + "account_type": "Expense Account" + }, + "Résultat positif à reporter": { + "account_number": "692", + "account_type": "Expense Account" + }, + "account_number": "69", + "account_type": "Expense Account" + } + }, + "CLASSE 7 : PRODUITS": { + "root_type": "Income", + "Chiffre d'affaires": { + "Remises, ristournes et rabais accordés (-)": { + "account_number": "708", + "account_type": "Income Account" + }, + "account_number": "70", + "account_type": "Income Account" + }, + "Variation des stocks et des commandes en cours d'exécution": { + "Des en-cours de fabrication": { + "account_number": "712", + "account_type": "Income Account" + }, + "Des produits finis": { + "account_number": "713", + "account_type": "Income Account" + }, + "Des immeubles construits destinés à la vente": { + "account_number": "715", + "account_type": "Income Account" + }, + "Des commandes en cours d'exécution": { + "Valeur d'acquisition": { + "account_number": "7170", + "account_type": "Income Account" + }, + "Bénéfice pris en compte": { + "account_number": "7171", + "account_type": "Income Account" + }, + "account_number": "717", + "account_type": "Income Account" + }, + "account_number": "71", + "account_type": "Income Account" + }, + "Production immobilisée": { + "account_number": "72", + "account_type": "Income Account" + }, + "Cotisations, dons, legs et subsides": { + "Cotisations": { + "account_number": "730", + "account_type": "Income Account" + }, + "Dons": { + "account_number": "731", + "account_type": "Income Account" + }, + "Legs": { + "account_number": "732", + "account_type": "Income Account" + }, + "Subsides": { + "account_number": "733", + "account_type": "Income Account" + }, + "account_number": "73", + "account_type": "Income Account" + }, + "Autres produits d'exploitation": { + "Plus-values sur réalisations courantes d'immobilisations corporelles": { + "account_number": "741", + "account_type": "Income Account" + }, + "Plus-values sur réalisation de créances commerciales": { + "account_number": "742", + "account_type": "Income Account" + }, + "Produits d'exploitation divers (743 à 749)": { + "account_number": "743", + "account_type": "Income Account" + }, + "account_number": "74", + "account_type": "Income Account" + }, + "Produits financiers": { + "Produits des immobilisations financières": { + "account_number": "750", + "account_type": "Income Account" + }, + "Produits des actifs circulants": { + "account_number": "751", + "account_type": "Income Account" + }, + "Plus-values sur la réalisation d'actifs circulants": { + "account_number": "752", + "account_type": "Income Account" + }, + "Différences de change": { + "account_number": "754", + "account_type": "Income Account" + }, + "Écarts de conversion des devises": { + "account_number": "755", + "account_type": "Income Account" + }, + "Produits financiers divers (756 à 759)": { + "account_number": "756", + "account_type": "Income Account" + }, + "account_number": "75", + "account_type": "Income Account" + }, + "Produits d'exploitation ou financiers non récurrents": { + "Reprise d'amortissements et réductions de valeur": { + "sur immobilisations incorporelles": { + "account_number": "7600", + "account_type": "Income Account" + }, + "sur immobilisations corporelles": { + "account_number": "7601", + "account_type": "Income Account" + }, + "account_number": "760", + "account_type": "Income Account" + }, + "Reprises de réductions de valeur sur immobilisations financières": { + "account_number": "761", + "account_type": "Income Account" + }, + "Reprises de provisions pour risques et charges non récurrents": { + "Reprises de provisions pour risques et charges d'exploitation non récurrents": { + "account_number": "7620", + "account_type": "Income Account" + }, + "Reprises de provisions pour risques et charges financiers non récurrents": { + "account_number": "7621", + "account_type": "Income Account" + }, + "account_number": "762", + "account_type": "Income Account" + }, + "Plus-values sur réalisation d'actifs immobilisés": { + "Plus-values sur réalisation d'immobilisations incorporelles et corporelles": { + "account_number": "7630", + "account_type": "Income Account" + }, + "Plus-values sur réalisation d'immobilisations financières": { + "account_number": "7631", + "account_type": "Income Account" + }, + "account_number": "763", + "account_type": "Income Account" + }, + "Autres produits d'exploitation non récurrents (764 à 768)": { + "account_number": "764", + "account_type": "Income Account" + }, + "Autres produits financiers non récurrents": { + "account_number": "769", + "account_type": "Income Account" + }, + "account_number": "76", + "account_type": "Income Account" + }, + "Régularisation d'impôts": { + "account_number": "77", + "account_type": "Income Account" + }, + "Prélèvements sur les réserves immunisées et les impôts différés": { + "Prélèvements sur les impôts différés": { + "account_number": "780", + "account_type": "Income Account" + }, + "Prélèvement sur les réserves immunisées": { + "account_number": "789", + "account_type": "Income Account" + }, + "account_number": "78", + "account_type": "Income Account" + }, + "Affectations et prélèvements": { + "Résultat positif de l'exercice antérieur reporté": { + "account_number": "790", + "account_type": "Income Account" + }, + "Autres réserves": { + "account_number": "791", + "account_type": "Income Account" + }, + "Résultat négatif à reporter": { + "account_number": "792", + "account_type": "Income Account" + }, + "account_number": "79", + "account_type": "Income Account" + } + } + } +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/be_plan_comptable_minimum_normalise_entreprises.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/be_plan_comptable_minimum_normalise_entreprises.json new file mode 100644 index 00000000000..2deb46f7300 --- /dev/null +++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/be_plan_comptable_minimum_normalise_entreprises.json @@ -0,0 +1,1597 @@ +{ + "country_code": "be", + "name": "Belgique - Plan comptable minimum normalisé (PCMN) des entreprises", + "tree": { + "CLASSE 1 : CAPITAUX PROPRES": { + "root_type": "Equity", + "Capital": { + "Capital souscrit": { + "account_number": "100", + "account_type": "Equity" + }, + "Capital non appelé (-)": { + "account_number": "101", + "account_type": "Equity" + }, + "account_number": "10", + "account_type": "Equity" + }, + "Apport hors capital": { + "Apport disponible hors capital": { + "Prime d'émission": { + "account_number": "1100", + "account_type": "Equity" + }, + "Autres": { + "account_number": "1109", + "account_type": "Equity" + }, + "account_number": "110", + "account_type": "Equity" + }, + "Apport indisponible hors capital": { + "Prime d'émission": { + "account_number": "1110", + "account_type": "Equity" + }, + "Autres": { + "account_number": "1119", + "account_type": "Equity" + }, + "account_number": "111", + "account_type": "Equity" + }, + "account_number": "11", + "account_type": "Equity" + }, + "Plus-values de réévaluation": { + "Plus-values de réévaluation sur immobilisations incorporelles": { + "account_number": "120", + "account_type": "Equity" + }, + "Plus-values de réévaluation sur immobilisations corporelles": { + "account_number": "121", + "account_type": "Equity" + }, + "Plus-values de réévaluation sur immobilisations financières": { + "account_number": "122", + "account_type": "Equity" + }, + "Plus-values de réévaluation sur stocks": { + "account_number": "123", + "account_type": "Equity" + }, + "Reprises de réductions de valeur sur placements de trésorerie": { + "account_number": "124", + "account_type": "Equity" + }, + "account_number": "12", + "account_type": "Equity" + }, + "Réserves": { + "Réserves légales": { + "account_number": "130", + "account_type": "Equity" + }, + "Autres réserves indisponibles": { + "Réserves statutairement indisponibles": { + "account_number": "1311", + "account_type": "Equity" + }, + "Réserve pour actions propres": { + "account_number": "1312", + "account_type": "Equity" + }, + "Soutien financier": { + "account_number": "1313", + "account_type": "Equity" + }, + "Autres": { + "account_number": "1319", + "account_type": "Equity" + }, + "account_number": "131", + "account_type": "Equity" + }, + "Réserves immunisées": { + "account_number": "132", + "account_type": "Equity" + }, + "Réserves disponibles": { + "account_number": "133", + "account_type": "Equity" + }, + "account_number": "13", + "account_type": "Equity" + }, + "Bénéfice reporté ou perte reportée (-)": { + "account_number": "14", + "account_type": "Equity" + }, + "Subsides en capital": { + "account_number": "15", + "account_type": "Equity" + } + }, + "CLASSE 1 : PROVISIONS ET DETTES À PLUS D'UN AN": { + "root_type": "Liability", + "Provisions et impôts différés": { + "Provisions pour pensions et obligations similaires": { + "account_number": "160", + "account_type": "Liability" + }, + "Provisions pour charges fiscales": { + "account_number": "161", + "account_type": "Liability" + }, + "Provisions pour grosses réparations et gros entretien": { + "account_number": "162", + "account_type": "Liability" + }, + "Provisions pour obligations environnementales": { + "account_number": "163", + "account_type": "Liability" + }, + "Impôts différés": { + "Impôts différés afférents à des subsides en capital": { + "account_number": "1680", + "account_type": "Liability" + }, + "Impôts différés afférents à des plus-values réalisées sur immobilisations incorporelles": { + "account_number": "1681", + "account_type": "Liability" + }, + "Impôts différés afférents à des plus-values réalisées sur immobilisations corporelles": { + "account_number": "1682", + "account_type": "Liability" + }, + "Impôts différés afférents à des plus-values réalisées sur titres émis par le secteur public belge": { + "account_number": "1687", + "account_type": "Liability" + }, + "Impôts différés étrangers": { + "account_number": "1688", + "account_type": "Liability" + }, + "account_number": "168", + "account_type": "Liability" + }, + "account_number": "16", + "account_type": "Liability" + }, + "Dettes à plus d'un an": { + "Emprunts subordonnés": { + "Convertibles": { + "account_number": "1700", + "account_type": "Liability" + }, + "Non convertibles": { + "account_number": "1701", + "account_type": "Liability" + }, + "account_number": "170", + "account_type": "Liability" + }, + "Emprunts obligataires non subordonnés": { + "Convertibles": { + "account_number": "1710", + "account_type": "Liability" + }, + "Non convertibles": { + "account_number": "1711", + "account_type": "Liability" + }, + "account_number": "171", + "account_type": "Liability" + }, + "Dettes de location-financement et assimilées": { + "account_number": "172", + "account_type": "Liability" + }, + "Établissements de crédit": { + "Dettes en compte": { + "account_number": "1730", + "account_type": "Liability" + }, + "Promesses": { + "account_number": "1731", + "account_type": "Liability" + }, + "Crédits d'acceptation": { + "account_number": "1732", + "account_type": "Liability" + }, + "account_number": "173", + "account_type": "Liability" + }, + "Autres emprunts": { + "account_number": "174", + "account_type": "Liability" + }, + "Dettes commerciales": { + "Fournisseurs": { + "account_number": "1750", + "account_type": "Liability" + }, + "Effets à payer": { + "account_number": "1751", + "account_type": "Liability" + }, + "account_number": "175", + "account_type": "Liability" + }, + "Acomptes sur commandes": { + "account_number": "176", + "account_type": "Liability" + }, + "Cautionnements reçus en numéraire": { + "account_number": "178", + "account_type": "Liability" + }, + "Dettes diverses": { + "account_number": "179", + "account_type": "Liability" + }, + "account_number": "17", + "account_type": "Liability" + }, + "Acompte aux associés sur le partage de l'actif net (-)": { + "account_number": "19", + "account_type": "Liability" + } + }, + "CLASSE 2 : FRAIS D'ÉTABLISSEMENT, ACTIFS IMMOBILISÉS ET CRÉANCES À PLUS D'UN AN": { + "root_type": "Asset", + "Frais d'établissement": { + "Frais de constitution, d'augmentation de capital ou d'augmentation de l'apport": { + "account_number": "200", + "account_type": "Fixed Asset" + }, + "Frais d'émission d'emprunts": { + "account_number": "201", + "account_type": "Fixed Asset" + }, + "Autres frais d'établissement": { + "account_number": "202", + "account_type": "Fixed Asset" + }, + "Frais de restructuration": { + "account_number": "204", + "account_type": "Fixed Asset" + }, + "account_number": "20", + "account_type": "Fixed Asset" + }, + "Immobilisations incorporelles": { + "Frais de recherche et de développement": { + "account_number": "210", + "account_type": "Fixed Asset" + }, + "Concessions, brevets, licences, savoir-faire, marques et droits similaires": { + "account_number": "211", + "account_type": "Fixed Asset" + }, + "Goodwill": { + "account_number": "212", + "account_type": "Fixed Asset" + }, + "Acomptes versés": { + "account_number": "213", + "account_type": "Fixed Asset" + }, + "Amortissements et réductions de valeur actées (-)": { + "account_number": "219", + "account_type": "Accumulated Depreciation" + }, + "account_number": "21", + "account_type": "Fixed Asset" + }, + "Terrains et constructions": { + "Terrains": { + "account_number": "220", + "account_type": "Fixed Asset" + }, + "Constructions": { + "account_number": "221", + "account_type": "Fixed Asset" + }, + "Terrains bâtis": { + "account_number": "222", + "account_type": "Fixed Asset" + }, + "Autres droits réels sur des immeubles": { + "account_number": "223", + "account_type": "Fixed Asset" + }, + "Amortissements et réductions de valeur actées (-)": { + "account_number": "229", + "account_type": "Accumulated Depreciation" + }, + "account_number": "22", + "account_type": "Fixed Asset" + }, + "Installations, machines et outillage": { + "account_number": "23", + "account_type": "Fixed Asset" + }, + "Mobilier et matériel roulant": { + "account_number": "24", + "account_type": "Fixed Asset" + }, + "Immobilisations détenues en location-financement et droits similaires": { + "Terrains et constructions": { + "account_number": "250", + "account_type": "Fixed Asset" + }, + "Installations, machines et outillage": { + "account_number": "251", + "account_type": "Fixed Asset" + }, + "Mobilier et matériel roulant": { + "account_number": "252", + "account_type": "Fixed Asset" + }, + "Amortissements et réductions de valeur actées (-)": { + "account_number": "259", + "account_type": "Accumulated Depreciation" + }, + "account_number": "25", + "account_type": "Fixed Asset" + }, + "Autres immobilisations corporelles": { + "account_number": "26", + "account_type": "Fixed Asset" + }, + "Immobilisations corporelles en cours et acomptes versés": { + "account_number": "27", + "account_type": "Fixed Asset" + }, + "Immobilisations financières": { + "Participations dans des entreprises liées": { + "Valeur d'acquisition": { + "account_number": "2800", + "account_type": "Fixed Asset" + }, + "Montants non appelés (-)": { + "account_number": "2801", + "account_type": "Fixed Asset" + }, + "Plus-values actées": { + "account_number": "2808", + "account_type": "Fixed Asset" + }, + "Réductions de valeur actées (-)": { + "account_number": "2809", + "account_type": "Fixed Asset" + }, + "account_number": "280", + "account_type": "Fixed Asset" + }, + "Créances sur des entreprises liées": { + "Créances en compte": { + "account_number": "2810", + "account_type": "Fixed Asset" + }, + "Effets à recevoir": { + "account_number": "2811", + "account_type": "Fixed Asset" + }, + "Titres à revenu fixe": { + "account_number": "2812", + "account_type": "Fixed Asset" + }, + "Créances douteuses": { + "account_number": "2817", + "account_type": "Fixed Asset" + }, + "Réductions de valeur actées (-)": { + "account_number": "2819", + "account_type": "Fixed Asset" + }, + "account_number": "281", + "account_type": "Fixed Asset" + }, + "Participations dans des entreprises avec lesquelles il existe un lien de participation": { + "Valeur d'acquisition": { + "account_number": "2820", + "account_type": "Fixed Asset" + }, + "Montants non appelés (-)": { + "account_number": "2821", + "account_type": "Fixed Asset" + }, + "Plus-values actées": { + "account_number": "2828", + "account_type": "Fixed Asset" + }, + "Réductions de valeur actées (-)": { + "account_number": "2829", + "account_type": "Fixed Asset" + }, + "account_number": "282", + "account_type": "Fixed Asset" + }, + "Créances sur des entreprises avec lesquelles il existe un lien de participation": { + "Créances en compte": { + "account_number": "2830", + "account_type": "Fixed Asset" + }, + "Effets à recevoir": { + "account_number": "2831", + "account_type": "Fixed Asset" + }, + "Titres à revenu fixe": { + "account_number": "2832", + "account_type": "Fixed Asset" + }, + "Créances douteuses": { + "account_number": "2837", + "account_type": "Fixed Asset" + }, + "Réductions de valeur actées (-)": { + "account_number": "2839", + "account_type": "Fixed Asset" + }, + "account_number": "283", + "account_type": "Fixed Asset" + }, + "Autres actions et parts": { + "Valeur d'acquisition": { + "account_number": "2840", + "account_type": "Fixed Asset" + }, + "Montants non appelés (-)": { + "account_number": "2841", + "account_type": "Fixed Asset" + }, + "Plus-values actées": { + "account_number": "2848", + "account_type": "Fixed Asset" + }, + "Réductions de valeur actées (-)": { + "account_number": "2849", + "account_type": "Fixed Asset" + }, + "account_number": "284", + "account_type": "Fixed Asset" + }, + "Autres créances": { + "Créances en compte": { + "account_number": "2850", + "account_type": "Fixed Asset" + }, + "Effets à recevoir": { + "account_number": "2851", + "account_type": "Fixed Asset" + }, + "Titres à revenu fixe": { + "account_number": "2852", + "account_type": "Fixed Asset" + }, + "Créances douteuses": { + "account_number": "2857", + "account_type": "Fixed Asset" + }, + "Réductions de valeur actées (-)": { + "account_number": "2859", + "account_type": "Fixed Asset" + }, + "account_number": "285", + "account_type": "Fixed Asset" + }, + "Cautionnements versés en numéraire": { + "account_number": "288", + "account_type": "Fixed Asset" + }, + "account_number": "28", + "account_type": "Fixed Asset" + }, + "Créances à plus d'un an": { + "Créances commerciales": { + "Clients": { + "account_number": "2900" + }, + "Effets à recevoir": { + "account_number": "2901" + }, + "Acomptes versés": { + "account_number": "2906" + }, + "Créances douteuses": { + "account_number": "2907" + }, + "Réductions de valeur actées (-)": { + "account_number": "2909" + }, + "account_number": "290" + }, + "Autres créances": { + "Créances en compte": { + "account_number": "2910" + }, + "Effets à recevoir": { + "account_number": "2911" + }, + "Créances douteuses": { + "account_number": "2917" + }, + "Réductions de valeur actées (-)": { + "account_number": "2919" + }, + "account_number": "291" + }, + "account_number": "29" + } + }, + "CLASSE 3 : STOCKS ET COMMANDES EN COURS D'EXÉCUTION": { + "root_type": "Asset", + "Matières premières": { + "Valeur d'acquisition": { + "account_number": "300" + }, + "Réductions de valeur actées (-)": { + "account_number": "309" + }, + "account_number": "30" + }, + "Approvisionnements et fournitures": { + "Valeur d'acquisition": { + "account_number": "310" + }, + "Réductions de valeur actées (-)": { + "account_number": "319" + }, + "account_number": "31" + }, + "En-cours de fabrication": { + "Valeur d'acquisition": { + "account_number": "320" + }, + "Réductions de valeur actées (-)": { + "account_number": "329" + }, + "account_number": "32" + }, + "Produits finis": { + "Valeur d'acquisition": { + "account_number": "330" + }, + "Réductions de valeur actées (-)": { + "account_number": "339" + }, + "account_number": "33" + }, + "Marchandises": { + "Valeur d'acquisition": { + "account_number": "340", + "account_type": "Stock" + }, + "Réductions de valeur actées (-)": { + "account_number": "349" + }, + "account_number": "34", + "account_type": "Stock" + }, + "Immeubles destinés à la vente": { + "Valeur d'acquisition": { + "account_number": "350" + }, + "Réductions de valeur actées (-)": { + "account_number": "359" + }, + "account_number": "35" + }, + "Acomptes versés sur achats pour stocks": { + "Acomptes versés": { + "account_number": "360" + }, + "Réductions de valeur actées (-)": { + "account_number": "369" + }, + "account_number": "36" + }, + "Commandes en cours d'exécution": { + "Valeur d'acquisition": { + "account_number": "370" + }, + "Bénéfice pris en compte": { + "account_number": "371" + }, + "Réductions de valeur actées (-)": { + "account_number": "379" + }, + "account_number": "37" + }, + "Stock livré non facturé": { + "account_type": "Stock Delivered But Not Billed" + } + }, + "CLASSE 4 : CRÉANCES À UN AN AU PLUS": { + "root_type": "Asset", + "Créances commerciales": { + "Clients": { + "account_number": "400", + "account_type": "Receivable" + }, + "Effets à recevoir": { + "account_number": "401", + "account_type": "Receivable" + }, + "Produits à recevoir": { + "account_number": "404", + "account_type": "Receivable" + }, + "Acomptes versés": { + "account_number": "406" + }, + "Créances douteuses": { + "account_number": "407", + "account_type": "Receivable" + }, + "Réductions de valeur actées (-)": { + "account_number": "409" + }, + "account_number": "40", + "account_type": "Receivable" + }, + "Autres créances": { + "Capital ou apport appelé, non versé": { + "account_number": "410" + }, + "TVA à récupérer": { + "account_number": "411", + "account_type": "Tax" + }, + "Impôts et précomptes à récupérer": { + "Impôts et taxes étrangers": { + "account_number": "4128" + }, + "account_number": "412" + }, + "Produits à recevoir": { + "account_number": "414" + }, + "Créances diverses": { + "account_number": "416" + }, + "Créances douteuses": { + "account_number": "417" + }, + "Cautionnements versés en numéraire": { + "account_number": "418" + }, + "Réductions de valeur actées (-)": { + "account_number": "419" + }, + "account_number": "41" + } + }, + "CLASSE 4 : DETTES À UN AN AU PLUS": { + "root_type": "Liability", + "Dettes à plus d'un an échéant dans l'année (16) (même subdivision que le compte 17)": { + "account_number": "42" + }, + "Dettes financières": { + "Établissements de crédit - Emprunts en compte à terme fixe": { + "account_number": "430" + }, + "Établissements de crédit - Promesses": { + "account_number": "431" + }, + "Établissements de crédit - Crédits d'acceptation": { + "account_number": "432" + }, + "Établissements de crédit - Dettes en compte courant": { + "account_number": "433" + }, + "Autres emprunts": { + "account_number": "439" + }, + "account_number": "43" + }, + "Dettes commerciales": { + "Fournisseurs": { + "account_number": "440", + "account_type": "Payable" + }, + "Effets à payer": { + "account_number": "441", + "account_type": "Payable" + }, + "Factures à recevoir": { + "account_number": "444", + "account_type": "Stock Received But Not Billed" + }, + "account_number": "44", + "account_type": "Payable" + }, + "Dettes fiscales, salariales et sociales": { + "Dettes fiscales estimées": { + "Impôts et taxes étrangers": { + "account_number": "4508" + }, + "account_number": "450" + }, + "TVA à payer": { + "account_number": "451", + "account_type": "Tax" + }, + "Impôts et taxes à payer": { + "Impôts et taxes étrangers": { + "account_number": "4528" + }, + "account_number": "452" + }, + "Précomptes retenus": { + "account_number": "453" + }, + "Office national de la Sécurité sociale": { + "account_number": "454" + }, + "Rémunérations": { + "account_number": "455" + }, + "Pécules de vacances": { + "account_number": "456" + }, + "Autres dettes sociales": { + "account_number": "459" + }, + "account_number": "45" + }, + "Acomptes sur commandes": { + "account_number": "46" + }, + "Dettes découlant de l'affectation du résultat": { + "Dividendes et tantièmes d'exercices antérieurs": { + "account_number": "470" + }, + "Dividendes de l'exercice": { + "account_number": "471" + }, + "Tantièmes de l'exercice": { + "account_number": "472" + }, + "Autres allocataires": { + "account_number": "473" + }, + "account_number": "47" + }, + "Dettes diverses": { + "Obligations et coupons échus": { + "account_number": "480" + }, + "Cautionnements reçus en numéraire": { + "account_number": "488" + }, + "Autres dettes diverses": { + "account_number": "489" + }, + "account_number": "48" + }, + "Comptes de régularisation et d'attente": { + "Charges à reporter": { + "account_number": "490" + }, + "Produits acquis": { + "account_number": "491" + }, + "Charges à imputer": { + "account_number": "492" + }, + "Produits à reporter": { + "account_number": "493" + }, + "Comptes d'attente": { + "account_number": "499" + }, + "account_number": "49" + } + }, + "CLASSE 5 : PLACEMENTS DE TRÉSORERIE ET VALEURS DISPONIBLES": { + "root_type": "Asset", + "Actions propres": { + "account_number": "50" + }, + "Actions, parts et placements de trésorerie autres que placements à revenu fixe": { + "Valeur d'acquisition": { + "Actions et parts": { + "account_number": "5100" + }, + "Placements de trésorerie autres que placements à revenu fixe": { + "account_number": "5101" + }, + "account_number": "510" + }, + "Montants non appelés (-)": { + "Actions et parts": { + "account_number": "5110" + }, + "account_number": "511" + }, + "Réductions de valeur actées (-)": { + "Actions et parts": { + "account_number": "5190" + }, + "Placements de trésorerie autres que placements à revenu fixe": { + "account_number": "5191" + }, + "account_number": "519" + }, + "account_number": "51" + }, + "Titres à revenu fixe": { + "Valeur d'acquisition": { + "account_number": "520" + }, + "Réductions de valeur actées (-)": { + "account_number": "529" + }, + "account_number": "52" + }, + "Dépôts à terme": { + "De plus d'un an": { + "account_number": "530" + }, + "De plus d'un mois et à un an au plus": { + "account_number": "531" + }, + "D'un mois au plus": { + "account_number": "532" + }, + "Réductions de valeur actées (-)": { + "account_number": "539" + }, + "account_number": "53" + }, + "Valeurs échues à l'encaissement": { + "account_number": "54" + }, + "Établissements de crédit": { + "Banque": { + "account_number": "5500", + "account_type": "Bank" + }, + "account_number": "55", + "account_type": "Bank" + }, + "Caisses": { + "Caisses-timbres": { + "account_number": "578", + "account_type": "Cash" + }, + "account_number": "57", + "account_type": "Cash" + }, + "Virements internes": { + "account_number": "58", + "account_type": "Temporary" + } + }, + "CLASSE 6 : CHARGES": { + "root_type": "Expense", + "Approvisionnements et marchandises": { + "Achats de matières premières": { + "account_number": "600", + "account_type": "Expense Account" + }, + "Achats de fournitures": { + "account_number": "601", + "account_type": "Expense Account" + }, + "Achats de services, travaux et études": { + "account_number": "602", + "account_type": "Expense Account" + }, + "Sous-traitances générales": { + "account_number": "603", + "account_type": "Expense Account" + }, + "Achats de marchandises": { + "account_number": "604", + "account_type": "Cost of Goods Sold" + }, + "Achats d'immeubles destinés à la vente": { + "account_number": "605", + "account_type": "Expense Account" + }, + "Remises, ristournes et rabais (-)": { + "account_number": "608", + "account_type": "Expense Account" + }, + "Variations des stocks": { + "de matières premières": { + "account_number": "6090", + "account_type": "Stock Adjustment" + }, + "de fournitures": { + "account_number": "6091", + "account_type": "Stock Adjustment" + }, + "de marchandises": { + "account_number": "6094", + "account_type": "Stock Adjustment" + }, + "d'immeubles achetés destinés à la vente": { + "account_number": "6095", + "account_type": "Stock Adjustment" + }, + "account_number": "609", + "account_type": "Stock Adjustment" + }, + "account_number": "60", + "account_type": "Expense Account" + }, + "Services et biens divers": { + "Personnel intérimaire et personnes mises à la disposition de l'entreprise": { + "account_number": "617", + "account_type": "Expense Account" + }, + "Rémunérations et pensions des administrateurs, gérants et associés actifs, hors contrat de travail": { + "account_number": "618", + "account_type": "Expense Account" + }, + "Frais accessoires d'achat inclus dans la valeur des stocks": { + "account_type": "Expenses Included In Valuation" + }, + "account_number": "61", + "account_type": "Expense Account" + }, + "Rémunérations, charges sociales et pensions": { + "Rémunérations et avantages sociaux directs": { + "Administrateurs ou gérants": { + "account_number": "6200", + "account_type": "Expense Account" + }, + "Personnel de direction": { + "account_number": "6201", + "account_type": "Expense Account" + }, + "Employés": { + "account_number": "6202", + "account_type": "Expense Account" + }, + "Ouvriers": { + "account_number": "6203", + "account_type": "Expense Account" + }, + "Autres membres du personnel": { + "account_number": "6204", + "account_type": "Expense Account" + }, + "account_number": "620", + "account_type": "Expense Account" + }, + "Cotisations patronales pour assurances sociales": { + "account_number": "621", + "account_type": "Expense Account" + }, + "Primes patronales pour assurances extra-légales": { + "account_number": "622", + "account_type": "Expense Account" + }, + "Autres frais du personnel": { + "account_number": "623", + "account_type": "Expense Account" + }, + "Pensions de retraite et de survie": { + "Administrateurs ou gérants": { + "account_number": "6240", + "account_type": "Expense Account" + }, + "Personnel": { + "account_number": "6241", + "account_type": "Expense Account" + }, + "account_number": "624", + "account_type": "Expense Account" + }, + "account_number": "62", + "account_type": "Expense Account" + }, + "Amortissements, réductions de valeur et provisions pour risques": { + "Dotations aux amortissements et aux réductions de valeur sur immobilisations": { + "Dotations aux amortissements sur frais d'établissement": { + "account_number": "6300", + "account_type": "Depreciation" + }, + "Dotation aux amortissements sur immobilisations incorporelles": { + "account_number": "6301", + "account_type": "Depreciation" + }, + "Dotation aux amortissements sur immobilisations corporelles": { + "account_number": "6302", + "account_type": "Depreciation" + }, + "Dotation aux réductions de valeur sur immobilisations incorporelles": { + "account_number": "6308", + "account_type": "Depreciation" + }, + "Dotation aux réductions de valeur sur immobilisations corporelles": { + "account_number": "6309", + "account_type": "Depreciation" + }, + "account_number": "630", + "account_type": "Depreciation" + }, + "Réductions de valeur sur stocks": { + "Dotations": { + "account_number": "6310", + "account_type": "Depreciation" + }, + "Reprises (-)": { + "account_number": "6311", + "account_type": "Depreciation" + }, + "account_number": "631", + "account_type": "Depreciation" + }, + "Réductions de valeur sur commandes en cours d'exécution": { + "Dotations": { + "account_number": "6320", + "account_type": "Depreciation" + }, + "Reprises (-)": { + "account_number": "6321", + "account_type": "Depreciation" + }, + "account_number": "632", + "account_type": "Depreciation" + }, + "Réductions de valeur sur créances commerciales à plus d'un an": { + "Dotations": { + "account_number": "6330", + "account_type": "Depreciation" + }, + "Reprises (-)": { + "account_number": "6331", + "account_type": "Depreciation" + }, + "account_number": "633", + "account_type": "Depreciation" + }, + "Réductions de valeur sur créances à un an au plus": { + "Dotations": { + "account_number": "6340", + "account_type": "Depreciation" + }, + "Reprises (-)": { + "account_number": "6341", + "account_type": "Depreciation" + }, + "account_number": "634", + "account_type": "Depreciation" + }, + "Provisions pour pensions et obligations similaires": { + "Dotations": { + "account_number": "6350", + "account_type": "Depreciation" + }, + "Utilisations et reprises (-)": { + "account_number": "6351", + "account_type": "Depreciation" + }, + "account_number": "635", + "account_type": "Depreciation" + }, + "Provisions pour grosses réparations et gros entretien": { + "Dotations": { + "account_number": "6360", + "account_type": "Depreciation" + }, + "Utilisations et reprises (-)": { + "account_number": "6361", + "account_type": "Depreciation" + }, + "account_number": "636", + "account_type": "Depreciation" + }, + "Provisions pour obligations environnementales": { + "Dotations": { + "account_number": "6370", + "account_type": "Depreciation" + }, + "Utilisations et reprises (-)": { + "account_number": "6371", + "account_type": "Depreciation" + }, + "account_number": "637", + "account_type": "Depreciation" + }, + "Provisions pour autres risques et charges": { + "Dotations": { + "account_number": "6380", + "account_type": "Expense Account" + }, + "Utilisations et reprises (-)": { + "account_number": "6381", + "account_type": "Expense Account" + }, + "account_number": "638", + "account_type": "Expense Account" + }, + "account_number": "63", + "account_type": "Expense Account" + }, + "Autres charges d'exploitation": { + "Charges fiscales d'exploitation": { + "account_number": "640", + "account_type": "Expense Account" + }, + "Moins-values sur réalisations courantes d'immobilisations corporelles": { + "account_number": "641", + "account_type": "Expense Account" + }, + "Moins-values sur réalisations de créances commerciales": { + "account_number": "642", + "account_type": "Expense Account" + }, + "Charges d'exploitations diverses (643 à 648)": { + "account_number": "643", + "account_type": "Expense Account" + }, + "Charges d'exploitation portées à l'actif au titre de frais de restructuration (-)": { + "account_number": "649", + "account_type": "Expense Account" + }, + "account_number": "64", + "account_type": "Expense Account" + }, + "Charges financières": { + "Charges des dettes": { + "Intérêts, commissions et frais afférents aux dettes": { + "account_number": "6500", + "account_type": "Expense Account" + }, + "Amortissements des frais d'émission d'emprunts": { + "account_number": "6501", + "account_type": "Expense Account" + }, + "Intérêts intercalaires portés à l'actif (-)": { + "account_number": "6502", + "account_type": "Expense Account" + }, + "account_number": "650", + "account_type": "Expense Account" + }, + "Réductions de valeur sur actifs circulants": { + "Dotations": { + "account_number": "6510", + "account_type": "Expense Account" + }, + "Reprises (-)": { + "account_number": "6511", + "account_type": "Expense Account" + }, + "account_number": "651", + "account_type": "Expense Account" + }, + "Moins-values sur réalisation d'actifs circulants": { + "account_number": "652", + "account_type": "Expense Account" + }, + "Charges d'escompte de créances": { + "account_number": "653", + "account_type": "Expense Account" + }, + "Différences de change": { + "account_number": "654", + "account_type": "Expense Account" + }, + "Écarts de conversion des devises": { + "account_number": "655", + "account_type": "Expense Account" + }, + "Provisions à caractère financier": { + "Dotations": { + "account_number": "6560", + "account_type": "Expense Account" + }, + "Utilisations et reprises (-)": { + "account_number": "6561", + "account_type": "Expense Account" + }, + "account_number": "656", + "account_type": "Expense Account" + }, + "Charges financières diverses (657 à 658)": { + "account_number": "657", + "account_type": "Expense Account" + }, + "Charges financières portées à l'actif au titre de frais de restructuration (-)": { + "account_number": "659", + "account_type": "Expense Account" + }, + "account_number": "65", + "account_type": "Expense Account" + }, + "Charges d'exploitation et charges financières non récurrentes": { + "Amortissements et réductions de valeur non récurrents (dotations)": { + "sur frais d'établissement": { + "account_number": "6600", + "account_type": "Expense Account" + }, + "sur immobilisations incorporelles": { + "account_number": "6601", + "account_type": "Expense Account" + }, + "sur immobilisations corporelles": { + "account_number": "6602", + "account_type": "Expense Account" + }, + "account_number": "660", + "account_type": "Expense Account" + }, + "Réduction de valeur sur immobilisations financières (dotation)": { + "account_number": "661", + "account_type": "Expense Account" + }, + "Provisions pour risques et charges non récurrents": { + "Provisions pour risques et charges d'exploitation non récurrents": { + "Dotations": { + "account_number": "66200", + "account_type": "Expense Account" + }, + "Utilisations (-)": { + "account_number": "66201", + "account_type": "Expense Account" + }, + "account_number": "6620", + "account_type": "Expense Account" + }, + "Provisions pour risques et charges financiers non récurrents": { + "Dotations": { + "account_number": "66210", + "account_type": "Expense Account" + }, + "Utilisations (-)": { + "account_number": "66211", + "account_type": "Expense Account" + }, + "account_number": "6621", + "account_type": "Expense Account" + }, + "account_number": "662", + "account_type": "Expense Account" + }, + "Moins-values sur réalisation d'actifs immobilisés": { + "Moins-values sur réalisation d'immobilisations incorporelles et corporelles": { + "account_number": "6630", + "account_type": "Expense Account" + }, + "Moins-values sur réalisation d'immobilisations financières": { + "account_number": "6631", + "account_type": "Expense Account" + }, + "account_number": "663", + "account_type": "Expense Account" + }, + "Autres charges d'exploitation non récurrentes (664 à 667)": { + "account_number": "664", + "account_type": "Expense Account" + }, + "Autres charges financières non récurrentes": { + "account_number": "668", + "account_type": "Expense Account" + }, + "Charges d'exploitation non récurrentes portées à l'actif au titre de frais de restructuration (-)": { + "account_number": "6690", + "account_type": "Expense Account" + }, + "Charges financières non récurrentes portées à l'actif au titre de frais de restructuration (-)": { + "account_number": "6691", + "account_type": "Expense Account" + }, + "account_number": "66", + "account_type": "Expense Account" + }, + "Impôts sur le résultat": { + "Impôts belges sur le résultat de l'exercice": { + "Impôts et précomptes dus ou versés": { + "account_number": "6700", + "account_type": "Expense Account" + }, + "Excédent de versements d'impôts et de précomptes porté à l'actif (-)": { + "account_number": "6701", + "account_type": "Expense Account" + }, + "Charges fiscales estimées": { + "account_number": "6702", + "account_type": "Expense Account" + }, + "account_number": "670", + "account_type": "Expense Account" + }, + "Impôts belges sur le résultat d'exercices antérieurs": { + "Suppléments d'impôts dus ou versés": { + "account_number": "6710", + "account_type": "Expense Account" + }, + "Suppléments d'impôts estimés": { + "account_number": "6711", + "account_type": "Expense Account" + }, + "Provisions fiscales constituées": { + "account_number": "6712", + "account_type": "Expense Account" + }, + "account_number": "671", + "account_type": "Expense Account" + }, + "Impôts étrangers sur le résultat de l'exercice": { + "account_number": "672", + "account_type": "Expense Account" + }, + "Impôts étrangers sur le résultat d'exercices antérieurs": { + "account_number": "673", + "account_type": "Expense Account" + }, + "account_number": "67", + "account_type": "Expense Account" + }, + "Transferts aux impôts différés et aux réserves immunisées": { + "Transferts aux impôts différés": { + "account_number": "680", + "account_type": "Expense Account" + }, + "Transferts aux réserves immunisées": { + "account_number": "689", + "account_type": "Expense Account" + }, + "account_number": "68", + "account_type": "Expense Account" + }, + "Affectations et prélèvements": { + "Perte reportée de l'exercice précédent": { + "account_number": "690", + "account_type": "Expense Account" + }, + "Affectations à l'apport": { + "account_number": "691", + "account_type": "Expense Account" + }, + "Dotation aux réserves": { + "Dotation à la réserve légale": { + "account_number": "6920", + "account_type": "Expense Account" + }, + "Dotation aux autres réserves": { + "account_number": "6921", + "account_type": "Expense Account" + }, + "account_number": "692", + "account_type": "Expense Account" + }, + "Bénéfices à reporter": { + "account_number": "693", + "account_type": "Expense Account" + }, + "Rémunération de l'apport": { + "account_number": "694", + "account_type": "Expense Account" + }, + "Administrateurs ou gérants": { + "account_number": "695", + "account_type": "Expense Account" + }, + "Employés": { + "account_number": "696", + "account_type": "Expense Account" + }, + "Autres applications": { + "account_number": "697", + "account_type": "Expense Account" + }, + "account_number": "69", + "account_type": "Expense Account" + } + }, + "CLASSE 7 : PRODUITS": { + "root_type": "Income", + "Chiffre d'affaires": { + "Remises, ristournes et rabais accordés (-)": { + "account_number": "708", + "account_type": "Income Account" + }, + "account_number": "70", + "account_type": "Income Account" + }, + "Variation des stocks et des commandes en cours d'exécution": { + "Des en-cours de fabrication": { + "account_number": "712", + "account_type": "Income Account" + }, + "Des produits finis": { + "account_number": "713", + "account_type": "Income Account" + }, + "Des immeubles construits destinés à la vente": { + "account_number": "715", + "account_type": "Income Account" + }, + "Des commandes en cours d'exécution": { + "Valeur d'acquisition": { + "account_number": "7170", + "account_type": "Income Account" + }, + "Bénéfice pris en compte": { + "account_number": "7171", + "account_type": "Income Account" + }, + "account_number": "717", + "account_type": "Income Account" + }, + "account_number": "71", + "account_type": "Income Account" + }, + "Production immobilisée": { + "account_number": "72", + "account_type": "Income Account" + }, + "Autres produits d'exploitation": { + "Subsides d'exploitation et montants compensatoires": { + "account_number": "740", + "account_type": "Income Account" + }, + "Plus-values sur réalisations courantes d'immobilisations corporelles": { + "account_number": "741", + "account_type": "Income Account" + }, + "Plus-values sur réalisation de créances commerciales": { + "account_number": "742", + "account_type": "Income Account" + }, + "Produits d'exploitation divers (743 à 749)": { + "account_number": "743", + "account_type": "Income Account" + }, + "account_number": "74", + "account_type": "Income Account" + }, + "Produits financiers": { + "Produits des immobilisations financières": { + "account_number": "750", + "account_type": "Income Account" + }, + "Produits des actifs circulants": { + "account_number": "751", + "account_type": "Income Account" + }, + "Plus-values sur réalisation d'actifs circulants": { + "account_number": "752", + "account_type": "Income Account" + }, + "Subsides en capital et en intérêts": { + "account_number": "753", + "account_type": "Income Account" + }, + "Différences de change": { + "account_number": "754", + "account_type": "Income Account" + }, + "Écarts de conversion des devises": { + "account_number": "755", + "account_type": "Income Account" + }, + "Produits financiers divers (756 à 759)": { + "account_number": "756", + "account_type": "Income Account" + }, + "account_number": "75", + "account_type": "Income Account" + }, + "Produits d'exploitation ou financiers non récurrents": { + "Reprises d'amortissements et de réductions de valeur": { + "sur immobilisations incorporelles": { + "account_number": "7600", + "account_type": "Income Account" + }, + "sur immobilisations corporelles": { + "account_number": "7601", + "account_type": "Income Account" + }, + "account_number": "760", + "account_type": "Income Account" + }, + "Reprises de réductions de valeur sur immobilisations financières": { + "account_number": "761", + "account_type": "Income Account" + }, + "Reprises de provisions pour risques et charges non récurrents": { + "Reprises de provisions pour risques et charges d'exploitation non récurrents": { + "account_number": "7620", + "account_type": "Income Account" + }, + "Reprises de provisions pour risques et charges financiers non récurrents": { + "account_number": "7621", + "account_type": "Income Account" + }, + "account_number": "762", + "account_type": "Income Account" + }, + "Plus-values sur réalisation d'actifs immobilisés": { + "Plus-values sur réalisation d'immobilisations incorporelles et corporelles": { + "account_number": "7630", + "account_type": "Income Account" + }, + "Plus-values sur réalisation d'immobilisations financières": { + "account_number": "7631", + "account_type": "Income Account" + }, + "account_number": "763", + "account_type": "Income Account" + }, + "Autres bénéfices d'exploitation non récurrents (764 à 768)": { + "account_number": "764", + "account_type": "Income Account" + }, + "Autres produits financiers non récurrents": { + "account_number": "769", + "account_type": "Income Account" + }, + "account_number": "76", + "account_type": "Income Account" + }, + "Régularisations d'impôts et reprises de provisions fiscales": { + "Impôts belges sur le résultat": { + "Régularisation d'impôts dus ou versés": { + "account_number": "7710", + "account_type": "Income Account" + }, + "Régularisation d'impôts estimés": { + "account_number": "7711", + "account_type": "Income Account" + }, + "Reprises de provisions fiscales": { + "account_number": "7712", + "account_type": "Income Account" + }, + "account_number": "771", + "account_type": "Income Account" + }, + "Impôts étrangers sur le résultat": { + "account_number": "773", + "account_type": "Income Account" + }, + "account_number": "77", + "account_type": "Income Account" + }, + "Prélèvements sur les réserves immunisées et les impôts différés": { + "Prélèvements sur les impôts différés": { + "account_number": "780", + "account_type": "Income Account" + }, + "Prélèvement sur les réserves immunisées": { + "account_number": "789", + "account_type": "Income Account" + }, + "account_number": "78", + "account_type": "Income Account" + }, + "Affectations et prélèvements": { + "Bénéfice reporté de l'exercice précédent": { + "account_number": "790", + "account_type": "Income Account" + }, + "Prélèvements sur l'apport": { + "account_number": "791", + "account_type": "Income Account" + }, + "Prélèvements sur les réserves": { + "account_number": "792", + "account_type": "Income Account" + }, + "Perte à reporter": { + "account_number": "793", + "account_type": "Income Account" + }, + "Intervention d'associés (ou du propriétaire) dans la perte": { + "account_number": "794", + "account_type": "Income Account" + }, + "account_number": "79", + "account_type": "Income Account" + } + } + } +} \ No newline at end of file From 89d3701e3ba677d3f339c2a5d2aafd059b116183 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 15 Aug 2026 08:02:23 +0000 Subject: [PATCH 26/59] fix: get items from sales order in sales invoice (backport #58163) (#58187) Co-authored-by: Mihir Kandoi --- erpnext/selling/doctype/sales_order/sales_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index a825d08cc15..583486960c1 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -1125,8 +1125,8 @@ def get_qty_net_of_returns(so_item) -> float: def make_sales_invoice( source_name: str, target_doc: str | dict | Document | None = None, - ignore_permissions: bool = False, args: str | dict | None = None, + ignore_permissions: bool = False, ): if args is None: args = {} From a5f4d3abebe40b76f6cbafcc1d7dc0b19f77675f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 15 Aug 2026 10:51:12 +0000 Subject: [PATCH 27/59] feat: validate purchase receipt exchange rate parity on purchase invoice (backport #58177) (#58189) * feat: validate purchase receipt exchange rate parity on purchase invoice (#58177) (cherry picked from commit 70a8a2d0c521c4295348446567da631066a0c552) # Conflicts: # erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py * chore: fix conflicts Removed assertion for exchange rate discrepancy in purchase invoice test. * test: fix backport of exchange rate difference test for non stock item The conflict resolution left behind stale amount/discrepancy lookups referencing a removed second item row (IndexError in CI and F841 ruff failures). Align the test with the develop version: single non stock item, PR at 80 / PI at 70, and assert no exchange gain/loss GL entry. Co-Authored-By: Claude Fable 5 --------- Co-authored-by: rohitwaghchaure Co-authored-by: Claude Fable 5 --- .../purchase_invoice/purchase_invoice.py | 42 +++++++++++++ .../purchase_invoice/test_purchase_invoice.py | 60 ++++++++----------- 2 files changed, 68 insertions(+), 34 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 79385db3a24..d023182e255 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -291,6 +291,7 @@ class PurchaseInvoice(BuyingController): self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount") self.set_status() self.validate_purchase_receipt_if_update_stock() + self.validate_exchange_rate_with_purchase_receipt() validate_inter_company_party( self.doctype, self.supplier, self.company, self.inter_company_invoice_reference ) @@ -313,6 +314,47 @@ class PurchaseInvoice(BuyingController): if total_billed_qty and total_received_qty: self.per_received = total_received_qty / total_billed_qty * 100 + def validate_exchange_rate_with_purchase_receipt(self): + if self.is_internal_transfer() or not erpnext.is_perpetual_inventory_enabled(self.company): + return + + stock_items = self.get_stock_items() + receipts = { + item.purchase_receipt + for item in self.items + if item.purchase_receipt and item.item_code in stock_items + } + if not receipts: + return + + if frappe.db.get_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate"): + return + + mismatched = [ + f"{frappe.bold(row.name)} ({row.conversion_rate})" + for row in frappe.get_all( + "Purchase Receipt", + filters={"name": ("in", list(receipts))}, + fields=["name", "currency", "conversion_rate"], + ) + if row.currency == self.currency + and flt(row.conversion_rate) + and flt(row.conversion_rate) != flt(self.conversion_rate) + ] + if not mismatched: + return + + frappe.throw( + _( + "Exchange rate {0} does not match the exchange rate of Purchase Receipt {1}. Use the same exchange rate as the Purchase Receipt or enable {2} in {3} to adjust the landed cost based on this invoice." + ).format( + frappe.bold(self.conversion_rate), + ", ".join(mismatched), + frappe.bold(_("Set Landed Cost Based on Purchase Invoice Rate")), + get_link_to_form("Buying Settings", "Buying Settings", _("Buying Settings")), + ) + ) + def validate_invoice_hold(self): if self.is_return: frappe.throw(_("Return Purchase Invoice cannot be held.")) diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 50d6f76e3dc..78bfcfd1550 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -513,6 +513,12 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): ) frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 0) + self.addCleanup( + frappe.db.set_single_value, + "Buying Settings", + "set_landed_cost_based_on_purchase_invoice_rate", + original_value, + ) pr = make_purchase_receipt( company="_Test Company with perpetual inventory", @@ -524,25 +530,15 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): pi = create_purchase_invoice(pr.name) pi.conversion_rate = 80 + self.assertRaises(frappe.ValidationError, pi.insert) + + pi.conversion_rate = 70 pi.insert() pi.submit() - # Get exchnage gain and loss account exchange_gain_loss_account = frappe.db.get_value("Company", pi.company, "exchange_gain_loss_account") - - # fetching the latest GL Entry with exchange gain and loss account account - amount = frappe.db.get_value( - "GL Entry", {"account": exchange_gain_loss_account, "voucher_no": pi.name}, "debit" - ) - - discrepancy_caused_by_exchange_rate_diff = abs( - pi.items[0].base_net_amount - pr.items[0].base_net_amount - ) - - self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount) - - frappe.db.set_single_value( - "Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", original_value + self.assertFalse( + frappe.db.exists("GL Entry", {"account": exchange_gain_loss_account, "voucher_no": pi.name}) ) def test_purchase_invoice_with_exchange_rate_difference_for_non_stock_item(self): @@ -550,11 +546,21 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): make_purchase_invoice as create_purchase_invoice, ) - # Creating Purchase Invoice with USD currency + original_value = frappe.db.get_single_value( + "Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate" + ) + frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 0) + self.addCleanup( + frappe.db.set_single_value, + "Buying Settings", + "set_landed_cost_based_on_purchase_invoice_rate", + original_value, + ) + pr = frappe.new_doc("Purchase Receipt") pr.currency = "USD" pr.company = "_Test Company with perpetual inventory" - pr.conversion_rate = (70,) + pr.conversion_rate = 80 pr.supplier = "_Test Supplier USD" pr.append( "items", @@ -564,34 +570,20 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): "rate": 100, }, ) - pr.append( - "items", - {"item_code": "_Test Item", "qty": 1, "rate": 5, "warehouse": "Stores - TCP1"}, - ) pr.insert() pr.submit() - # Createing purchase invoice against Purchase Receipt pi = create_purchase_invoice(pr.name) - pi.conversion_rate = 80 + pi.conversion_rate = 70 pi.credit_to = "_Test Payable USD - TCP1" pi.insert() pi.submit() - # Get exchnage gain and loss account exchange_gain_loss_account = frappe.db.get_value("Company", pi.company, "exchange_gain_loss_account") - - # fetching the latest GL Entry with exchange gain and loss account account - amount = frappe.db.get_value( - "GL Entry", {"account": exchange_gain_loss_account, "voucher_no": pi.name}, "debit" + self.assertFalse( + frappe.db.exists("GL Entry", {"account": exchange_gain_loss_account, "voucher_no": pi.name}) ) - discrepancy_caused_by_exchange_rate_diff = abs( - pi.items[1].base_net_amount - pr.items[1].base_net_amount - ) - - self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount) - def test_purchase_invoice_change_naming_series(self): pi = frappe.copy_doc(test_records[1]) pi.insert() From c6211eb075fc9cac9101ad483297297bb21bbfd7 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sat, 15 Aug 2026 16:47:02 +0000 Subject: [PATCH 28/59] fix(email_digest): added permission check for `get_msg_html` (backport #58197) (#58199) Co-authored-by: diptanilsaha --- erpnext/setup/doctype/email_digest/email_digest.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py index ea25bd033b9..eb01d7234d4 100644 --- a/erpnext/setup/doctype/email_digest/email_digest.py +++ b/erpnext/setup/doctype/email_digest/email_digest.py @@ -901,7 +901,9 @@ def send(): @frappe.whitelist() def get_digest_msg(name): - return frappe.get_doc("Email Digest", name).get_msg_html() + email_digest = frappe.get_doc("Email Digest", name) + email_digest.check_permission() + return email_digest.get_msg_html() def get_incomes_expenses_for_period(account, from_date, to_date): From 9e6a16658d8e6ac0d2168691ff5a2c2d97a383dd Mon Sep 17 00:00:00 2001 From: Shllokkk Date: Sun, 16 Aug 2026 15:34:10 +0530 Subject: [PATCH 29/59] feat: add status filter to Supplier Quotation Comparison report (cherry picked from commit 2b84ed78e8df27f1345e293b0bafff4822cb7922) --- .../supplier_quotation_comparison.js | 11 +++++++++++ .../supplier_quotation_comparison.py | 9 ++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js index 0df7e0787a9..5073459636e 100644 --- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js +++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js @@ -85,6 +85,17 @@ frappe.query_reports["Supplier Quotation Comparison"] = { ], default: __("Categorize by Supplier"), }, + { + fieldname: "status", + label: __("Status"), + fieldtype: "Select", + options: [ + { label: "", value: "" }, + { label: __("Draft"), value: "Draft" }, + { label: __("Submitted"), value: "Submitted" }, + ], + default: "Submitted", + }, { fieldtype: "Check", label: __("Include Expired"), diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py index 7c11dda7225..47ce00fa692 100644 --- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py +++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.py @@ -58,13 +58,20 @@ def get_data(filters): ) .where( (sq_item.parent == sq.name) - & (sq_item.docstatus < 2) & (sq.company == filters.get("company")) & (sq.transaction_date.between(filters.get("from_date"), filters.get("to_date"))) ) .orderby(sq.transaction_date, sq_item.item_code) ) + # blank -> Draft + Submitted, else filter to the chosen docstatus + if filters.get("status") == "Draft": + query = query.where(sq_item.docstatus == 0) + elif filters.get("status") == "Submitted": + query = query.where(sq_item.docstatus == 1) + else: + query = query.where(sq_item.docstatus < 2) + if filters.get("item_code"): query = query.where(sq_item.item_code == filters.get("item_code")) From 32a5b23b3c1b2ad13c0c36f566eb9a9b0964256f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 16 Aug 2026 17:22:06 +0530 Subject: [PATCH 30/59] fix: drop removed Restaurant doctype from sales tax template dashboard (backport #58191) (#58210) --- .../sales_taxes_and_charges_template_dashboard.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template_dashboard.py b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template_dashboard.py index 6432acaae93..fca17cc14ec 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template_dashboard.py +++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template_dashboard.py @@ -7,10 +7,9 @@ def get_data(): "non_standard_fieldnames": { "Tax Rule": "sales_tax_template", "Subscription": "sales_tax_template", - "Restaurant": "default_tax_template", }, "transactions": [ {"label": _("Transactions"), "items": ["Sales Invoice", "Sales Order", "Delivery Note"]}, - {"label": _("References"), "items": ["POS Profile", "Subscription", "Restaurant", "Tax Rule"]}, + {"label": _("References"), "items": ["POS Profile", "Subscription", "Tax Rule"]}, ], } From 46d883d00d81223715618328a0d650baec4f7afe Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 16 Aug 2026 18:42:58 +0530 Subject: [PATCH 31/59] fix: correct Item Group doctype name in item tax template dashboard (backport #58192) (#58212) --- .../doctype/item_tax_template/item_tax_template_dashboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/item_tax_template/item_tax_template_dashboard.py b/erpnext/accounts/doctype/item_tax_template/item_tax_template_dashboard.py index 5a2bd720dd3..58320b237c3 100644 --- a/erpnext/accounts/doctype/item_tax_template/item_tax_template_dashboard.py +++ b/erpnext/accounts/doctype/item_tax_template/item_tax_template_dashboard.py @@ -8,6 +8,6 @@ def get_data(): {"label": _("Pre Sales"), "items": ["Quotation", "Supplier Quotation"]}, {"label": _("Sales"), "items": ["Sales Invoice", "Sales Order", "Delivery Note"]}, {"label": _("Purchase"), "items": ["Purchase Invoice", "Purchase Order", "Purchase Receipt"]}, - {"label": _("Stock"), "items": ["Item Groups", "Item"]}, + {"label": _("Stock"), "items": ["Item Group", "Item"]}, ], } From 0c625ff69ba9b8c623e9b8feaa61ad25dc8d3b37 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 16 Aug 2026 14:21:44 +0000 Subject: [PATCH 32/59] fix(crm)!: remove unused `get_last_interaction` endpoint (backport #58214) (#58215) Co-authored-by: Diptanil Saha --- erpnext/crm/doctype/utils.py | 47 ------------------------------------ 1 file changed, 47 deletions(-) diff --git a/erpnext/crm/doctype/utils.py b/erpnext/crm/doctype/utils.py index cacc5a16607..f3230e36568 100644 --- a/erpnext/crm/doctype/utils.py +++ b/erpnext/crm/doctype/utils.py @@ -1,53 +1,6 @@ import frappe -@frappe.whitelist() -def get_last_interaction(contact=None, lead=None): - if not contact and not lead: - return - - last_communication = None - last_issue = None - if contact: - query_condition = "" - values = [] - contact = frappe.get_doc("Contact", contact) - for link in contact.links: - if link.link_doctype == "Customer": - last_issue = get_last_issue_from_customer(link.link_name) - query_condition += "(`reference_doctype`=%s AND `reference_name`=%s) OR" - values += [link.link_doctype, link.link_name] - - if query_condition: - # remove extra appended 'OR' - query_condition = query_condition[:-2] - last_communication = frappe.db.sql( - f""" - SELECT `name`, `content` - FROM `tabCommunication` - WHERE `sent_or_received`='Received' - AND ({query_condition}) - ORDER BY `modified` - LIMIT 1 - """, - values, - as_dict=1, - ) # nosec - - if lead: - last_communication = frappe.get_all( - "Communication", - filters={"reference_doctype": "Lead", "reference_name": lead, "sent_or_received": "Received"}, - fields=["name", "content"], - order_by="`creation` DESC", - limit=1, - ) - - last_communication = last_communication[0] if last_communication else None - - return {"last_communication": last_communication, "last_issue": last_issue} - - def get_last_issue_from_customer(customer_name): issues = frappe.get_all( "Issue", From 1464a34fc6895eb1e9e3e71e0f7695fc5cbaadaf Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 16 Aug 2026 15:11:25 +0000 Subject: [PATCH 33/59] fix(bank_statement_import): add missing permission check on `get_import_status` (backport #58217) (#58218) Co-authored-by: Diptanil Saha --- .../doctype/bank_statement_import/bank_statement_import.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py index aefaf8c12fe..db68b6d894d 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py @@ -252,6 +252,7 @@ def get_import_status(docname): import_status = {} data_import = frappe.get_doc("Bank Statement Import", docname) + data_import.check_permission() import_status["status"] = data_import.status logs = frappe.get_all( From 48bd3139f350e1fc7aa004285b1a5feb751bfd0f Mon Sep 17 00:00:00 2001 From: Pandiyan P Date: Mon, 17 Aug 2026 15:26:05 +0530 Subject: [PATCH 34/59] =?UTF-8?q?fix(manufacturing):=20fall=20back=20to=20?= =?UTF-8?q?item=20group=20defaults=20for=20work=20order=20w=E2=80=A6=20(#5?= =?UTF-8?q?8236)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../doctype/work_order/work_order.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index ae3520ab222..da26f87a9a1 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -34,6 +34,7 @@ from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings from erpnext.manufacturing.doctype.work_order.services.material_coverage import ( get_minimum_material_coverage_fraction, ) +from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults from erpnext.stock.doctype.batch.batch import make_batch from erpnext.stock.doctype.item.item import get_item_defaults, validate_end_of_life from erpnext.stock.doctype.serial_no.serial_no import get_available_serial_nos, get_serial_nos @@ -315,7 +316,18 @@ class WorkOrder(Document): if not self.wip_warehouse and not self.skip_transfer: self.wip_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_wip_warehouse") if not self.fg_warehouse: - self.fg_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_fg_warehouse") + self.fg_warehouse = ( + frappe.db.get_single_value("Manufacturing Settings", "default_fg_warehouse") + or self.get_production_item_warehouse() + ) + + def get_production_item_warehouse(self): + if not self.production_item: + return None + + return get_item_defaults(self.production_item, self.company).get( + "default_warehouse" + ) or get_item_group_defaults(self.production_item, self.company).get("default_warehouse") def check_wip_warehouse_skip(self): if self.skip_transfer and not self.from_wip_warehouse: @@ -1232,7 +1244,10 @@ class WorkOrder(Document): "description": item.description, "allow_alternative_item": item.allow_alternative_item, "required_qty": item.qty, - "source_warehouse": item.source_warehouse or item.default_warehouse, + "source_warehouse": item.source_warehouse + or item.default_warehouse + or self.source_warehouse + or get_item_group_defaults(item.item_code, self.company).get("default_warehouse"), "include_item_in_manufacturing": item.include_item_in_manufacturing, }, ) From 73b7ec32b697708a0594ae8066f3269ad96b8760 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Mon, 17 Aug 2026 18:13:13 +0530 Subject: [PATCH 35/59] fix: don't set work order status to In Process only due to skip material transfer (#58246) --- erpnext/manufacturing/doctype/work_order/work_order.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index da26f87a9a1..3397e7940de 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -427,11 +427,7 @@ class WorkOrder(Document): elif self.docstatus == 1: if status not in ["Closed", "Stopped"]: status = "Not Started" - if ( - flt(self.material_transferred_for_manufacturing) > 0 - or self.skip_transfer - or self.has_transferred_material() - ): + if flt(self.material_transferred_for_manufacturing) > 0 or self.has_transferred_material(): status = "In Process" precision = frappe.get_precision("Work Order", "produced_qty") From b03e098684376c26b77a9e189e48a2039d443cd8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 18 Aug 2026 12:07:43 +0200 Subject: [PATCH 36/59] fix: mirror rounding adjustment on distributed_discount_amount (backport #58047) (#58054) Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> --- erpnext/controllers/taxes_and_totals.py | 3 ++- .../tests/test_distributed_discount.py | 26 ++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 69a0e05ea60..aa36cab6f8d 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -826,8 +826,9 @@ class calculate_taxes_and_totals: item.net_amount = flt( item.net_amount + rounding_difference, item.precision("net_amount") ) + # net_amount went up by rounding_difference, so its discount share goes down item.distributed_discount_amount = flt( - distributed_amount + rounding_difference, + distributed_amount - rounding_difference, item.precision("distributed_discount_amount"), ) net_total += rounding_difference diff --git a/erpnext/controllers/tests/test_distributed_discount.py b/erpnext/controllers/tests/test_distributed_discount.py index 74ae69c1750..d87ea1b07b0 100644 --- a/erpnext/controllers/tests/test_distributed_discount.py +++ b/erpnext/controllers/tests/test_distributed_discount.py @@ -1,4 +1,4 @@ -from frappe.tests.utils import FrappeTestCase +from frappe.tests.utils import FrappeTestCase, change_settings from erpnext.accounts.test.accounts_mixin import AccountsTestMixin from erpnext.controllers.taxes_and_totals import calculate_taxes_and_totals @@ -60,6 +60,30 @@ class TestTaxesAndTotals(AccountsTestMixin, FrappeTestCase): self.assertAlmostEqual(so.net_total, 1272.73, places=2) self.assertEqual(so.grand_total, 1400) + @change_settings("Selling Settings", {"allow_multiple_items": 1}) + def test_distributed_discount_amount_with_rounding_adjustment(self): + so = make_sales_order(do_not_save=1) + so.apply_discount_on = "Net Total" + so.discount_amount = 10 + so.items[0].qty = 1 + so.items[0].rate = 100 + so.append("items", so.items[0].as_dict()) + so.append("items", so.items[0].as_dict()) + so.save() + + calculate_taxes_and_totals(so) + + # the rounding adjustment lands on the second line + self.assertAlmostEqual(so.items[1].net_amount, 96.66, places=2) + self.assertAlmostEqual(so.items[1].distributed_discount_amount, 3.34, places=2) + + for item in so.items: + self.assertAlmostEqual(item.amount - item.distributed_discount_amount, item.net_amount, places=2) + self.assertAlmostEqual( + sum(i.distributed_discount_amount for i in so.items), so.discount_amount, places=2 + ) + self.assertEqual(so.net_total, 290) + def test_100_percent_discount_with_inclusive_tax(self): """Test that 100% discount with inclusive taxes results in zero net_total""" so = make_sales_order(do_not_save=1) From 37f2770809c3c69684efc6fe5b74acc9ad480b84 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 00:35:45 +0530 Subject: [PATCH 37/59] fix: escape interpolated values in portal, print and desk templates (backport #58273) (#58277) Co-authored-by: diptanilsaha --- erpnext/manufacturing/doctype/bom/bom.js | 4 +++- .../doctype/bom/bom_item_preview.html | 4 ++-- .../doctype/work_order/work_order_preview.html | 4 ++-- .../doctype/workstation/workstation_job_card.html | 8 ++++---- .../production_plan_summary.js | 5 ++++- .../doctype/project/project_dashboard.html | 2 +- erpnext/public/js/templates/call_link.html | 4 ++-- erpnext/public/js/templates/crm_activities.html | 8 ++++---- erpnext/stock/doctype/item/item.js | 5 ++++- erpnext/stock/doctype/shipment/shipment.js | 5 ++++- .../templates/form_grid/includes/visible_cols.html | 2 +- erpnext/templates/form_grid/item_grid.html | 12 ++++++------ .../templates/form_grid/material_request_grid.html | 8 ++++---- erpnext/templates/form_grid/stock_entry_grid.html | 10 +++++----- erpnext/templates/generators/sales_partner.html | 4 ++-- erpnext/templates/includes/macros.html | 10 +++++----- .../templates/includes/projects/project_row.html | 4 ++-- .../templates/includes/projects/project_tasks.html | 4 ++-- .../includes/projects/project_timesheets.html | 4 ++-- erpnext/templates/includes/rfq.js | 8 ++++---- erpnext/templates/includes/transaction_row.html | 2 +- erpnext/templates/pages/help.html | 14 +++++++------- erpnext/templates/pages/order.html | 2 +- erpnext/templates/pages/partners.html | 8 ++++---- erpnext/templates/pages/projects.html | 2 +- erpnext/templates/pages/projects.js | 6 +++--- .../includes/item_table_description.html | 2 +- erpnext/www/support/index.html | 4 ++-- 28 files changed, 83 insertions(+), 72 deletions(-) diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 88a6c3bad66..4608cbcb6cf 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -173,7 +173,9 @@ frappe.ui.form.on("BOM", { frm.set_intro( __("This is a Template BOM and will be used to make the work order for {0} of the item {1}", [ `variants`, - `${frm.doc.item}`, + `${frappe.utils.escape_html( + frm.doc.item + )}`, ]), true ); diff --git a/erpnext/manufacturing/doctype/bom/bom_item_preview.html b/erpnext/manufacturing/doctype/bom/bom_item_preview.html index 2c0f091da58..15d121e5caf 100644 --- a/erpnext/manufacturing/doctype/bom/bom_item_preview.html +++ b/erpnext/manufacturing/doctype/bom/bom_item_preview.html @@ -17,11 +17,11 @@

{% if data.value && data.value != "BOM" %} - + {{ __("Open BOM {0}", [data.value.bold()]) }} {% endif %} {% if data.item_code %} - + {{ __("Open Item {0}", [data.item_code.bold()]) }} {% endif %}

diff --git a/erpnext/manufacturing/doctype/work_order/work_order_preview.html b/erpnext/manufacturing/doctype/work_order/work_order_preview.html index 95bdd291ee0..112c8d8250e 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order_preview.html +++ b/erpnext/manufacturing/doctype/work_order/work_order_preview.html @@ -20,11 +20,11 @@

{% if data.value %} - + {{ __("Open Work Order {0}", [data.value.bold()]) }} {% endif %} {% if data.item_code %} - + {{ __("Open Item {0}", [data.item_code.bold()]) }} {% endif %}

diff --git a/erpnext/manufacturing/doctype/workstation/workstation_job_card.html b/erpnext/manufacturing/doctype/workstation/workstation_job_card.html index 97707855db0..db3d3bffcc3 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation_job_card.html +++ b/erpnext/manufacturing/doctype/workstation/workstation_job_card.html @@ -11,7 +11,7 @@
{% $.each(data, (idx, d) => { %} - @@ -77,7 +77,7 @@
{% if(d.make_material_request) { %}
- +
{% } %} diff --git a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.js b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.js index 0e89a25c228..8536ccd1993 100644 --- a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.js +++ b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.js @@ -23,7 +23,10 @@ frappe.query_reports["Production Plan Summary"] = { if (column.fieldname == "item_code") { var color = data.pending_qty > 0 ? "red" : "green"; - value = `${data["item_code"]}`; + value = `${frappe.utils.escape_html(data["item_code"])}`; } return value; diff --git a/erpnext/projects/doctype/project/project_dashboard.html b/erpnext/projects/doctype/project/project_dashboard.html index 1f299e30833..d8467317582 100644 --- a/erpnext/projects/doctype/project/project_dashboard.html +++ b/erpnext/projects/doctype/project/project_dashboard.html @@ -3,7 +3,7 @@ {% for d in data %}
+ name="{{ frappe.utils.escape_html(tasks[i].name) }}" title="{{ __('Mark As Closed') }}">
{% if(tasks[i].date) { %} @@ -73,13 +73,13 @@ - + {%= events[i].subject %}
+ name="{{ frappe.utils.escape_html(events[i].name) }}" title="{{ __('Mark As Closed') }}">
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 47dd2f50656..cfdb4d04250 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -188,7 +188,10 @@ frappe.ui.form.on("Item", { if (frm.doc.variant_of) { frm.set_intro( __("This Item is a Variant of {0} (Template).", [ - `${frm.doc.variant_of}`, + `${frappe.utils.escape_html(frm.doc.variant_of)}`, ]), true ); diff --git a/erpnext/stock/doctype/shipment/shipment.js b/erpnext/stock/doctype/shipment/shipment.js index f22139c89f5..9c86fefc183 100644 --- a/erpnext/stock/doctype/shipment/shipment.js +++ b/erpnext/stock/doctype/shipment/shipment.js @@ -174,7 +174,10 @@ frappe.ui.form.on("Shipment", { __("Email or Phone/Mobile of the Contact are mandatory to continue.") + "
" + __("Please set Email/Phone for the contact") + - ` ${contact_name}` + ` ${frappe.utils.escape_html(contact_name)}` ); } let contact_display = r.message.contact_display; diff --git a/erpnext/templates/form_grid/includes/visible_cols.html b/erpnext/templates/form_grid/includes/visible_cols.html index 3a0cc8d7239..578335ddcd4 100644 --- a/erpnext/templates/form_grid/includes/visible_cols.html +++ b/erpnext/templates/form_grid/includes/visible_cols.html @@ -3,7 +3,7 @@ if((df.fieldname !== "description" && df.fieldname !== "item_name") && val) { %}
- {%= __(df.label) %}: + {%= frappe.utils.escape_html(__(df.label, null, df.parent)) %}:
{%= doc.get_formatted(df.fieldname) %} diff --git a/erpnext/templates/form_grid/item_grid.html b/erpnext/templates/form_grid/item_grid.html index 72db6c8e653..fac31c7c1a1 100644 --- a/erpnext/templates/form_grid/item_grid.html +++ b/erpnext/templates/form_grid/item_grid.html @@ -31,7 +31,7 @@ } %} - {%= doc.warehouse %} + {%= frappe.utils.escape_html(doc.warehouse) %} {% } %} @@ -42,14 +42,14 @@ doc.delivered_qty : doc.received_qty; var pending = flt(doc.qty) - flt(delivered); %} - {%= doc.item_code %} + {%= frappe.utils.escape_html(doc.item_code) %} {% } else { %} - {%= doc.item_code %} + {%= frappe.utils.escape_html(doc.item_code) %} {% } %} {% if(doc.item_name != doc.item_code && in_list(visible_column_fieldnames, "item_name")) { %} {% if (doc.item_code) { %}
{% } %} - {%= doc.item_name %}{% } %} + {%= frappe.utils.escape_html(doc.item_name) %}{% } %} {% include "templates/form_grid/includes/visible_cols.html" %}
@@ -57,7 +57,7 @@ @@ -84,7 +84,7 @@ {% if (frappe.perm.is_visible("rate", doc, frm.perm)) { %}
- {%= doc.get_formatted("qty") %} {%= doc.uom || doc.stock_uom %} + {%= doc.get_formatted("qty") %} {%= frappe.utils.escape_html(doc.uom || doc.stock_uom) %} x {%= doc.get_formatted("rate") %}
{% } %} diff --git a/erpnext/templates/form_grid/material_request_grid.html b/erpnext/templates/form_grid/material_request_grid.html index 866c06e5863..ff58da62ab5 100644 --- a/erpnext/templates/form_grid/material_request_grid.html +++ b/erpnext/templates/form_grid/material_request_grid.html @@ -11,9 +11,9 @@ {% } else { %}
- {%= doc.item_code %} + {%= frappe.utils.escape_html(doc.item_code) %} {% if(doc.item_name != doc.item_code) { %} -
{%= doc.item_name %}{% } %} +
{%= frappe.utils.escape_html(doc.item_name) %}{% } %} {% include "templates/form_grid/includes/visible_cols.html" %} @@ -35,7 +35,7 @@ {% if(doc.warehouse) { %} - {%= doc.warehouse %} + {%= frappe.utils.escape_html(doc.warehouse) %} {% } %}
@@ -43,7 +43,7 @@
{%= doc.get_formatted("qty") %} - {%= doc.uom || doc.stock_uom %} + {%= frappe.utils.escape_html(doc.uom || doc.stock_uom) %}
{% } %} diff --git a/erpnext/templates/form_grid/stock_entry_grid.html b/erpnext/templates/form_grid/stock_entry_grid.html index 8604881812d..d0afa926c50 100644 --- a/erpnext/templates/form_grid/stock_entry_grid.html +++ b/erpnext/templates/form_grid/stock_entry_grid.html @@ -12,9 +12,9 @@
{% } else { %}
-
{%= doc.item_code %} +
{%= frappe.utils.escape_html(doc.item_code) %} {% if(doc.item_name != doc.item_code) { %} -
{%= doc.item_name %}{% } %} +
{%= frappe.utils.escape_html(doc.item_name) %}{% } %} {% include "templates/form_grid/includes/visible_cols.html" %}
@@ -30,11 +30,11 @@ } %} - {%= doc.s_warehouse %} + {%= frappe.utils.escape_html(doc.s_warehouse) %} {% }; %} {% if(doc.t_warehouse) { %}
- {%= doc.t_warehouse %} + {%= frappe.utils.escape_html(doc.t_warehouse) %}
{% }; %}
@@ -42,7 +42,7 @@
{%= doc.get_formatted("qty") %} -
{%= doc.uom || doc.stock_uom %} +
{%= frappe.utils.escape_html(doc.uom || doc.stock_uom) %}
diff --git a/erpnext/templates/generators/sales_partner.html b/erpnext/templates/generators/sales_partner.html index 39138d3c6cc..8c9e75fdf57 100644 --- a/erpnext/templates/generators/sales_partner.html +++ b/erpnext/templates/generators/sales_partner.html @@ -9,8 +9,8 @@
{% if logo -%} - +

{%- endif %}
diff --git a/erpnext/templates/includes/macros.html b/erpnext/templates/includes/macros.html index dc9ee234d9e..5945d487d29 100644 --- a/erpnext/templates/includes/macros.html +++ b/erpnext/templates/includes/macros.html @@ -2,7 +2,7 @@
{% endmacro %} @@ -10,7 +10,7 @@ {% macro product_image(website_image, css_class="product-image", alt="", no_border=False) %}
{% if website_image %} - {{ alt }} + {{ alt | e }} {% else %}
{{ frappe.utils.get_abbr(alt) or "NA" }} @@ -22,11 +22,11 @@ {% macro media_image(website_image, name, css_class="") %} {% endif %}
- Link + Link
diff --git a/erpnext/templates/pages/help.html b/erpnext/templates/pages/help.html index 726d5e1b881..7e11bcb7d54 100644 --- a/erpnext/templates/pages/help.html +++ b/erpnext/templates/pages/help.html @@ -17,12 +17,12 @@ {% for section in get_started_sections %}
-

{{ section["name"] }}

+

{{ section["name"] | e }}

{% for item in section["items"] %}
- {{ item.title }} + {{ item.title | e }} {% if item.description -%} -

{{ item.description }}

+

{{ item.description | e }}

{%- endif %}
{% endfor %} @@ -35,15 +35,15 @@

{{ _("Forum Activity") }}

{% for topic in topics %}
- - {{ topic[post_params.title] }} + + {{ topic[post_params.title] | e }} {% if topic[post_params.description] -%} -

{{ topic[post_params.description] }}

+

{{ topic[post_params.description] | e }}

{%- endif %}
{% endfor %} -

{{ _("Visit the forums") }}

+

{{ _("Visit the forums") }}


diff --git a/erpnext/templates/pages/order.html b/erpnext/templates/pages/order.html index 5563a58b730..3107209a75e 100644 --- a/erpnext/templates/pages/order.html +++ b/erpnext/templates/pages/order.html @@ -151,7 +151,7 @@ {% if doc.terms %}

-

{{ doc.terms }}

+

{{ frappe.sanitize_html(doc.terms) }}

{% endif %} {% endblock %} diff --git a/erpnext/templates/pages/partners.html b/erpnext/templates/pages/partners.html index 72d6a6478f7..71962b90afa 100644 --- a/erpnext/templates/pages/partners.html +++ b/erpnext/templates/pages/partners.html @@ -10,14 +10,14 @@
{% if partner_info.logo -%} - - + + {%- endif %}
- +

{{ partner_info.partner_name }}

{{ partner_info.territory }} - {{ partner_info.partner_type }}

diff --git a/erpnext/templates/pages/projects.html b/erpnext/templates/pages/projects.html index e671e91db2f..10466a6cb2f 100644 --- a/erpnext/templates/pages/projects.html +++ b/erpnext/templates/pages/projects.html @@ -34,7 +34,7 @@ {% if doc.tasks %} diff --git a/erpnext/templates/pages/projects.js b/erpnext/templates/pages/projects.js index 622ed42f85f..24c16268e61 100644 --- a/erpnext/templates/pages/projects.js +++ b/erpnext/templates/pages/projects.js @@ -45,7 +45,7 @@ frappe.ready(function () { dataType: "json", data: { cmd: "erpnext.templates.pages.projects.get_" + item + "_html", - project: "{{ doc.name }}", + project: "{{ doc.name | e }}", item_status: item_status, }, success: function (data) { @@ -80,7 +80,7 @@ frappe.ready(function () { dataType: "json", data: { cmd: "erpnext.templates.pages.projects.get_" + item + "_html", - project: "{{ doc.name }}", + project: "{{ doc.name | e }}", start: start, item_status: item_status, }, @@ -96,7 +96,7 @@ frappe.ready(function () { var close_item = function (item, item_name) { var args = { - project: "{{ doc.name }}", + project: "{{ doc.name | e }}", item_name: item_name, }; frappe.call({ diff --git a/erpnext/templates/print_formats/includes/item_table_description.html b/erpnext/templates/print_formats/includes/item_table_description.html index 7569e50b45c..4ad3d6dd1d1 100644 --- a/erpnext/templates/print_formats/includes/item_table_description.html +++ b/erpnext/templates/print_formats/includes/item_table_description.html @@ -5,7 +5,7 @@ {% if doc.in_format_data("image") and doc.get("image") and "image" in display_columns -%}
- +
{%- endif %} diff --git a/erpnext/www/support/index.html b/erpnext/www/support/index.html index 3c19198cc16..6702e57cae7 100644 --- a/erpnext/www/support/index.html +++ b/erpnext/www/support/index.html @@ -53,7 +53,7 @@

{{ favorite_article['title'] }}

{{ favorite_article['description'] }}

- +
{% endfor %} @@ -72,7 +72,7 @@
{{ item['category'].name }}
{% for article in item['articles'] %} - {{ article.title }} + {{ article.title }} {% endfor %}
From d047caf4aa131363fc0e940ef7652c09392c2124 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 18 Aug 2026 20:27:31 +0000 Subject: [PATCH 38/59] fix: escape on status image for workstations in production status (backport #58279) (#58280) Co-authored-by: Diptanil Saha --- erpnext/manufacturing/doctype/workstation/workstation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/manufacturing/doctype/workstation/workstation.py b/erpnext/manufacturing/doctype/workstation/workstation.py index ac7b5f043e2..dfdd656bccd 100644 --- a/erpnext/manufacturing/doctype/workstation/workstation.py +++ b/erpnext/manufacturing/doctype/workstation/workstation.py @@ -413,7 +413,7 @@ def get_workstations(**kwargs): for d in data: d.workstation_name = get_link_to_form("Workstation", d.name) - d.status_image = d.on_status_image + d.status_image = frappe.utils.escape_html(d.on_status_image) d.background_color = color_map.get(d.status, "var(--red-600)") d.workstation_link = get_url_to_form("Workstation", d.name) if d.status != "Production": From 1210c6187dbc049ff927627358a98cf7835c621d Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 02:44:35 +0530 Subject: [PATCH 39/59] fix: escape interpolated values in text positions across portal and desk templates (backport #58286) (#58287) Co-authored-by: diptanilsaha --- erpnext/public/js/templates/crm_activities.html | 2 +- erpnext/templates/generators/sales_partner.html | 6 +++--- erpnext/templates/includes/projects/project_row.html | 2 +- erpnext/templates/includes/projects/project_tasks.html | 2 +- erpnext/templates/includes/transaction_row.html | 2 +- erpnext/templates/pages/order.html | 4 ++-- erpnext/templates/pages/partners.html | 2 +- erpnext/templates/pages/projects.html | 4 ++-- erpnext/templates/pages/projects.js | 6 +++--- erpnext/www/support/index.html | 8 ++++---- 10 files changed, 19 insertions(+), 19 deletions(-) diff --git a/erpnext/public/js/templates/crm_activities.html b/erpnext/public/js/templates/crm_activities.html index 8699ee596e1..c6561dabf03 100644 --- a/erpnext/public/js/templates/crm_activities.html +++ b/erpnext/public/js/templates/crm_activities.html @@ -74,7 +74,7 @@ - {%= events[i].subject %} + {%= frappe.utils.escape_html(events[i].subject) %}
diff --git a/erpnext/templates/generators/sales_partner.html b/erpnext/templates/generators/sales_partner.html index 8c9e75fdf57..0cd886f1ce7 100644 --- a/erpnext/templates/generators/sales_partner.html +++ b/erpnext/templates/generators/sales_partner.html @@ -16,9 +16,9 @@
{% if partner_website -%}

{{ partner_website }}

{%- endif %} - {% if partner_address -%}

{{ partner_address }}

{%- endif %} - {% if phone -%}

{{ phone }}

{%- endif %} - {% if email -%}

{{ email }}

{%- endif %} + {% if partner_address -%}

{{ partner_address | e }}

{%- endif %} + {% if phone -%}

{{ phone | e }}

{%- endif %} + {% if email -%}

{{ email | e }}

{%- endif %}
diff --git a/erpnext/templates/includes/projects/project_row.html b/erpnext/templates/includes/projects/project_row.html index b0a9a276f2a..1d131314d9f 100644 --- a/erpnext/templates/includes/projects/project_row.html +++ b/erpnext/templates/includes/projects/project_row.html @@ -6,7 +6,7 @@ {{ doc.name }}
- {{ doc.project_name }} + {{ doc.project_name | e }}
{% if doc.percent_complete %} diff --git a/erpnext/templates/includes/projects/project_tasks.html b/erpnext/templates/includes/projects/project_tasks.html index 132fc7cf625..32719adf71d 100644 --- a/erpnext/templates/includes/projects/project_tasks.html +++ b/erpnext/templates/includes/projects/project_tasks.html @@ -8,7 +8,7 @@ {% endif %} - {{ task.subject }} + {{ task.subject | e }}
{{ task.status }}
diff --git a/erpnext/templates/includes/transaction_row.html b/erpnext/templates/includes/transaction_row.html index e8f71391b1c..03e04a3a1e8 100644 --- a/erpnext/templates/includes/transaction_row.html +++ b/erpnext/templates/includes/transaction_row.html @@ -12,7 +12,7 @@
- {{ doc.items_preview }} + {{ doc.items_preview | e }}
{% if doc.is_rounded_total_disabled() and doc.get('grand_total') %} diff --git a/erpnext/templates/pages/order.html b/erpnext/templates/pages/order.html index 3107209a75e..ec9d75e350a 100644 --- a/erpnext/templates/pages/order.html +++ b/erpnext/templates/pages/order.html @@ -73,11 +73,11 @@
{%- set party_name = doc.supplier_name if doc.doctype in ['Supplier Quotation', 'Purchase Invoice', 'Purchase Order'] else doc.customer_name %} - {{ party_name }} + {{ party_name | e }} {% if doc.contact_display and doc.contact_display != party_name %}
- {{ doc.contact_display }} + {{ doc.contact_display | e }} {% endif %}
diff --git a/erpnext/templates/pages/partners.html b/erpnext/templates/pages/partners.html index 71962b90afa..5b6ae2c5670 100644 --- a/erpnext/templates/pages/partners.html +++ b/erpnext/templates/pages/partners.html @@ -21,7 +21,7 @@

{{ partner_info.partner_name }}

{{ partner_info.territory }} - {{ partner_info.partner_type }}

-

{{ partner_info.introduction }}

+

{{ partner_info.introduction | e }}


diff --git a/erpnext/templates/pages/projects.html b/erpnext/templates/pages/projects.html index 10466a6cb2f..2b55679fa71 100644 --- a/erpnext/templates/pages/projects.html +++ b/erpnext/templates/pages/projects.html @@ -1,7 +1,7 @@ {% extends "templates/web.html" %} {% block title %} - {{ doc.project_name }} + {{ doc.project_name | e }} {% endblock %} {% block head_include %} @@ -9,7 +9,7 @@ {% endblock %} {% block header %} - + {% endblock %} {% block style %} diff --git a/erpnext/templates/pages/projects.js b/erpnext/templates/pages/projects.js index 24c16268e61..bf037bcf6a5 100644 --- a/erpnext/templates/pages/projects.js +++ b/erpnext/templates/pages/projects.js @@ -45,7 +45,7 @@ frappe.ready(function () { dataType: "json", data: { cmd: "erpnext.templates.pages.projects.get_" + item + "_html", - project: "{{ doc.name | e }}", + project: frappe.utils.get_url_arg("project"), item_status: item_status, }, success: function (data) { @@ -80,7 +80,7 @@ frappe.ready(function () { dataType: "json", data: { cmd: "erpnext.templates.pages.projects.get_" + item + "_html", - project: "{{ doc.name | e }}", + project: frappe.utils.get_url_arg("project"), start: start, item_status: item_status, }, @@ -96,7 +96,7 @@ frappe.ready(function () { var close_item = function (item, item_name) { var args = { - project: "{{ doc.name | e }}", + project: frappe.utils.get_url_arg("project"), item_name: item_name, }; frappe.call({ diff --git a/erpnext/www/support/index.html b/erpnext/www/support/index.html index 6702e57cae7..d2625cd55ad 100644 --- a/erpnext/www/support/index.html +++ b/erpnext/www/support/index.html @@ -49,9 +49,9 @@
- {{ favorite_article['category'] }}
-

{{ favorite_article['title'] }}

-

{{ favorite_article['description'] }}

+ {{ favorite_article['category'] | e }} +

{{ favorite_article['title'] | e }}

+

{{ favorite_article['description'] | e }}

@@ -72,7 +72,7 @@
{{ item['category'].name }}
{% for article in item['articles'] %} - {{ article.title }} + {{ article.title | e }} {% endfor %}
From 74c3eeaa44a939f42af542f032a3a7ef65a3db75 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 10:25:47 +0530 Subject: [PATCH 40/59] fix: use user data fields hook (backport #58274) (#58282) Co-authored-by: Mihir Kandoi --- erpnext/hooks.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 78352735ec1..56222a452d9 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -597,16 +597,16 @@ regional_overrides = { "erpnext.controllers.accounts_controller.validate_regional": "erpnext.regional.italy.utils.sales_invoice_validate", }, } -user_privacy_documents = [ +user_data_fields = [ { "doctype": "Lead", - "match_field": "email_id", - "personal_fields": ["phone", "mobile_no", "fax", "website", "lead_name"], + "filter_by": "email_id", + "redact_fields": ["phone", "mobile_no", "fax", "website", "lead_name"], }, { "doctype": "Opportunity", - "match_field": "contact_email", - "personal_fields": ["contact_mobile", "contact_display", "customer_name"], + "filter_by": "contact_email", + "redact_fields": ["contact_mobile", "contact_display", "customer_name"], }, ] From c132b99b4e50bf4222bf3f0044c17d9b474d0d22 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 14:41:41 +0530 Subject: [PATCH 41/59] fix: update stock variance account logic which defaults to default expense (backport #57656) (#57675) Co-authored-by: Afsal Syed Co-authored-by: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com> --- .../purchase_invoice/purchase_invoice.py | 30 ++++++- .../purchase_invoice/test_purchase_invoice.py | 90 +++++++++++++++++++ 2 files changed, 118 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index d023182e255..e4cc191ffb0 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -1420,7 +1420,20 @@ class PurchaseInvoice(BuyingController): ) if flt(stock_amount, net_amt_precision) != flt(warehouse_debit_amount, net_amt_precision): - cost_of_goods_sold_account = self.get_company_default("default_expense_account") + stock_asset_rbnb = ( + self.get_company_default("asset_received_but_not_billed", ignore_validation=True) + if item.is_fixed_asset + else self.get_company_default("stock_received_but_not_billed", ignore_validation=True) + ) + fallback_account = ( + (item.expense_account or stock_asset_rbnb) + if self.is_return + else (stock_asset_rbnb or item.expense_account) + ) + cost_of_goods_sold_account = ( + self.get_company_default("default_expense_account", ignore_validation=True) + or fallback_account + ) stock_adjustment_amt = stock_amount - warehouse_debit_amount gl_entries.append( @@ -1445,7 +1458,20 @@ class PurchaseInvoice(BuyingController): and warehouse_debit_amount != flt(voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision) ): - cost_of_goods_sold_account = self.get_company_default("default_expense_account") + stock_asset_rbnb = ( + self.get_company_default("asset_received_but_not_billed", ignore_validation=True) + if item.is_fixed_asset + else self.get_company_default("stock_received_but_not_billed", ignore_validation=True) + ) + fallback_account = ( + (item.expense_account or stock_asset_rbnb) + if self.is_return + else (stock_asset_rbnb or item.expense_account) + ) + cost_of_goods_sold_account = ( + self.get_company_default("default_expense_account", ignore_validation=True) + or fallback_account + ) stock_amount = flt(voucher_wise_stock_value.get((item.name, item.warehouse)), net_amt_precision) stock_adjustment_amt = warehouse_debit_amount - stock_amount diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 78bfcfd1550..44d94499516 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1654,6 +1654,96 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin): ) frappe.db.set_value("Company", "_Test Company", "exchange_gain_loss_account", original_account) + def test_stock_adjustment_account_fallbacks_when_default_expense_account_unset(self): + from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import PurchaseInvoice + + class StockAdjustmentInvoice: + company = "_Test Company" + conversion_rate = 1 + update_stock = 1 + is_internal_supplier = 0 + return_against = None + project = None + + def __init__(self, is_return, defaults): + self.is_return = is_return + self.defaults = defaults + + def get(self, fieldname): + return None + + def get_company_default(self, fieldname, ignore_validation=False): + return self.defaults.get(fieldname) + + def get_gl_dict(self, args, *unused_args, **unused_kwargs): + return frappe._dict(args) + + def make_invoice(is_return, defaults): + return StockAdjustmentInvoice(is_return, defaults) + + def make_item(is_fixed_asset=0, expense_account="Item Expense - _TC"): + return frappe._dict( + { + "name": "row-1", + "warehouse": "Stores - _TC", + "valuation_rate": 10, + "qty": 10, + "conversion_factor": 1, + "base_net_amount": 100, + "item_tax_amount": 0, + "landed_cost_voucher_amount": 0, + "sales_incoming_rate": 0, + "is_fixed_asset": is_fixed_asset, + "expense_account": expense_account, + "cost_center": "Main - _TC", + "project": None, + "precision": lambda fieldname: 2, + } + ) + + defaults = { + "default_expense_account": None, + "stock_received_but_not_billed": "Stock Received But Not Billed - _TC", + "asset_received_but_not_billed": "Asset Received But Not Billed - _TC", + } + test_cases = ( + ( + "company default expense", + 0, + make_item(), + {**defaults, "default_expense_account": "Default Expense - _TC"}, + "Default Expense - _TC", + ), + ("stock rbnb", 0, make_item(), defaults, "Stock Received But Not Billed - _TC"), + ( + "asset rbnb", + 0, + make_item(is_fixed_asset=1), + defaults, + "Asset Received But Not Billed - _TC", + ), + ("return item expense", 1, make_item(), defaults, "Item Expense - _TC"), + ( + "return without item expense", + 1, + make_item(expense_account=None), + defaults, + "Stock Received But Not Billed - _TC", + ), + ) + + for label, is_return, item, company_defaults, expected_account in test_cases: + with self.subTest(label=label): + invoice = make_invoice(is_return, company_defaults) + gl_entries = [] + PurchaseInvoice.make_stock_adjustment_entry( + invoice, gl_entries, item, {(item.name, item.warehouse): 90}, "INR" + ) + + self.assertEqual(gl_entries[0].account, expected_account) + self.assertEqual(gl_entries[0].debit, 10) + self.assertEqual(gl_entries[0].debit_in_transaction_currency, 10) + @change_settings("Accounts Settings", {"unlink_payment_on_cancellation_of_invoice": 1}) def test_purchase_invoice_advance_taxes(self): from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry From 7883f595d7e21dfbc57f4c9a8f72842c6a8d78bc Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 10:36:34 +0000 Subject: [PATCH 42/59] fix(stock): fetch item stock UOM in stock reconciliation (backport #58284) (#58290) Co-authored-by: Sudharsanan Ashok <135326972+Sudharsanan11@users.noreply.github.com> Co-authored-by: Mihir Kandoi --- .../stock_reconciliation/stock_reconciliation.py | 15 +++++++++++---- .../test_stock_reconciliation.py | 5 +++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 9f84909b432..9470795f13f 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -1162,12 +1162,15 @@ def get_item_and_warehouses(item_code, warehouse): from frappe.utils.nestedset import get_descendants_of items = [] + stock_uom = frappe.get_cached_value("Item", item_code, "stock_uom") if frappe.get_cached_value("Warehouse", warehouse, "is_group"): childrens = get_descendants_of("Warehouse", warehouse, ignore_permissions=True, order_by="lft") for ch_warehouse in childrens: - items.append(frappe._dict({"item_code": item_code, "warehouse": ch_warehouse})) + items.append( + frappe._dict({"item_code": item_code, "warehouse": ch_warehouse, "stock_uom": stock_uom}) + ) else: - items = [frappe._dict({"item_code": item_code, "warehouse": warehouse})] + items = [frappe._dict({"item_code": item_code, "warehouse": warehouse, "stock_uom": stock_uom})] return items @@ -1177,7 +1180,8 @@ def get_items_for_stock_reco(warehouse, company): items = frappe.db.sql( f""" select - i.name as item_code, i.item_name, bin.warehouse as warehouse, i.has_serial_no, i.has_batch_no + i.name as item_code, i.item_name, bin.warehouse as warehouse, i.has_serial_no, i.has_batch_no, + i.stock_uom from `tabBin` bin, `tabItem` i where @@ -1195,7 +1199,8 @@ def get_items_for_stock_reco(warehouse, company): items += frappe.db.sql( """ select - i.name as item_code, i.item_name, id.default_warehouse as warehouse, i.has_serial_no, i.has_batch_no + i.name as item_code, i.item_name, id.default_warehouse as warehouse, i.has_serial_no, + i.has_batch_no, i.stock_uom from `tabItem` i, `tabItem Default` id where @@ -1241,6 +1246,7 @@ def get_item_data(row, qty, valuation_rate, serial_no=None): "current_serial_no": serial_no, "serial_no": serial_no, "batch_no": row.get("batch_no"), + "stock_uom": row.get("stock_uom"), } @@ -1268,6 +1274,7 @@ def get_itemwise_batch(warehouse, posting_date, company, item_code=None): "valuation_rate": row[9], "item_name": row[1], "batch_no": row[4], + "stock_uom": row[11], } ) ) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 11d7850913e..835f2a25f9f 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -141,6 +141,7 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): "_Test Stock Reco Item", is_stock_item=1, valuation_rate=100, + stock_uom="_Test UOM", warehouse="_Test Warehouse Ledger 1 - _TC", opening_stock=100, ) @@ -148,8 +149,8 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): items = get_items("_Test Warehouse Group 1 - _TC", nowdate(), nowtime(), "_Test Company") self.assertEqual( - ["_Test Stock Reco Item", "_Test Warehouse Ledger 1 - _TC", 100], - [items[0]["item_code"], items[0]["warehouse"], items[0]["qty"]], + ["_Test Stock Reco Item", "_Test Warehouse Ledger 1 - _TC", 100, "_Test UOM"], + [items[0]["item_code"], items[0]["warehouse"], items[0]["qty"], items[0]["stock_uom"]], ) def test_stock_reco_for_serialized_item(self): From 88a36a800a5a338ae33018b5281cf75d701280ac Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 16:58:21 +0530 Subject: [PATCH 43/59] fix: block disabled/frozen party on Opportunity and Request for Quotation (backport #57983) (#58034) * fix: block disabled/frozen customers on Opportunity Opportunity inherits TransactionBase instead of AccountsController, so it never ran validate_party_frozen_disabled like Quotation, Sales Order and Sales Invoice do. A disabled Customer could be saved as an Opportunity's party and only get caught later at Quotation stage. Also fixes the party_name Link query on the client: it referenced erpnext.queries.customer, which was never defined, so disabled customers showed up in the picker. (cherry picked from commit 90937ce6d93c42ee767a768991f2facb2ba52a7e) # Conflicts: # erpnext/crm/doctype/opportunity/test_opportunity.py * fix: block disabled/frozen suppliers on Request for Quotation Request for Quotation overrides validate() entirely and never calls super().validate(), so it never goes through AccountsController's party validation. Suppliers also sit in a child table, so the shared PartyValidator wouldn't have caught it anyway (it only checks a single top-level party field). A disabled or frozen Supplier could be added to an RFQ and the RFQ submitted without any warning. Also filters the suppliers grid's supplier Link field to disabled=0, matching the same client-side fix applied to Opportunity's party_name. (cherry picked from commit 4bf65ffc1de15e9f39daf77511327ff35fa3cdd6) # Conflicts: # erpnext/buying/doctype/request_for_quotation/request_for_quotation.py # erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py * fix: scope Opportunity party validation to Customer only validate_party_frozen_disabled only enforces Customer/Supplier/Employee, so passing opportunity_from straight through silently no-op'd for Lead and Prospect. Made the Customer-only scope explicit instead of relying on that implicit fallthrough. Lead.disabled is not enforced anywhere else in the codebase (lead_query, the picker used for this same field, only filters status/docstatus), so deliberately not extending validation to Lead-sourced Opportunities. (cherry picked from commit 8c0a94541708a6d10be19900a45305be24063ac9) * refactor: move RFQ supplier disabled filter to link_filters Static filters with no doc-dependent values belong on the field definition, not in JS. Matches the existing pattern used for Warehouse/Item link_filters elsewhere (e.g. job_card_item.json, product_bundle_item.json). (cherry picked from commit 6b35c51ff1dfd974db5865ff93c4e421a9d0e654) * fix: resolve backport conflicts for disabled/frozen party validation The automated backport left unresolved merge conflict markers committed in request_for_quotation.py, test_request_for_quotation.py and test_opportunity.py. Also fixes validate_party_frozen_disabled being called with 3 args here, this branch's version only takes (party_type, party_name), unlike develop's (company, party_type, party_name). Dropped test_duplicate_supplier_rejected, test_rfq_blocked_for_supplier_with_prevent_rfqs and test_rfq_status_lifecycle from the conflict resolution, they don't exist on this branch and aren't part of this backport. --------- Co-authored-by: Jatin3128 --- .../request_for_quotation.py | 8 +++++++- .../test_request_for_quotation.py | 12 ++++++++++++ .../request_for_quotation_supplier.json | 1 + erpnext/crm/doctype/opportunity/opportunity.py | 6 ++++++ .../doctype/opportunity/test_opportunity.py | 18 ++++++++++++++++++ erpnext/public/js/queries.js | 4 ++++ 6 files changed, 48 insertions(+), 1 deletion(-) 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 cc1919afd57..a9e92bdaf58 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -15,7 +15,11 @@ from frappe.utils import get_url from frappe.utils.print_format import download_pdf from frappe.utils.user import get_user_fullname -from erpnext.accounts.party import _get_party_details, get_party_account_currency +from erpnext.accounts.party import ( + _get_party_details, + get_party_account_currency, + validate_party_frozen_disabled, +) from erpnext.buying.utils import validate_for_items from erpnext.controllers.buying_controller import BuyingController from erpnext.stock.doctype.material_request.material_request import set_missing_values @@ -123,6 +127,8 @@ class RequestforQuotation(BuyingController): def validate_supplier_list(self): for d in self.suppliers: + validate_party_frozen_disabled("Supplier", d.supplier) + prevent_rfqs = frappe.db.get_value("Supplier", d.supplier, "prevent_rfqs") if prevent_rfqs: standing = frappe.db.get_value("Supplier Scorecard", d.supplier, "status") diff --git a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py index a92d8d95626..7f91ed3b9cb 100644 --- a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py @@ -17,6 +17,7 @@ from erpnext.buying.doctype.request_for_quotation.request_for_quotation import ( from erpnext.controllers.accounts_controller import InvalidQtyError from erpnext.crm.doctype.opportunity.opportunity import make_request_for_quotation as make_rfq from erpnext.crm.doctype.opportunity.test_opportunity import make_opportunity +from erpnext.exceptions import PartyDisabled from erpnext.stock.doctype.item.test_item import make_item from erpnext.templates.pages.rfq import check_supplier_has_docname_access @@ -57,6 +58,17 @@ class TestRequestforQuotation(FrappeTestCase): self.assertEqual(rfq.get("suppliers")[0].quote_status, "Received") self.assertEqual(rfq.get("suppliers")[1].quote_status, "Pending") + def test_rfq_blocked_for_disabled_supplier(self): + frappe.db.set_value("Supplier", "_Test Supplier", "disabled", 1) + rfq = make_request_for_quotation( + supplier_data=[{"supplier": "_Test Supplier", "supplier_name": "_Test Supplier"}], + do_not_save=True, + ) + self.assertRaises(PartyDisabled, rfq.save) + + frappe.db.set_value("Supplier", "_Test Supplier", "disabled", 0) + rfq.save() + def test_make_supplier_quotation(self): rfq = make_request_for_quotation() diff --git a/erpnext/buying/doctype/request_for_quotation_supplier/request_for_quotation_supplier.json b/erpnext/buying/doctype/request_for_quotation_supplier/request_for_quotation_supplier.json index 69d530da7ee..5862b8cde18 100644 --- a/erpnext/buying/doctype/request_for_quotation_supplier/request_for_quotation_supplier.json +++ b/erpnext/buying/doctype/request_for_quotation_supplier/request_for_quotation_supplier.json @@ -40,6 +40,7 @@ "fieldtype": "Link", "in_list_view": 1, "label": "Supplier", + "link_filters": "[[\"Supplier\",\"disabled\",\"=\",0]]", "options": "Supplier", "reqd": 1 }, diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py index aa8d75e1826..c8db39103f3 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.py +++ b/erpnext/crm/doctype/opportunity/opportunity.py @@ -13,6 +13,7 @@ from frappe.query_builder import DocType, Interval from frappe.query_builder.functions import Now from frappe.utils import flt, get_fullname +from erpnext.accounts.party import validate_party_frozen_disabled from erpnext.crm.utils import ( CRMNote, copy_comments, @@ -131,6 +132,7 @@ class Opportunity(TransactionBase, CRMNote): self.validate_item_details() self.validate_uom_is_integer("uom", "qty") self.validate_cust_name() + self.validate_party() self.map_fields() self.validate_qty() self.set_exchange_rate() @@ -346,6 +348,10 @@ class Opportunity(TransactionBase, CRMNote): return False return True + def validate_party(self) -> None: + if self.opportunity_from == "Customer": + validate_party_frozen_disabled("Customer", self.party_name) + def validate_cust_name(self): if self.party_name: if self.opportunity_from == "Customer": diff --git a/erpnext/crm/doctype/opportunity/test_opportunity.py b/erpnext/crm/doctype/opportunity/test_opportunity.py index f346946568e..2179e35bf7e 100644 --- a/erpnext/crm/doctype/opportunity/test_opportunity.py +++ b/erpnext/crm/doctype/opportunity/test_opportunity.py @@ -10,6 +10,7 @@ from erpnext.crm.doctype.lead.lead import make_customer from erpnext.crm.doctype.lead.test_lead import make_lead from erpnext.crm.doctype.opportunity.opportunity import make_quotation from erpnext.crm.utils import get_linked_communication_list +from erpnext.exceptions import PartyDisabled test_records = frappe.get_test_records("Opportunity") @@ -52,6 +53,23 @@ class TestOpportunity(unittest.TestCase): opportunity_doc = make_opportunity(with_items=1, rate=1100, qty=2) self.assertEqual(opportunity_doc.total, 2200) + def test_disabled_customer_not_allowed(self): + frappe.db.set_value("Customer", "_Test Customer", "disabled", 1) + + self.assertRaises(PartyDisabled, make_opportunity, with_items=0) + + frappe.db.set_value("Customer", "_Test Customer", "disabled", 0) + make_opportunity(with_items=0) + + def test_disabled_lead_not_blocked(self): + # Lead.disabled isn't enforced anywhere else (e.g. the Lead picker query only + # excludes Converted leads), so it shouldn't block Opportunity creation either. + lead_doc = make_lead() + frappe.db.set_value("Lead", lead_doc.name, "disabled", 1) + + opp_doc = make_opportunity(opportunity_from="Lead", lead=lead_doc.name) + self.assertEqual(opp_doc.party_name, lead_doc.name) + def test_carry_forward_of_email_and_comments(self): frappe.db.set_single_value("CRM Settings", "carry_forward_communication_and_comments", 1) lead_doc = make_lead() diff --git a/erpnext/public/js/queries.js b/erpnext/public/js/queries.js index 8b174da244f..32a4e0958c9 100644 --- a/erpnext/public/js/queries.js +++ b/erpnext/public/js/queries.js @@ -12,6 +12,10 @@ $.extend(erpnext.queries, { return { query: "erpnext.controllers.queries.lead_query" }; }, + customer: function () { + return { filters: { disabled: 0 } }; + }, + item: function (filters) { var args = { query: "erpnext.controllers.queries.item_query" }; if (filters) args["filters"] = filters; From fd82c7d691c5788494cd536db81d29b76ca82e78 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 11:58:32 +0000 Subject: [PATCH 44/59] fix: new docs should refetch incoming rates (backport #58097) (#58294) Co-authored-by: Mihir Kandoi --- erpnext/controllers/selling_controller.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index f1e3baebc51..fd5b564b6b0 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -560,7 +560,8 @@ class SellingController(StockController): reset_incoming_rate() if ( - not d.incoming_rate + (not d.incoming_rate or self.is_new()) + and not is_standalone or self.is_internal_transfer() or (get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return")) ): From b578fb52d5ef79e0974b1100ef45caf981f503e1 Mon Sep 17 00:00:00 2001 From: Vishnu Priya Baskaran <145791817+ervishnucs@users.noreply.github.com> Date: Wed, 19 Aug 2026 22:18:13 +0530 Subject: [PATCH 45/59] fix: allow custom remark on reversal journal entry (#58308) fix: allow user remark on reversal journal entry --- erpnext/accounts/doctype/journal_entry/journal_entry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js index 7c396c94eed..b4f7bfeefba 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.js +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js @@ -516,7 +516,7 @@ $.extend(erpnext.journal_entry, { lock_reversal_entry: function (frm) { frm.fields .filter((field) => field.has_input) - .filter((field) => field.df.fieldname != "posting_date") + .filter((field) => !["posting_date", "user_remark"].includes(field.df.fieldname)) .forEach((field) => frm.set_df_property(field.df.fieldname, "read_only", 1)); frm.set_df_property("accounts", "read_only", 1); }, From 743f7d87139c1ae876f0fbf028e66cfe76ab1b8e Mon Sep 17 00:00:00 2001 From: ervishnucs Date: Fri, 14 Aug 2026 17:58:57 +0530 Subject: [PATCH 46/59] fix(accounts): supplier group filter not applied on accounts payable report (cherry picked from commit 513f19924dbe5bb7f6061d1ca78102e4635afc17) --- .../report/accounts_payable/accounts_payable.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js index 8de9d60a8fd..50920ceecb4 100644 --- a/erpnext/accounts/report/accounts_payable/accounts_payable.js +++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js @@ -94,10 +94,15 @@ frappe.query_reports["Accounts Payable"] = { options: get_party_type_options(), on_change: function () { frappe.query_report.set_filter_value("party", ""); - frappe.query_report.toggle_filter_display( - "supplier_group", - frappe.query_report.get_filter_value("party_type") !== "Supplier" - ); + let is_supplier = frappe.query_report.get_filter_value("party_type") === "Supplier"; + let supplier_group_filter = frappe.query_report.get_filter("supplier_group"); + if (supplier_group_filter) { + supplier_group_filter.df.hidden = !is_supplier; + } + frappe.query_report.toggle_filter_display("supplier_group", !is_supplier); + if (!is_supplier) { + frappe.query_report.set_filter_value("supplier_group", []); + } }, }, { From 3d4245b9b44125ff7de18cb3988055b7bdca3667 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 24 Aug 2026 14:46:36 +0530 Subject: [PATCH 47/59] fix: include time logs ending at midnight in timesheet billing summary (backport #58355) (#58356) Co-authored-by: Mihir Kandoi --- .../timesheet_billing_summary.py | 198 ++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 erpnext/projects/report/timesheet_billing_summary/timesheet_billing_summary.py diff --git a/erpnext/projects/report/timesheet_billing_summary/timesheet_billing_summary.py b/erpnext/projects/report/timesheet_billing_summary/timesheet_billing_summary.py new file mode 100644 index 00000000000..70b3d0c0c80 --- /dev/null +++ b/erpnext/projects/report/timesheet_billing_summary/timesheet_billing_summary.py @@ -0,0 +1,198 @@ +import frappe +from frappe import _ +from frappe.desk.query_report import get_filtered_data +from frappe.model.docstatus import DocStatus +from frappe.utils import add_days, getdate + +VALUE_FIELDNAMES = ("hours", "billing_hours", "billing_amount") + + +def execute(filters=None): + group_fieldname = filters.pop("group_by", None) + + filters = frappe._dict(filters or {}) + columns = get_columns(filters, group_fieldname) + + data = get_data(filters) + data = get_filtered_data("Timesheet", columns, data, frappe.session.user) + report_summary = get_report_summary(data) + + if group_fieldname: + data = group_by(data, group_fieldname) + + return columns, data, None, None, report_summary, 1 + + +def get_columns(filters, group_fieldname=None): + group_columns = { + "date": { + "label": _("Date"), + "fieldtype": "Date", + "fieldname": "date", + "width": 150, + }, + "project": { + "label": _("Project"), + "fieldtype": "Link", + "fieldname": "project", + "options": "Project", + "width": 200, + "hidden": int(bool(filters.get("project"))), + }, + "employee": { + "label": _("Employee ID"), + "fieldtype": "Link", + "fieldname": "employee", + "options": "Employee", + "width": 200, + "hidden": int(bool(filters.get("employee"))), + }, + } + columns = [] + if group_fieldname in group_columns: + # the grouped column labels the group rows: keep it visible even when it is filtered too + group_columns[group_fieldname]["hidden"] = 0 + columns.append(group_columns.pop(group_fieldname)) + + columns.extend(group_columns.values()) + + columns.extend( + [ + { + "label": _("Employee Name"), + "fieldtype": "data", + "fieldname": "employee_name", + "hidden": 1, + }, + { + "label": _("Timesheet"), + "fieldtype": "Link", + "fieldname": "timesheet", + "options": "Timesheet", + "width": 150, + }, + {"label": _("Working Hours"), "fieldtype": "Float", "fieldname": "hours", "width": 150}, + { + "label": _("Billing Hours"), + "fieldtype": "Float", + "fieldname": "billing_hours", + "width": 150, + }, + { + "label": _("Billing Amount"), + "fieldtype": "Currency", + "fieldname": "billing_amount", + "width": 150, + }, + ] + ) + + return columns + + +def get_data(filters): + _filters = [] + if filters.get("employee"): + _filters.append(("employee", "=", filters.get("employee"))) + if filters.get("project"): + _filters.append(("Timesheet Detail", "project", "=", filters.get("project"))) + if filters.get("from_date"): + _filters.append(("Timesheet Detail", "from_time", ">=", filters.get("from_date"))) + if filters.get("to_date"): + _filters.append(("Timesheet Detail", "from_time", "<", add_days(getdate(filters.get("to_date")), 1))) + if not filters.get("include_draft_timesheets"): + _filters.append(("docstatus", "=", DocStatus.submitted())) + else: + _filters.append(("docstatus", "in", (DocStatus.submitted(), DocStatus.draft()))) + + data = frappe.get_list( + "Timesheet", + fields=[ + "name as timesheet", + "`tabTimesheet`.employee", + "`tabTimesheet`.employee_name", + "`tabTimesheet Detail`.from_time as date", + "`tabTimesheet Detail`.project", + "`tabTimesheet Detail`.hours", + "`tabTimesheet Detail`.billing_hours", + "`tabTimesheet Detail`.billing_amount", + ], + filters=_filters, + order_by="`tabTimesheet Detail`.from_time", + ) + + return data + + +def group_by(data, fieldname): + groups = {} + for row in data: + groups.setdefault(get_group_value(row, fieldname), []).append(row) + + grouped_data = [] + for group in sorted(groups, key=lambda g: (g is None, g)): + hours = billing_hours = billing_amount = 0 + child_rows = [] + for row in groups[group]: + hours += row.get("hours") or 0 + billing_hours += row.get("billing_hours") or 0 + billing_amount += row.get("billing_amount") or 0 + + _row = row.copy() + _row[fieldname] = None + _row["indent"] = 1 + _row["is_group"] = 0 + child_rows.append(_row) + + group_row = { + fieldname: group, + "hours": hours, + "billing_hours": billing_hours, + "billing_amount": billing_amount, + "indent": 0, + "is_group": 1, + } + if fieldname == "employee": + group_row["employee_name"] = groups[group][0].get("employee_name") + + grouped_data.append(group_row) + grouped_data.extend(child_rows) + + return grouped_data + + +def get_group_value(row, fieldname): + value = row.get(fieldname) + # `date` is `Timesheet Detail.from_time`, a datetime: everything logged on a day is one group + return getdate(value) if fieldname == "date" and value else value + + +def get_report_summary(data): + if not data: + return None + + totals = dict.fromkeys(VALUE_FIELDNAMES, 0.0) + for row in data: + for value_fieldname in VALUE_FIELDNAMES: + totals[value_fieldname] += row.get(value_fieldname) or 0 + + return [ + { + "value": totals["hours"], + "indicator": "Blue", + "label": _("Total Working Hours"), + "datatype": "Float", + }, + { + "value": totals["billing_hours"], + "indicator": "Blue", + "label": _("Total Billing Hours"), + "datatype": "Float", + }, + { + "value": totals["billing_amount"], + "indicator": "Green", + "label": _("Total Billing Amount"), + "datatype": "Currency", + }, + ] From 61238e7c4c9b7bbd34d6ff877bd7497fcf830292 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 24 Aug 2026 10:33:16 +0000 Subject: [PATCH 48/59] fix: aggregate child warehouses in Stock Qty vs Serial No Count report (backport #58134) (#58365) Co-authored-by: Mohd Haris Co-authored-by: Claude Opus 4.8 Co-authored-by: Mihir Kandoi --- .../stock_qty_vs_serial_no_count.py | 36 ++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.py b/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.py index 001ae8f1a53..bc83c8a35e5 100644 --- a/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.py +++ b/erpnext/stock/report/stock_qty_vs_serial_no_count/stock_qty_vs_serial_no_count.py @@ -5,7 +5,7 @@ import frappe from frappe import _ from frappe.query_builder import Order -from frappe.query_builder.functions import Coalesce +from frappe.query_builder.functions import Coalesce, Sum from frappe.utils import cstr, flt from pypika import analytics as an @@ -44,7 +44,20 @@ def get_columns(): return columns +def get_warehouses(warehouse): + if frappe.db.get_value("Warehouse", warehouse, "is_group"): + from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses + + return get_child_warehouses(warehouse) + + return [warehouse] + + def get_data(warehouse, show_disabled_items): + # A group (parent) warehouse holds no stock itself; stock lives in its child + # warehouses. Expand it to all its descendants so the report aggregates them. + warehouses = get_warehouses(warehouse) + filters = {"has_serial_no": True} if not show_disabled_items: filters["disabled"] = False @@ -59,16 +72,23 @@ def get_data(warehouse, show_disabled_items): for item in serial_item_list: total_serial_no = frappe.db.count( "Serial No", - filters={"item_code": item.item_code, "status": ("in", status_list), "warehouse": warehouse}, + filters={ + "item_code": item.item_code, + "status": ("in", status_list), + "warehouse": ("in", warehouses), + }, ) - actual_qty = frappe.db.get_value( - "Bin", fieldname=["actual_qty"], filters={"warehouse": warehouse, "item_code": item.item_code} - ) + bin_table = frappe.qb.DocType("Bin") + bin_qty = ( + frappe.qb.from_(bin_table) + .select(Sum(bin_table.actual_qty)) + .where(bin_table.item_code == item.item_code) + .where(bin_table.warehouse.isin(warehouses)) + ).run() - # frappe.db.get_value returns null if no record exist. - if not actual_qty: - actual_qty = 0 + # Sum is null when no Bin record exists for the item in these warehouses. + actual_qty = flt(bin_qty[0][0]) if bin_qty else 0 difference = total_serial_no - actual_qty From b33475e7cfd575166faab7b20711924905c21464 Mon Sep 17 00:00:00 2001 From: Pandiyan P Date: Mon, 24 Aug 2026 16:28:17 +0530 Subject: [PATCH 49/59] fix: render missing terms before printing (#58367) --- erpnext/controllers/accounts_controller.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index c19d254b56c..30bac060a48 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -719,6 +719,8 @@ class AccountsController(TransactionBase): self.validate_non_invoice_documents_schedule() def before_print(self, settings=None): + self.set_missing_terms() + if self.doctype in [ "Purchase Order", "Sales Order", @@ -742,6 +744,16 @@ class AccountsController(TransactionBase): set_print_templates_for_item_table(self, settings) set_print_templates_for_taxes(self, settings) + def set_missing_terms(self): + if not self.get("tc_name") or self.get("terms"): + return + + from erpnext.setup.doctype.terms_and_conditions.terms_and_conditions import ( + get_terms_and_conditions, + ) + + self.terms = get_terms_and_conditions(self.tc_name, self.as_dict()) + def calculate_paid_amount(self): if hasattr(self, "is_pos") or hasattr(self, "is_paid"): is_paid = self.get("is_pos") or self.get("is_paid") From 2dbd2246436921adc6f2da54fab98a9d6902b1ab Mon Sep 17 00:00:00 2001 From: Pandiyan P Date: Mon, 24 Aug 2026 16:29:05 +0530 Subject: [PATCH 50/59] fix: hide rfq status in supplier portal (#58368) (cherry picked from commit 75d6183bb6497058ae5b30b1f4d2815c0016fe7d) --- erpnext/templates/includes/transaction_row.html | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/erpnext/templates/includes/transaction_row.html b/erpnext/templates/includes/transaction_row.html index 03e04a3a1e8..49524b92431 100644 --- a/erpnext/templates/includes/transaction_row.html +++ b/erpnext/templates/includes/transaction_row.html @@ -7,9 +7,11 @@ {{ frappe.utils.global_date_format(doc.modified) }} -
- {{ _(doc.status) }} -
+ {% if doc.doctype != "Request for Quotation" %} +
+ {{ _(doc.status) }} +
+ {% endif %}
{{ doc.items_preview | e }} From cb8ae93fa333dfdf6fd3491631b2066e9239e811 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 24 Aug 2026 17:39:24 +0530 Subject: [PATCH 51/59] fix(italy): handle none price_list_rate in e-invoice xml generation (backport #58242) (#58369) Co-authored-by: Vishnu Priya Baskaran <145791817+ervishnucs@users.noreply.github.com> --- erpnext/regional/italy/e-invoice.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/regional/italy/e-invoice.xml b/erpnext/regional/italy/e-invoice.xml index ef1e94ff27b..43ea59a26d9 100644 --- a/erpnext/regional/italy/e-invoice.xml +++ b/erpnext/regional/italy/e-invoice.xml @@ -191,7 +191,7 @@ {{ html2text(item.description or '') or item.item_name }} {{ format_float(item.qty) }} {{ item.stock_uom }} - {%- set item_unit_net_price = (item.price_list_rate / tax_divisor) or (item.net_rate) or (item.rate / tax_divisor) %} + {%- set item_unit_net_price = ((item.price_list_rate or 0) / tax_divisor) or (item.net_rate) or (item.rate / tax_divisor) %} {{ format_float(item_unit_net_price, item_meta.get_field("rate").precision) }} {{ render_discount_or_margin(item, tax_divisor) }} {{ format_float(item.net_amount, item_meta.get_field("amount").precision) }} From 4b569c3ec3666abd6fe6225474043ba476037b2a Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 24 Aug 2026 17:39:56 +0530 Subject: [PATCH 52/59] Fix/return qty validation different uom (backport #58298) (#58363) Co-authored-by: Vishnu Priya Baskaran <145791817+ervishnucs@users.noreply.github.com> Co-authored-by: Afsal Syed --- .../controllers/sales_and_purchase_return.py | 2 +- .../tests/test_sales_and_purchase_return.py | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index b5cc37e48fa..4d348112446 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -213,7 +213,7 @@ def validate_quantity(doc, key, args, ref, valid_items, already_returned_items): else 0 ) - if column == "stock_qty" and not args.get("return_qty_from_rejected_warehouse"): + if column in ("stock_qty", "qty") and not args.get("return_qty_from_rejected_warehouse"): reference_qty = ref.get(column) current_stock_qty = args.get(column) elif args.get("return_qty_from_rejected_warehouse"): diff --git a/erpnext/controllers/tests/test_sales_and_purchase_return.py b/erpnext/controllers/tests/test_sales_and_purchase_return.py index 0de679352f7..cd21d321776 100644 --- a/erpnext/controllers/tests/test_sales_and_purchase_return.py +++ b/erpnext/controllers/tests/test_sales_and_purchase_return.py @@ -87,3 +87,35 @@ class TestSalesAndPurchaseReturn(FrappeTestCase): return_si.items[0].qty = 0 self.assertRaises(frappe.ValidationError, return_si.save) + + def test_sales_invoice_partial_return_with_different_stock_uom(self): + from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice + from erpnext.controllers.sales_and_purchase_return import make_return_doc + from erpnext.stock.doctype.item.test_item import make_item + + item_properties = {"is_stock_item": 1, "stock_uom": "Kg"} + if frappe.get_meta("Item").has_field("gst_hsn_code") and frappe.db.exists("GST HSN Code", "010121"): + item_properties["gst_hsn_code"] = "010121" + + item = make_item( + "_Test SI Return Different Stock UOM", + item_properties, + uoms=[{"uom": "Nos", "conversion_factor": 0.013888889}], + ) + + si = create_sales_invoice(item_code=item.name, qty=48, do_not_save=True) + si.items[0].uom = "Nos" + si.items[0].stock_uom = "Kg" + si.items[0].conversion_factor = 0.013888889 + si.save().submit() + self.addCleanup(self._cancel_and_delete, "Sales Invoice", si.name) + + first_return = make_return_doc(si.doctype, si.name) + first_return.items[0].qty = -24 + first_return.save().submit() + self.addCleanup(self._cancel_and_delete, "Sales Invoice", first_return.name) + + second_return = make_return_doc(si.doctype, si.name) + self.assertEqual(second_return.items[0].qty, -24) + second_return.save().submit() + self.addCleanup(self._cancel_and_delete, "Sales Invoice", second_return.name) From 91fc99957598419bdf6163bd3ecc5eb428098f49 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 24 Aug 2026 17:40:38 +0530 Subject: [PATCH 53/59] fix: hide supplier name in rfq portal (backport #58373) (#58375) Co-authored-by: Pandiyan P --- erpnext/templates/pages/rfq.html | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/erpnext/templates/pages/rfq.html b/erpnext/templates/pages/rfq.html index d371bf2161d..8d55d6b47e1 100644 --- a/erpnext/templates/pages/rfq.html +++ b/erpnext/templates/pages/rfq.html @@ -22,10 +22,7 @@ {% block page_content %}
-
-
{{ doc.supplier }}
-
-
+
{{ doc.get_formatted("transaction_date") }}
From e1246ae95d39f74ebdcab2071ab7d2070a6e312c Mon Sep 17 00:00:00 2001 From: pandiyan Date: Mon, 24 Aug 2026 17:51:07 +0530 Subject: [PATCH 54/59] fix: prevent duplicate supplier quotations from portal (cherry picked from commit 39e15c7b2d2e79d712b6d2bf2a29a0bd19bb9d74) # Conflicts: # erpnext/buying/doctype/request_for_quotation/request_for_quotation.py --- .../request_for_quotation.py | 77 ++++++++++++++++--- erpnext/templates/pages/rfq.html | 2 +- 2 files changed, 66 insertions(+), 13 deletions(-) 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 a9e92bdaf58..80466feba4d 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -9,6 +9,7 @@ from frappe import _ from frappe.contacts.doctype.contact.contact import get_full_name from frappe.core.doctype.communication.email import make from frappe.desk.form.load import get_attachments +from frappe.model.document import Document from frappe.model.mapper import get_mapped_doc from frappe.query_builder import Order from frappe.utils import get_url @@ -485,15 +486,15 @@ def make_supplier_quotation_from_rfq(source_name, target_doc=None, for_supplier= # This method is used to make supplier quotation from supplier's portal. @frappe.whitelist() -def create_supplier_quotation(doc): +def create_supplier_quotation(doc: str | Document | dict): if isinstance(doc, str): doc = json.loads(doc) + supplier = doc.get("supplier") - if frappe.session.user not in frappe.get_all( - "Portal User", {"parent": doc.get("supplier")}, pluck="user" - ): + if frappe.session.user not in frappe.get_all("Portal User", {"parent": supplier}, pluck="user"): frappe.throw(_("Not Permitted"), frappe.PermissionError) +<<<<<<< HEAD try: sq_doc = frappe.get_doc( { @@ -506,15 +507,67 @@ def create_supplier_quotation(doc): "buying_price_list": doc.get("buying_price_list") or frappe.db.get_value("Buying Settings", None, "buying_price_list"), } +======= + validate_existing_supplier_quotation(supplier, doc.get("items")) + + sq_doc = frappe.get_doc( + { + "doctype": "Supplier Quotation", + "supplier": supplier, + "terms": doc.get("terms"), + "company": doc.get("company"), + "currency": doc.get("currency") + or get_party_account_currency("Supplier", supplier, doc.get("company")), + "buying_price_list": doc.get("buying_price_list") + or frappe.db.get_single_value("Buying Settings", "buying_price_list"), + } + ) + add_items(sq_doc, supplier, doc.get("items")) + sq_doc.flags.ignore_permissions = True + sq_doc.run_method("set_missing_values") + sq_doc.save() + frappe.msgprint(_("Supplier Quotation {0} Created").format(sq_doc.name)) + return sq_doc.name + + +def validate_existing_supplier_quotation(supplier, items): + request_for_quotations = {item.get("parent") for item in items if item.get("parent")} + if not request_for_quotations: + return + + rfq = frappe.qb.DocType("Request for Quotation") + ( + frappe.qb.from_(rfq) + .select(rfq.name) + .where(rfq.name.isin(request_for_quotations)) + .orderby(rfq.name) + .for_update() + ).run() + + sq = frappe.qb.DocType("Supplier Quotation") + sqi = frappe.qb.DocType("Supplier Quotation Item") + existing_quotation = ( + frappe.qb.from_(sq) + .inner_join(sqi) + .on(sq.name == sqi.parent) + .select(sq.name, sqi.request_for_quotation) + .where( + (sq.docstatus < 2) + & (sq.supplier == supplier) + & (sqi.request_for_quotation.isin(request_for_quotations)) + ) + .limit(1) + ).run(as_dict=True) + + if existing_quotation: + existing_quotation = existing_quotation[0] + frappe.throw( + _("Supplier Quotation {0} already exists against Request for Quotation {1}").format( + frappe.bold(existing_quotation.name), + frappe.bold(existing_quotation.request_for_quotation), + ) +>>>>>>> 39e15c7b2d (fix: prevent duplicate supplier quotations from portal) ) - add_items(sq_doc, doc.get("supplier"), doc.get("items")) - sq_doc.flags.ignore_permissions = True - sq_doc.run_method("set_missing_values") - sq_doc.save() - frappe.msgprint(_("Supplier Quotation {0} Created").format(sq_doc.name)) - return sq_doc.name - except Exception: - return None def add_items(sq_doc, supplier, items): diff --git a/erpnext/templates/pages/rfq.html b/erpnext/templates/pages/rfq.html index 8d55d6b47e1..d2a9382dc6b 100644 --- a/erpnext/templates/pages/rfq.html +++ b/erpnext/templates/pages/rfq.html @@ -13,7 +13,7 @@ {% endblock %} {% block header_actions %} -{% if doc.items %} +{% if doc.items and not doc.rfq_links %} From c861fbf438fcd65d9e96b92dfedf3b71b504045a Mon Sep 17 00:00:00 2001 From: pandiyan Date: Mon, 24 Aug 2026 17:55:35 +0530 Subject: [PATCH 55/59] test: verify duplicate supplier quotations are rejected (cherry picked from commit efe5571ca72ff15f20f8a6bb13b8a4ed72cf5e31) --- .../test_request_for_quotation.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py index 7f91ed3b9cb..1727fb9835e 100644 --- a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py @@ -161,6 +161,18 @@ class TestRequestforQuotation(FrappeTestCase): self.assertEqual(supplier_quotation_doc.get("items")[0].qty, 5) self.assertEqual(supplier_quotation_doc.get("items")[0].amount, 500) + def test_make_duplicate_supplier_quotation_from_portal(self): + rfq = make_request_for_quotation() + rfq.supplier = rfq.suppliers[0].supplier + supplier_quotation = frappe.get_doc("Supplier Quotation", create_supplier_quotation(rfq)) + supplier_quotation.submit() + + with self.assertRaisesRegex(frappe.ValidationError, "already exists"): + create_supplier_quotation(rfq) + + supplier_quotation.cancel() + self.assertTrue(create_supplier_quotation(rfq)) + def test_make_multi_uom_supplier_quotation(self): item_code = "_Test Multi UOM RFQ Item" if not frappe.db.exists("Item", item_code): From f8e614f0c71a008e083794309da7767fa947a3b0 Mon Sep 17 00:00:00 2001 From: Sudharsanan11 Date: Tue, 25 Aug 2026 11:47:06 +0530 Subject: [PATCH 56/59] fix: fix conflicts --- .../request_for_quotation.py | 15 --------------- 1 file changed, 15 deletions(-) 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 80466feba4d..4483ce7c79a 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py @@ -494,20 +494,6 @@ def create_supplier_quotation(doc: str | Document | dict): if frappe.session.user not in frappe.get_all("Portal User", {"parent": supplier}, pluck="user"): frappe.throw(_("Not Permitted"), frappe.PermissionError) -<<<<<<< HEAD - try: - sq_doc = frappe.get_doc( - { - "doctype": "Supplier Quotation", - "supplier": doc.get("supplier"), - "terms": doc.get("terms"), - "company": doc.get("company"), - "currency": doc.get("currency") - or get_party_account_currency("Supplier", doc.get("supplier"), doc.get("company")), - "buying_price_list": doc.get("buying_price_list") - or frappe.db.get_value("Buying Settings", None, "buying_price_list"), - } -======= validate_existing_supplier_quotation(supplier, doc.get("items")) sq_doc = frappe.get_doc( @@ -566,7 +552,6 @@ def validate_existing_supplier_quotation(supplier, items): frappe.bold(existing_quotation.name), frappe.bold(existing_quotation.request_for_quotation), ) ->>>>>>> 39e15c7b2d (fix: prevent duplicate supplier quotations from portal) ) From 6a8462116bdb1e1999766765f5c6f347237dac9a Mon Sep 17 00:00:00 2001 From: Pandiyan P Date: Tue, 25 Aug 2026 15:36:06 +0530 Subject: [PATCH 57/59] fix: respect zero currency precision (#58395) (cherry picked from commit ce23fcc0553324825cb96c992be6a56b9165c66e) # Conflicts: # erpnext/accounts/test/test_utils.py --- erpnext/accounts/test/test_utils.py | 16 ++++++++++++++++ erpnext/accounts/utils.py | 10 +++++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/test/test_utils.py b/erpnext/accounts/test/test_utils.py index b86c8161f03..41e3c4f0451 100644 --- a/erpnext/accounts/test/test_utils.py +++ b/erpnext/accounts/test/test_utils.py @@ -7,6 +7,7 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_ent from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice from erpnext.accounts.party import get_party_shipping_address from erpnext.accounts.utils import ( + get_currency_precision, get_future_stock_vouchers, get_voucherwise_gl_entries, get_zero_cutoff, @@ -164,6 +165,21 @@ class TestUtils(unittest.TestCase): self.assertEqual(get_zero_cutoff("EUR"), 0.005) self.assertEqual(get_zero_cutoff("BHD"), 0.0005) + def test_get_currency_precision_respects_zero_and_fallback(self): + currency_precision = frappe.db.get_default("currency_precision") + number_format = frappe.db.get_default("number_format") + + try: + frappe.db.set_default("number_format", "#,###.##") + frappe.db.set_default("currency_precision", "0") + self.assertEqual(get_currency_precision(), 0) + + frappe.db.set_default("currency_precision", "") + self.assertEqual(get_currency_precision(), 2) + finally: + frappe.db.set_default("currency_precision", currency_precision or "") + frappe.db.set_default("number_format", number_format or "#,###.##") + ADDRESS_RECORDS = [ { diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 05b4c0219a3..5f55df4608d 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -1131,12 +1131,12 @@ def fix_total_debit_credit(): def get_currency_precision(): - precision = cint(frappe.db.get_default("currency_precision")) - if not precision: - number_format = frappe.db.get_default("number_format") or "#,###.##" - precision = get_number_format_info(number_format)[2] + currency_precision = frappe.db.get_default("currency_precision") + if currency_precision not in (None, ""): + return cint(currency_precision) - return precision + number_format = frappe.db.get_default("number_format") or "#,###.##" + return get_number_format_info(number_format)[2] def get_fraction_units(currency: str) -> int: From 3ae30091296ebd70ac7e2d957f4ce8bcff2ecef1 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Tue, 25 Aug 2026 13:50:03 +0200 Subject: [PATCH 58/59] fix!: tax net_amount and not_applicable (#54687) --- .../advance_taxes_and_charges.json | 18 ++- .../advance_taxes_and_charges.py | 1 + .../item_tax_template/item_tax_template.js | 9 ++ .../item_tax_template/item_tax_template.py | 7 + .../item_tax_template_detail.json | 16 +- .../item_tax_template_detail.py | 1 + .../purchase_taxes_and_charges.json | 34 ++++- .../purchase_taxes_and_charges.py | 3 + .../sales_taxes_and_charges.json | 33 ++++- .../sales_taxes_and_charges.py | 3 + erpnext/controllers/accounts_controller.py | 13 +- erpnext/controllers/taxes_and_totals.py | 61 ++++++-- .../tests/test_taxes_and_totals.py | 138 ++++++++++++++++++ .../public/js/controllers/taxes_and_totals.js | 29 +++- erpnext/public/js/controllers/transaction.js | 7 +- erpnext/regional/italy/test_italy.py | 80 ++++++++++ erpnext/regional/italy/utils.py | 23 ++- erpnext/stock/get_item_details.py | 7 +- 18 files changed, 429 insertions(+), 54 deletions(-) create mode 100644 erpnext/regional/italy/test_italy.py diff --git a/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json index 8ea57191024..5382fa4f9d6 100644 --- a/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json +++ b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.json @@ -94,11 +94,11 @@ "fieldtype": "Column Break" }, { - "allow_on_submit": 1, - "fieldname": "project", - "fieldtype": "Link", - "label": "Project", - "options": "Project" + "allow_on_submit": 1, + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project" }, { "fieldname": "section_break_8", @@ -187,12 +187,14 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-11-25 11:10:10.945027", + "modified": "2026-05-01 00:38:53.368737", "modified_by": "Administrator", "module": "Accounts", "name": "Advance Taxes and Charges", "owner": "Administrator", "permissions": [], + "row_format": "Dynamic", "sort_field": "modified", - "sort_order": "ASC" -} \ No newline at end of file + "sort_order": "ASC", + "states": [] +} diff --git a/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.py b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.py index 47e97ba015a..7e2bcfd5834 100644 --- a/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.py +++ b/erpnext/accounts/doctype/advance_taxes_and_charges/advance_taxes_and_charges.py @@ -30,6 +30,7 @@ class AdvanceTaxesandCharges(Document): parent: DF.Data parentfield: DF.Data parenttype: DF.Data + project: DF.Link | None rate: DF.Float row_id: DF.Data | None tax_amount: DF.Currency diff --git a/erpnext/accounts/doctype/item_tax_template/item_tax_template.js b/erpnext/accounts/doctype/item_tax_template/item_tax_template.js index b608ccd3568..94c87fcae93 100644 --- a/erpnext/accounts/doctype/item_tax_template/item_tax_template.js +++ b/erpnext/accounts/doctype/item_tax_template/item_tax_template.js @@ -47,3 +47,12 @@ frappe.ui.form.on("Item Tax Template", { }); }, }); + +frappe.ui.form.on("Item Tax Template Detail", { + not_applicable: function (frm, cdt, cdn) { + let row = locals[cdt][cdn]; + if (row.not_applicable) { + frappe.model.set_value(cdt, cdn, "tax_rate", 0); + } + }, +}); diff --git a/erpnext/accounts/doctype/item_tax_template/item_tax_template.py b/erpnext/accounts/doctype/item_tax_template/item_tax_template.py index 02b7455fb9c..57bc2b60f36 100644 --- a/erpnext/accounts/doctype/item_tax_template/item_tax_template.py +++ b/erpnext/accounts/doctype/item_tax_template/item_tax_template.py @@ -27,8 +27,15 @@ class ItemTaxTemplate(Document): # end: auto-generated types def validate(self): + self.set_zero_rate_for_not_applicable_tax() self.validate_tax_accounts() + def set_zero_rate_for_not_applicable_tax(self): + """Ensure tax_rate is 0 for any row marked as not applicable.""" + for row in self.get("taxes"): + if row.not_applicable: + row.tax_rate = 0 + def autoname(self): if self.company and self.title: abbr = frappe.get_cached_value("Company", self.company, "abbr") diff --git a/erpnext/accounts/doctype/item_tax_template_detail/item_tax_template_detail.json b/erpnext/accounts/doctype/item_tax_template_detail/item_tax_template_detail.json index 7e487cccf19..42ef8832fd4 100644 --- a/erpnext/accounts/doctype/item_tax_template_detail/item_tax_template_detail.json +++ b/erpnext/accounts/doctype/item_tax_template_detail/item_tax_template_detail.json @@ -6,7 +6,8 @@ "engine": "InnoDB", "field_order": [ "tax_type", - "tax_rate" + "tax_rate", + "not_applicable" ], "fields": [ { @@ -21,12 +22,21 @@ "fieldname": "tax_rate", "fieldtype": "Float", "in_list_view": 1, - "label": "Tax Rate" + "label": "Tax Rate", + "read_only_depends_on": "eval:doc.not_applicable" + }, + { + "default": "0", + "description": "Check if this tax is not applicable to items (distinct from 0% rate)", + "fieldname": "not_applicable", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Not Applicable" } ], "istable": 1, "links": [], - "modified": "2026-04-30 23:49:27.020639", + "modified": "2026-04-30 23:59:22.020639", "modified_by": "Administrator", "module": "Accounts", "name": "Item Tax Template Detail", diff --git a/erpnext/accounts/doctype/item_tax_template_detail/item_tax_template_detail.py b/erpnext/accounts/doctype/item_tax_template_detail/item_tax_template_detail.py index 810235e3691..a98fbc6ba86 100644 --- a/erpnext/accounts/doctype/item_tax_template_detail/item_tax_template_detail.py +++ b/erpnext/accounts/doctype/item_tax_template_detail/item_tax_template_detail.py @@ -14,6 +14,7 @@ class ItemTaxTemplateDetail(Document): if TYPE_CHECKING: from frappe.types import DF + not_applicable: DF.Check parent: DF.Data parentfield: DF.Data parenttype: DF.Data diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json index ea26d2b5460..aa6a3c5a1f2 100644 --- a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json +++ b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.json @@ -25,10 +25,12 @@ "project", "section_break_9", "account_currency", + "net_amount", "tax_amount", "tax_amount_after_discount_amount", "total", "column_break_14", + "base_net_amount", "base_tax_amount", "base_total", "base_tax_amount_after_discount_amount", @@ -213,11 +215,11 @@ "fieldtype": "Column Break" }, { - "allow_on_submit": 1, - "fieldname": "project", - "fieldtype": "Link", - "label": "Project", - "options": "Project" + "allow_on_submit": 1, + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project" }, { "default": "0", @@ -241,20 +243,38 @@ "fieldtype": "Check", "label": "Is Tax Withholding Account", "read_only": 1 + }, + { + "description": "Basis for tax calculation", + "fieldname": "net_amount", + "fieldtype": "Currency", + "label": "Net Amount", + "options": "currency", + "read_only": 1 + }, + { + "description": "Basis for tax calculation", + "fieldname": "base_net_amount", + "fieldtype": "Currency", + "label": "Net Amount (Company Currency)", + "options": "Company:company:default_currency", + "read_only": 1 } ], "grid_page_length": 50, "idx": 1, "istable": 1, "links": [], - "modified": "2025-04-15 13:14:48.936047", + "modified": "2026-05-01 00:38:29.543523", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Taxes and Charges", "naming_rule": "Random", "owner": "Administrator", "permissions": [], + "row_format": "Dynamic", "sort_field": "modified", "sort_order": "DESC", + "states": [], "track_changes": 1 -} +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.py b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.py index 585d5e65ad1..66c2b29d04b 100644 --- a/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.py +++ b/erpnext/accounts/doctype/purchase_taxes_and_charges/purchase_taxes_and_charges.py @@ -17,6 +17,7 @@ class PurchaseTaxesandCharges(Document): account_currency: DF.Link | None account_head: DF.Link add_deduct_tax: DF.Literal["Add", "Deduct"] + base_net_amount: DF.Currency base_tax_amount: DF.Currency base_tax_amount_after_discount_amount: DF.Currency base_total: DF.Currency @@ -35,9 +36,11 @@ class PurchaseTaxesandCharges(Document): included_in_print_rate: DF.Check is_tax_withholding_account: DF.Check item_wise_tax_detail: DF.Code | None + net_amount: DF.Currency parent: DF.Data parentfield: DF.Data parenttype: DF.Data + project: DF.Link | None rate: DF.Float row_id: DF.Data | None tax_amount: DF.Currency diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json index 8f7b1ece3c7..96992b364a0 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json +++ b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json @@ -21,10 +21,12 @@ "rate", "section_break_9", "account_currency", + "net_amount", "tax_amount", "total", "tax_amount_after_discount_amount", "column_break_13", + "base_net_amount", "base_tax_amount", "base_total", "base_tax_amount_after_discount_amount", @@ -190,11 +192,11 @@ "fieldtype": "Column Break" }, { - "allow_on_submit": 1, - "fieldname": "project", - "fieldtype": "Link", - "label": "Project", - "options": "Project" + "allow_on_submit": 1, + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project" }, { "default": "0", @@ -220,19 +222,36 @@ "label": "Account Currency", "options": "Currency", "read_only": 1 + }, + { + "description": "Basis for tax calculation", + "fieldname": "net_amount", + "fieldtype": "Currency", + "label": "Net Amount", + "options": "currency", + "read_only": 1 + }, + { + "description": "Basis for tax calculation", + "fieldname": "base_net_amount", + "fieldtype": "Currency", + "label": "Net Amount (Company Currency)", + "options": "Company:company:default_currency", + "read_only": 1 } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2024-01-14 10:08:17.776528", + "modified": "2026-05-01 00:37:57.880071", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Taxes and Charges", "owner": "Administrator", "permissions": [], + "row_format": "Dynamic", "sort_field": "modified", "sort_order": "ASC", "states": [] -} +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.py b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.py index 7936178fda8..6aa05432622 100644 --- a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.py +++ b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.py @@ -16,6 +16,7 @@ class SalesTaxesandCharges(Document): account_currency: DF.Link | None account_head: DF.Link + base_net_amount: DF.Currency base_tax_amount: DF.Currency base_tax_amount_after_discount_amount: DF.Currency base_total: DF.Currency @@ -33,9 +34,11 @@ class SalesTaxesandCharges(Document): included_in_paid_amount: DF.Check included_in_print_rate: DF.Check item_wise_tax_detail: DF.Code | None + net_amount: DF.Currency parent: DF.Data parentfield: DF.Data parenttype: DF.Data + project: DF.Link | None rate: DF.Float row_id: DF.Data | None tax_amount: DF.Currency diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 30bac060a48..74325b7c0bc 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -67,6 +67,7 @@ from erpnext.setup.utils import get_exchange_rate from erpnext.stock.doctype.item.item import get_uom_conv_factor from erpnext.stock.doctype.packed_item.packed_item import make_packing_list from erpnext.stock.get_item_details import ( + NOT_APPLICABLE_TAX, _get_item_tax_template, _get_item_tax_template_from_item_group, get_bin_details, @@ -1294,7 +1295,10 @@ class AccountsController(TransactionBase): if isinstance(item_tax_rate, str): item_tax_rate = parse_json(item_tax_rate) - for account_head, _rate in item_tax_rate.items(): + for account_head, rate in item_tax_rate.items(): + if rate == NOT_APPLICABLE_TAX: + continue + row = self.get_tax_row(account_head) if not row: @@ -3721,8 +3725,11 @@ def add_taxes_from_tax_template(child_item, parent_doc, db_insert=True): if child_item.get("item_tax_rate") and add_taxes_from_item_tax_template: tax_map = json.loads(child_item.get("item_tax_rate")) - for tax_type in tax_map: - tax_rate = flt(tax_map[tax_type]) + for tax_type, tax_rate in tax_map.items(): + if tax_rate == NOT_APPLICABLE_TAX: + continue + + tax_rate = flt(tax_rate) taxes = parent_doc.get("taxes") or [] # add new row for tax head only if missing found = any(tax.account_head == tax_type for tax in taxes) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index aa36cab6f8d..33d253ddc14 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -19,7 +19,11 @@ from erpnext.controllers.accounts_controller import ( validate_inclusive_tax, validate_taxes_and_charges, ) -from erpnext.stock.get_item_details import _get_item_tax_template, get_item_tax_map +from erpnext.stock.get_item_details import ( + NOT_APPLICABLE_TAX, + _get_item_tax_template, + get_item_tax_map, +) from erpnext.utilities.regional import temporary_flag @@ -275,6 +279,7 @@ class calculate_taxes_and_totals: tax.item_wise_tax_detail = {} tax_fields = [ + "net_amount", "total", "tax_amount_after_discount_amount", "tax_amount_for_current_item", @@ -345,6 +350,9 @@ class calculate_taxes_and_totals: if cint(tax.included_in_print_rate): tax_rate = self._get_tax_rate(tax, item_tax_map) + if tax_rate == NOT_APPLICABLE_TAX: + return tax_slope, tax_intercept + if tax.charge_type == "On Net Total": tax_slope = tax_rate / 100.0 @@ -376,9 +384,12 @@ class calculate_taxes_and_totals: def _get_tax_rate(self, tax, item_tax_map): if tax.account_head in item_tax_map: - return flt(item_tax_map.get(tax.account_head), self.doc.precision("rate", tax)) - else: - return tax.rate + rate = item_tax_map[tax.account_head] + if rate == NOT_APPLICABLE_TAX: + return NOT_APPLICABLE_TAX + return flt(rate, self.doc.precision("rate", tax)) + + return tax.rate def calculate_net_total(self): self.doc.total_qty = ( @@ -426,9 +437,12 @@ class calculate_taxes_and_totals: item_tax_map = self._load_item_tax_rate(item.item_tax_rate) for i, tax in enumerate(doc.taxes): # tax_amount represents the amount of tax for the current step - current_tax_amount = self.get_current_tax_amount(item, tax, item_tax_map) + current_net_amount, current_tax_amount = self.get_current_tax_and_net_amount( + item, tax, item_tax_map + ) if frappe.flags.round_row_wise_tax: current_tax_amount = flt(current_tax_amount, tax.precision("tax_amount")) + current_net_amount = flt(current_net_amount, tax.precision("net_amount")) # Adjust divisional loss to the last item if tax.charge_type == "Actual": @@ -436,6 +450,10 @@ class calculate_taxes_and_totals: if n == len(self._items) - 1: current_tax_amount += actual_tax_dict[tax.idx] + # net_amount is the taxable basis, it feeds no total and is always + # accumulated, unlike tax_amount which is kept from the first pass + tax.net_amount += current_net_amount + # accumulate tax amount into tax.tax_amount if tax.charge_type != "Actual" and not ( self.discount_amount_applied and self.doc.apply_discount_on == "Grand Total" @@ -486,7 +504,9 @@ class calculate_taxes_and_totals: for i, tax in enumerate(doc.taxes): self.round_off_totals(tax) - self._set_in_company_currency(tax, ["tax_amount", "tax_amount_after_discount_amount"]) + self._set_in_company_currency( + tax, ["tax_amount", "tax_amount_after_discount_amount", "net_amount"] + ) self.round_off_base_values(tax) self.set_cumulative_total(i, tax) @@ -517,8 +537,17 @@ class calculate_taxes_and_totals: tax.total = flt(self.doc.get("taxes")[row_idx - 1].total + tax_amount, tax.precision("total")) def get_current_tax_amount(self, item, tax, item_tax_map): + # kept for backwards compatibility with callers outside this module + _, current_tax_amount = self.get_current_tax_and_net_amount(item, tax, item_tax_map) + return current_tax_amount + + def get_current_tax_and_net_amount(self, item, tax, item_tax_map): tax_rate = self._get_tax_rate(tax, item_tax_map) current_tax_amount = 0.0 + current_net_amount = 0.0 + + if tax_rate == NOT_APPLICABLE_TAX: + return current_net_amount, current_tax_amount if tax.charge_type == "Actual": # distribute the tax amount proportionally to each item row @@ -528,23 +557,25 @@ class calculate_taxes_and_totals: if not item.get("apply_tds") or not self.doc.tax_withholding_net_total: current_tax_amount = 0.0 else: - current_tax_amount = item.net_amount * actual / self.doc.tax_withholding_net_total + current_net_amount = item.net_amount + current_tax_amount = current_net_amount * actual / self.doc.tax_withholding_net_total else: + current_net_amount = item.net_amount current_tax_amount = ( - item.net_amount * actual / self.doc.net_total if self.doc.net_total else 0.0 + current_net_amount * actual / self.doc.net_total if self.doc.net_total else 0.0 ) elif tax.charge_type == "On Net Total": + current_net_amount = item.net_amount current_tax_amount = (tax_rate / 100.0) * item.net_amount elif tax.charge_type == "On Previous Row Amount": - current_tax_amount = (tax_rate / 100.0) * self.doc.get("taxes")[ - cint(tax.row_id) - 1 - ].tax_amount_for_current_item + current_net_amount = self.doc.get("taxes")[cint(tax.row_id) - 1].tax_amount_for_current_item + current_tax_amount = (tax_rate / 100.0) * current_net_amount elif tax.charge_type == "On Previous Row Total": - current_tax_amount = (tax_rate / 100.0) * self.doc.get("taxes")[ - cint(tax.row_id) - 1 - ].grand_total_for_current_item + current_net_amount = self.doc.get("taxes")[cint(tax.row_id) - 1].grand_total_for_current_item + current_tax_amount = (tax_rate / 100.0) * current_net_amount elif tax.charge_type == "On Item Quantity": + # don't sum current net amount: net_amount field is currency-denominated current_tax_amount = tax_rate * item.qty else: # Custom charge_type: rate applies to the resolver-provided base. @@ -553,7 +584,7 @@ class calculate_taxes_and_totals: if not (self.doc.get("is_consolidated") or tax.get("dont_recompute_tax")): self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount) - return current_tax_amount + return current_net_amount, current_tax_amount def get_item_taxable_base(self, item, tax): """Per-item base a custom charge_type's rate is applied to. diff --git a/erpnext/controllers/tests/test_taxes_and_totals.py b/erpnext/controllers/tests/test_taxes_and_totals.py index d9fcbda701c..481651c0cce 100644 --- a/erpnext/controllers/tests/test_taxes_and_totals.py +++ b/erpnext/controllers/tests/test_taxes_and_totals.py @@ -158,3 +158,141 @@ class TestTaxesAndTotals(FrappeTestCase): self.assertEqual(so.rounding_adjustment, 0) self.assertEqual(so.base_rounded_total, 0) self.assertEqual(so.base_rounding_adjustment, 0) + + def test_tax_net_amount_with_not_applicable_item_tax(self): + """Each tax row records only the net of the items it actually applies to. + + Two items of 100 each, one per template. Template A applies VAT 7 and + marks VAT 19 not applicable, template B does the reverse. Both tax rows + must report a net_amount of 100, not the full net total of 200. + """ + vat_7 = "_Test Account VAT - _TC" + vat_19 = "_Test Account Service Tax - _TC" + + templates = {} + for title, rows in { + "_Test NA Template A": [(vat_7, 7, 0), (vat_19, 0, 1)], + "_Test NA Template B": [(vat_7, 0, 1), (vat_19, 19, 0)], + }.items(): + doc = frappe.new_doc("Item Tax Template") + doc.title = title + doc.company = "_Test Company" + for tax_type, tax_rate, not_applicable in rows: + doc.append( + "taxes", + {"tax_type": tax_type, "tax_rate": tax_rate, "not_applicable": not_applicable}, + ) + templates[title] = doc.insert().name + + so = make_sales_order(do_not_save=True) + so.items = [] + for title in templates: + so.append( + "items", + { + "item_code": "_Test Item", + "qty": 1, + "rate": 100, + "warehouse": "_Test Warehouse - _TC", + "item_tax_template": templates[title], + }, + ) + + so.set("taxes", []) + for account_head in (vat_7, vat_19): + so.append( + "taxes", + { + "charge_type": "On Net Total", + "account_head": account_head, + "description": account_head, + "rate": 0, + "cost_center": "_Test Cost Center - _TC", + }, + ) + + so.save() + + self.assertEqual(so.net_total, 200.0) + self.assertEqual(so.taxes[0].net_amount, 100.0) + self.assertEqual(so.taxes[0].tax_amount, 7.0) + self.assertEqual(so.taxes[1].net_amount, 100.0) + self.assertEqual(so.taxes[1].tax_amount, 19.0) + + def test_inclusive_tax_with_not_applicable_item_tax(self): + """An inclusive tax row meeting an item that marks it not applicable must + contribute no fraction, instead of raising in get_current_tax_fraction.""" + vat_19 = "_Test Account Service Tax - _TC" + + template = frappe.new_doc("Item Tax Template") + template.title = "_Test NA Template Inclusive" + template.company = "_Test Company" + template.append("taxes", {"tax_type": vat_19, "tax_rate": 0, "not_applicable": 1}) + template.insert() + + so = make_sales_order(do_not_save=True) + so.items = [] + so.append( + "items", + { + "item_code": "_Test Item", + "qty": 1, + "rate": 119, + "warehouse": "_Test Warehouse - _TC", + "item_tax_template": template.name, + }, + ) + so.set("taxes", []) + so.append( + "taxes", + { + "charge_type": "On Net Total", + "account_head": vat_19, + "description": vat_19, + "rate": 19, + "included_in_print_rate": 1, + "cost_center": "_Test Cost Center - _TC", + }, + ) + + so.save() + + # the tax does not apply, so nothing is backed out of the printed rate + self.assertEqual(so.net_total, 119.0) + self.assertEqual(so.taxes[0].tax_amount, 0.0) + self.assertEqual(so.taxes[0].net_amount, 0.0) + self.assertEqual(so.grand_total, 119.0) + + def test_tax_net_amount_survives_grand_total_discount(self): + """A discount on Grand Total re-runs the calculation with + discount_amount_applied set. net_amount is reset on that second pass, so + it has to be accumulated there too instead of being left at zero.""" + so = make_sales_order(do_not_save=True) + so.items = [] + so.append( + "items", + { + "item_code": "_Test Item", + "qty": 10, + "rate": 100, + "warehouse": "_Test Warehouse - _TC", + }, + ) + so.set("taxes", []) + so.append( + "taxes", + { + "charge_type": "On Net Total", + "account_head": "_Test Account VAT - _TC", + "description": "VAT", + "rate": 19, + "cost_center": "_Test Cost Center - _TC", + }, + ) + so.apply_discount_on = "Grand Total" + so.discount_amount = 100 + + calculate_taxes_and_totals(so) + + self.assertEqual(so.taxes[0].net_amount, so.net_total) + self.assertEqual(so.grand_total, 1090.0) diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 5fb9a6b6080..dc1d85b87ab 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -1,6 +1,9 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt +// mirror of erpnext.stock.get_item_details.NOT_APPLICABLE_TAX +erpnext.NOT_APPLICABLE_TAX = "N/A"; + // Per-charge_type base resolvers, mirror of the `erpnext_taxable_base_resolvers` // server hook. A localization registers `fn(calc, item, tax)` returning the per-item // base, so the client preview matches the server for custom charge types. @@ -300,6 +303,10 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { if(cint(tax.included_in_print_rate)) { var tax_rate = this._get_tax_rate(tax, item_tax_map); + if (tax_rate === erpnext.NOT_APPLICABLE_TAX) { + return [tax_slope, tax_intercept]; + } + if(tax.charge_type == "On Net Total") { tax_slope = (tax_rate / 100.0); @@ -339,8 +346,14 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { } _get_tax_rate(tax, item_tax_map) { - return (Object.keys(item_tax_map).indexOf(tax.account_head) != -1) ? - flt(item_tax_map[tax.account_head], precision("rate", tax)) : tax.rate; + if (tax.account_head in item_tax_map) { + let rate = item_tax_map[tax.account_head]; + if (rate === erpnext.NOT_APPLICABLE_TAX) { + return erpnext.NOT_APPLICABLE_TAX; + } + return flt(rate, precision("rate", tax)); + } + return tax.rate; } calculate_net_total() { @@ -379,6 +392,9 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { } $.each(item_tax_map, function(tax, rate) { + if (rate === erpnext.NOT_APPLICABLE_TAX) { + return; + } let found = (me.frm.doc.taxes || []).find(d => d.account_head === tax); if (!found) { let child = frappe.model.add_child(me.frm.doc, "taxes"); @@ -429,11 +445,14 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { } } + // net_amount is the taxable basis, it feeds no total and is always + // accumulated, unlike tax_amount which is kept from the first pass + tax.net_amount += current_net_amount; + // accumulate tax amount into tax.tax_amount if (tax.charge_type != "Actual" && !(me.discount_amount_applied && me.frm.doc.apply_discount_on=="Grand Total")) { tax.tax_amount += current_tax_amount; - tax.net_amount += current_net_amount; } // store tax_amount for current item as it will be used for @@ -519,6 +538,10 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { var current_tax_amount = 0.0; var current_net_amount = 0.0; + if (tax_rate === erpnext.NOT_APPLICABLE_TAX) { + return [current_net_amount, current_tax_amount]; + } + // To set row_id by default as previous row. if(["On Previous Row Amount", "On Previous Row Total"].includes(tax.charge_type)) { if (tax.idx === 1) { diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index e7f4cdec979..7a133acaf9e 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -818,6 +818,9 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } $.each(item_tax_map, function(tax, rate) { + if (rate === erpnext.NOT_APPLICABLE_TAX) { + return; + } let found = (me.frm.doc.taxes || []).find(d => d.account_head === tax); if(!found) { let child = frappe.model.add_child(me.frm.doc, "taxes"); @@ -1611,9 +1614,9 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe } if (this.frm.doc.taxes && this.frm.doc.taxes.length > 0) { - this.frm.set_currency_labels(["tax_amount", "total", "tax_amount_after_discount"], this.frm.doc.currency, "taxes"); + this.frm.set_currency_labels(["net_amount", "tax_amount", "total", "tax_amount_after_discount"], this.frm.doc.currency, "taxes"); - this.frm.set_currency_labels(["base_tax_amount", "base_total", "base_tax_amount_after_discount"], company_currency, "taxes"); + this.frm.set_currency_labels(["base_net_amount", "base_tax_amount", "base_total", "base_tax_amount_after_discount"], company_currency, "taxes"); } if (this.frm.doc.advances && this.frm.doc.advances.length > 0) { diff --git a/erpnext/regional/italy/test_italy.py b/erpnext/regional/italy/test_italy.py new file mode 100644 index 00000000000..57b6704e867 --- /dev/null +++ b/erpnext/regional/italy/test_italy.py @@ -0,0 +1,80 @@ +import frappe +from frappe.tests.utils import FrappeTestCase + +from erpnext.regional.italy.utils import get_invoice_summary + +VAT_7 = "_Test Italy VAT 7 - _TC" +VAT_19 = "_Test Italy VAT 19 - _TC" + + +def make_item(item_code, net_amount, tax_amount, item_tax_rate): + return frappe._dict( + item_code=item_code, + net_amount=net_amount, + tax_amount=tax_amount, + item_tax_rate=item_tax_rate, + ) + + +def make_tax(account_head, total, charge_type="On Net Total", **kwargs): + return frappe._dict( + charge_type=charge_type, + account_head=account_head, + rate=0, + total=total, + tax_exemption_reason="N4-esenti", + tax_exemption_law="Art.10", + **kwargs, + ) + + +class TestItalyInvoiceSummary(FrappeTestCase): + def test_not_applicable_tax_excluded_from_summary(self): + """An item that marks a tax not applicable belongs to another summary + block. Counting it here inflates DatiRiepilogo and emits a block with + AliquotaIVA 0.00 and no Natura, which SDI rejects.""" + items = [ + make_item("A", 100.0, 7.0, {VAT_7: 7.0, VAT_19: "N/A"}), + make_item("B", 100.0, 19.0, {VAT_7: "N/A", VAT_19: 19.0}), + ] + taxes = [make_tax(VAT_7, 107.0), make_tax(VAT_19, 126.0)] + + summary = get_invoice_summary(items, taxes) + + self.assertEqual(sorted(summary.keys()), ["19.0", "7.0"]) + self.assertEqual(summary["7.0"]["taxable_amount"], 100.0) + self.assertEqual(summary["19.0"]["taxable_amount"], 100.0) + + def test_zero_rated_tax_keeps_exemption_reason(self): + """A genuine 0% rate is still exempt and must carry its Natura.""" + items = [make_item("C", 100.0, 0.0, {VAT_7: 0.0})] + + summary = get_invoice_summary(items, [make_tax(VAT_7, 100.0)]) + + self.assertEqual(list(summary.keys()), ["0.0"]) + self.assertEqual(summary["0.0"]["taxable_amount"], 100.0) + self.assertEqual(summary["0.0"]["tax_exemption_reason"], "N4-esenti") + + def test_all_items_not_applicable_falls_back_to_zero_vat(self): + """With every item excluded the summary would be empty, so the existing + zero VAT fallback has to supply the block and its Natura.""" + items = [make_item("D", 100.0, 0.0, {VAT_7: "N/A"})] + + summary = get_invoice_summary(items, [make_tax(VAT_7, 100.0)]) + + self.assertEqual(list(summary.keys()), ["0.0"]) + self.assertEqual(summary["0.0"]["tax_exemption_reason"], "N4-esenti") + + def test_previous_row_tax_with_only_not_applicable_items(self): + """The summary key leaks out of the item loop and is read again for + previous-row charges. Every item being excluded leaves it unset.""" + items = [make_item("A", 100.0, 0.0, {VAT_7: 0.0, VAT_19: "N/A"})] + taxes = [ + make_tax(VAT_7, 100.0, idx=1), + make_tax(VAT_19, 100.0, charge_type="On Previous Row Total", idx=2, row_id=None), + ] + + summary = get_invoice_summary(items, taxes) + + self.assertEqual(list(summary.keys()), ["0.0"]) + self.assertEqual(summary["0.0"]["taxable_amount"], 100.0) diff --git a/erpnext/regional/italy/utils.py b/erpnext/regional/italy/utils.py index 8e216644857..ef35ad26ab6 100644 --- a/erpnext/regional/italy/utils.py +++ b/erpnext/regional/italy/utils.py @@ -8,6 +8,7 @@ from frappe.utils.file_manager import remove_file from erpnext.controllers.taxes_and_totals import get_itemised_tax from erpnext.regional.italy import state_codes +from erpnext.stock.get_item_details import NOT_APPLICABLE_TAX def update_itemised_tax_data(doc): @@ -171,13 +172,20 @@ def get_invoice_summary(items, taxes): # Check item tax rates if tax rate is zero. if tax.rate == 0: + key = None for item in items: item_tax_rate = item.item_tax_rate if isinstance(item.item_tax_rate, str): item_tax_rate = json.loads(item.item_tax_rate) if item_tax_rate and tax.account_head in item_tax_rate: - key = cstr(item_tax_rate[tax.account_head]) + rate = item_tax_rate[tax.account_head] + if rate == NOT_APPLICABLE_TAX: + # the tax does not apply to this item, so the item belongs + # to another summary block and must not be counted here + continue + + key = cstr(rate) if key not in summary_data: summary_data.setdefault( key, @@ -195,10 +203,15 @@ def get_invoice_summary(items, taxes): summary_data[key]["tax_exemption_reason"] = tax.tax_exemption_reason summary_data[key]["tax_exemption_law"] = tax.tax_exemption_law - if summary_data.get("0.0") and tax.charge_type in [ - "On Previous Row Total", - "On Previous Row Amount", - ]: + if ( + key + and summary_data.get("0.0") + and tax.charge_type + in [ + "On Previous Row Total", + "On Previous Row Amount", + ] + ): summary_data[key]["taxable_amount"] = tax.total if summary_data == {}: # Implies that Zero VAT has not been set on any item. diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index aecc20d8aa2..c9fa5fe6e16 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -34,6 +34,8 @@ purchase_doctypes = [ "Purchase Invoice", ] +NOT_APPLICABLE_TAX = "N/A" + @frappe.whitelist() def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=True): @@ -806,7 +808,10 @@ def get_item_tax_map(company, item_tax_template, as_json=True): template = frappe.get_cached_doc("Item Tax Template", item_tax_template) for d in template.taxes: if frappe.get_cached_value("Account", d.tax_type, "company") == company: - item_tax_map[d.tax_type] = d.tax_rate + if d.get("not_applicable"): + item_tax_map[d.tax_type] = NOT_APPLICABLE_TAX + else: + item_tax_map[d.tax_type] = d.tax_rate return json.dumps(item_tax_map) if as_json else item_tax_map From d4815cb23137cfc1f92df9578ad2025ee61e0c59 Mon Sep 17 00:00:00 2001 From: Diptanil Saha Date: Tue, 25 Aug 2026 22:09:13 +0530 Subject: [PATCH 59/59] fix(party_ledger_summary): added missing filters for `cost_center` and `projects` (#58411) --- .../customer_ledger_summary.js | 22 +++++++++++++++++++ .../supplier_ledger_summary.js | 22 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js index c28815df62e..136c09dbe21 100644 --- a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js +++ b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.js @@ -93,5 +93,27 @@ frappe.query_reports["Customer Ledger Summary"] = { fieldtype: "Data", hidden: 1, }, + { + fieldname: "cost_center", + label: __("Cost Center"), + fieldtype: "MultiSelectList", + options: "Cost Center", + get_data: function (txt) { + return frappe.db.get_link_options("Cost Center", txt, { + company: frappe.query_report.get_filter_value("company"), + }); + }, + }, + { + fieldname: "project", + label: __("Project"), + fieldtype: "MultiSelectList", + options: "Project", + get_data: function (txt) { + return frappe.db.get_link_options("Project", txt, { + company: frappe.query_report.get_filter_value("company"), + }); + }, + }, ], }; diff --git a/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js b/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js index 5d91575b8b2..535bc7bfad2 100644 --- a/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js +++ b/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js @@ -74,5 +74,27 @@ frappe.query_reports["Supplier Ledger Summary"] = { fieldtype: "Data", hidden: 1, }, + { + fieldname: "cost_center", + label: __("Cost Center"), + fieldtype: "MultiSelectList", + options: "Cost Center", + get_data: function (txt) { + return frappe.db.get_link_options("Cost Center", txt, { + company: frappe.query_report.get_filter_value("company"), + }); + }, + }, + { + fieldname: "project", + label: __("Project"), + fieldtype: "MultiSelectList", + options: "Project", + get_data: function (txt) { + return frappe.db.get_link_options("Project", txt, { + company: frappe.query_report.get_filter_value("company"), + }); + }, + }, ], };