Compare commits

..

1 Commits

Author SHA1 Message Date
Imesha Sudasingha
aec62a652c fix: remove defunct payment gateway links from ERPNext Integrations workspace
- Remove GoCardless Settings link (DocType deleted in v15.0)
- Remove Mpesa Settings link (DocType deleted in v15.0)
- Update Payments card link_count from 3 to 1
- Only Plaid Settings remains in Payments section

Fixes broken links that resulted in errors when clicked.
Closes #49352

(cherry picked from commit 17f7351f85)

# Conflicts:
#	erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json
2025-09-01 06:04:47 +00:00
122 changed files with 23727 additions and 28222 deletions

View File

@@ -10,10 +10,8 @@ from frappe.contacts.doctype.address.address import (
class ERPNextAddress(Address):
def validate(self):
self.validate_reference()
self.update_company_address()
if hasattr(super(), "validate"):
super().validate()
self.update_compnay_address()
super().validate()
def link_address(self):
"""Link address based on owner"""
@@ -22,7 +20,7 @@ class ERPNextAddress(Address):
return super().link_address()
def update_company_address(self):
def update_compnay_address(self):
for link in self.get("links"):
if link.link_doctype == "Company":
self.is_your_company_address = 1
@@ -40,10 +38,6 @@ class ERPNextAddress(Address):
"""
After Address is updated, update the related 'Primary Address' on Customer.
"""
if hasattr(super(), "on_update"):
super().on_update()
address_display = get_address_display(self.as_dict())
filters = {"customer_primary_address": self.name}
customers = frappe.db.get_all("Customer", filters=filters, as_list=True)

View File

@@ -11,9 +11,6 @@
"cost_center",
"debit",
"credit",
"reporting_currency_exchange_rate",
"debit_in_reporting_currency",
"credit_in_reporting_currency",
"account_currency",
"debit_in_account_currency",
"credit_in_account_currency",
@@ -127,30 +124,12 @@
"fieldname": "is_period_closing_voucher_entry",
"fieldtype": "Check",
"label": "Is Period Closing Voucher Entry"
},
{
"fieldname": "debit_in_reporting_currency",
"fieldtype": "Currency",
"label": "Debit Amount in Reporting Currency",
"options": "Company:company:reporting_currency"
},
{
"fieldname": "credit_in_reporting_currency",
"fieldtype": "Currency",
"label": "Credit Amount in Reporting Currency",
"options": "Company:company:reporting_currency"
},
{
"fieldname": "reporting_currency_exchange_rate",
"fieldtype": "Float",
"label": "Reporting Currency Exchange Rate",
"precision": "9"
}
],
"icon": "fa fa-list",
"in_create": 1,
"links": [],
"modified": "2025-08-22 19:13:50.400404",
"modified": "2024-03-27 13:05:56.710541",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Account Closing Balance",
@@ -179,8 +158,7 @@
"role": "Auditor"
}
],
"row_format": "Dynamic",
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -2,15 +2,12 @@
# For license information, please see license.txt
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import cint, cstr, flt
from frappe.utils import cint, cstr
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_accounting_dimensions,
)
from erpnext.exceptions import ReportingCurrencyExchangeNotFoundError
from erpnext.setup.utils import get_exchange_rate
class AccountClosingBalance(Document):
@@ -29,15 +26,12 @@ class AccountClosingBalance(Document):
cost_center: DF.Link | None
credit: DF.Currency
credit_in_account_currency: DF.Currency
credit_in_reporting_currency: DF.Currency
debit: DF.Currency
debit_in_account_currency: DF.Currency
debit_in_reporting_currency: DF.Currency
finance_book: DF.Link | None
is_period_closing_voucher_entry: DF.Check
period_closing_voucher: DF.Link | None
project: DF.Link | None
reporting_currency_exchange_rate: DF.Float
# end: auto-generated types
pass
@@ -61,7 +55,6 @@ def make_closing_entries(closing_entries, voucher_name, company, closing_date):
"closing_date": closing_date,
}
)
set_amount_in_reporting_currency(cle, company, closing_date)
cle.flags.ignore_permissions = True
cle.flags.ignore_links = True
cle.submit()
@@ -151,29 +144,3 @@ def get_previous_closing_entries(company, closing_date, accounting_dimensions):
entries = query.run(as_dict=1)
return entries
def set_amount_in_reporting_currency(cle, company, closing_date):
default_currency, reporting_currency = frappe.get_cached_value(
"Company", company, ["default_currency", "reporting_currency"]
)
reporting_currency_exchange_rate = get_exchange_rate(default_currency, reporting_currency, closing_date)
if not reporting_currency_exchange_rate:
frappe.throw(
title=_("Reporting Currency Exchange Not Found"),
msg=_(
"Unable to find exchange rate for {0} to {1} for key date {2}. Please create a Currency Exchange record manually."
).format(default_currency, reporting_currency, closing_date),
exc=ReportingCurrencyExchangeNotFoundError,
)
debit_in_reporting_currency = flt(cle.get("debit", 0) * reporting_currency_exchange_rate)
credit_in_reporting_currency = flt(cle.get("credit", 0) * reporting_currency_exchange_rate)
cle.update(
{
"reporting_currency_exchange_rate": reporting_currency_exchange_rate,
"debit_in_reporting_currency": debit_in_reporting_currency,
"credit_in_reporting_currency": credit_in_reporting_currency,
}
)

View File

@@ -17,7 +17,6 @@
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"project",
"section_break_8",
"rate",
"section_break_9",
@@ -96,13 +95,6 @@
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
},
{
"allow_on_submit": 1,
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"options": "Project"
},
{
"fieldname": "section_break_8",
"fieldtype": "Section Break"

View File

@@ -132,8 +132,7 @@
"fieldtype": "Data",
"in_list_view": 1,
"label": "IBAN",
"length": 34,
"options": "IBAN"
"length": 30
},
{
"fieldname": "column_break_12",
@@ -209,7 +208,6 @@
"label": "Disabled"
}
],
"grid_page_length": 50,
"links": [
{
"group": "Transactions",
@@ -252,7 +250,7 @@
"link_fieldname": "default_bank_account"
}
],
"modified": "2025-08-29 12:32:01.081687",
"modified": "2024-10-30 09:41:14.113414",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Account",
@@ -284,10 +282,9 @@
"write": 1
}
],
"row_format": "Dynamic",
"search_fields": "bank,account",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}

View File

