diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index 1a5ee36b95af..2c876e09725f 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -103,14 +103,12 @@ def autoname(self): self.name = get_autoname_with_number(self.account_number, self.account_name, self.company) def validate(self): - from erpnext.accounts.utils import validate_field_number - if frappe.local.flags.allow_unverified_charts: return self.validate_parent() self.validate_parent_child_account_type() self.validate_root_details() - validate_field_number("Account", self.name, self.account_number, self.company, "account_number") + self.validate_account_number() self.validate_group_or_ledger() self.set_root_and_report_type() self.validate_mandatory() @@ -311,6 +309,22 @@ def validate_account_currency(self): if frappe.db.get_value("GL Entry", {"account": self.name}): frappe.throw(_("Currency can not be changed after making entries using some other currency")) + def validate_account_number(self, account_number=None): + if not account_number: + account_number = self.account_number + + if account_number: + account_with_same_number = frappe.db.get_value( + "Account", + {"account_number": account_number, "company": self.company, "name": ["!=", self.name]}, + ) + if account_with_same_number: + frappe.throw( + _("Account Number {0} already used in account {1}").format( + account_number, account_with_same_number + ) + ) + def create_account_for_child_company(self, parent_acc_name_map, descendants, parent_acc_name): for company in descendants: company_bold = frappe.bold(company) @@ -464,19 +478,6 @@ def get_account_autoname(account_number, account_name, company): return " - ".join(parts) -def validate_account_number(name, account_number, company): - if account_number: - account_with_same_number = frappe.db.get_value( - "Account", {"account_number": account_number, "company": company, "name": ["!=", name]} - ) - if account_with_same_number: - frappe.throw( - _("Account Number {0} already used in account {1}").format( - account_number, account_with_same_number - ) - ) - - @frappe.whitelist() def update_account_number(name, account_name, account_number=None, from_descendant=False): account = frappe.get_cached_doc("Account", name) @@ -517,7 +518,7 @@ def update_account_number(name, account_name, account_number=None, from_descenda frappe.throw(message, title=_("Rename Not Allowed")) - validate_account_number(name, account_number, account.company) + account.validate_account_number(account_number) if account_number: frappe.db.set_value("Account", name, "account_number", account_number.strip()) else: diff --git a/erpnext/accounts/doctype/bank_account/bank_account.json b/erpnext/accounts/doctype/bank_account/bank_account.json index 32f1c675d3b1..a5a7691eb76e 100644 --- a/erpnext/accounts/doctype/bank_account/bank_account.json +++ b/erpnext/accounts/doctype/bank_account/bank_account.json @@ -208,8 +208,54 @@ "label": "Disabled" } ], - "links": [], - "modified": "2023-09-22 21:31:34.763977", + "links": [ + { + "group": "Transactions", + "link_doctype": "Payment Request", + "link_fieldname": "bank_account" + }, + { + "group": "Transactions", + "link_doctype": "Payment Order", + "link_fieldname": "bank_account" + }, + { + "group": "Transactions", + "link_doctype": "Bank Guarantee", + "link_fieldname": "bank_account" + }, + { + "group": "Transactions", + "link_doctype": "Payroll Entry", + "link_fieldname": "bank_account" + }, + { + "group": "Transactions", + "link_doctype": "Bank Transaction", + "link_fieldname": "bank_account" + }, + { + "group": "Accounting", + "link_doctype": "Payment Entry", + "link_fieldname": "bank_account" + }, + { + "group": "Accounting", + "link_doctype": "Journal Entry", + "link_fieldname": "bank_account" + }, + { + "group": "Party", + "link_doctype": "Customer", + "link_fieldname": "default_bank_account" + }, + { + "group": "Party", + "link_doctype": "Supplier", + "link_fieldname": "default_bank_account" + } + ], + "modified": "2024-09-24 06:57:41.292970", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Account", @@ -246,4 +292,4 @@ "sort_order": "DESC", "states": [], "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/bank_account/bank_account_dashboard.py b/erpnext/accounts/doctype/bank_account/bank_account_dashboard.py deleted file mode 100644 index 8bf8d8a5cd01..000000000000 --- a/erpnext/accounts/doctype/bank_account/bank_account_dashboard.py +++ /dev/null @@ -1,20 +0,0 @@ -from frappe import _ - - -def get_data(): - return { - "fieldname": "bank_account", - "non_standard_fieldnames": { - "Customer": "default_bank_account", - "Supplier": "default_bank_account", - }, - "transactions": [ - { - "label": _("Payments"), - "items": ["Payment Entry", "Payment Request", "Payment Order", "Payroll Entry"], - }, - {"label": _("Party"), "items": ["Customer", "Supplier"]}, - {"items": ["Bank Guarantee"]}, - {"items": ["Journal Entry"]}, - ], - } diff --git a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py index 85713c6c9a6a..92abb8cea89b 100644 --- a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py +++ b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py @@ -108,8 +108,18 @@ def update_clearance_date(self): if not d.clearance_date: d.clearance_date = None - payment_entry = frappe.get_doc(d.payment_document, d.payment_entry) - payment_entry.db_set("clearance_date", d.clearance_date) + if d.payment_document == "Sales Invoice": + frappe.db.set_value( + "Sales Invoice Payment", + {"parent": d.payment_entry, "account": self.get("account"), "amount": [">", 0]}, + "clearance_date", + d.clearance_date, + ) + + else: + frappe.db.set_value( + d.payment_document, d.payment_entry, "clearance_date", d.clearance_date + ) clearance_date_updated = True diff --git a/erpnext/accounts/doctype/bank_clearance/test_bank_clearance.py b/erpnext/accounts/doctype/bank_clearance/test_bank_clearance.py index d785bfbfef22..658a69a48037 100644 --- a/erpnext/accounts/doctype/bank_clearance/test_bank_clearance.py +++ b/erpnext/accounts/doctype/bank_clearance/test_bank_clearance.py @@ -6,16 +6,29 @@ import frappe from frappe.utils import add_months, getdate +from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice +from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice +from erpnext.stock.doctype.item.test_item import create_item +from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.tests.utils import if_lending_app_installed, if_lending_app_not_installed class TestBankClearance(unittest.TestCase): @classmethod def setUpClass(cls): + create_warehouse( + warehouse_name="_Test Warehouse", + properties={"parent_warehouse": "All Warehouses - _TC"}, + company="_Test Company", + ) + create_item("_Test Item") + create_cost_center(cost_center_name="_Test Cost Center", company="_Test Company") + clear_payment_entries() clear_loan_transactions() + clear_pos_sales_invoices() make_bank_account() add_transactions() @@ -83,11 +96,41 @@ def make_loan(): bank_clearance.get_payment_entries() self.assertEqual(len(bank_clearance.payment_entries), 3) + def test_update_clearance_date_on_si(self): + sales_invoice = make_pos_sales_invoice() + + date = getdate() + bank_clearance = frappe.get_doc("Bank Clearance") + bank_clearance.account = "_Test Bank Clearance - _TC" + bank_clearance.from_date = add_months(date, -1) + bank_clearance.to_date = date + bank_clearance.include_pos_transactions = 1 + bank_clearance.get_payment_entries() + + self.assertNotEqual(len(bank_clearance.payment_entries), 0) + for payment in bank_clearance.payment_entries: + if payment.payment_entry == sales_invoice.name: + payment.clearance_date = date + + bank_clearance.update_clearance_date() + + si_clearance_date = frappe.db.get_value( + "Sales Invoice Payment", + {"parent": sales_invoice.name, "account": bank_clearance.account}, + "clearance_date", + ) + + self.assertEqual(si_clearance_date, date) + def clear_payment_entries(): frappe.db.delete("Payment Entry") +def clear_pos_sales_invoices(): + frappe.db.delete("Sales Invoice", {"is_pos": 1}) + + @if_lending_app_installed def clear_loan_transactions(): for dt in [ @@ -115,9 +158,45 @@ def add_transactions(): def make_payment_entry(): - pi = make_purchase_invoice(supplier="_Test Supplier", qty=1, rate=690) + from erpnext.buying.doctype.supplier.test_supplier import create_supplier + + supplier = create_supplier(supplier_name="_Test Supplier") + pi = make_purchase_invoice( + supplier=supplier, + supplier_warehouse="_Test Warehouse - _TC", + expense_account="Cost of Goods Sold - _TC", + uom="Nos", + qty=1, + rate=690, + ) pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank Clearance - _TC") pe.reference_no = "Conrad Oct 18" pe.reference_date = "2018-10-24" pe.insert() pe.submit() + + +def make_pos_sales_invoice(): + from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import ( + make_customer, + ) + + mode_of_payment = frappe.get_doc({"doctype": "Mode of Payment", "name": "Cash"}) + + if not frappe.db.get_value("Mode of Payment Account", {"company": "_Test Company", "parent": "Cash"}): + mode_of_payment.append( + "accounts", {"company": "_Test Company", "default_account": "_Test Bank Clearance - _TC"} + ) + mode_of_payment.save() + + customer = make_customer(customer="_Test Customer") + + si = create_sales_invoice(customer=customer, item="_Test Item", is_pos=1, qty=1, rate=1000, do_not_save=1) + si.set("payments", []) + si.append( + "payments", {"mode_of_payment": "Cash", "account": "_Test Bank Clearance - _TC", "amount": 1000} + ) + si.insert() + si.submit() + + return si diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py index c53faf9ff390..3d4b182d52dc 100644 --- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py @@ -515,6 +515,23 @@ def test_negative_debit_and_credit_with_same_account_head(self): self.assertEqual(row.debit_in_account_currency, 100) self.assertEqual(row.credit_in_account_currency, 100) + def test_transaction_exchange_rate_on_journals(self): + jv = make_journal_entry("_Test Bank - _TC", "_Test Receivable USD - _TC", 100, save=False) + jv.accounts[0].update({"debit_in_account_currency": 8500, "exchange_rate": 1}) + jv.accounts[1].update({"party_type": "Customer", "party": "_Test Customer USD", "exchange_rate": 85}) + jv.submit() + actual = frappe.db.get_all( + "GL Entry", + filters={"voucher_no": jv.name, "is_cancelled": 0}, + fields=["account", "transaction_exchange_rate"], + order_by="account", + ) + expected = [ + {"account": "_Test Bank - _TC", "transaction_exchange_rate": 1.0}, + {"account": "_Test Receivable USD - _TC", "transaction_exchange_rate": 85.0}, + ] + self.assertEqual(expected, actual) + def make_journal_entry( account1, diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index 13baff9e7a82..625608b5374b 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -145,9 +145,21 @@ def set_liability_account(self): self.is_opening = "No" return - liability_account = get_party_account( - self.party_type, self.party, self.company, include_advance=True - )[1] + accounts = get_party_account(self.party_type, self.party, self.company, include_advance=True) + + liability_account = accounts[1] if len(accounts) > 1 else None + fieldname = ( + "default_advance_received_account" + if self.party_type == "Customer" + else "default_advance_paid_account" + ) + + if not liability_account: + throw( + _("Please set default {0} in Company {1}").format( + frappe.bold(frappe.get_meta("Company").get_label(fieldname)), frappe.bold(self.company) + ) + ) self.set(self.party_account_field, liability_account) diff --git a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js index 68a85f7ee963..c171713dc611 100644 --- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js +++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.js @@ -194,7 +194,9 @@ function refresh_payments(d, frm) { } if (payment) { payment.expected_amount += flt(p.amount); - payment.closing_amount = payment.expected_amount; + if (payment.closing_amount === 0) { + payment.closing_amount = payment.expected_amount; + } payment.difference = payment.closing_amount - payment.expected_amount; } else { frm.add_child("payment_reconciliation", { diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js index a6e8bfa62869..8707ee88860d 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js @@ -40,6 +40,19 @@ erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnex }; }); + this.frm.set_query("item_code", "items", function (doc) { + return { + query: "erpnext.accounts.doctype.pos_invoice.pos_invoice.item_query", + filters: { + has_variants: ["=", 0], + is_sales_item: ["=", 1], + disabled: ["=", 0], + is_fixed_asset: ["=", 0], + pos_profile: ["=", doc.pos_profile], + }, + }; + }); + erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype); } diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index d517c9d6da22..742af7dd0fe0 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -6,6 +6,7 @@ from frappe import _, bold from frappe.query_builder.functions import IfNull, Sum from frappe.utils import cint, flt, get_link_to_form, getdate, nowdate +from frappe.utils.nestedset import get_descendants_of from erpnext.accounts.doctype.loyalty_program.loyalty_program import validate_loyalty_points from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request @@ -15,6 +16,7 @@ update_multi_mode_option, ) from erpnext.accounts.party import get_due_date, get_party_account +from erpnext.controllers.queries import item_query as _item_query from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos @@ -449,7 +451,7 @@ def validate_payment_amount(self): if self.is_return and entry.amount > 0: frappe.throw(_("Row #{0} (Payment Table): Amount must be negative").format(entry.idx)) - if self.is_return: + if self.is_return and self.docstatus != 0: invoice_total = self.rounded_total or self.grand_total total_amount_in_payments = flt(total_amount_in_payments, self.precision("grand_total")) if total_amount_in_payments and total_amount_in_payments < invoice_total: @@ -837,3 +839,29 @@ def append_payment(payment_mode): ]: payment_mode = get_mode_of_payment_info(mode_of_payment, doc.company) append_payment(payment_mode[0]) + + +@frappe.whitelist() +@frappe.validate_and_sanitize_search_inputs +def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False): + if pos_profile := filters.get("pos_profile")[1]: + pos_profile = frappe.get_cached_doc("POS Profile", pos_profile) + if item_groups := get_item_group(pos_profile): + filters["item_group"] = ["in", tuple(item_groups)] + + del filters["pos_profile"] + + else: + filters.pop("pos_profile", None) + + return _item_query(doctype, txt, searchfield, start, page_len, filters, as_dict) + + +def get_item_group(pos_profile): + item_groups = [] + if pos_profile.get("item_groups"): + # Get items based on the item groups defined in the POS profile + for row in pos_profile.get("item_groups"): + item_groups.extend(get_descendants_of("Item Group", row.item_group)) + + return list(set(item_groups)) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 1214ddee8cd2..010d3b8bcd5c 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -340,6 +340,7 @@ def validate(self): ): validate_loyalty_points(self, self.loyalty_points) + self.allow_write_off_only_on_pos() self.reset_default_field_value("set_warehouse", "items", "warehouse") def validate_accounts(self): @@ -1021,6 +1022,10 @@ def validate_delivery_note(self): raise_exception=1, ) + def allow_write_off_only_on_pos(self): + if not self.is_pos and self.write_off_account: + self.write_off_account = None + def validate_write_off_account(self): if flt(self.write_off_amount) and not self.write_off_account: self.write_off_account = frappe.get_cached_value("Company", self.company, "write_off_account") diff --git a/erpnext/accounts/doctype/tax_rule/tax_rule.py b/erpnext/accounts/doctype/tax_rule/tax_rule.py index ed623c6635e6..1c0c0a3c1d61 100644 --- a/erpnext/accounts/doctype/tax_rule/tax_rule.py +++ b/erpnext/accounts/doctype/tax_rule/tax_rule.py @@ -185,7 +185,7 @@ def get_tax_template(posting_date, args): conditions.append("(from_date is null) and (to_date is null)") conditions.append( - "ifnull(tax_category, '') = {}".format(frappe.db.escape(cstr(args.get("tax_category")))) + "ifnull(tax_category, '') = {}".format(frappe.db.escape(cstr(args.get("tax_category")), False)) ) if "tax_category" in args.keys(): del args["tax_category"] diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 1a7638aaae46..43100c812852 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -310,8 +310,8 @@ def build_data(self): must_consider = False if self.filters.get("for_revaluation_journals"): - if (abs(row.outstanding) >= 0.0 / 10**self.currency_precision) or ( - abs(row.outstanding_in_account_currency) >= 0.0 / 10**self.currency_precision + if (abs(row.outstanding) >= 1.0 / 10**self.currency_precision) or ( + abs(row.outstanding_in_account_currency) >= 1.0 / 10**self.currency_precision ): must_consider = True else: diff --git a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py index b288e8e5ac73..64e6a3404646 100644 --- a/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py +++ b/erpnext/accounts/report/asset_depreciations_and_balances/asset_depreciations_and_balances.py @@ -37,7 +37,7 @@ def get_group_by_asset_category_data(filters): - flt(row.cost_of_sold_asset) - flt(row.cost_of_scrapped_asset) ) - + # Update row with corresponding asset data row.update( next( asset @@ -68,7 +68,7 @@ def get_group_by_asset_category_data(filters): def get_asset_categories_for_grouped_by_category(filters): condition = "" if filters.get("asset_category"): - condition += " and asset_category = %(asset_category)s" + condition += " and a.asset_category = %(asset_category)s" if filters.get("finance_book"): condition += " and exists (select 1 from `tabAsset Depreciation Schedule` ads where ads.asset = a.name and ads.finance_book = %(finance_book)s)" @@ -113,8 +113,13 @@ def get_asset_categories_for_grouped_by_category(filters): 0 end), 0) as cost_of_scrapped_asset from `tabAsset` a - where docstatus=1 and company=%(company)s and purchase_date <= %(to_date)s {condition} - and not exists(select name from `tabAsset Capitalization Asset Item` where asset = a.name) + where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s {condition} + and not exists( + select 1 from `tabAsset Capitalization Asset Item` acai join `tabAsset Capitalization` ac on acai.parent=ac.name + where acai.asset = a.name + and ac.posting_date <= %(to_date)s + and ac.docstatus=1 + ) group by a.asset_category """, { @@ -131,53 +136,59 @@ def get_asset_categories_for_grouped_by_category(filters): def get_asset_details_for_grouped_by_category(filters): condition = "" if filters.get("asset"): - condition += " and name = %(asset)s" + condition += " and a.name = %(asset)s" if filters.get("finance_book"): - condition += " and exists (select 1 from `tabAsset Depreciation Schedule` ads where ads.asset = `tabAsset`.name and ads.finance_book = %(finance_book)s)" + condition += " and exists (select 1 from `tabAsset Depreciation Schedule` ads where ads.asset = a.name and ads.finance_book = %(finance_book)s)" # nosemgrep return frappe.db.sql( f""" - SELECT name, - ifnull(sum(case when purchase_date < %(from_date)s then - case when ifnull(disposal_date, 0) = 0 or disposal_date >= %(from_date)s then - gross_purchase_amount + SELECT a.name, + ifnull(sum(case when a.purchase_date < %(from_date)s then + case when ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s then + a.gross_purchase_amount else 0 end else 0 end), 0) as cost_as_on_from_date, - ifnull(sum(case when purchase_date >= %(from_date)s then - gross_purchase_amount + ifnull(sum(case when a.purchase_date >= %(from_date)s then + a.gross_purchase_amount else 0 end), 0) as cost_of_new_purchase, - ifnull(sum(case when ifnull(disposal_date, 0) != 0 - and disposal_date >= %(from_date)s - and disposal_date <= %(to_date)s then - case when status = "Sold" then - gross_purchase_amount + ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 + and a.disposal_date >= %(from_date)s + and a.disposal_date <= %(to_date)s then + case when a.status = "Sold" then + a.gross_purchase_amount else 0 end else 0 end), 0) as cost_of_sold_asset, - ifnull(sum(case when ifnull(disposal_date, 0) != 0 - and disposal_date >= %(from_date)s - and disposal_date <= %(to_date)s then - case when status = "Scrapped" then - gross_purchase_amount + ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 + and a.disposal_date >= %(from_date)s + and a.disposal_date <= %(to_date)s then + case when a.status = "Scrapped" then + a.gross_purchase_amount else 0 end else 0 end), 0) as cost_of_scrapped_asset - from `tabAsset` - where docstatus=1 and company=%(company)s and purchase_date <= %(to_date)s {condition} - group by name + from `tabAsset` a + where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s {condition} + and not exists( + select 1 from `tabAsset Capitalization Asset Item` acai join `tabAsset Capitalization` ac on acai.parent=ac.name + where acai.asset = a.name + and ac.posting_date <= %(to_date)s + and ac.docstatus=1 + ) + group by a.name """, { "to_date": filters.to_date, diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.py b/erpnext/accounts/report/balance_sheet/balance_sheet.py index e89a177a8671..274c8a7a3714 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.py +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.py @@ -95,7 +95,7 @@ def execute(filters=None): filters.periodicity, period_list, filters.accumulated_values, company=filters.company ) - chart = get_chart_data(filters, columns, asset, liability, equity) + chart = get_chart_data(filters, columns, asset, liability, equity, currency) report_summary, primitive_summary = get_report_summary( period_list, asset, liability, equity, provisional_profit_loss, currency, filters @@ -221,7 +221,7 @@ def get_report_summary( ], (net_asset - net_liability + net_equity) -def get_chart_data(filters, columns, asset, liability, equity): +def get_chart_data(filters, columns, asset, liability, equity, currency): labels = [d.get("label") for d in columns[2:]] asset_data, liability_data, equity_data = [], [], [] @@ -249,4 +249,8 @@ def get_chart_data(filters, columns, asset, liability, equity): else: chart["type"] = "line" + chart["fieldtype"] = "Currency" + chart["options"] = "currency" + chart["currency"] = currency + return chart diff --git a/erpnext/accounts/report/cash_flow/cash_flow.py b/erpnext/accounts/report/cash_flow/cash_flow.py index c034f95ec002..9b4416714158 100644 --- a/erpnext/accounts/report/cash_flow/cash_flow.py +++ b/erpnext/accounts/report/cash_flow/cash_flow.py @@ -111,7 +111,7 @@ def execute(filters=None): ) columns = get_columns(filters.periodicity, period_list, filters.accumulated_values, filters.company) - chart = get_chart_data(columns, data) + chart = get_chart_data(columns, data, company_currency) report_summary = get_report_summary(summary_data, company_currency) @@ -252,7 +252,7 @@ def get_report_summary(summary_data, currency): return report_summary -def get_chart_data(columns, data): +def get_chart_data(columns, data, currency): labels = [d.get("label") for d in columns[2:]] datasets = [ { @@ -267,5 +267,7 @@ def get_chart_data(columns, data): chart = {"data": {"labels": labels, "datasets": datasets}, "type": "bar"} chart["fieldtype"] = "Currency" + chart["options"] = "currency" + chart["currency"] = currency return chart diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index 2931b728a424..e6aa215924d1 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -115,7 +115,7 @@ def get_balance_sheet_data(fiscal_year, companies, columns, filters): True, ) - chart = get_chart_data(filters, columns, asset, liability, equity) + chart = get_chart_data(filters, columns, asset, liability, equity, company_currency) return data, message, chart, report_summary @@ -173,7 +173,7 @@ def get_profit_loss_data(fiscal_year, companies, columns, filters): if net_profit_loss: data.append(net_profit_loss) - chart = get_pl_chart_data(filters, columns, income, expense, net_profit_loss) + chart = get_pl_chart_data(filters, columns, income, expense, net_profit_loss, company_currency) report_summary, primitive_summary = get_pl_summary( companies, "", income, expense, net_profit_loss, company_currency, filters, True diff --git a/erpnext/accounts/report/general_and_payment_ledger_comparison/general_and_payment_ledger_comparison.py b/erpnext/accounts/report/general_and_payment_ledger_comparison/general_and_payment_ledger_comparison.py index 89cf7e504f0c..9d079eb9ebd2 100644 --- a/erpnext/accounts/report/general_and_payment_ledger_comparison/general_and_payment_ledger_comparison.py +++ b/erpnext/accounts/report/general_and_payment_ledger_comparison/general_and_payment_ledger_comparison.py @@ -199,8 +199,7 @@ def get_columns(self): dict( label=_("Voucher Type"), fieldname="voucher_type", - fieldtype="Link", - options="DocType", + fieldtype="Data", width="100", ) ) @@ -219,8 +218,7 @@ def get_columns(self): dict( label=_("Party Type"), fieldname="party_type", - fieldtype="Link", - options="DocType", + fieldtype="Data", width="100", ) ) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 2564eb0800f2..1c1d10c087b9 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -35,6 +35,9 @@ def execute(filters=None): if filters.get("party"): filters.party = frappe.parse_json(filters.get("party")) + if filters.get("voucher_no") and not filters.get("group_by"): + filters.group_by = "Group by Voucher (Consolidated)" + validate_filters(filters, account_details) validate_party(filters) diff --git a/erpnext/accounts/report/payment_ledger/payment_ledger.py b/erpnext/accounts/report/payment_ledger/payment_ledger.py index 9852c6e7ab99..d4f0f0a107d9 100644 --- a/erpnext/accounts/report/payment_ledger/payment_ledger.py +++ b/erpnext/accounts/report/payment_ledger/payment_ledger.py @@ -210,7 +210,7 @@ def get_columns(self): ) ) self.columns.append( - dict(label=_("Currency"), fieldname="currency", fieldtype="Currency", hidden=True) + dict(label=_("Currency"), fieldname="currency", fieldtype="Link", options="Currency", hidden=True) ) def run(self): diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py index 58610b22a93c..35453f2ec442 100644 --- a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py +++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py @@ -59,11 +59,11 @@ def execute(filters=None): columns = get_columns(filters.periodicity, period_list, filters.accumulated_values, filters.company) - chart = get_chart_data(filters, columns, income, expense, net_profit_loss) - currency = filters.presentation_currency or frappe.get_cached_value( "Company", filters.company, "default_currency" ) + chart = get_chart_data(filters, columns, income, expense, net_profit_loss, currency) + report_summary, primitive_summary = get_report_summary( period_list, filters.periodicity, income, expense, net_profit_loss, currency, filters ) @@ -152,7 +152,7 @@ def get_net_profit_loss(income, expense, period_list, company, currency=None, co return net_profit_loss -def get_chart_data(filters, columns, income, expense, net_profit_loss): +def get_chart_data(filters, columns, income, expense, net_profit_loss, currency): labels = [d.get("label") for d in columns[2:]] income_data, expense_data, net_profit = [], [], [] @@ -181,5 +181,7 @@ def get_chart_data(filters, columns, income, expense, net_profit_loss): chart["type"] = "line" chart["fieldtype"] = "Currency" + chart["options"] = "currency" + chart["currency"] = currency return chart diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 0a21516c1773..c6e76abb0b59 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -213,7 +213,7 @@ frappe.ui.form.on("Asset", {
- Failed to post depreciation entries + ${__("Failed to post depreciation entries")}
`; diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 92efa5168f3b..44a9813c89eb 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1036,7 +1036,9 @@ def get_gl_dict(self, args, account_currency=None, item=None): gl_dict.update( { "transaction_currency": self.get("currency") or self.company_currency, - "transaction_exchange_rate": self.get("conversion_rate", 1), + "transaction_exchange_rate": item.get("exchange_rate", 1) + if self.doctype == "Journal Entry" and item + else self.get("conversion_rate", 1), "debit_in_transaction_currency": self.get_value_in_transaction_currency( account_currency, gl_dict, "debit" ), diff --git a/erpnext/manufacturing/doctype/bom_creator/bom_creator.json b/erpnext/manufacturing/doctype/bom_creator/bom_creator.json index 9d2384e31a0e..65664be2bead 100644 --- a/erpnext/manufacturing/doctype/bom_creator/bom_creator.json +++ b/erpnext/manufacturing/doctype/bom_creator/bom_creator.json @@ -280,6 +280,7 @@ "read_only": 1 } ], + "hide_toolbar": 1, "icon": "fa fa-sitemap", "is_submittable": 1, "links": [ @@ -288,7 +289,7 @@ "link_fieldname": "bom_creator" } ], - "modified": "2024-04-02 16:30:59.779190", + "modified": "2024-09-21 09:05:52.945112", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM Creator", diff --git a/erpnext/manufacturing/doctype/plant_floor/plant_floor.js b/erpnext/manufacturing/doctype/plant_floor/plant_floor.js index becdbdb9c95a..3d18900b70cb 100644 --- a/erpnext/manufacturing/doctype/plant_floor/plant_floor.js +++ b/erpnext/manufacturing/doctype/plant_floor/plant_floor.js @@ -6,6 +6,22 @@ frappe.ui.form.on("Plant Floor", { frm.trigger("setup_queries"); }, + add_workstation(frm) { + frm.add_custom_button(__("Create Workstation"), () => { + var doc = frappe.model.get_new_doc("Workstation"); + doc.plant_floor = frm.doc.name; + doc.status = "Off"; + frappe.ui.form.make_quick_entry( + "Workstation", + () => { + frm.trigger("prepare_workstation_dashboard"); + }, + null, + doc + ); + }).addClass("btn-primary"); + }, + setup_queries(frm) { frm.set_query("warehouse", (doc) => { if (!doc.company) { @@ -24,6 +40,11 @@ frappe.ui.form.on("Plant Floor", { refresh(frm) { frm.trigger("prepare_stock_dashboard"); frm.trigger("prepare_workstation_dashboard"); + + if (!frm.is_new()) { + frm.trigger("add_workstation"); + frm.disable_save(); + } }, prepare_workstation_dashboard(frm) { diff --git a/erpnext/manufacturing/doctype/plant_floor/plant_floor.json b/erpnext/manufacturing/doctype/plant_floor/plant_floor.json index be0052c47bf5..c1c167c395b6 100644 --- a/erpnext/manufacturing/doctype/plant_floor/plant_floor.json +++ b/erpnext/manufacturing/doctype/plant_floor/plant_floor.json @@ -69,9 +69,10 @@ "options": "Company" } ], + "hide_toolbar": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2024-01-30 11:59:07.508535", + "modified": "2024-09-19 19:06:36.481625", "modified_by": "Administrator", "module": "Manufacturing", "name": "Plant Floor", @@ -94,4 +95,4 @@ "sort_field": "modified", "sort_order": "DESC", "states": [] -} \ No newline at end of file +} diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index ae888c797144..df72b1e6b510 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -201,7 +201,7 @@ frappe.ui.form.on("Work Order", { frm.doc.produced_qty > 0 ) { frm.add_custom_button( - __("Disassembly Order"), + __("Disassemble Order"), () => { frm.trigger("make_disassembly_order"); }, diff --git a/erpnext/public/js/bom_configurator/bom_configurator.bundle.js b/erpnext/public/js/bom_configurator/bom_configurator.bundle.js index 5061be9d20a2..21aa70fe7ae5 100644 --- a/erpnext/public/js/bom_configurator/bom_configurator.bundle.js +++ b/erpnext/public/js/bom_configurator/bom_configurator.bundle.js @@ -25,6 +25,9 @@ class BOMConfigurator { }; frappe.views.trees["BOM Configurator"] = new frappe.views.TreeView(options); + let node = frappe.views.trees["BOM Configurator"].tree.root_node; + frappe.views.trees["BOM Configurator"].tree.show_toolbar(node); + frappe.views.trees["BOM Configurator"].tree.load_children(node, true); this.tree_view = frappe.views.trees["BOM Configurator"]; } @@ -137,7 +140,7 @@ class BOMConfigurator { btnClass: "hidden-xs", }, { - label: __("Expand All"), + label: __("Collapse All"), click: function (node) { let view = frappe.views.trees["BOM Configurator"]; @@ -283,6 +286,13 @@ class BOMConfigurator { fieldtype: "Float", reqd: 1, read_only: read_only, + change() { + this.layout.fields_dict.items.grid.data.forEach((row) => { + row.qty = flt(this.value); + }); + + this.layout.fields_dict.items.grid.refresh(); + }, }, { fieldtype: "Section Break" }, { diff --git a/erpnext/public/js/templates/visual_plant_floor_template.html b/erpnext/public/js/templates/visual_plant_floor_template.html index 2e67085c0221..a1639f073702 100644 --- a/erpnext/public/js/templates/visual_plant_floor_template.html +++ b/erpnext/public/js/templates/visual_plant_floor_template.html @@ -1,5 +1,16 @@ {% $.each(workstations, (idx, row) => { %}
+
+ {% if(row.status == "Production") { %} +
+
+
+
+ {% } %} + + {{row.status}} + +
-
-

{{row.status}}

-
{{row.workstation_name}}
+
+ + {{row.workstation_name}} +
-{% }); %} \ No newline at end of file +{% }); %} diff --git a/erpnext/public/js/utils/sales_common.js b/erpnext/public/js/utils/sales_common.js index 91a85aa02a54..e503fb3339c1 100644 --- a/erpnext/public/js/utils/sales_common.js +++ b/erpnext/public/js/utils/sales_common.js @@ -15,10 +15,11 @@ erpnext.sales_common = { onload() { super.onload(); this.setup_queries(); - this.frm.set_query("shipping_rule", function () { + this.frm.set_query("shipping_rule", function (doc) { return { filters: { shipping_rule_type: "Selling", + company: doc.company, }, }; }); diff --git a/erpnext/public/scss/erpnext.scss b/erpnext/public/scss/erpnext.scss index 03dd31104e1e..29a2696470f4 100644 --- a/erpnext/public/scss/erpnext.scss +++ b/erpnext/public/scss/erpnext.scss @@ -507,6 +507,47 @@ body[data-route="pos"] { position: relative; } +.ring-container { + position: relative; +} + +.circle { + width: 9px; + height: 9px; + background-color: #278f5e; + border-radius: 50%; + position: absolute; + left: 9px; + top: 8px; +} + +@keyframes pulsate { + 0% { + -webkit-transform: scale(0.1, 0.1); + opacity: 0; + } + 50% { + opacity: 1; + } + 100% { + -webkit-transform: scale(1.2, 1.2); + opacity: 0; + } +} + +.ringring { + border: 2px solid #62bd19; + -webkit-border-radius: 40px; + height: 15px; + width: 15px; + position: absolute; + left: 6px; + top: 5px; + -webkit-animation: pulsate 3s ease-out; + -webkit-animation-iteration-count: infinite; + opacity: 0; +} + .plant-floor { padding-bottom: 25px; } diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index cdcd1047bd89..5387286c7099 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -1251,7 +1251,7 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex ], }, ], - primary_action_label: "Create Purchase Order", + primary_action_label: __("Create Purchase Order"), primary_action(args) { if (!args) return; diff --git a/erpnext/stock/dashboard_chart/oldest_items/oldest_items.json b/erpnext/stock/dashboard_chart/oldest_items/oldest_items.json index 9c10a5346be9..46ad308f2309 100644 --- a/erpnext/stock/dashboard_chart/oldest_items/oldest_items.json +++ b/erpnext/stock/dashboard_chart/oldest_items/oldest_items.json @@ -1,22 +1,23 @@ { "chart_name": "Oldest Items", "chart_type": "Report", - "creation": "2020-07-20 21:01:04.336845", + "creation": "2022-03-30 00:58:02.041721", "custom_options": "{\"colors\": [\"#5e64ff\"]}", "docstatus": 0, "doctype": "Dashboard Chart", - "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"to_date\":\"frappe.datetime.nowdate()\"}", - "filters_json": "{\"range1\":30,\"range2\":60,\"range3\":90,\"show_warehouse_wise_stock\":0}", + "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"to_date\":\"frappe.datetime.nowdate()\",\"range1\":\"30\",\"range2\":\"60\",\"range3\":\"90\"}", + "filters_json": "{\"range\":\"30, 60, 90\",\"show_warehouse_wise_stock\":0}", "idx": 0, "is_public": 1, "is_standard": 1, - "modified": "2020-07-29 14:50:26.846482", + "modified": "2024-09-23 22:37:51.392999", "modified_by": "Administrator", "module": "Stock", "name": "Oldest Items", "number_of_groups": 0, - "owner": "Administrator", + "owner": "rohitw1991@gmail.com", "report_name": "Stock Ageing", + "roles": [], "timeseries": 0, "type": "Bar", "use_report_chart": 1, diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.js b/erpnext/stock/doctype/quality_inspection/quality_inspection.js index fc487514a2c7..99e25bf5eb6f 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.js +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.js @@ -60,7 +60,7 @@ frappe.ui.form.on("Quality Inspection", { refresh: function (frm) { // Ignore cancellation of reference doctype on cancel all. - frm.ignore_doctypes_on_cancel_all = [frm.doc.reference_type]; + frm.ignore_doctypes_on_cancel_all = [frm.doc.reference_type, "Serial and Batch Bundle"]; }, item_code: function (frm) { diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py index 60f048673fb8..5dca440adbef 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py @@ -109,6 +109,8 @@ def on_submit(self): self.update_qc_reference() def on_cancel(self): + self.ignore_linked_doctypes = "Serial and Batch Bundle" + self.update_qc_reference() def on_trash(self): diff --git a/erpnext/stock/report/stock_balance/stock_balance.js b/erpnext/stock/report/stock_balance/stock_balance.js index d80261895aa5..1d86634fd951 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.js +++ b/erpnext/stock/report/stock_balance/stock_balance.js @@ -107,6 +107,12 @@ frappe.query_reports["Stock Balance"] = { fieldtype: "Check", default: 0, }, + { + fieldname: "show_dimension_wise_stock", + label: __("Show Dimension Wise Stock"), + fieldtype: "Check", + default: 0, + }, ], formatter: function (value, row, column, data, default_formatter) { diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index 2694ba03c8be..8ea07338cf1b 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -252,7 +252,10 @@ def get_group_by_key(self, row) -> tuple: group_by_key = [row.company, row.item_code, row.warehouse] for fieldname in self.inventory_dimensions: - if self.filters.get(fieldname): + if not row.get(fieldname): + continue + + if self.filters.get(fieldname) or self.filters.get("show_dimension_wise_stock"): group_by_key.append(row.get(fieldname)) return tuple(group_by_key) diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv index b9d790d24284..ed05b8d66953 100644 --- a/erpnext/translations/de.csv +++ b/erpnext/translations/de.csv @@ -4480,10 +4480,23 @@ Payment Reconciliation,Zahlungsabgleich, Receivable / Payable Account,Forderungen-/Verbindlichkeiten-Konto, Bank / Cash Account,Bank / Geldkonto, From Invoice Date,Ab Rechnungsdatum, -To Invoice Date,Um Datum Rechnung, -Minimum Invoice Amount,Mindestabrechnung, +To Invoice Date,Bis Rechnungsdatum, +Invoice Limit,Max. Anzahl Rechnungen, +From Payment Date,Ab Zahlungsdatum, +To Payment Date,Bis Zahlungsdatum, +Payment Limit,Max. Anzahl Zahlungen, +Minimum Invoice Amount,Minimaler Rechnungsbetrag, Maximum Invoice Amount,Maximaler Rechnungsbetrag, -System will fetch all the entries if limit value is zero.,"Das System ruft alle Einträge ab, wenn der Grenzwert Null ist.", +Minimum Payment Amount,Minimaler Zahlungsbetrag, +Maximum Payment Amount,Maximaler Zahlungsbetrag, +Filter on Invoice,Filter auf Rechnungsnr., +Filter on Payment,Filter auf Zahlungsnr., +"If you need to reconcile particular transactions against each other, then please select accordingly. If not, all the transactions will be allocated in FIFO order.","Wenn Sie bestimmte Transaktionen gegeneinander abgleichen müssen, wählen Sie diese bitte entsprechend aus. Andernfalls werden alle Transaktionen in FIFO-Reihenfolge zugewiesen.", +System will fetch all the entries if limit value is zero.,"Das System ruft alle Einträge ab, wenn die max. Anzahl Null ist.", +This filter will be applied to Journal Entry.,Dieser Filter wird auf Buchungssätze angewendet., +Unreconciled Entries,Nicht zugeordnete Buchungen, +Allocated Entries,Zugewiesene Buchungen, +Accounting Dimensions Filter,Filetr nach Buchhaltungsdimensionen, Get Unreconciled Entries,Nicht zugeordnete Buchungen aufrufen, Unreconciled Payment Details,Nicht abgeglichene Zahlungen, Invoice/Journal Entry Details,Einzelheiten zu Rechnungs-/Journalbuchungen,