diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js index 46ba27c004d..28e79b5d2c6 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js @@ -12,6 +12,9 @@ frappe.ui.form.on("Bank Reconciliation Tool", { }, }; }); + let no_bank_transactions_text = + `
${__("No Matching Bank Transactions Found")}
` + set_field_options("no_bank_transactions", no_bank_transactions_text); }, onload: function (frm) { diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json index b643e6e0912..f666101d3fd 100644 --- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json +++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json @@ -81,8 +81,7 @@ }, { "fieldname": "no_bank_transactions", - "fieldtype": "HTML", - "options": "
No Matching Bank Transactions Found
" + "fieldtype": "HTML" } ], "hide_toolbar": 1, @@ -109,4 +108,4 @@ "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC" -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js index f74562086ef..04af32346bb 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js @@ -100,7 +100,7 @@ frappe.ui.form.on("Bank Statement Import", { if (frm.doc.status.includes("Success")) { frm.add_custom_button( - __("Go to {0} List", [frm.doc.reference_doctype]), + __("Go to {0} List", [__(frm.doc.reference_doctype)]), () => frappe.set_route("List", frm.doc.reference_doctype) ); } diff --git a/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json index 3f85668eded..4bb18655b47 100644 --- a/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json +++ b/erpnext/accounts/doctype/pos_invoice_item/pos_invoice_item.json @@ -8,6 +8,7 @@ "engine": "InnoDB", "field_order": [ "barcode", + "has_item_scanned", "item_code", "col_break1", "item_name", @@ -808,11 +809,19 @@ "fieldtype": "Check", "label": "Grant Commission", "read_only": 1 + }, + { + "default": "0", + "depends_on": "barcode", + "fieldname": "has_item_scanned", + "fieldtype": "Check", + "label": "Has Item Scanned", + "read_only": 1 } ], "istable": 1, "links": [], - "modified": "2021-10-05 12:23:47.506290", + "modified": "2022-11-02 12:52:39.125295", "modified_by": "Administrator", "module": "Accounts", "name": "POS Invoice Item", @@ -820,5 +829,6 @@ "owner": "Administrator", "permissions": [], "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "states": [] } \ No newline at end of file diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json index e73d6023328..370c0fc9605 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json @@ -57,6 +57,8 @@ "column_break_28", "total", "net_total", + "tax_withholding_net_total", + "base_tax_withholding_net_total", "taxes_section", "taxes_and_charges", "column_break_58", @@ -89,7 +91,6 @@ "section_break_44", "apply_discount_on", "base_discount_amount", - "additional_discount_account", "column_break_46", "additional_discount_percentage", "discount_amount", @@ -1421,6 +1422,26 @@ "label": "Is Old Subcontracting Flow", "read_only": 1 }, + { + "default": "0", + "fieldname": "tax_withholding_net_total", + "fieldtype": "Currency", + "hidden": 1, + "label": "Tax Withholding Net Total", + "no_copy": 1, + "options": "currency", + "read_only": 1 + }, + { + "fieldname": "base_tax_withholding_net_total", + "fieldtype": "Currency", + "hidden": 1, + "label": "Base Tax Withholding Net Total", + "no_copy": 1, + "options": "currency", + "print_hide": 1, + "read_only": 1 + }, { "collapsible_depends_on": "tax_withheld_vouchers", "fieldname": "tax_withheld_vouchers_section", @@ -1519,7 +1540,7 @@ "idx": 204, "is_submittable": 1, "links": [], - "modified": "2022-10-11 13:04:44.304389", + "modified": "2022-11-04 01:02:44.544878", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice", diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index a8f6f80b6b9..5c1cb0dcc68 100644 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -40,6 +40,7 @@ "discount_amount", "base_rate_with_margin", "sec_break2", + "apply_tds", "rate", "amount", "item_tax_template", @@ -868,6 +869,12 @@ "label": "Product Bundle", "options": "Product Bundle", "read_only": 1 + }, + { + "default": "1", + "fieldname": "apply_tds", + "fieldtype": "Check", + "label": "Apply TDS" } ], "idx": 1, diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json index 7f1a1eccc45..77055f94455 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -8,6 +8,7 @@ "engine": "InnoDB", "field_order": [ "barcode", + "has_item_scanned", "item_code", "col_break1", "item_name", @@ -872,12 +873,20 @@ "label": "Purchase Order Item", "print_hide": 1, "read_only": 1 + }, + { + "default": "0", + "depends_on": "barcode", + "fieldname": "has_item_scanned", + "fieldtype": "Check", + "label": "Has Item Scanned", + "read_only": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2022-10-26 11:38:36.119339", + "modified": "2022-11-02 12:53:12.693217", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 7eddd81ee03..30ed91b9744 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -61,6 +61,9 @@ def get_party_details(inv): def get_party_tax_withholding_details(inv, tax_withholding_category=None): + if inv.doctype == "Payment Entry": + inv.tax_withholding_net_total = inv.net_total + pan_no = "" parties = [] party_type, party = get_party_details(inv) @@ -242,7 +245,7 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N if party_type == "Supplier": ldc = get_lower_deduction_certificate(tax_details, pan_no) if tax_deducted: - net_total = inv.net_total + net_total = inv.tax_withholding_net_total if ldc: tax_amount = get_tds_amount_from_ldc( ldc, parties, pan_no, tax_details, posting_date, net_total @@ -272,6 +275,11 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"): doctype = "Purchase Invoice" if party_type == "Supplier" else "Sales Invoice" + field = ( + "base_tax_withholding_net_total as base_net_total" + if party_type == "Supplier" + else "base_net_total" + ) voucher_wise_amount = {} vouchers = [] @@ -288,7 +296,7 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"): {"apply_tds": 1, "tax_withholding_category": tax_details.get("tax_withholding_category")} ) - invoices_details = frappe.get_all(doctype, filters=filters, fields=["name", "base_net_total"]) + invoices_details = frappe.get_all(doctype, filters=filters, fields=["name", field]) for d in invoices_details: vouchers.append(d.name) @@ -392,7 +400,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers): tds_amount = 0 invoice_filters = {"name": ("in", vouchers), "docstatus": 1, "apply_tds": 1} - field = "sum(net_total)" + field = "sum(tax_withholding_net_total)" if cint(tax_details.consider_party_ledger_amount): invoice_filters.pop("apply_tds", None) @@ -415,12 +423,12 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers): ) supp_credit_amt += supp_jv_credit_amt - supp_credit_amt += inv.net_total + supp_credit_amt += inv.tax_withholding_net_total threshold = tax_details.get("threshold", 0) cumulative_threshold = tax_details.get("cumulative_threshold", 0) - if (threshold and inv.net_total >= threshold) or ( + if (threshold and inv.tax_withholding_net_total >= threshold) or ( cumulative_threshold and supp_credit_amt >= cumulative_threshold ): if (cumulative_threshold and supp_credit_amt >= cumulative_threshold) and cint( @@ -428,11 +436,11 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers): ): # Get net total again as TDS is calculated on net total # Grand is used to just check for threshold breach - net_total = 0 - if vouchers: - net_total = frappe.db.get_value("Purchase Invoice", invoice_filters, "sum(net_total)") - - net_total += inv.net_total + net_total = ( + frappe.db.get_value("Purchase Invoice", invoice_filters, "sum(tax_withholding_net_total)") + or 0.0 + ) + net_total += inv.tax_withholding_net_total supp_credit_amt = net_total - cumulative_threshold if ldc and is_valid_certificate( @@ -440,7 +448,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers): ldc.valid_upto, inv.get("posting_date") or inv.get("transaction_date"), tax_deducted, - inv.net_total, + inv.tax_withholding_net_total, ldc.certificate_limit, ): tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details) @@ -523,7 +531,7 @@ def get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net limit_consumed = frappe.db.get_value( "Purchase Invoice", {"supplier": ("in", parties), "apply_tds": 1, "docstatus": 1}, - "sum(net_total)", + "sum(tax_withholding_net_total)", ) if is_valid_certificate( diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py index e80fe11ab30..40c732bae52 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py @@ -186,6 +186,46 @@ class TestTaxWithholdingCategory(unittest.TestCase): for d in reversed(invoices): d.cancel() + def test_tds_calculation_on_net_total_partial_tds(self): + frappe.db.set_value( + "Supplier", "Test TDS Supplier4", "tax_withholding_category", "Cumulative Threshold TDS" + ) + invoices = [] + + pi = create_purchase_invoice(supplier="Test TDS Supplier4", rate=20000, do_not_save=True) + pi.extend( + "items", + [ + { + "doctype": "Purchase Invoice Item", + "item_code": frappe.db.get_value("Item", {"item_name": "TDS Item"}, "name"), + "qty": 1, + "rate": 20000, + "cost_center": "Main - _TC", + "expense_account": "Stock Received But Not Billed - _TC", + "apply_tds": 0, + }, + { + "doctype": "Purchase Invoice Item", + "item_code": frappe.db.get_value("Item", {"item_name": "TDS Item"}, "name"), + "qty": 1, + "rate": 35000, + "cost_center": "Main - _TC", + "expense_account": "Stock Received But Not Billed - _TC", + "apply_tds": 1, + }, + ], + ) + pi.save() + pi.submit() + invoices.append(pi) + + self.assertEqual(pi.taxes[0].tax_amount, 5500) + + # cancel invoices to avoid clashing + for d in reversed(invoices): + d.cancel() + def test_multi_category_single_supplier(self): frappe.db.set_value( "Supplier", "Test TDS Supplier5", "tax_withholding_category", "Test Service Category" diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index f2ee1eb10ea..a195c575866 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -1009,7 +1009,7 @@ class ReceivablePayableReport(object): "{range3}-{range4}".format( range3=cint(self.filters["range3"]) + 1, range4=self.filters["range4"] ), - "{range4}-{above}".format(range4=cint(self.filters["range4"]) + 1, above=_("Above")), + _("{range4}-Above").format(range4=cint(self.filters["range4"]) + 1), ] ): self.add_column(label=label, fieldname="range" + str(i + 1)) diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.js b/erpnext/accounts/report/budget_variance_report/budget_variance_report.js index 718b6e2fcb6..5955c2e0fc9 100644 --- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.js +++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.js @@ -75,7 +75,7 @@ frappe.query_reports["Budget Variance Report"] = { "formatter": function (value, row, column, data, default_formatter) { value = default_formatter(value, row, column, data); - if (column.fieldname.includes('variance')) { + if (column.fieldname.includes(__("variance"))) { if (data[column.fieldname] < 0) { value = "" + value + ""; diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py index 7b774ba740b..96cfab9f11f 100644 --- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py +++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py @@ -383,8 +383,8 @@ def get_chart_data(filters, columns, data): "data": { "labels": labels, "datasets": [ - {"name": "Budget", "chartType": "bar", "values": budget_values}, - {"name": "Actual Expense", "chartType": "bar", "values": actual_values}, + {"name": _("Budget"), "chartType": "bar", "values": budget_values}, + {"name": _("Actual Expense"), "chartType": "bar", "values": actual_values}, ], }, "type": "bar", diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py index 1eb257ac853..6cc86c3efec 100644 --- a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py +++ b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py @@ -396,7 +396,7 @@ class Deferred_Revenue_and_Expense_Report(object): "labels": [period.label for period in self.period_list], "datasets": [ { - "name": "Actual Posting", + "name": _("Actual Posting"), "chartType": "bar", "values": [x.actual for x in self.period_total], } @@ -410,7 +410,7 @@ class Deferred_Revenue_and_Expense_Report(object): if self.filters.with_upcoming_postings: chart["data"]["datasets"].append( - {"name": "Expected", "chartType": "line", "values": [x.total for x in self.period_total]} + {"name": _("Expected"), "chartType": "line", "values": [x.total for x in self.period_total]} ) return chart diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 5512d4159d8..7e542197407 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -432,7 +432,11 @@ frappe.ui.form.on('Asset', { set_values_from_purchase_doc: function(frm, doctype, purchase_doc) { frm.set_value('company', purchase_doc.company); - frm.set_value('purchase_date', purchase_doc.posting_date); + if (purchase_doc.bill_date) { + frm.set_value('purchase_date', purchase_doc.bill_date); + } else { + frm.set_value('purchase_date', purchase_doc.posting_date); + } const item = purchase_doc.items.find(item => item.item_code === frm.doc.item_code); if (!item) { doctype_field = frappe.scrub(doctype) diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 6c1bcc7dd49..5a96131157b 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -5,7 +5,7 @@ import json import frappe -from frappe.tests.utils import FrappeTestCase +from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import add_days, flt, getdate, nowdate from frappe.utils.data import today @@ -709,13 +709,10 @@ class TestPurchaseOrder(FrappeTestCase): pi.insert() self.assertTrue(pi.get("payment_schedule")) + @change_settings("Accounts Settings", {"unlink_advance_payment_on_cancelation_of_order": 1}) def test_advance_payment_entry_unlink_against_purchase_order(self): from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry - frappe.db.set_value( - "Accounts Settings", "Accounts Settings", "unlink_advance_payment_on_cancelation_of_order", 1 - ) - po_doc = create_purchase_order() pe = get_payment_entry("Purchase Order", po_doc.name, bank_account="_Test Bank - _TC") @@ -735,9 +732,31 @@ class TestPurchaseOrder(FrappeTestCase): pe_doc = frappe.get_doc("Payment Entry", pe.name) pe_doc.cancel() - frappe.db.set_value( - "Accounts Settings", "Accounts Settings", "unlink_advance_payment_on_cancelation_of_order", 0 - ) + @change_settings("Accounts Settings", {"unlink_advance_payment_on_cancelation_of_order": 1}) + def test_advance_paid_upon_payment_entry_cancellation(self): + from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry + + po_doc = create_purchase_order() + + pe = get_payment_entry("Purchase Order", po_doc.name, bank_account="_Test Bank - _TC") + pe.reference_no = "1" + pe.reference_date = nowdate() + pe.paid_from_account_currency = po_doc.currency + pe.paid_to_account_currency = po_doc.currency + pe.source_exchange_rate = 1 + pe.target_exchange_rate = 1 + pe.paid_amount = po_doc.grand_total + pe.save(ignore_permissions=True) + pe.submit() + + po_doc.reload() + self.assertEqual(po_doc.advance_paid, po_doc.base_grand_total) + + pe_doc = frappe.get_doc("Payment Entry", pe.name) + pe_doc.cancel() + + po_doc.reload() + self.assertEqual(po_doc.advance_paid, 0) def test_schedule_date(self): po = create_purchase_order(do_not_submit=True) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 22291a35441..216c9f45d3f 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -7,7 +7,7 @@ import json import frappe from frappe import _, throw from frappe.model.workflow import get_workflow_name, is_transition_condition_satisfied -from frappe.query_builder.functions import Sum +from frappe.query_builder.functions import Abs, Sum from frappe.utils import ( add_days, add_months, @@ -151,6 +151,7 @@ class AccountsController(TransactionBase): self.validate_inter_company_reference() self.disable_pricing_rule_on_internal_transfer() + self.disable_tax_included_prices_for_internal_transfer() self.set_incoming_rate() if self.meta.get_field("currency"): @@ -226,7 +227,7 @@ class AccountsController(TransactionBase): for item in self.get("items"): if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"): if not item.get(field_map.get(self.doctype)): - default_deferred_account = frappe.db.get_value( + default_deferred_account = frappe.get_cached_value( "Company", self.company, "default_" + field_map.get(self.doctype) ) if not default_deferred_account: @@ -398,6 +399,20 @@ class AccountsController(TransactionBase): alert=1, ) + def disable_tax_included_prices_for_internal_transfer(self): + if self.is_internal_transfer(): + tax_updated = False + for tax in self.get("taxes"): + if tax.get("included_in_print_rate"): + tax.included_in_print_rate = 0 + tax_updated = True + + if tax_updated: + frappe.msgprint( + _("Disabled tax included prices since this {} is an internal transfer").format(self.doctype), + alert=1, + ) + def validate_due_date(self): if self.get("is_pos"): return @@ -661,7 +676,7 @@ class AccountsController(TransactionBase): def validate_enabled_taxes_and_charges(self): taxes_and_charges_doctype = self.meta.get_options("taxes_and_charges") - if frappe.db.get_value(taxes_and_charges_doctype, self.taxes_and_charges, "disabled"): + if frappe.get_cached_value(taxes_and_charges_doctype, self.taxes_and_charges, "disabled"): frappe.throw( _("{0} '{1}' is disabled").format(taxes_and_charges_doctype, self.taxes_and_charges) ) @@ -669,7 +684,7 @@ class AccountsController(TransactionBase): def validate_tax_account_company(self): for d in self.get("taxes"): if d.account_head: - tax_account_company = frappe.db.get_value("Account", d.account_head, "company") + tax_account_company = frappe.get_cached_value("Account", d.account_head, "company") if tax_account_company != self.company: frappe.throw( _("Row #{0}: Account {1} does not belong to company {2}").format( @@ -804,15 +819,12 @@ class AccountsController(TransactionBase): self.set("advances", []) advance_allocated = 0 for d in res: - if d.against_order: - allocated_amount = flt(d.amount) + if self.get("party_account_currency") == self.company_currency: + amount = self.get("base_rounded_total") or self.base_grand_total else: - if self.get("party_account_currency") == self.company_currency: - amount = self.get("base_rounded_total") or self.base_grand_total - else: - amount = self.get("rounded_total") or self.grand_total + amount = self.get("rounded_total") or self.grand_total - allocated_amount = min(amount - advance_allocated, d.amount) + allocated_amount = min(amount - advance_allocated, d.amount) advance_allocated += flt(allocated_amount) advance_row = { @@ -917,7 +929,9 @@ class AccountsController(TransactionBase): party_account = self.credit_to if is_purchase_invoice else self.debit_to party_type = "Supplier" if is_purchase_invoice else "Customer" - gain_loss_account = frappe.db.get_value("Company", self.company, "exchange_gain_loss_account") + gain_loss_account = frappe.get_cached_value( + "Company", self.company, "exchange_gain_loss_account" + ) if not gain_loss_account: frappe.throw( _("Please set default Exchange Gain/Loss Account in Company {}").format(self.get("company")) @@ -1014,7 +1028,7 @@ class AccountsController(TransactionBase): else self.grand_total ), "outstanding_amount": self.outstanding_amount, - "difference_account": frappe.db.get_value( + "difference_account": frappe.get_cached_value( "Company", self.company, "exchange_gain_loss_account" ), "exchange_gain_loss": flt(d.get("exchange_gain_loss")), @@ -1334,30 +1348,20 @@ class AccountsController(TransactionBase): return stock_items def set_total_advance_paid(self): - if self.doctype == "Sales Order": - dr_or_cr = "credit_in_account_currency" - rev_dr_or_cr = "debit_in_account_currency" - party = self.customer - else: - dr_or_cr = "debit_in_account_currency" - rev_dr_or_cr = "credit_in_account_currency" - party = self.supplier - - advance = frappe.db.sql( - """ - select - account_currency, sum({dr_or_cr}) - sum({rev_dr_cr}) as amount - from - `tabGL Entry` - where - against_voucher_type = %s and against_voucher = %s and party=%s - and docstatus = 1 - """.format( - dr_or_cr=dr_or_cr, rev_dr_cr=rev_dr_or_cr - ), - (self.doctype, self.name, party), - as_dict=1, - ) # nosec + ple = frappe.qb.DocType("Payment Ledger Entry") + party = self.customer if self.doctype == "Sales Order" else self.supplier + advance = ( + frappe.qb.from_(ple) + .select(ple.account_currency, Abs(Sum(ple.amount)).as_("amount")) + .where( + (ple.against_voucher_type == self.doctype) + & (ple.against_voucher_no == self.name) + & (ple.party == party) + & (ple.delinked == 0) + & (ple.company == self.company) + ) + .run(as_dict=True) + ) if advance: advance = advance[0] @@ -1392,7 +1396,7 @@ class AccountsController(TransactionBase): @property def company_abbr(self): if not hasattr(self, "_abbr"): - self._abbr = frappe.db.get_value("Company", self.company, "abbr") + self._abbr = frappe.get_cached_value("Company", self.company, "abbr") return self._abbr @@ -1778,7 +1782,7 @@ class AccountsController(TransactionBase): """ if self.is_internal_transfer() and not self.unrealized_profit_loss_account: - unrealized_profit_loss_account = frappe.db.get_value( + unrealized_profit_loss_account = frappe.get_cached_value( "Company", self.company, "unrealized_profit_loss_account" ) @@ -1893,7 +1897,9 @@ class AccountsController(TransactionBase): @frappe.whitelist() def get_tax_rate(account_head): - return frappe.db.get_value("Account", account_head, ["tax_rate", "account_name"], as_dict=True) + return frappe.get_cached_value( + "Account", account_head, ["tax_rate", "account_name"], as_dict=True + ) @frappe.whitelist() @@ -1902,7 +1908,7 @@ def get_default_taxes_and_charges(master_doctype, tax_template=None, company=Non return {} if tax_template and company: - tax_template_company = frappe.db.get_value(master_doctype, tax_template, "company") + tax_template_company = frappe.get_cached_value(master_doctype, tax_template, "company") if tax_template_company == company: return diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 04a0dfa3d40..39ef68a9fb5 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -326,7 +326,7 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos company = frappe.db.get_value("Delivery Note", source_name, "company") - default_warehouse_for_sales_return = frappe.db.get_value( + default_warehouse_for_sales_return = frappe.get_cached_value( "Company", company, "default_warehouse_for_sales_return" ) @@ -340,11 +340,11 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None): # look for Print Heading "Credit Note" if not doc.select_print_heading: - doc.select_print_heading = frappe.db.get_value("Print Heading", _("Credit Note")) + doc.select_print_heading = frappe.get_cached_value("Print Heading", _("Credit Note")) elif doctype == "Purchase Invoice": # look for Print Heading "Debit Note" - doc.select_print_heading = frappe.db.get_value("Print Heading", _("Debit Note")) + doc.select_print_heading = frappe.get_cached_value("Print Heading", _("Debit Note")) for tax in doc.get("taxes") or []: if tax.charge_type == "Actual": @@ -503,7 +503,7 @@ def make_return_doc(doctype: str, source_name: str, target_doc=None): doctype + " Item": { "doctype": doctype + " Item", - "field_map": {"serial_no": "serial_no", "batch_no": "batch_no"}, + "field_map": {"serial_no": "serial_no", "batch_no": "batch_no", "bom": "bom"}, "postprocess": update_item, }, "Payment Schedule": {"doctype": "Payment Schedule", "postprocess": update_terms}, diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index 98dc58677b9..1e4fabe0d26 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -57,7 +57,7 @@ class StockController(AccountsController): make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name) provisional_accounting_for_non_stock_items = cint( - frappe.db.get_value( + frappe.get_cached_value( "Company", self.company, "enable_provisional_accounting_for_non_stock_items" ) ) @@ -200,7 +200,7 @@ class StockController(AccountsController): elif self.get("is_internal_supplier"): warehouse_asset_account = warehouse_account[item_row.get("warehouse")]["account"] - expense_account = frappe.db.get_value("Company", self.company, "default_expense_account") + expense_account = frappe.get_cached_value("Company", self.company, "default_expense_account") gl_list.append( self.get_gl_dict( @@ -235,7 +235,7 @@ class StockController(AccountsController): if warehouse_with_no_account: for wh in warehouse_with_no_account: - if frappe.db.get_value("Warehouse", wh, "company"): + if frappe.get_cached_value("Warehouse", wh, "company"): frappe.throw( _( "Warehouse {0} is not linked to any account, please mention the account in the warehouse record or set default inventory account in company {1}." @@ -449,15 +449,15 @@ class StockController(AccountsController): # Get value based on doctype name if not sl_dict.get(dimension.target_fieldname): - fieldname = frappe.get_cached_value( - "DocField", {"parent": self.doctype, "options": dimension.fetch_from_parent}, "fieldname" + fieldname = next( + ( + field.fieldname + for field in frappe.get_meta(self.doctype).fields + if field.options == dimension.fetch_from_parent + ), + None, ) - if not fieldname: - fieldname = frappe.get_cached_value( - "Custom Field", {"dt": self.doctype, "options": dimension.fetch_from_parent}, "fieldname" - ) - if fieldname and self.get(fieldname): sl_dict[dimension.target_fieldname] = self.get(fieldname) diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index aa4468c04e4..7819fa586ca 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -89,6 +89,9 @@ class SubcontractingController(StockController): if bom.item != item.item_code: msg = f"Please select an valid BOM for Item {item.item_name}." frappe.throw(_(msg)) + else: + msg = f"Please select a BOM for Item {item.item_name}." + frappe.throw(_(msg)) def __get_data_before_save(self): item_dict = {} @@ -97,7 +100,7 @@ class SubcontractingController(StockController): and self._doc_before_save ): for row in self._doc_before_save.get("items"): - item_dict[row.name] = (row.item_code, row.qty) + item_dict[row.name] = (row.item_code, row.received_qty or row.qty) return item_dict @@ -115,7 +118,9 @@ class SubcontractingController(StockController): for row in self.items: self.__reference_name.append(row.name) - if (row.name not in item_dict) or (row.item_code, row.qty) != item_dict[row.name]: + if (row.name not in item_dict) or (row.item_code, row.received_qty or row.qty) != item_dict[ + row.name + ]: self.__changed_name.append(row.name) if item_dict.get(row.name): @@ -458,12 +463,13 @@ class SubcontractingController(StockController): def __get_qty_based_on_material_transfer(self, item_row, transfer_item): key = (item_row.item_code, item_row.get(self.subcontract_data.order_field)) + item_qty = item_row.received_qty or item_row.qty - if self.qty_to_be_received == item_row.qty: + if self.qty_to_be_received.get(key) == item_qty: return transfer_item.qty if self.qty_to_be_received: - qty = (flt(item_row.qty) * flt(transfer_item.qty)) / flt(self.qty_to_be_received.get(key, 0)) + qty = (flt(item_qty) * flt(transfer_item.qty)) / flt(self.qty_to_be_received.get(key, 0)) transfer_item.item_details.required_qty = transfer_item.qty if transfer_item.serial_no or frappe.get_cached_value( @@ -488,7 +494,11 @@ class SubcontractingController(StockController): for bom_item in self.__get_materials_from_bom( row.item_code, row.bom, row.get("include_exploded_items") ): - qty = flt(bom_item.qty_consumed_per_unit) * flt(row.qty) * row.conversion_factor + qty = ( + flt(bom_item.qty_consumed_per_unit) + * flt(row.received_qty or row.qty) + * row.conversion_factor + ) bom_item.main_item_code = row.item_code self.__update_reserve_warehouse(bom_item, row) self.__set_alternative_item(bom_item) diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index b5836c9070a..c6a634ba806 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -58,12 +58,25 @@ class calculate_taxes_and_totals(object): self.initialize_taxes() self.determine_exclusive_rate() self.calculate_net_total() + self.calculate_tax_withholding_net_total() self.calculate_taxes() self.manipulate_grand_total_for_inclusive_tax() self.calculate_totals() self._cleanup() self.calculate_total_net_weight() + def calculate_tax_withholding_net_total(self): + if hasattr(self.doc, "tax_withholding_net_total"): + sum_net_amount = 0 + sum_base_net_amount = 0 + for item in self.doc.get("items"): + if hasattr(item, "apply_tds") and item.apply_tds: + sum_net_amount += item.net_amount + sum_base_net_amount += item.base_net_amount + + self.doc.tax_withholding_net_total = sum_net_amount + self.doc.base_tax_withholding_net_total = sum_base_net_amount + def validate_item_tax_template(self): for item in self.doc.get("items"): if item.item_code and item.get("item_tax_template"): @@ -1043,7 +1056,7 @@ class init_landed_taxes_and_totals(object): company_currency = erpnext.get_company_currency(self.doc.company) for d in self.doc.get(self.tax_field): if not d.account_currency: - account_currency = frappe.db.get_value("Account", d.expense_account, "account_currency") + account_currency = frappe.get_cached_value("Account", d.expense_account, "account_currency") d.account_currency = account_currency or company_currency def set_exchange_rate(self): diff --git a/erpnext/controllers/tests/test_subcontracting_controller.py b/erpnext/controllers/tests/test_subcontracting_controller.py index 8490d145286..0e6fe95d45f 100644 --- a/erpnext/controllers/tests/test_subcontracting_controller.py +++ b/erpnext/controllers/tests/test_subcontracting_controller.py @@ -815,6 +815,7 @@ def add_second_row_in_scr(scr): "item_name", "qty", "uom", + "bom", "warehouse", "stock_uom", "subcontracting_order", diff --git a/erpnext/controllers/trends.py b/erpnext/controllers/trends.py index 1d6c5dc0be0..1fb722e1123 100644 --- a/erpnext/controllers/trends.py +++ b/erpnext/controllers/trends.py @@ -80,7 +80,7 @@ def get_data(filters, conditions): if conditions.get("trans") == "Quotation" and filters.get("group_by") == "Customer": cond += " and t1.quotation_to = 'Customer'" - year_start_date, year_end_date = frappe.db.get_value( + year_start_date, year_end_date = frappe.get_cached_value( "Fiscal Year", filters.get("fiscal_year"), ["year_start_date", "year_end_date"] ) @@ -275,7 +275,7 @@ def get_period_date_ranges(period, fiscal_year=None, year_start_date=None): from dateutil.relativedelta import relativedelta if not year_start_date: - year_start_date, year_end_date = frappe.db.get_value( + year_start_date, year_end_date = frappe.get_cached_value( "Fiscal Year", fiscal_year, ["year_start_date", "year_end_date"] ) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 4aab3fa3734..62476182485 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -589,66 +589,69 @@ erpnext.work_order = { } } - if(!frm.doc.skip_transfer){ + if (frm.doc.status != 'Stopped') { // If "Material Consumption is check in Manufacturing Settings, allow Material Consumption - if (flt(doc.material_transferred_for_manufacturing) > 0 && frm.doc.status != 'Stopped') { - if ((flt(doc.produced_qty) < flt(doc.material_transferred_for_manufacturing))) { - frm.has_finish_btn = true; - - if (frm.doc.__onload && frm.doc.__onload.material_consumption == 1) { - // Only show "Material Consumption" when required_qty > consumed_qty - var counter = 0; - var tbl = frm.doc.required_items || []; - var tbl_lenght = tbl.length; - for (var i = 0, len = tbl_lenght; i < len; i++) { - let wo_item_qty = frm.doc.required_items[i].transferred_qty || frm.doc.required_items[i].required_qty; - if (flt(wo_item_qty) > flt(frm.doc.required_items[i].consumed_qty)) { - counter += 1; - } - } - if (counter > 0) { - var consumption_btn = frm.add_custom_button(__('Material Consumption'), function() { - const backflush_raw_materials_based_on = frm.doc.__onload.backflush_raw_materials_based_on; - erpnext.work_order.make_consumption_se(frm, backflush_raw_materials_based_on); - }); - consumption_btn.addClass('btn-primary'); + if (frm.doc.__onload && frm.doc.__onload.material_consumption == 1) { + if (flt(doc.material_transferred_for_manufacturing) > 0 || frm.doc.skip_transfer) { + // Only show "Material Consumption" when required_qty > consumed_qty + var counter = 0; + var tbl = frm.doc.required_items || []; + var tbl_lenght = tbl.length; + for (var i = 0, len = tbl_lenght; i < len; i++) { + let wo_item_qty = frm.doc.required_items[i].transferred_qty || frm.doc.required_items[i].required_qty; + if (flt(wo_item_qty) > flt(frm.doc.required_items[i].consumed_qty)) { + counter += 1; } } + if (counter > 0) { + var consumption_btn = frm.add_custom_button(__('Material Consumption'), function() { + const backflush_raw_materials_based_on = frm.doc.__onload.backflush_raw_materials_based_on; + erpnext.work_order.make_consumption_se(frm, backflush_raw_materials_based_on); + }); + consumption_btn.addClass('btn-primary'); + } + } + } + if(!frm.doc.skip_transfer){ + if (flt(doc.material_transferred_for_manufacturing) > 0) { + if ((flt(doc.produced_qty) < flt(doc.material_transferred_for_manufacturing))) { + frm.has_finish_btn = true; + + var finish_btn = frm.add_custom_button(__('Finish'), function() { + erpnext.work_order.make_se(frm, 'Manufacture'); + }); + + if(doc.material_transferred_for_manufacturing>=doc.qty) { + // all materials transferred for manufacturing, make this primary + finish_btn.addClass('btn-primary'); + } + } else { + frappe.db.get_doc("Manufacturing Settings").then((doc) => { + let allowance_percentage = doc.overproduction_percentage_for_work_order; + + if (allowance_percentage > 0) { + let allowed_qty = frm.doc.qty + ((allowance_percentage / 100) * frm.doc.qty); + + if ((flt(doc.produced_qty) < allowed_qty)) { + frm.add_custom_button(__('Finish'), function() { + erpnext.work_order.make_se(frm, 'Manufacture'); + }); + } + } + }); + } + } + } else { + if ((flt(doc.produced_qty) < flt(doc.qty))) { var finish_btn = frm.add_custom_button(__('Finish'), function() { erpnext.work_order.make_se(frm, 'Manufacture'); }); - - if(doc.material_transferred_for_manufacturing>=doc.qty) { - // all materials transferred for manufacturing, make this primary - finish_btn.addClass('btn-primary'); - } - } else { - frappe.db.get_doc("Manufacturing Settings").then((doc) => { - let allowance_percentage = doc.overproduction_percentage_for_work_order; - - if (allowance_percentage > 0) { - let allowed_qty = frm.doc.qty + ((allowance_percentage / 100) * frm.doc.qty); - - if ((flt(doc.produced_qty) < allowed_qty)) { - frm.add_custom_button(__('Finish'), function() { - erpnext.work_order.make_se(frm, 'Manufacture'); - }); - } - } - }); + finish_btn.addClass('btn-primary'); } } - } else { - if ((flt(doc.produced_qty) < flt(doc.qty)) && frm.doc.status != 'Stopped') { - var finish_btn = frm.add_custom_button(__('Finish'), function() { - erpnext.work_order.make_se(frm, 'Manufacture'); - }); - finish_btn.addClass('btn-primary'); - } } } - }, calculate_cost: function(doc) { if (doc.operations){ diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 5ed8f3f4a91..8e726bf0c0a 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -317,4 +317,4 @@ erpnext.patches.v14_0.fix_subcontracting_receipt_gl_entries erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger erpnext.patches.v14_0.create_accounting_dimensions_for_asset_capitalization erpnext.patches.v13_0.update_schedule_type_in_loans - +erpnext.patches.v14_0.update_tds_fields \ No newline at end of file diff --git a/erpnext/patches/v14_0/update_tds_fields.py b/erpnext/patches/v14_0/update_tds_fields.py new file mode 100644 index 00000000000..a333c5d7a50 --- /dev/null +++ b/erpnext/patches/v14_0/update_tds_fields.py @@ -0,0 +1,29 @@ +import frappe +from frappe.utils import nowdate + +from erpnext.accounts.utils import FiscalYearError, get_fiscal_year + + +def execute(): + # Only do for current fiscal year, no need to repost for all years + for company in frappe.get_all("Company"): + try: + fiscal_year_details = get_fiscal_year(date=nowdate(), company=company.name, as_dict=True) + + purchase_invoice = frappe.qb.DocType("Purchase Invoice") + + frappe.qb.update(purchase_invoice).set( + purchase_invoice.tax_withholding_net_total, purchase_invoice.net_total + ).set( + purchase_invoice.base_tax_withholding_net_total, purchase_invoice.base_net_total + ).where( + purchase_invoice.company == company.name + ).where( + purchase_invoice.apply_tds == 1 + ).where( + purchase_invoice.posting_date >= fiscal_year_details.year_start_date + ).where( + purchase_invoice.docstatus == 1 + ).run() + except FiscalYearError: + pass diff --git a/erpnext/public/js/bank_reconciliation_tool/data_table_manager.js b/erpnext/public/js/bank_reconciliation_tool/data_table_manager.js index 5bb58faf2fc..9ef8ce6b63e 100644 --- a/erpnext/public/js/bank_reconciliation_tool/data_table_manager.js +++ b/erpnext/public/js/bank_reconciliation_tool/data_table_manager.js @@ -30,28 +30,28 @@ erpnext.accounts.bank_reconciliation.DataTableManager = class DataTableManager { get_dt_columns() { this.columns = [ { - name: "Date", + name: __("Date"), editable: false, width: 100, }, { - name: "Party Type", + name: __("Party Type"), editable: false, width: 95, }, { - name: "Party", + name: __("Party"), editable: false, width: 100, }, { - name: "Description", + name: __("Description"), editable: false, width: 350, }, { - name: "Deposit", + name: __("Deposit"), editable: false, width: 100, format: (value) => @@ -60,7 +60,7 @@ erpnext.accounts.bank_reconciliation.DataTableManager = class DataTableManager { "", }, { - name: "Withdrawal", + name: __("Withdrawal"), editable: false, width: 100, format: (value) => @@ -69,26 +69,26 @@ erpnext.accounts.bank_reconciliation.DataTableManager = class DataTableManager { "", }, { - name: "Unallocated Amount", + name: __("Unallocated Amount"), editable: false, width: 100, format: (value) => - "" + + "" + format_currency(value, this.currency) + "", }, { - name: "Reference Number", + name: __("Reference Number"), editable: false, width: 140, }, { - name: "Actions", + name: __("Actions"), editable: false, sortable: false, focusable: false, dropdown: false, - width: 80, + width: 100, }, ]; } @@ -118,7 +118,7 @@ erpnext.accounts.bank_reconciliation.DataTableManager = class DataTableManager { row["reference_number"], `