@@ -52,6 +52,7 @@ class BankAccount(Document):
def validate(self):
self.validate_company()
self.validate_iban()
self.validate_account()
self.update_default_bank_account()
@@ -71,6 +72,35 @@ class BankAccount(Document):
if self.is_company_account and not self.company:
frappe.throw(_("Company is mandatory for company account"))
def validate_iban(self):
"""
Algorithm: https://en.wikipedia.org/wiki/International_Bank_Account_Number#Validating_the_IBAN
"""
# IBAN field is optional
if not self.iban:
return
def encode_char(c):
# Position in the alphabet (A=1, B=2, ...) plus nine
return str(9 + ord(c) - 64)
# remove whitespaces, upper case to get the right number from ord()
iban = "".join(self.iban.split(" ")).upper()
# Move country code and checksum from the start to the end
flipped = iban[4:] + iban[:4]
# Encode characters as numbers
encoded = [encode_char(c) if ord(c) >= 65 and ord(c) <= 90 else c for c in flipped]
try:
to_check = int("".join(encoded))
except ValueError:
frappe.throw(_("IBAN is not valid"))
if to_check % 97 != 1:
frappe.throw(_("IBAN is not valid"))
def update_default_bank_account(self):
if self.is_default and not self.disabled:
frappe.db.set_value(

View File

@@ -8,4 +8,38 @@ from frappe.tests import IntegrationTestCase
class TestBankAccount(IntegrationTestCase):
pass
def test_validate_iban(self):
valid_ibans = [
"GB82 WEST 1234 5698 7654 32",
"DE91 1000 0000 0123 4567 89",
"FR76 3000 6000 0112 3456 7890 189",
]
invalid_ibans = [
# wrong checksum (3rd place)
"GB72 WEST 1234 5698 7654 32",
"DE81 1000 0000 0123 4567 89",
"FR66 3000 6000 0112 3456 7890 189",
]
bank_account = frappe.get_doc({"doctype": "Bank Account"})
try:
bank_account.validate_iban()
except AttributeError:
msg = "BankAccount.validate_iban() failed for empty IBAN"
self.fail(msg=msg)
for iban in valid_ibans:
bank_account.iban = iban
try:
bank_account.validate_iban()
except ValidationError:
msg = f"BankAccount.validate_iban() failed for valid IBAN {iban}"
self.fail(msg=msg)
for not_iban in invalid_ibans:
bank_account.iban = not_iban
msg = f"BankAccount.validate_iban() accepted invalid IBAN {not_iban}"
with self.assertRaises(ValidationError, msg=msg):
bank_account.validate_iban()

View File

@@ -155,10 +155,8 @@ def get_payment_entries_for_bank_clearance(
entries = []
condition = ""
pe_condition = ""
if not include_reconciled_entries:
condition = "and (clearance_date IS NULL or clearance_date='0000-00-00')"
pe_condition = "and (pe.clearance_date IS NULL or pe.clearance_date='0000-00-00')"
journal_entries = frappe.db.sql(
f"""
@@ -183,20 +181,19 @@ def get_payment_entries_for_bank_clearance(
payment_entries = frappe.db.sql(
f"""
select
"Payment Entry" as payment_document, pe.name as payment_entry,
pe.reference_no as cheque_number, pe.reference_date as cheque_date,
if(pe.paid_from=%(account)s, pe.paid_amount + if(pe.payment_type = 'Pay' and c.default_currency = pe.paid_from_account_currency, pe.base_total_taxes_and_charges, pe.total_taxes_and_charges) , 0) as credit,
if(pe.paid_from=%(account)s, 0, pe.received_amount + pe.total_taxes_and_charges) as debit,
pe.posting_date, ifnull(pe.party,if(pe.paid_from=%(account)s,pe.paid_to,pe.paid_from)) as against_account, pe.clearance_date,
if(pe.paid_to=%(account)s, pe.paid_to_account_currency, pe.paid_from_account_currency) as account_currency
from `tabPayment Entry` as pe
join `tabCompany` c on c.name = pe.company
"Payment Entry" as payment_document, name as payment_entry,
reference_no as cheque_number, reference_date as cheque_date,
if(paid_from=%(account)s, paid_amount + total_taxes_and_charges, 0) as credit,
if(paid_from=%(account)s, 0, received_amount + total_taxes_and_charges) as debit,
posting_date, ifnull(party,if(paid_from=%(account)s,paid_to,paid_from)) as against_account, clearance_date,
if(paid_to=%(account)s, paid_to_account_currency, paid_from_account_currency) as account_currency
from `tabPayment Entry`
where
(pe.paid_from=%(account)s or pe.paid_to=%(account)s) and pe.docstatus=1
and pe.posting_date >= %(from)s and pe.posting_date <= %(to)s
{pe_condition}
(paid_from=%(account)s or paid_to=%(account)s) and docstatus=1
and posting_date >= %(from)s and posting_date <= %(to)s
{condition}
order by
pe.posting_date ASC, pe.name DESC
posting_date ASC, name DESC
""",
{
"account": account,

View File

@@ -146,7 +146,6 @@
"fieldname": "iban",
"fieldtype": "Data",
"label": "IBAN",
"options": "IBAN",
"read_only": 1
},
{
@@ -215,10 +214,9 @@
"read_only": 1
}
],
"grid_page_length": 50,
"is_submittable": 1,
"links": [],
"modified": "2025-08-29 11:52:33.550847",
"modified": "2024-03-27 13:06:37.731207",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Guarantee",
@@ -252,10 +250,9 @@
}
],
"quick_entry": 1,
"row_format": "Dynamic",
"search_fields": "customer",
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"title_field": "customer"
}
}

View File

@@ -76,18 +76,6 @@ class BankStatementImport(DataImport):
self.validate_google_sheets_url()
def start_import(self):
"""
Start a background import job for this Bank Statement Import.
Validates that the preview contains a "Bank Account" column and that the scheduler is active (unless running in test or developer mode). If validation passes and there is not already an enqueued job for this document, enqueue a background worker to perform the import.
Returns:
str | None: The enqueued job_id when a new job was queued, otherwise None.
Raises:
frappe.ValidationError: If the preview is missing a "Bank Account" column.
frappe.ValidationError: If the scheduler is inactive and import is not allowed to run immediately.
"""
preview = frappe.get_doc("Bank Statement Import", self.name).get_preview_from_template(
self.import_file, self.google_sheets_url
)
@@ -123,94 +111,20 @@ class BankStatementImport(DataImport):
return None
def preprocess_mt940_content(content: str) -> str:
"""
Truncate overly long MT940 statement numbers found in `:28C:` tags to the last 5 digits.
This function fixes MT940 files where banks supply statement numbers longer than the MT940-expected maximum (5 digits),
which can break parsers. It only processes lines that start with the `:28C:` tag and:
- leaves content unchanged if no `:28C:` tag is present,
- truncates numeric statement numbers longer than 5 digits to their last 5 digits,
- preserves any `/sequence` suffix and trailing whitespace on the same line.
Parameters:
content (str): Raw MT940 file content.
Returns:
str: The processed content with corrected `:28C:` statement numbers.
"""
# Fast-path: bail if no :28C: tag exists
if ":28C:" not in content:
return content
# Match :28C: at start of line, capture digits and optional /seq, preserve whitespace
pattern = re.compile(r'(?m)^(:28C:)(\d{6,})(/\d+)?(\s*)$')
def replace_statement_number(match):
"""
Replace a matched MT940 :28C: statement number by truncating it to the last five digits if it is longer.
Parameters:
match (re.Match): A regex match with groups:
1: prefix (e.g., ':28C:')
2: numeric statement number
3: optional sequence part (e.g., '/1')
4: optional trailing whitespace
Returns:
str: Reconstructed replacement string preserving prefix, (possibly truncated) statement number, sequence part, and trailing whitespace.
"""
prefix = match.group(1) # ':28C:'
statement_num = match.group(2) # The statement number
sequence_part = match.group(3) or '' # The sequence part like '/1'
trailing_space = match.group(4) or '' # Preserve trailing whitespace
# If statement number is longer than 5 digits, truncate to last 5 digits
if len(statement_num) > 5:
statement_num = statement_num[-5:]
return prefix + statement_num + sequence_part + trailing_space
# Apply the replacement
processed_content = pattern.sub(replace_statement_number, content)
return processed_content
@frappe.whitelist()
def convert_mt940_to_csv(data_import, mt940_file_path):
"""
Convert an MT940 file to a CSV and save it to the Frappe File Manager, returning the saved file URL.
This function:
- Loads the specified MT940 file, verifies it is MT940 format, preprocesses content to fix statement number formatting, and parses transactions.
- Writes parsed transactions to an in-memory CSV with headers: Date, Deposit, Withdrawal, Description, Reference Number, Bank Account, Currency.
- Saves the CSV as a private attachment on the Bank Statement Import document and returns the file URL.
Parameters:
data_import (str): Name (docname) of the Bank Statement Import document to attach the converted CSV to.
mt940_file_path (str): File path or file identifier pointing to the uploaded MT940 file to convert.
Returns:
str: URL of the saved CSV file in the File Manager.
Raises:
frappe.ValidationError: If the file is not MT940, MT940 import is not enabled on the document, parsing fails, or no transactions are found.
"""
doc = frappe.get_doc("Bank Statement Import", data_import)
file_doc, content = get_file(mt940_file_path)
is_mt940 = is_mt940_format(content)
if not is_mt940:
if not is_mt940_format(content):
frappe.throw(_("The uploaded file does not appear to be in valid MT940 format."))
if is_mt940 and not doc.import_mt940_fromat:
if is_mt940_format(content) and not doc.import_mt940_fromat:
frappe.throw(_("MT940 file detected. Please enable 'Import MT940 Format' to proceed."))
try:
# Preprocess MT940 content to fix statement number format issues
processed_content = preprocess_mt940_content(content)
transactions = mt940.parse(processed_content)
transactions = mt940.parse(content)
except Exception as e:
frappe.throw(_("Failed to parse MT940 format. Error: {0}").format(str(e)))
@@ -335,20 +249,6 @@ def start_import(data_import, bank_account, import_file_path, google_sheets_url,
def update_mapping_db(bank, template_options):
"""
Update a Bank document's transaction field mappings to match the provided template options.
This replaces all existing entries in the Bank.bank_transaction_mapping child table with mappings from
the JSON-encoded template_options. The expected template_options JSON contains a "column_to_field_map"
object mapping file column names (keys) to bank transaction field names (values).
Parameters:
bank (str | frappe.model.document.Document): Bank name/docname or a Bank document.
template_options (str): JSON string containing a "column_to_field_map" mapping of file column -> bank field.
Side effects:
Overwrites the Bank.bank_transaction_mapping entries and saves the Bank document.
"""
bank = frappe.get_doc("Bank", bank)
for d in bank.bank_transaction_mapping:
d.delete()
@@ -360,17 +260,6 @@ def update_mapping_db(bank, template_options):
def add_bank_account(data, bank_account):
"""
Ensure every data row contains the given bank account value.
Assumes `data` is a list of rows where data[0] is the header row. If the header row does not contain "Bank Account",
this function appends that header and appends the `bank_account` value to each subsequent row. If the header exists,
it sets the `bank_account` value into the existing "Bank Account" column for every data row. Mutates `data` in place.
Parameters:
data (list[list]): Table-like data with the first row as headers.
bank_account (str): Bank account value to set for each data row.
"""
bank_account_loc = None
if "Bank Account" not in data[0]:
data[0].append("Bank Account")
@@ -387,21 +276,6 @@ def add_bank_account(data, bank_account):
def write_files(import_file, data):
"""
Write processed tabular data back to the original import file path (CSV or Excel).
This function overwrites the file referenced by import_file.file_doc.get_full_path().
- If the file extension is "csv", writes rows using the csv writer (expects `data` as an iterable of row iterables).
- If the extension is "xlsx" or "xls", writes to an Excel workbook using write_xlsx with sheet name "trans".
Parameters:
import_file: object
File wrapper whose `.file_doc.get_full_path()` and `.file_doc.get_extension()` are used to determine the target path and extension.
data: Iterable[Iterable]
Sequence of rows (each row is an iterable of cell values) to be written.
No return value.
"""
full_file_path = import_file.file_doc.get_full_path()
parts = import_file.file_doc.get_extension()
extension = parts[1]
@@ -411,26 +285,11 @@ def write_files(import_file, data):
with open(full_file_path, "w", newline="") as file:
writer = csv.writer(file)
writer.writerows(data)
elif extension in ("xlsx", "xls"):
elif extension == "xlsx" or "xls":
write_xlsx(data, "trans", file_path=full_file_path)
def write_xlsx(data, sheet_name, wb=None, column_widths=None, file_path=None):
"""
Write rows of data to an Excel worksheet and save the workbook.
Creates a sheet named `sheet_name` in the provided openpyxl workbook (or a new write-only workbook if `wb` is None), applies optional column widths, converts HTML in string cells (except for sheets named "Data Import Template" or "Data Export"), strips characters illegal in Excel, and saves the workbook to `file_path`.
Parameters:
data (Iterable[Sequence]): Iterable of rows, where each row is a sequence of cell values.
sheet_name (str): Name of the worksheet to create.
wb (openpyxl.Workbook, optional): Workbook to append the sheet to. If not provided, a new write-only Workbook is created.
column_widths (Sequence[Number], optional): Sequence of column widths; indexes correspond to columns starting at 1.
file_path (str): File path where the workbook will be saved.
Returns:
bool: True on successful save.
"""
# from xlsx utils with changes
column_widths = column_widths or []
if wb is None:

View File

@@ -1,220 +1,10 @@
# Copyright (c) 2020, Frappe Technologies and Contributors
# See license.txt
# import frappe
import unittest
from erpnext.accounts.doctype.bank_statement_import.bank_statement_import import (
preprocess_mt940_content,
is_mt940_format,
)
from frappe.tests import IntegrationTestCase
class TestBankStatementImport(unittest.TestCase):
"""Unit tests for Bank Statement Import functions"""
def test_preprocess_mt940_content_with_long_statement_number(self):
"""Test that statement numbers longer than 5 digits are truncated to last 5 digits"""
# Test case with 6-digit statement number (167619 -> 67619)
mt940_content = ":28C:167619/1"
expected_content = ":28C:67619/1"
result = preprocess_mt940_content(mt940_content)
self.assertEqual(result, expected_content)
def test_preprocess_mt940_content_with_normal_statement_number(self):
"""Test that statement numbers with 5 or fewer digits are unchanged"""
# Test case with 5-digit statement number (should remain unchanged)
mt940_content = ":28C:12345/1"
result = preprocess_mt940_content(mt940_content)
self.assertEqual(result, mt940_content) # Should be unchanged
# Test case with 4-digit statement number (should remain unchanged)
mt940_content = ":28C:1234/1"
result = preprocess_mt940_content(mt940_content)
self.assertEqual(result, mt940_content) # Should be unchanged
def test_preprocess_mt940_content_without_sequence_number(self):
"""Test statement number truncation without sequence number"""
# Test case with long statement number but no sequence (no /1)
mt940_content = ":28C:987654321"
expected_content = ":28C:54321"
result = preprocess_mt940_content(mt940_content)
self.assertEqual(result, expected_content)
def test_preprocess_mt940_content_multiple_occurrences(self):
"""Test multiple statement numbers in the same content"""
mt940_content = """:28C:167619/1
:28C:987654/2"""
expected_content = """:28C:67619/1
:28C:87654/2"""
result = preprocess_mt940_content(mt940_content)
self.assertEqual(result, expected_content)
def test_preprocess_mt940_content_edge_cases(self):
"""Test edge cases like empty content and content without :28C: tags"""
# Test empty content
self.assertEqual(preprocess_mt940_content(""), "")
# Test content without :28C: tags
content_without_28c = """:20:STARTUMSE
:25:12345678901234567890
:60F:C031002EUR0,00"""
result = preprocess_mt940_content(content_without_28c)
self.assertEqual(result, content_without_28c) # Should be unchanged
def test_preprocess_mt940_content_with_full_mt940_document(self):
"""Test preprocessing with complete MT940 document"""
mt940_content = """:20:STARTUMSE
:25:12345678901234567890
:28C:167619/1
:60F:C031002EUR0,00
:61:0310021002DR123,45NMSCNONREF//8327000090031789
:86:806?20EREF+NONREF?21MREF+M180031?22CRED+DE98ZZZ09999999999
:62F:C031002EUR-123,45
-"""
expected_content = """:20:STARTUMSE
:25:12345678901234567890
:28C:67619/1
:60F:C031002EUR0,00
:61:0310021002DR123,45NMSCNONREF//8327000090031789
:86:806?20EREF+NONREF?21MREF+M180031?22CRED+DE98ZZZ09999999999
:62F:C031002EUR-123,45
-"""
result = preprocess_mt940_content(mt940_content)
self.assertEqual(result, expected_content)
def test_is_mt940_format_detection(self):
"""Test MT940 format detection function"""
# Valid MT940 content with all required tags
valid_mt940 = """:20:STARTUMSE
:25:12345678901234567890
:28C:167619/1
:60F:C031002EUR0,00
:61:0310021002DR123,45NMSCNONREF//8327000090031789"""
self.assertTrue(is_mt940_format(valid_mt940))
# Invalid MT940 content (CSV format)
invalid_mt940 = """Date,Description,Amount
2023-01-01,Test Transaction,100.00
2023-01-02,Another Transaction,-50.00"""
self.assertFalse(is_mt940_format(invalid_mt940))
# Partially valid MT940 (missing some required tags)
partial_mt940 = """:20:STARTUMSE
:25:12345678901234567890
:60F:C031002EUR0,00"""
self.assertFalse(is_mt940_format(partial_mt940))
# Empty content
self.assertFalse(is_mt940_format(""))
def test_preprocess_mt940_content_boundary_conditions(self):
"""
Verify preprocessing handles statement-number length boundaries in `:28C:` tags.
Checks that:
- A 6-digit statement number is truncated to its last 5 digits.
- A 5-digit statement number remains unchanged.
- A very long statement number is reduced to its last 5 digits.
"""
# Test exactly 6 digits (should be truncated)
mt940_content = ":28C:123456/1"
expected_content = ":28C:23456/1"
result = preprocess_mt940_content(mt940_content)
self.assertEqual(result, expected_content)
# Test exactly 5 digits (should remain unchanged)
mt940_content = ":28C:12345/1"
result = preprocess_mt940_content(mt940_content)
self.assertEqual(result, mt940_content)
# Test very long statement number
mt940_content = ":28C:123456789012345/1"
expected_content = ":28C:12345/1" # Last 5 digits
result = preprocess_mt940_content(mt940_content)
self.assertEqual(result, expected_content)
def test_preprocess_mt940_content_real_world_case(self):
"""
Verify preprocessing of a real-world MT940 document: truncate 6-digit `:28C:` statement numbers to their last 5 digits and preserve all other content.
Uses a sanitized, production-failing MT940 sample where `:28C:167619/1` must become `:28C:67619/1`. Asserts the entire document matches the expected transformed output, that the truncated tag is present and the original is absent, and that unrelated fields (e.g., `:20:` reference and UPI details) remain unchanged.
"""
# This is based on actual MT940 content that was causing parsing errors (sanitized)
mt940_content = """{1:F0112345678901X0000000000}{2:I94012345678901XN}{4:
:20:STMTREF167619
:25:1234567890
:28C:167619/1
:60F:C250622USD0,00
:61:2507170717C100000,00NMSCNOREF
:86:BY EXAMPLE INST 123456/03-07-25/TESTBANK/CITY
:61:2507240724C1,00NMSCNEFTINW-1234567890
:86:NEFT TEST123456789 EXAMPLE MERCHANT SERVICES
:61:2507310731D305,62NMSCTBMS-1234567890
:86:Chrg: Debit Card Annual Fee 1234 for 2025
:61:2508030803D1066,00NMSC123456789
:86:PCD/1234/EXAMPLE DOMAIN/01234567890123/23:27
:61:2508060806D2000,00NMSCUPI-123456789
:86:UPI/TEST USER/123456789/PaidViaTestApp
:61:2508140814D5000,00NMSCUPI-123456789
:86:UPI/TEST USER/123456789/PaidViaTestApp
:61:2509190919D900,00NMSCUPI-123456789
:86:UPI/EXAMPLE MERCHANT/123456789/Pay
:61:2509190919D2606,00NMSCUPI-123456789
:86:UPI/JOHN DOE/123456789/PaidViaTestApp
:62F:C250922USD88123,38
-}"""
# Expected result with statement number 167619 truncated to 67619
expected_content = """{1:F0112345678901X0000000000}{2:I94012345678901XN}{4:
:20:STMTREF167619
:25:1234567890
:28C:67619/1
:60F:C250622USD0,00
:61:2507170717C100000,00NMSCNOREF
:86:BY EXAMPLE INST 123456/03-07-25/TESTBANK/CITY
:61:2507240724C1,00NMSCNEFTINW-1234567890
:86:NEFT TEST123456789 EXAMPLE MERCHANT SERVICES
:61:2507310731D305,62NMSCTBMS-1234567890
:86:Chrg: Debit Card Annual Fee 1234 for 2025
:61:2508030803D1066,00NMSC123456789
:86:PCD/1234/EXAMPLE DOMAIN/01234567890123/23:27
:61:2508060806D2000,00NMSCUPI-123456789
:86:UPI/TEST USER/123456789/PaidViaTestApp
:61:2508140814D5000,00NMSCUPI-123456789
:86:UPI/TEST USER/123456789/PaidViaTestApp
:61:2509190919D900,00NMSCUPI-123456789
:86:UPI/EXAMPLE MERCHANT/123456789/Pay
:61:2509190919D2606,00NMSCUPI-123456789
:86:UPI/JOHN DOE/123456789/PaidViaTestApp
:62F:C250922USD88123,38
-}"""
result = preprocess_mt940_content(mt940_content)
self.assertEqual(result, expected_content)
# Verify that the problematic statement number was actually changed
self.assertIn(":28C:67619/1", result)
self.assertNotIn(":28C:167619/1", result)
# Verify that other content remains unchanged
self.assertIn(":20:STMTREF167619", result) # Reference should remain unchanged
self.assertIn("UPI/TEST USER/123456789/PaidViaTestApp", result)
def test_preprocess_mt940_content_whitespace_variants(self):
"""Test handling of whitespace and different line endings"""
# Test with trailing spaces
mt940_content = ":28C:167619/1 \n"
expected_content = ":28C:67619/1 \n"
result = preprocess_mt940_content(mt940_content)
self.assertEqual(result, expected_content)
# Test with Windows line endings (CRLF)
mt940_content = ":28C:167619/1\r\n"
expected_content = ":28C:67619/1\r\n"
result = preprocess_mt940_content(mt940_content)
self.assertEqual(result, expected_content)
# Test with leading spaces (should not match as it's not line start)
mt940_content = " :28C:167619/1\n"
result = preprocess_mt940_content(mt940_content)
self.assertEqual(result, mt940_content) # Should remain unchanged
class TestBankStatementImport(IntegrationTestCase):
pass

View File

@@ -223,8 +223,7 @@
{
"fieldname": "bank_party_iban",
"fieldtype": "Data",
"label": "Party IBAN (Bank Statement)",
"options": "IBAN"
"label": "Party IBAN (Bank Statement)"
},
{
"fieldname": "bank_party_account_number",
@@ -239,7 +238,7 @@
"grid_page_length": 50,
"is_submittable": 1,
"links": [],
"modified": "2025-08-29 11:53:45.908169",
"modified": "2025-06-18 17:24:57.044666",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Bank Transaction",

View File

@@ -7,9 +7,6 @@ from frappe.utils import nowdate
from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import create_bank_account
IBAN_1 = "DE02000000003716541159"
IBAN_2 = "DE02500105170137075030"
class TestAutoMatchParty(IntegrationTestCase):
@classmethod
@@ -25,24 +22,24 @@ class TestAutoMatchParty(IntegrationTestCase):
frappe.db.set_single_value("Accounts Settings", "enable_fuzzy_matching", 0)
def test_match_by_account_number(self):
create_supplier_for_match(account_no=IBAN_1[11:])
create_supplier_for_match(account_no="000000003716541159")
doc = create_bank_transaction(
withdrawal=1200,
transaction_id="562213b0ca1bf838dab8f2c6a39bbc3b",
account_no=IBAN_1[11:],
iban=IBAN_1,
account_no="000000003716541159",
iban="DE02000000003716541159",
)
self.assertEqual(doc.party_type, "Supplier")
self.assertEqual(doc.party, "John Doe & Co.")
def test_match_by_iban(self):
create_supplier_for_match(iban=IBAN_1)
create_supplier_for_match(iban="DE02000000003716541159")
doc = create_bank_transaction(
withdrawal=1200,
transaction_id="c5455a224602afaa51592a9d9250600d",
account_no=IBAN_1[11:],
iban=IBAN_1,
account_no="000000003716541159",
iban="DE02000000003716541159",
)
self.assertEqual(doc.party_type, "Supplier")
@@ -54,7 +51,7 @@ class TestAutoMatchParty(IntegrationTestCase):
withdrawal=1200,
transaction_id="1f6f661f347ff7b1ea588665f473adb1",
party_name="Ella Jackson",
iban=IBAN_2,
iban="DE04000000003716545346",
)
self.assertEqual(doc.party_type, "Supplier")
self.assertEqual(doc.party, "Jackson Ella W.")

View File

@@ -145,10 +145,8 @@ def validate_expense_against_budget(args, expense_amount=0):
if not frappe.db.count("Budget", cache=True):
return
if not args.fiscal_year:
if args.get("company") and not args.fiscal_year:
args.fiscal_year = get_fiscal_year(args.get("posting_date"), company=args.get("company"))[0]
if args.get("company"):
frappe.flags.exception_approver_role = frappe.get_cached_value(
"Company", args.get("company"), "exception_budget_approver_role"
)

View File

@@ -134,8 +134,7 @@ class ExchangeRateRevaluation(Document):
accounts = self.get_accounts_data()
if accounts:
for acc in accounts:
if acc.get("gain_loss"):
self.append("accounts", acc)
self.append("accounts", acc)
@frappe.whitelist()
def get_accounts_data(self):

View File

@@ -29,17 +29,14 @@
"against_voucher",
"voucher_detail_no",
"transaction_exchange_rate",
"reporting_currency_exchange_rate",
"amounts_section",
"debit_in_account_currency",
"debit",
"debit_in_transaction_currency",
"debit_in_reporting_currency",
"column_break_bm1w",
"credit_in_account_currency",
"credit",
"credit_in_transaction_currency",
"credit_in_reporting_currency",
"dimensions_section",
"cost_center",
"column_break_lmnm",
@@ -356,31 +353,13 @@
{
"fieldname": "column_break_8abq",
"fieldtype": "Column Break"
},
{
"fieldname": "debit_in_reporting_currency",
"fieldtype": "Currency",
"label": "Debit Amount in Reporting Currency",
"options": "Company:company:reporting_currency"
},
{
"fieldname": "credit_in_reporting_currency",
"fieldtype": "Currency",
"label": "Credit Amount in Reporting Currency",
"options": "Company:company:reporting_currency"
},
{
"fieldname": "reporting_currency_exchange_rate",
"fieldtype": "Float",
"label": "Reporting Currency Exchange Rate",
"precision": "9"
}
],
"icon": "fa fa-list",
"idx": 1,
"in_create": 1,
"links": [],
"modified": "2025-08-22 12:57:17.750252",
"modified": "2025-03-21 15:29:11.221890",
"modified_by": "Administrator",
"module": "Accounts",
"name": "GL Entry",
@@ -411,9 +390,8 @@
}
],
"quick_entry": 1,
"row_format": "Dynamic",
"search_fields": "voucher_no,account,posting_date,against_voucher",
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -19,8 +19,7 @@ from erpnext.accounts.party import (
validate_party_gle_currency,
)
from erpnext.accounts.utils import OUTSTANDING_DOCTYPES, get_account_currency, get_fiscal_year
from erpnext.exceptions import InvalidAccountCurrency, ReportingCurrencyExchangeNotFoundError
from erpnext.setup.utils import get_exchange_rate
from erpnext.exceptions import InvalidAccountCurrency
exclude_from_linked_with = True
@@ -43,11 +42,9 @@ class GLEntry(Document):
cost_center: DF.Link | None
credit: DF.Currency
credit_in_account_currency: DF.Currency
credit_in_reporting_currency: DF.Currency
credit_in_transaction_currency: DF.Currency
debit: DF.Currency
debit_in_account_currency: DF.Currency
debit_in_reporting_currency: DF.Currency
debit_in_transaction_currency: DF.Currency
due_date: DF.Date | None
finance_book: DF.Link | None
@@ -60,7 +57,6 @@ class GLEntry(Document):
posting_date: DF.Date | None
project: DF.Link | None
remarks: DF.Text | None
reporting_currency_exchange_rate: DF.Float
to_rename: DF.Check
transaction_currency: DF.Link | None
transaction_date: DF.Date | None
@@ -92,8 +88,6 @@ class GLEntry(Document):
self.validate_party()
self.validate_currency()
self.set_amount_in_reporting_currency()
def on_update(self):
adv_adj = self.flags.adv_adj
if not self.flags.from_repost and self.voucher_type != "Period Closing Voucher":
@@ -137,20 +131,18 @@ class GLEntry(Document):
if not self.is_cancelled and not (self.party_type and self.party):
account_type = frappe.get_cached_value("Account", self.account, "account_type")
# skipping validation for payroll entry creation in case party is not required
if not frappe.flags.party_not_required_for_receivable_payable:
if account_type == "Receivable":
frappe.throw(
_("{0} {1}: Customer is required against Receivable account {2}").format(
self.voucher_type, self.voucher_no, self.account
)
if account_type == "Receivable":
frappe.throw(
_("{0} {1}: Customer is required against Receivable account {2}").format(
self.voucher_type, self.voucher_no, self.account
)
elif account_type == "Payable":
frappe.throw(
_("{0} {1}: Supplier is required against Payable account {2}").format(
self.voucher_type, self.voucher_no, self.account
)
)
elif account_type == "Payable":
frappe.throw(
_("{0} {1}: Supplier is required against Payable account {2}").format(
self.voucher_type, self.voucher_no, self.account
)
)
# Zero value transaction is not allowed
if not (
@@ -300,25 +292,6 @@ class GLEntry(Document):
if self.party_type and self.party:
validate_party_gle_currency(self.party_type, self.party, self.company, self.account_currency)
def set_amount_in_reporting_currency(self):
default_currency, reporting_currency = frappe.get_cached_value(
"Company", self.company, ["default_currency", "reporting_currency"]
)
transaction_date = self.transaction_date or self.posting_date
self.reporting_currency_exchange_rate = get_exchange_rate(
default_currency, reporting_currency, transaction_date
)
if not self.reporting_currency_exchange_rate:
frappe.throw(
title=_("Reporting Currency Exchange Not Found"),
msg=_(
"Unable to find exchange rate for {0} to {1} for key date {2}. Please create a Currency Exchange record manually."
).format(default_currency, reporting_currency, transaction_date),
exc=ReportingCurrencyExchangeNotFoundError,
)
self.debit_in_reporting_currency = flt(self.debit * self.reporting_currency_exchange_rate)
self.credit_in_reporting_currency = flt(self.credit * self.reporting_currency_exchange_rate)
def validate_and_set_fiscal_year(self):
if not self.fiscal_year:
self.fiscal_year = get_fiscal_year(self.posting_date, company=self.company)[0]
@@ -335,7 +308,7 @@ def validate_balance_type(account, adv_adj=False):
if balance_must_be:
balance = frappe.db.sql(
"""select sum(debit) - sum(credit)
from `tabGL Entry` where is_cancelled = 0 and account = %s""",
from `tabGL Entry` where account = %s""",
account,
)[0][0]

View File

@@ -644,11 +644,8 @@ class JournalEntry(AccountsController):
def validate_party(self):
for d in self.get("accounts"):
account_type = frappe.get_cached_value("Account", d.account, "account_type")
# skipping validation for payroll entry creation
skip_validation = frappe.flags.party_not_required_for_receivable_payable
if account_type in ["Receivable", "Payable"]:
if not (d.party_type and d.party) and not skip_validation:
if not (d.party_type and d.party):
frappe.throw(
_(
"Row {0}: Party Type and Party is required for Receivable / Payable account {1}"

View File

@@ -25,7 +25,6 @@
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"project",
"help_section",
"loyalty_program_help"
],
@@ -145,12 +144,6 @@
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
},
{
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"options": "Project"
}
],
"links": [],

View File

@@ -14,7 +14,6 @@
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"project",
"section_break_4",
"invoices"
],
@@ -64,12 +63,6 @@
"label": "Cost Center",
"options": "Cost Center"
},
{
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"options": "Project"
},
{
"collapsible": 1,
"fieldname": "accounting_dimensions_section",

View File

@@ -74,6 +74,6 @@ def create_party_link(primary_role, primary_party, secondary_party):
party_link.secondary_role = "Customer" if primary_role == "Supplier" else "Supplier"
party_link.secondary_party = secondary_party
party_link.save()
party_link.save(ignore_permissions=True)
return party_link

View File

@@ -28,7 +28,6 @@
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"project",
"sec_break1",
"invoice_name",
"invoices",
@@ -195,12 +194,6 @@
"label": "Cost Center",
"options": "Cost Center"
},
{
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"options": "Project"
},
{
"depends_on": "eval:doc.party",
"description": "Only 'Payment Entries' made against this advance account are supported.",

View File

@@ -5,7 +5,6 @@
import frappe
from frappe import _, msgprint, qb
from frappe.model.document import Document
from frappe.model.meta import get_field_precision
from frappe.query_builder import Criterion
from frappe.query_builder.custom import ConstantColumn
from frappe.utils import flt, fmt_money, get_link_to_form, getdate, nowdate, today
@@ -393,12 +392,6 @@ class PaymentReconciliation(Document):
inv.outstanding_amount = flt(entry.get("outstanding_amount"))
def get_difference_amount(self, payment_entry, invoice, allocated_amount):
allocated_amount_precision = get_field_precision(
frappe.get_meta("Payment Reconciliation Allocation").get_field("allocated_amount")
)
difference_amount_precision = get_field_precision(
frappe.get_meta("Payment Reconciliation Allocation").get_field("difference_amount")
)
difference_amount = 0
if frappe.get_cached_value(
"Account", self.receivable_payable_account, "account_currency"
@@ -406,14 +399,8 @@ class PaymentReconciliation(Document):
if invoice.get("exchange_rate") and payment_entry.get("exchange_rate", 1) != invoice.get(
"exchange_rate", 1
):
allocated_amount_in_ref_rate = flt(
payment_entry.get("exchange_rate", 1) * flt(allocated_amount, allocated_amount_precision),
difference_amount_precision,
)
allocated_amount_in_inv_rate = flt(
invoice.get("exchange_rate", 1) * flt(allocated_amount, allocated_amount_precision),
difference_amount_precision,
)
allocated_amount_in_ref_rate = payment_entry.get("exchange_rate", 1) * allocated_amount
allocated_amount_in_inv_rate = invoice.get("exchange_rate", 1) * allocated_amount
difference_amount = allocated_amount_in_ref_rate - allocated_amount_in_inv_rate
return difference_amount

View File

@@ -228,8 +228,7 @@
"fetch_from": "bank_account.iban",
"fieldname": "iban",
"fieldtype": "Read Only",
"label": "IBAN",
"options": "IBAN"
"label": "IBAN"
},
{
"fetch_from": "bank_account.branch_code",
@@ -459,12 +458,11 @@
"label": "Phone Number"
}
],
"grid_page_length": 50,
"in_create": 1,
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2025-08-29 11:52:48.555415",
"modified": "2025-01-04 05:39:32.448857",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Request",
@@ -499,9 +497,8 @@
"write": 1
}
],
"row_format": "Dynamic",
"show_preview_popup": 1,
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}
}

View File

@@ -75,17 +75,6 @@ class PeriodClosingVoucher(AccountsController):
return
previous_fiscal_year_start_date = previous_fiscal_year[0][1]
previous_fiscal_year_closed = frappe.db.exists(
"Period Closing Voucher",
{
"period_end_date": ("between", [previous_fiscal_year_start_date, last_year_closing]),
"docstatus": 1,
"company": self.company,
},
)
if previous_fiscal_year_closed:
return
gle_exists_in_previous_year = frappe.db.exists(
"GL Entry",
{
@@ -97,7 +86,16 @@ class PeriodClosingVoucher(AccountsController):
if not gle_exists_in_previous_year:
return
frappe.throw(_("Previous Year is not closed, please close it first"))
previous_fiscal_year_closed = frappe.db.exists(
"Period Closing Voucher",
{
"period_end_date": ("between", [previous_fiscal_year_start_date, last_year_closing]),
"docstatus": 1,
"company": self.company,
},
)
if not previous_fiscal_year_closed:
frappe.throw(_("Previous Year is not closed, please close it first"))
def block_if_future_closing_voucher_exists(self):
future_closing_voucher = self.get_future_closing_voucher()

View File

@@ -83,16 +83,6 @@ frappe.ui.form.on("Process Statement Of Accounts", {
},
};
});
frm.set_query("print_format", function () {
return {
filters: {
print_format_for: "Report",
report: frm.doc.report,
disabled: 0,
print_format_type: "Jinja",
},
};
});
if (frm.doc.__islocal) {
frm.set_value("from_date", frappe.datetime.add_months(frappe.datetime.get_today(), -1));
frm.set_value("to_date", frappe.datetime.get_today());
@@ -116,16 +106,6 @@ frappe.ui.form.on("Process Statement Of Accounts", {
filters: filters,
};
});
frm.set_query("print_format", function () {
return {
filters: {
print_format_for: "Report",
report: frm.doc.report,
disabled: 0,
print_format_type: "Jinja",
},
};
});
},
customer_collection: function (frm) {
frm.set_value("collection_name", "");

View File

@@ -38,7 +38,6 @@
"column_break_17",
"customers",
"preferences",
"print_format",
"orientation",
"include_break",
"include_ageing",
@@ -407,16 +406,10 @@
"fieldname": "show_future_payments",
"fieldtype": "Check",
"label": "Show Future Payments"
},
{
"fieldname": "print_format",
"fieldtype": "Link",
"label": "Print Format",
"options": "Print Format"
}
],
"links": [],
"modified": "2025-09-03 14:24:43.608565",
"modified": "2025-08-29 00:20:08.088189",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Process Statement Of Accounts",

View File

@@ -67,7 +67,6 @@ class ProcessStatementOfAccounts(Document):
pdf_name: DF.Data | None
posting_date: DF.Date | None
primary_mandatory: DF.Check
print_format: DF.Link | None
project: DF.TableMultiSelect[PSOAProject]
report: DF.Literal["General Ledger", "Accounts Receivable"]
sales_partner: DF.Link | None
@@ -110,25 +109,6 @@ class ProcessStatementOfAccounts(Document):
self.to_date = self.start_date
self.from_date = add_months(self.to_date, -1 * self.filter_duration)
if self.print_format:
pf = frappe.db.get_value(
"Print Format",
self.print_format,
["print_format_type", "print_format_for", "report", "disabled"],
as_dict=True,
)
if not pf:
frappe.throw(title=_("Invalid Print Format"), msg=_("Selected Print Format does not exist."))
if pf.print_format_type != "Jinja":
frappe.throw(title=_("Invalid Print Format"), msg=_("Print Format Type should be Jinja."))
if pf.print_format_for != "Report" or pf.report != self.report or pf.disabled:
frappe.throw(
title=_("Invalid Print Format"),
msg=_(
"Print Format must be an enabled Report Print Format matching the selected Report."
),
)
def validate_account(self):
if not self.account:
return
@@ -310,10 +290,6 @@ def get_html(doc, filters, entry, col, res, ageing):
if process_soa_html and process_soa_html.get(doc.report):
template_path = process_soa_html[doc.report][-1]
if doc.print_format:
custom_html, custom_css = frappe.db.get_value("Print Format", doc.print_format, ["html", "css"])
template_path = f"<style>{custom_css}</style> {custom_html}"
if doc.letter_head:
from frappe.www.printview import get_letter_head

View File

@@ -328,20 +328,11 @@
<td></td>
<td></td>
<td></td>
{% if not(filters.show_future_payments) %}
<td></td>
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="invoiced"), currency=data[0]["currency"]) }}</b></td>
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="paid"), currency=data[0]["currency"]) }}</b></td>
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="credit_note"), currency=data[0]["currency"]) }}</b></td>
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="outstanding"), currency=data[0]["currency"]) }}</b></td>
{% else %}
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="invoiced"), currency=data[0]["currency"]) }}</b></td>
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="outstanding"), currency=data[0]["currency"]) }}</b></td>
<td></td>
<td></td>
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="future_amount"), currency=data[0]["currency"]) }}</b></td>
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="remaining_balance"), currency=data[0]["currency"]) }}</b></td>
{% endif %}
<td></td>
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="invoiced"), currency=data[0]["currency"]) }}</b></td>
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="paid"), currency=data[0]["currency"]) }}</b></td>
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="credit_note"), currency=data[0]["currency"]) }}</b></td>
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="outstanding"), currency=data[0]["currency"]) }}</b></td>
</tbody>
</table>
<br>

View File

@@ -23,7 +23,6 @@
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"project",
"section_break_9",
"account_currency",
"net_amount",
@@ -215,13 +214,6 @@
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
},
{
"allow_on_submit": 1,
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"options": "Project"
},
{
"default": "0",
"depends_on": "eval:['Purchase Taxes and Charges Template', 'Payment Entry'].includes(parent.doctype)",

View File

@@ -275,7 +275,6 @@
"read_only": 1
},
{
"fetch_from": "customer.tax_id",
"fieldname": "tax_id",
"fieldtype": "Data",
"hide_days": 1,
@@ -2242,7 +2241,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2025-09-09 14:48:59.472826",
"modified": "2025-08-04 19:20:28.732039",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@@ -17,7 +17,6 @@
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"project",
"section_break_8",
"rate",
"section_break_9",
@@ -192,13 +191,6 @@
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
},
{
"allow_on_submit": 1,
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"options": "Project"
},
{
"default": "0",
"depends_on": "eval:['Sales Taxes and Charges Template', 'Payment Entry'].includes(parent.doctype)",

View File

@@ -18,7 +18,6 @@
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"project",
"shipping_amount_section",
"calculate_based_on",
"column_break_8",
@@ -139,12 +138,6 @@
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
},
{
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"options": "Project"
}
],
"icon": "fa fa-truck",

View File

@@ -976,7 +976,6 @@ class ReceivablePayableReport:
if self.account_type == "Receivable":
self.add_customer_filters()
self.exclude_employee_transaction()
elif self.account_type == "Payable":
self.add_supplier_filters()
@@ -1056,9 +1055,6 @@ class ReceivablePayableReport:
)
)
def exclude_employee_transaction(self):
self.qb_selection_filter.append(self.ple.party_type != "Employee")
def add_supplier_filters(self):
supplier = qb.DocType("Supplier")
if self.filters.get("supplier_group"):

View File

@@ -38,7 +38,6 @@ from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement
get_report_summary as get_pl_summary,
)
from erpnext.accounts.report.utils import convert, convert_to_presentation_currency
from erpnext.accounts.utils import get_zero_cutoff
def execute(filters=None):
@@ -565,7 +564,7 @@ def prepare_data(accounts, start_date, end_date, balance_must_be, companies, com
row[company] = flt(d.get(company, 0.0), 3)
if abs(row[company]) >= get_zero_cutoff(filters.presentation_currency):
if abs(row[company]) >= 0.005:
# ignore zero values
has_value = True
total += flt(row[company])

View File

@@ -12,7 +12,6 @@ from erpnext.accounts.report.financial_statements import (
filter_out_zero_value_rows,
)
from erpnext.accounts.report.trial_balance.trial_balance import validate_filters
from erpnext.accounts.utils import get_zero_cutoff
def execute(filters=None):
@@ -155,7 +154,7 @@ def prepare_data(accounts, filters, company_currency, dimension_list):
for dimension in dimension_list:
row[frappe.scrub(dimension)] = flt(d.get(frappe.scrub(dimension), 0.0), 3)
if abs(row[frappe.scrub(dimension)]) >= get_zero_cutoff(company_currency):
if abs(row[frappe.scrub(dimension)]) >= 0.005:
# ignore zero values
has_value = True
total += flt(d.get(frappe.scrub(dimension), 0.0), 3)

View File

@@ -18,7 +18,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
get_dimension_with_children,
)
from erpnext.accounts.report.utils import convert_to_presentation_currency, get_currency
from erpnext.accounts.utils import get_fiscal_year, get_zero_cutoff
from erpnext.accounts.utils import get_fiscal_year
def get_period_list(
@@ -306,7 +306,7 @@ def prepare_data(accounts, balance_must_be, period_list, company_currency, accum
row[period.key] = flt(d.get(period.key, 0.0), 3)
if abs(row[period.key]) >= get_zero_cutoff(company_currency):
if abs(row[period.key]) >= 0.005:
# ignore zero values
has_value = True
total += flt(row[period.key])
@@ -530,7 +530,6 @@ def get_accounting_entries(
query = query.select(gl_entry.posting_date, gl_entry.is_opening, gl_entry.fiscal_year)
query = query.where(gl_entry.is_cancelled == 0)
query = query.where(gl_entry.posting_date <= to_date)
query = query.force_index("posting_date_company_index")
if ignore_opening_entries and not ignore_is_opening:
query = query.where(gl_entry.is_opening == "No")

View File

@@ -667,7 +667,7 @@ def get_columns(filters):
"options": "GL Entry",
"hidden": 1,
},
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 120},
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 100},
{
"label": _("Account"),
"fieldname": "account",

View File

@@ -12,7 +12,6 @@ from erpnext.accounts.report.financial_statements import (
filter_out_zero_value_rows,
)
from erpnext.accounts.report.trial_balance.trial_balance import validate_filters
from erpnext.accounts.utils import get_zero_cutoff
value_fields = ("income", "expense", "gross_profit_loss")
@@ -150,7 +149,7 @@ def prepare_data(accounts, filters, total_row, parent_children_map, based_on):
for key in value_fields:
row[key] = flt(d.get(key, 0.0), 3)
if abs(row[key]) >= get_zero_cutoff(company_currency):
if abs(row[key]) >= 0.005:
# ignore zero values
has_value = True

View File

@@ -18,7 +18,6 @@ from erpnext.accounts.report.financial_statements import (
set_gl_entries_by_account,
)
from erpnext.accounts.report.utils import convert_to_presentation_currency, get_currency
from erpnext.accounts.utils import get_zero_cutoff
value_fields = (
"opening_debit",
@@ -413,7 +412,7 @@ def prepare_data(accounts, filters, parent_children_map, company_currency):
for key in value_fields:
row[key] = flt(d.get(key, 0.0), 3)
if abs(row[key]) >= get_zero_cutoff(company_currency):
if abs(row[key]) >= 0.005:
# ignore zero values
has_value = True

View File

@@ -4,25 +4,15 @@
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [
{
"fieldname": "company",
"fieldtype": "Link",
"label": "Company",
"mandatory": 1,
"options": "Company",
"wildcard_filter": 0
}
],
"idx": 0,
"is_standard": "Yes",
"modified": "2025-08-28 19:06:54.273322",
"modified": "2019-01-17 17:20:42.374958",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Trial Balance (Simple)",
"owner": "Administrator",
"prepared_report": 0,
"query": "select fiscal_year as \"Fiscal Year:Data:80\",\n\tcompany as \"Company:Data:220\",\n\tposting_date as \"Posting Date:Date:100\",\n\taccount as \"Account:Data:380\",\n\tsum(debit) as \"Debit:Currency:140\",\n\tsum(credit) as \"Credit:Currency:140\",\n\tfinance_book as \"Finance Book:Link/Finance Book:140\"\nfrom `tabGL Entry`\nwhere is_cancelled = 0 and company = %(company)s\ngroup by fiscal_year, company, posting_date, account\norder by fiscal_year, company, posting_date, account",
"query": "select fiscal_year as \"Fiscal Year:Data:80\",\n\tcompany as \"Company:Data:220\",\n\tposting_date as \"Posting Date:Date:100\",\n\taccount as \"Account:Data:380\",\n\tsum(debit) as \"Debit:Currency:140\",\n\tsum(credit) as \"Credit:Currency:140\",\n\tfinance_book as \"Finance Book:Link/Finance Book:140\"\nfrom `tabGL Entry`\ngroup by fiscal_year, company, posting_date, account\norder by fiscal_year, company, posting_date, account",
"ref_doctype": "GL Entry",
"report_name": "Trial Balance (Simple)",
"report_type": "Query Report",

View File

@@ -10,7 +10,6 @@ from erpnext.accounts.party import get_party_shipping_address
from erpnext.accounts.utils import (
get_future_stock_vouchers,
get_voucherwise_gl_entries,
get_zero_cutoff,
sort_stock_vouchers_by_posting_date,
)
from erpnext.stock.doctype.item.test_item import make_item
@@ -158,11 +157,6 @@ class TestUtils(IntegrationTestCase):
self.assertSequenceEqual(doc_name[0:2], ("SUP", fiscal_year))
frappe.db.set_default("supp_master_name", "Supplier Name")
def test_get_zero_cutoff(self):
self.assertEqual(get_zero_cutoff(None), 0.005)
self.assertEqual(get_zero_cutoff("EUR"), 0.005)
self.assertEqual(get_zero_cutoff("BHD"), 0.0005)
ADDRESS_RECORDS = [
{

View File

@@ -27,7 +27,6 @@ from frappe.utils import (
now,
nowdate,
)
from frappe.utils.caching import site_cache
from pypika import Order
from pypika.functions import Coalesce
from pypika.terms import ExistsCriterion
@@ -1148,29 +1147,6 @@ def get_currency_precision():
return precision
def get_fraction_units(currency: str) -> int:
"""Returns the number of fraction units for a currency."""
fraction_units = frappe.db.get_value("Currency", currency, "fraction_units")
if fraction_units is None:
fraction_units = 100
return fraction_units
@site_cache()
def get_zero_cutoff(currency: str) -> float:
"""Returns the zero cutoff for a currency.
For example, if the Fraction Units for a currency are set to 100, then the zero cutoff is 0.005.
We don't want to display values less than the zero cutoff.
This value was chosen for compatibility with the previous hard-coded value of 0.005.
"""
fraction_units = get_fraction_units(currency)
return 0.5 / (fraction_units or 1)
def get_held_invoices(party_type, party):
"""
Returns a list of names Purchase Invoices for the given party that are on hold
@@ -2506,10 +2482,6 @@ def build_qb_match_conditions(doctype, user=None) -> list:
for filter in match_filters:
for link_option, allowed_values in filter.items():
fieldnames = link_fields_map.get(link_option, [])
cond = None
if link_option == doctype:
cond = _dt["name"].isin(allowed_values)
for fieldname in fieldnames:
field = _dt[fieldname]
@@ -2518,7 +2490,6 @@ def build_qb_match_conditions(doctype, user=None) -> list:
if not apply_strict_user_permissions:
cond = (Coalesce(field, "") == "") | cond
if cond:
criterion.append(cond)
return criterion

View File

@@ -41,7 +41,6 @@
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"project",
"target_fixed_asset_account"
],
"fields": [
@@ -276,12 +275,6 @@
"label": "Cost Center",
"options": "Cost Center"
},
{
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"options": "Project"
},
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"

View File

@@ -18,7 +18,6 @@
"accounting_dimensions_section",
"cost_center",
"dimension_col_break",
"project",
"fixed_asset_account"
],
"fields": [
@@ -99,13 +98,6 @@
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
},
{
"allow_on_submit": 1,
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"options": "Project"
},
{
"fieldname": "finance_book",
"fieldtype": "Link",

View File

@@ -61,7 +61,7 @@ class AssetMovement(Document):
if d.source_location:
if current_location != d.source_location:
frappe.throw(
_("Asset {0} does not belong to the location {1}").format(d.asset, d.source_location)
_("Asset {0} does not belongs to the location {1}").format(d.asset, d.source_location)
)
else:
d.source_location = current_location
@@ -76,11 +76,11 @@ class AssetMovement(Document):
frappe.throw(_("Target Location is required while receiving Asset {0}").format(d.asset))
if d.to_employee and frappe.db.get_value("Employee", d.to_employee, "company") != self.company:
frappe.throw(
_("Employee {0} does not belong to the company {1}").format(d.to_employee, self.company)
_("Employee {0} does not belongs to the company {1}").format(d.to_employee, self.company)
)
def validate_employee(self, d):
if self.purpose == "Transfer and Issue":
if self.purpose == "Tranfer and Issue":
if not d.from_employee:
frappe.throw(_("From Employee is required while issuing Asset {0}").format(d.asset))
@@ -89,7 +89,7 @@ class AssetMovement(Document):
if current_custodian != d.from_employee:
frappe.throw(
_("Asset {0} does not belong to the custodian {1}").format(d.asset, d.from_employee)
_("Asset {0} does not belongs to the custodian {1}").format(d.asset, d.from_employee)
)
if not d.to_employee:
@@ -97,7 +97,7 @@ class AssetMovement(Document):
if d.to_employee and frappe.db.get_value("Employee", d.to_employee, "company") != self.company:
frappe.throw(
_("Employee {0} does not belong to the company {1}").format(d.to_employee, self.company)
_("Employee {0} does not belongs to the company {1}").format(d.to_employee, self.company)
)
def on_submit(self):

View File

@@ -503,7 +503,6 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
}
onload() {
super.onload();
this.frm.set_query("supplier", function () {
return {
filters: {

View File

@@ -133,10 +133,7 @@ frappe.ui.form.on("Supplier", {
__("Actions")
);
if (
cint(frappe.defaults.get_default("enable_common_party_accounting")) &&
frappe.model.can_create("Party Link")
) {
if (cint(frappe.defaults.get_default("enable_common_party_accounting"))) {
frm.add_custom_button(
__("Link with Customer"),
function () {

View File

@@ -1070,9 +1070,7 @@ class StockController(AccountsController):
from erpnext.stock.stock_ledger import make_sl_entries
make_sl_entries(sl_entries, allow_negative_stock, via_landed_cost_voucher)
update_batch_qty(
self.doctype, self.name, self.docstatus, via_landed_cost_voucher=via_landed_cost_voucher
)
update_batch_qty(self.doctype, self.name, via_landed_cost_voucher=via_landed_cost_voucher)
def make_gl_entries_on_cancel(self, from_repost=False):
if not from_repost:

View File

@@ -0,0 +1,228 @@
{
"charts": [],
"content": "[{\"id\":\"e88ADOJ7WC\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Integrations</b></span>\",\"col\":12}},{\"id\":\"pZEYOOCdB0\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Browse Apps\",\"col\":3}},{\"id\":\"St7AHbhVOr\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"nu4oSjH5Rd\",\"type\":\"card\",\"data\":{\"card_name\":\"Authentication\",\"col\":4}},{\"id\":\"G0tyx9WOfm\",\"type\":\"card\",\"data\":{\"card_name\":\"Backup\",\"col\":4}},{\"id\":\"nG8cdkpzoc\",\"type\":\"card\",\"data\":{\"card_name\":\"Google Services\",\"col\":4}},{\"id\":\"4hwuQn6E95\",\"type\":\"card\",\"data\":{\"card_name\":\"Communication Channels\",\"col\":4}},{\"id\":\"sEGAzTJRmq\",\"type\":\"card\",\"data\":{\"card_name\":\"Payments\",\"col\":4}}]",
"creation": "2020-08-20 19:30:48.138801",
"custom_blocks": [],
"docstatus": 0,
"doctype": "Workspace",
"for_user": "",
"hide_custom": 0,
"icon": "integration",
"idx": 0,
"is_hidden": 0,
"label": "ERPNext Integrations",
"links": [
{
"hidden": 0,
"is_query_report": 0,
"label": "Backup",
"link_count": 3,
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Dropbox Settings",
"link_count": 0,
"link_to": "Dropbox Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "S3 Backup Settings",
"link_count": 0,
"link_to": "S3 Backup Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Google Drive",
"link_count": 0,
"link_to": "Google Drive",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Authentication",
"link_count": 4,
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Social Login",
"link_count": 0,
"link_to": "Social Login Key",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "LDAP Settings",
"link_count": 0,
"link_to": "LDAP Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "OAuth Client",
"link_count": 0,
"link_to": "OAuth Client",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "OAuth Provider Settings",
"link_count": 0,
"link_to": "OAuth Provider Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Communication Channels",
"link_count": 3,
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Webhook",
"link_count": 0,
"link_to": "Webhook",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "SMS Settings",
"link_count": 0,
"link_to": "SMS Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Slack Webhook URL",
"link_count": 0,
"link_to": "Slack Webhook URL",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Google Services",
"link_count": 4,
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Google Settings",
"link_count": 0,
"link_to": "Google Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Google Contacts",
"link_count": 0,
"link_to": "Google Contacts",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Google Calendar",
"link_count": 0,
"link_to": "Google Calendar",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Google Drive",
"link_count": 0,
"link_to": "Google Drive",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Payments",
"link_count": 1,
"onboard": 0,
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Plaid Settings",
"link_count": 0,
"link_to": "Plaid Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
}
],
"modified": "2023-08-29 15:48:59.010704",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "ERPNext Integrations",
"number_cards": [],
"owner": "Administrator",
"parent_page": "",
"public": 1,
"quick_lists": [],
"restrict_to_domain": "",
"roles": [],
"sequence_id": 21.0,
"shortcuts": [
{
"color": "Grey",
"doc_view": "List",
"label": "Browse Apps",
"type": "URL",
"url": "https://frappecloud.com/marketplace"
}
],
"title": "ERPNext Integrations"
}

View File

@@ -24,7 +24,3 @@ class InvalidAccountDimensionError(frappe.ValidationError):
class MandatoryAccountDimensionError(frappe.ValidationError):
pass
class ReportingCurrencyExchangeNotFoundError(frappe.ValidationError):
pass

View File

@@ -51,7 +51,7 @@ doctype_list_js = {
],
}
extend_doctype_class = {"Address": "erpnext.accounts.custom.address.ERPNextAddress"}
override_doctype_class = {"Address": "erpnext.accounts.custom.address.ERPNextAddress"}
override_whitelisted_methods = {"frappe.www.contact.send_message": "erpnext.templates.utils.send_message"}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1414,7 +1414,7 @@ def get_children(parent=None, is_root=False, **filters):
return bom_items
def add_additional_cost(stock_entry, work_order, job_card=None):
def add_additional_cost(stock_entry, work_order):
# Add non stock items cost in the additional cost
stock_entry.additional_costs = []
company_account = frappe.db.get_value(
@@ -1427,16 +1427,13 @@ def add_additional_cost(stock_entry, work_order, job_card=None):
expense_account = (
company_account.default_operating_cost_account or company_account.default_expense_account
)
add_non_stock_items_cost(stock_entry, work_order, expense_account, job_card=job_card)
add_operations_cost(stock_entry, work_order, expense_account, job_card=job_card)
add_non_stock_items_cost(stock_entry, work_order, expense_account)
add_operations_cost(stock_entry, work_order, expense_account)
def add_non_stock_items_cost(stock_entry, work_order, expense_account, job_card=None):
def add_non_stock_items_cost(stock_entry, work_order, expense_account):
bom = frappe.get_doc("BOM", work_order.bom_no)
table = "items"
if work_order and not job_card:
table = "exploded_items" if work_order.get("use_multi_level_bom") else "items"
table = "exploded_items" if work_order.get("use_multi_level_bom") else "items"
items = {}
for d in bom.get(table):
@@ -1467,16 +1464,13 @@ def add_non_stock_items_cost(stock_entry, work_order, expense_account, job_card=
def add_operating_cost_component_wise(
stock_entry, work_order=None, operating_cost_per_unit=None, op_expense_account=None, job_card=None
stock_entry, work_order=None, operating_cost_per_unit=None, op_expense_account=None
):
if not work_order:
return False
cost_added = False
for row in work_order.operations:
if job_card and job_card.operation_id != row.name:
continue
workstation_cost = frappe.get_all(
"Workstation Cost",
fields=["operating_component", "operating_cost"],
@@ -1517,14 +1511,14 @@ def get_component_account(parent):
return frappe.db.get_value("Workstation Operating Component Account", parent, "expense_account")
def add_operations_cost(stock_entry, work_order=None, expense_account=None, job_card=None):
def add_operations_cost(stock_entry, work_order=None, expense_account=None):
from erpnext.stock.doctype.stock_entry.stock_entry import get_operating_cost_per_unit
operating_cost_per_unit = get_operating_cost_per_unit(work_order, stock_entry.bom_no)
if operating_cost_per_unit:
cost_added = add_operating_cost_component_wise(
stock_entry, work_order, operating_cost_per_unit, expense_account, job_card=job_card
stock_entry, work_order, operating_cost_per_unit, expense_account
)
if not cost_added:

View File

@@ -115,8 +115,7 @@
"fieldname": "rate",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Rate",
"options": "currency"
"label": "Rate"
},
{
"columns": 1,
@@ -159,8 +158,7 @@
"fieldname": "amount",
"fieldtype": "Currency",
"label": "Amount",
"read_only": 1,
"options": "currency"
"read_only": 1
},
{
"fieldname": "column_break_yuca",
@@ -182,15 +180,13 @@
"fieldname": "base_amount",
"fieldtype": "Currency",
"hidden": 1,
"label": "Base Amount",
"options": "Company:company:default_currency"
"label": "Base Amount"
},
{
"fieldname": "base_rate",
"fieldtype": "Currency",
"hidden": 1,
"label": "Base Rate",
"options": "Company:company:default_currency"
"label": "Base Rate"
},
{
"default": "0",

View File

@@ -23,7 +23,6 @@ from frappe.utils import (
time_diff_in_hours,
)
from erpnext.manufacturing.doctype.bom.bom import add_additional_cost
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import (
get_mins_between_operations,
)
@@ -818,6 +817,9 @@ class JobCard(Document):
)
def update_work_order(self):
if self.track_semi_finished_goods:
return
if not self.work_order:
return
@@ -847,9 +849,9 @@ class JobCard(Document):
def update_semi_finished_good_details(self):
if self.operation_id:
qty = max(flt(self.manufactured_qty), flt(self.total_completed_qty))
frappe.db.set_value("Work Order Operation", self.operation_id, "completed_qty", qty)
frappe.db.set_value(
"Work Order Operation", self.operation_id, "completed_qty", self.manufactured_qty
)
if (
self.finished_good
and frappe.get_cached_value("Work Order", self.work_order, "production_item")
@@ -1320,9 +1322,6 @@ class JobCard(Document):
ste.make_stock_entry()
ste.stock_entry.flags.ignore_mandatory = True
wo_doc = frappe.get_doc("Work Order", self.work_order)
add_additional_cost(ste.stock_entry, wo_doc, self)
ste.stock_entry.save()
if auto_submit:

View File

@@ -24,9 +24,6 @@
"overproduction_percentage_for_sales_order",
"column_break_16",
"overproduction_percentage_for_work_order",
"section_break_xhtl",
"transfer_extra_materials_percentage",
"column_break_kemp",
"job_card_section",
"add_corrective_operation_cost_in_finished_good_valuation",
"enforce_time_logs",
@@ -246,28 +243,13 @@
"fieldname": "enforce_time_logs",
"fieldtype": "Check",
"label": "Enforce Time Logs"
},
{
"fieldname": "section_break_xhtl",
"fieldtype": "Section Break",
"label": "Extra Material Transfer"
},
{
"fieldname": "column_break_kemp",
"fieldtype": "Column Break"
},
{
"description": "The user will be able to transfer additional materials from the store to the Work in Progress (WIP) warehouse.",
"fieldname": "transfer_extra_materials_percentage",
"fieldtype": "Percent",
"label": "Transfer Extra Raw Materials to WIP (%)"
}
],
"icon": "icon-wrench",
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2025-09-08 19:48:31.726126",
"modified": "2025-05-16 11:23:16.916512",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Manufacturing Settings",

View File

@@ -35,7 +35,6 @@ class ManufacturingSettings(Document):
overproduction_percentage_for_sales_order: DF.Percent
overproduction_percentage_for_work_order: DF.Percent
set_op_cost_and_scrap_from_sub_assemblies: DF.Check
transfer_extra_materials_percentage: DF.Percent
update_bom_costs_automatically: DF.Check
validate_components_quantities_per_bom: DF.Check
# end: auto-generated types

View File

@@ -3005,36 +3005,6 @@ class TestWorkOrder(IntegrationTestCase):
wo.operations[3].planned_start_time, add_to_date(wo.operations[1].planned_end_time, minutes=10)
)
def test_allow_additional_material_transfer(self):
from erpnext.stock.doctype.stock_entry.test_stock_entry import (
make_stock_entry as make_stock_entry_test_record,
)
frappe.db.set_single_value("Manufacturing Settings", "transfer_extra_materials_percentage", 50)
wo_order = make_wo_order_test_record(planned_start_date=now(), qty=2)
for row in wo_order.required_items:
make_stock_entry_test_record(
item_code=row.item_code,
target=row.source_warehouse,
qty=row.required_qty * 2,
basic_rate=100,
)
stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 2))
stock_entry.insert()
stock_entry.submit()
wo_order.reload()
self.assertEqual(wo_order.material_transferred_for_manufacturing, 2)
stock_entry = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 1))
stock_entry.insert()
stock_entry.submit()
wo_order.reload()
self.assertEqual(wo_order.material_transferred_for_manufacturing, 3)
frappe.db.set_single_value("Manufacturing Settings", "transfer_extra_materials_percentage", 0)
def make_stock_in_entries_and_get_batches(rm_item, source_warehouse, wip_warehouse):
from erpnext.stock.doctype.stock_entry.test_stock_entry import (

View File

@@ -116,20 +116,6 @@ frappe.ui.form.on("Work Order", {
frm.set_indicator_formatter("operation", function (doc) {
return frm.doc.qty == doc.completed_qty ? "green" : "orange";
});
},
onload: function (frm) {
if (!frm.doc.status) frm.doc.status = "Draft";
frm.add_fetch("sales_order", "project", "project");
if (frm.doc.__islocal) {
frm.set_value({
actual_start_date: "",
actual_end_date: "",
});
erpnext.work_order.set_default_warehouse(frm);
}
if (frm.doc.docstatus == 0 && frm.doc.bom_no) {
frappe.call({
@@ -146,6 +132,20 @@ frappe.ui.form.on("Work Order", {
}
},
onload: function (frm) {
if (!frm.doc.status) frm.doc.status = "Draft";
frm.add_fetch("sales_order", "project", "project");
if (frm.doc.__islocal) {
frm.set_value({
actual_start_date: "",
actual_end_date: "",
});
erpnext.work_order.set_default_warehouse(frm);
}
},
source_warehouse: function (frm) {
let transaction_controller = new erpnext.TransactionController();
transaction_controller.autofill_warehouse(
@@ -732,19 +732,6 @@ erpnext.work_order = {
let pending_to_transfer = frm.doc.required_items.some(
(item) => flt(item.transferred_qty) < flt(item.required_qty)
);
let transfer_extra_materials_percentage =
frm.doc.__onload?.transfer_extra_materials_percentage;
let allowed_qty = 0;
let transfer_extra_materials = false;
if (!pending_to_transfer && transfer_extra_materials_percentage) {
allowed_qty = frm.doc.qty + (transfer_extra_materials_percentage / 100) * frm.doc.qty;
if (allowed_qty > frm.doc.material_transferred_for_manufacturing) {
transfer_extra_materials = true;
}
}
if (pending_to_transfer && frm.doc.status != "Stopped") {
frm.has_start_btn = true;
frm.add_custom_button(__("Create Pick List"), function () {
@@ -755,14 +742,6 @@ erpnext.work_order = {
erpnext.work_order.make_se(frm, "Material Transfer for Manufacture");
});
start_btn.addClass("btn-primary");
} else if (transfer_extra_materials && allowed_qty) {
let qty = allowed_qty - flt(frm.doc.material_transferred_for_manufacturing);
if (qty > 0) {
frm.add_custom_button(__("Transfer Extra Material"), function () {
erpnext.work_order.make_se(frm, "Material Transfer for Manufacture", qty);
});
}
}
}
}
@@ -988,35 +967,19 @@ erpnext.work_order = {
});
},
make_se: function (frm, purpose, qty) {
if (qty) {
frappe
.xcall("erpnext.manufacturing.doctype.work_order.work_order.make_stock_entry", {
make_se: function (frm, purpose) {
this.show_prompt_for_qty_input(frm, purpose)
.then((data) => {
return frappe.xcall("erpnext.manufacturing.doctype.work_order.work_order.make_stock_entry", {
work_order_id: frm.doc.name,
purpose: purpose,
qty: qty,
})
.then((stock_entry) => {
frappe.model.sync(stock_entry);
frappe.set_route("Form", stock_entry.doctype, stock_entry.name);
qty: data.qty,
});
} else {
this.show_prompt_for_qty_input(frm, purpose)
.then((data) => {
return frappe.xcall(
"erpnext.manufacturing.doctype.work_order.work_order.make_stock_entry",
{
work_order_id: frm.doc.name,
purpose: purpose,
qty: data.qty,
}
);
})
.then((stock_entry) => {
frappe.model.sync(stock_entry);
frappe.set_route("Form", stock_entry.doctype, stock_entry.name);
});
}
})
.then((stock_entry) => {
frappe.model.sync(stock_entry);
frappe.set_route("Form", stock_entry.doctype, stock_entry.name);
});
},
create_pick_list: function (frm, purpose = "Material Transfer for Manufacture") {

View File

@@ -149,7 +149,6 @@ class WorkOrder(Document):
self.set_onload("material_consumption", ms.material_consumption)
self.set_onload("backflush_raw_materials_based_on", ms.backflush_raw_materials_based_on)
self.set_onload("overproduction_percentage", ms.overproduction_percentage_for_work_order)
self.set_onload("transfer_extra_materials_percentage", ms.transfer_extra_materials_percentage)
self.set_onload("show_create_job_card_button", self.show_create_job_card_button())
self.set_onload(
"enable_stock_reservation",
@@ -355,10 +354,6 @@ class WorkOrder(Document):
def calculate_operating_cost(self):
self.planned_operating_cost, self.actual_operating_cost = 0.0, 0.0
for d in self.get("operations"):
if not d.hour_rate:
if d.workstation:
d.hour_rate = get_hour_rate(d.workstation)
d.planned_operating_cost = flt(
flt(d.hour_rate) * (flt(d.time_in_mins) / 60.0), d.precision("planned_operating_cost")
)
@@ -490,13 +485,6 @@ class WorkOrder(Document):
qty = self.get_transferred_or_manufactured_qty(purpose)
if not allowance_percentage and purpose == "Material Transfer for Manufacture":
allowance_percentage = flt(
frappe.db.get_single_value(
"Manufacturing Settings", "transfer_extra_materials_percentage"
)
)
completed_qty = self.qty + (allowance_percentage / 100 * self.qty)
if qty > completed_qty:
frappe.throw(
@@ -1420,8 +1408,8 @@ class WorkOrder(Document):
if not self.reserve_stock:
continue
warehouse = wip_warehouse or item.source_warehouse
self.update_consumed_qty_in_stock_reservation(item, consumed_qty, warehouse)
wip_warehouse = wip_warehouse or item.source_warehouse
self.update_consumed_qty_in_stock_reservation(item, consumed_qty, wip_warehouse)
def update_consumed_qty_in_stock_reservation(self, item, consumed_qty, wip_warehouse):
filters = {
@@ -2436,8 +2424,3 @@ def get_row_wise_serial_batch(work_order, purpose=None):
details.batch_nos[entry.batch_no] += abs(entry.qty)
return row_wise_serial_batch
@frappe.request_cache
def get_hour_rate(workstation):
return frappe.get_cached_value("Workstation", workstation, "hour_rate") or 0.0

View File

@@ -397,7 +397,7 @@ erpnext.patches.v15_0.update_cc_in_process_statement_of_accounts
erpnext.patches.v15_0.refactor_closing_stock_balance #5
erpnext.patches.v15_0.update_asset_status_to_work_in_progress
erpnext.patches.v15_0.rename_manufacturing_settings_field
erpnext.patches.v15_0.sync_auto_reconcile_config #2025-08-26
erpnext.patches.v15_0.sync_auto_reconcile_config
execute:frappe.db.set_single_value("Accounts Settings", "exchange_gain_loss_posting_date", "Payment")
erpnext.patches.v14_0.disable_add_row_in_gross_profit
erpnext.patches.v14_0.update_posting_datetime
@@ -418,7 +418,7 @@ erpnext.patches.v15_0.set_cancelled_status_to_cancelled_pos_invoice
erpnext.patches.v15_0.rename_group_by_to_categorize_by_in_custom_reports
erpnext.patches.v15_0.remove_agriculture_roles
erpnext.patches.v14_0.update_full_name_in_contract
erpnext.patches.v15_0.drop_sle_indexes #2025-09-18
erpnext.patches.v15_0.drop_sle_indexes
erpnext.patches.v15_0.update_pick_list_fields
execute:frappe.db.set_single_value("Accounts Settings", "confirm_before_resetting_posting_date", 1)
erpnext.patches.v15_0.rename_pos_closing_entry_fields #2025-06-13
@@ -436,4 +436,3 @@ erpnext.patches.v16_0.update_serial_no_reference_name
erpnext.patches.v16_0.set_invoice_type_in_pos_settings
erpnext.patches.v15_0.update_fieldname_in_accounting_dimension_filter
erpnext.patches.v16_0.make_workstation_operating_components #1
erpnext.patches.v16_0.set_reporting_currency

View File

@@ -4,7 +4,7 @@ import frappe
def execute():
table = "tabStock Ledger Entry"
index_list = ["posting_datetime_creation_index", "item_warehouse", "batch_no_item_code_warehouse_index"]
index_list = ["posting_datetime_creation_index", "item_warehouse"]
for index in index_list:
if not frappe.db.has_index(table, index):

View File

@@ -11,7 +11,7 @@ def execute():
frappe.db.set_single_value("Accounts Settings", "reconciliation_queue_size", 5)
# Create Scheduler Event record if it doesn't exist
if frappe.reload_doc("core", "doctype", "scheduler_event", force=True):
if frappe.reload_doc("core", "doctype", "scheduler_event"):
method = "erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation.trigger_reconciliation_for_queued_docs"
if not frappe.db.get_all(
"Scheduler Event", {"scheduled_against": "Process Payment Reconciliation", "method": method}

View File

@@ -1,159 +0,0 @@
import frappe
from frappe.utils import getdate
from frappe.utils.nestedset import get_descendants_of
from erpnext.accounts.utils import get_fiscal_year
from erpnext.setup.utils import get_exchange_rate
def execute():
set_company_reporting_currency()
set_amounts_in_reporting_currency_on_gle_and_acb()
def set_company_reporting_currency():
root_companies = frappe.db.get_all(
"Company", fields=["name", "default_currency"], filters={"parent_company": ""}, order_by="lft"
)
for d in root_companies:
company_subtree = get_descendants_of("Company", d.name)
company_subtree.append(d.name)
update_company_subtree_reporting_currency(company_subtree, d.default_currency)
def update_company_subtree_reporting_currency(companies, currency):
Company = frappe.qb.DocType("Company")
frappe.qb.update(Company).set(Company.reporting_currency, currency).where(
Company.name.isin(companies)
).run()
def set_amounts_in_reporting_currency_on_gle_and_acb():
# get all the companies
companies = frappe.db.get_all(
"Company", fields=["name", "default_currency", "reporting_currency"], order_by="lft"
)
# get current fiscal year
current_fiscal_year = get_fiscal_year(getdate(), as_dict=1, raise_on_missing=False)
if not current_fiscal_year:
return
previous_fiscal_year = frappe.db.get_value(
"Fiscal Year",
filters={"year_end_date": ("<", current_fiscal_year.year_start_date)},
fieldname=["name", "year_start_date", "year_end_date"],
order_by="year_end_date desc",
as_dict=1,
)
for d in companies:
posting_dates = get_posting_closing_date(d, current_fiscal_year, previous_fiscal_year)
exchange_rate_available = check_exchange_rate_availability(d, posting_dates)
if not exchange_rate_available:
continue
set_reporting_currency_by_doctype("GL Entry", d, posting_dates.get("GL Entry"))
set_reporting_currency_by_doctype(
"Account Closing Balance", d, posting_dates.get("Account Closing Balance")
)
def get_posting_closing_date(company_details, current_fiscal_year, previous_fiscal_year=None):
posting_dates = {}
posting_dates["GL Entry"] = get_closing_posting_dates(
"GL Entry", company_details.get("name"), current_fiscal_year
)
posting_dates["Account Closing Balance"] = get_closing_posting_dates(
"Account Closing Balance", company_details.get("name"), current_fiscal_year
)
if previous_fiscal_year:
prev_fy_last_pcv_closing_date = frappe.db.get_value(
"Period Closing Voucher",
filters={"fiscal_year": previous_fiscal_year.name, "company": company_details.get("name")},
fieldname=["transaction_date"],
order_by="period_start_date desc",
)
if prev_fy_last_pcv_closing_date:
prev_fy_acb_closing_dates = get_closing_posting_dates(
"Account Closing Balance",
company_details.get("name"),
closing_date=prev_fy_last_pcv_closing_date,
)
posting_dates.setdefault("Account Closing Balance", [])
posting_dates["Account Closing Balance"].extend(prev_fy_acb_closing_dates)
return posting_dates
def check_exchange_rate_availability(company_details, posting_dates):
exchange_rate_available = True
for doctype, values in posting_dates.items():
if not exchange_rate_available:
return False
date_column = "posting_date" if doctype == "GL Entry" else "closing_date"
for d in values:
exchange_rate = get_exchange_rate(
company_details.get("default_currency"),
company_details.get("reporting_currency"),
d[date_column],
)
if not exchange_rate:
exchange_rate_available = False
break
return exchange_rate_available
def set_reporting_currency_by_doctype(doctype, company_details, posting_closing_dates):
date_column = "posting_date" if doctype == "GL Entry" else "closing_date"
for d in posting_closing_dates:
exchange_rate = get_exchange_rate(
company_details.get("default_currency"),
company_details.get("reporting_currency"),
d[date_column],
)
set_reporting_currency_on_individual_documents(
doctype, company_details.get("name"), d[date_column], exchange_rate
)
def get_closing_posting_dates(doctype, company, fiscal_year=None, closing_date=None):
dt = frappe.qb.DocType(doctype)
date_column = "posting_date" if doctype == "GL Entry" else "closing_date"
query = frappe.qb.from_(dt).select(dt[date_column]).where(dt.company == company).groupby(dt[date_column])
if doctype == "GL Entry" and fiscal_year:
query = query.where(dt.fiscal_year == fiscal_year.name)
if doctype == "Account Closing Balance":
if fiscal_year:
query = query.where(dt.closing_date[fiscal_year.year_start_date : fiscal_year.year_end_date])
if closing_date:
query = query.where(dt.closing_date == closing_date)
posting_closing_dates = query.run(as_dict=1)
return posting_closing_dates
def set_reporting_currency_on_individual_documents(doctype, company, posting_closing_date, exchange_rate):
dt = frappe.qb.DocType(doctype)
date_column = "posting_date" if doctype == "GL Entry" else "closing_date"
frappe.qb.update(dt).set(dt.reporting_currency_exchange_rate, exchange_rate).set(
dt.debit_in_reporting_currency, exchange_rate * dt.debit
).set(dt.credit_in_reporting_currency, exchange_rate * dt.credit).where(
(dt.company == company) & (dt[date_column] == posting_closing_date)
).run()

View File

@@ -1069,7 +1069,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
if (me.frm.doc.doctype == "Quotation" && me.frm.doc.quotation_to == "Customer") {
(party_type = "Customer"), (party_name = me.frm.doc.party_name);
} else {
party_type = frappe.meta.has_field(me.frm.doc.doctype, "supplier") ? "Supplier" : "Customer";
party_type = frappe.meta.has_field(me.frm.doc.doctype, "customer") ? "Customer" : "Supplier";
party_name = me.frm.doc[party_type.toLowerCase()];
}
if (party_name) {

View File

@@ -264,5 +264,6 @@ erpnext.setup.fiscal_years = {
Pakistan: ["07-01", "06-30"],
Singapore: ["04-01", "03-31"],
"South Africa": ["03-01", "02-28"],
Thailand: ["10-01", "09-30"],
"United Kingdom": ["04-01", "03-31"],
};

View File

@@ -190,10 +190,7 @@ frappe.ui.form.on("Customer", {
__("Actions")
);
if (
cint(frappe.defaults.get_default("enable_common_party_accounting")) &&
frappe.model.can_create("Party Link")
) {
if (cint(frappe.defaults.get_default("enable_common_party_accounting"))) {
frm.add_custom_button(
__("Link with Supplier"),
function () {

View File

@@ -365,7 +365,6 @@
"fieldname": "base_net_rate",
"fieldtype": "Currency",
"label": "Net Rate (Company Currency)",
"options": "Company:company:default_currency",
"print_hide": 1,
"read_only": 1
},
@@ -699,7 +698,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2025-08-26 20:31:47.775890",
"modified": "2025-06-12 17:31:47.775890",
"modified_by": "Administrator",
"module": "Selling",
"name": "Quotation Item",

View File

@@ -75,7 +75,7 @@ def create_demo_company():
frappe.db.set_single_value("Global Defaults", "demo_company", new_company.name)
frappe.db.set_default("company", new_company.name)
bank_account = create_bank_account({"company_name": new_company.name}, demo=True)
bank_account = create_bank_account({"company_name": new_company.name})
frappe.db.set_value("Company", new_company.name, "default_bank_account", bank_account.name)
return new_company.name

View File

@@ -17,9 +17,6 @@ frappe.ui.form.on("Company", {
frm.toggle_enable("default_currency", !r.message);
});
}
if (frm.doc.__islocal) {
frm.set_value("reporting_currency", "");
}
},
setup: function (frm) {
frm.__rename_queue = "long";
@@ -159,10 +156,6 @@ frappe.ui.form.on("Company", {
}
}
if (frm.doc.__islocal) {
frm.set_value("reporting_currency", "");
}
erpnext.company.set_chart_of_accounts_options(frm.doc);
},

View File

@@ -22,7 +22,6 @@
"domain",
"date_of_establishment",
"parent_company",
"reporting_currency",
"company_info",
"company_logo",
"date_of_incorporation",
@@ -751,7 +750,6 @@
},
{
"default": "0",
"description": "Upon enabling this, the JV will be submitted for a different exchange rate.",
"fieldname": "submit_err_jv",
"fieldtype": "Check",
"label": "Submit ERR Journals?"
@@ -836,14 +834,6 @@
"fieldtype": "Select",
"label": "Reconciliation Takes Effect On",
"options": "Advance Payment Date\nOldest Of Invoice Or Advance\nReconciliation Date"
},
{
"fieldname": "reporting_currency",
"fieldtype": "Link",
"label": "Reporting Currency",
"options": "Currency",
"print_hide": 1,
"read_only": 1
}
],
"icon": "fa fa-building",
@@ -851,7 +841,7 @@
"image_field": "company_logo",
"is_tree": 1,
"links": [],
"modified": "2025-08-25 18:34:03.602046",
"modified": "2025-01-09 20:12:25.471544",
"modified_by": "Administrator",
"module": "Setup",
"name": "Company",
@@ -913,10 +903,9 @@
"select": 1
}
],
"row_format": "Dynamic",
"show_name_in_global_search": 1,
"sort_field": "creation",
"sort_order": "ASC",
"states": [],
"track_changes": 1
}
}

View File

@@ -91,7 +91,6 @@ class Company(NestedSet):
"Advance Payment Date", "Oldest Of Invoice Or Advance", "Reconciliation Date"
]
registration_details: DF.Code | None
reporting_currency: DF.Link | None
rgt: DF.Int
round_off_account: DF.Link | None
round_off_cost_center: DF.Link | None
@@ -154,7 +153,6 @@ class Company(NestedSet):
self.check_parent_changed()
self.set_chart_of_accounts()
self.validate_parent_company()
self.set_reporting_currency()
def validate_abbr(self):
if not self.abbr:
@@ -492,14 +490,6 @@ class Company(NestedSet):
if not is_group:
frappe.throw(_("Parent Company must be a group company"))
def set_reporting_currency(self):
self.reporting_currency = self.default_currency
if self.parent_company:
parent_reporting_currency = frappe.db.get_value(
"Company", self.parent_company, ["reporting_currency"]
)
self.reporting_currency = parent_reporting_currency
def set_default_accounts(self):
default_accounts = {
"default_cash_account": "Cash",
@@ -691,7 +681,7 @@ class Company(NestedSet):
frappe.db.sql("delete from tabBOM where company=%s", self.name)
for dt in ("BOM Operation", "BOM Item", "BOM Scrap Item", "BOM Explosion Item"):
frappe.db.sql(
"delete from `tab{}` where parent in ({})".format(dt, ", ".join(["%s"] * len(boms))),
"delete from `tab{}` where parent in ({})" "".format(dt, ", ".join(["%s"] * len(boms))),
tuple(boms),
)

View File

@@ -814,8 +814,7 @@
"depends_on": "eval:doc.salary_mode == 'Bank'",
"fieldname": "iban",
"fieldtype": "Data",
"label": "IBAN",
"options": "IBAN"
"label": "IBAN"
}
],
"icon": "fa fa-user",
@@ -823,7 +822,7 @@
"image_field": "image",
"is_tree": 1,
"links": [],
"modified": "2025-08-29 11:52:12.819878",
"modified": "2025-07-04 08:29:34.347269",
"modified_by": "Administrator",
"module": "Setup",
"name": "Employee",
@@ -865,7 +864,6 @@
"write": 1
}
],
"row_format": "Dynamic",
"search_fields": "employee_name",
"show_name_in_global_search": 1,
"sort_field": "creation",
@@ -873,4 +871,4 @@
"states": [],
"title_field": "employee_name",
"track_changes": 1
}
}

Some files were not shown because too many files have changed in this diff Show More